diff options
Diffstat (limited to 'src/mailman')
| -rw-r--r-- | src/mailman/bin/qrunner.py | 4 | ||||
| -rw-r--r-- | src/mailman/config/config.py | 32 | ||||
| -rw-r--r-- | src/mailman/queue/docs/rest.txt | 23 | ||||
| -rw-r--r-- | src/mailman/queue/rest.py | 6 | ||||
| -rw-r--r-- | src/mailman/rest/configuration.py | 1 | ||||
| -rw-r--r-- | src/mailman/rest/urls.py | 6 | ||||
| -rw-r--r-- | src/mailman/testing/helpers.py | 29 |
7 files changed, 83 insertions, 18 deletions
diff --git a/src/mailman/bin/qrunner.py b/src/mailman/bin/qrunner.py index 0016d082b..b6891c7f6 100644 --- a/src/mailman/bin/qrunner.py +++ b/src/mailman/bin/qrunner.py @@ -214,7 +214,7 @@ def main(): print _('$name runs $classname') sys.exit(0) - # Fast track for one infinite runner + # Fast track for one infinite runner. if len(options.options.runners) == 1 and not options.options.once: qrunner = make_qrunner(*options.options.runners[0]) class Loop: @@ -233,7 +233,7 @@ def main(): qrunner.run() log.info('%s qrunner exiting.', loop.name()) else: - # Anything else we have to handle a bit more specially + # Anything else we have to handle a bit more specially. qrunners = [] for runner, rslice, rrange in options.options.runners: qrunner = make_qrunner(runner, rslice, rrange, once=True) diff --git a/src/mailman/config/config.py b/src/mailman/config/config.py index 8ce5d3886..d7199f505 100644 --- a/src/mailman/config/config.py +++ b/src/mailman/config/config.py @@ -30,9 +30,8 @@ import sys import errno import logging -from StringIO import StringIO from lazr.config import ConfigSchema, as_boolean -from pkg_resources import resource_string +from pkg_resources import resource_stream from mailman import version from mailman.core import errors @@ -76,17 +75,24 @@ class Configuration(object): def load(self, filename=None): """Load the configuration from the schema and config files.""" - schema_string = resource_string('mailman.config', 'schema.cfg') - schema = ConfigSchema('schema.cfg', StringIO(schema_string)) - # If a configuration file was given, load it now too. First, load the - # absolute minimum default configuration, then if a configuration - # filename was given by the user, push it. - config_string = resource_string('mailman.config', 'mailman.cfg') - self._config = schema.loadFile(StringIO(config_string), 'mailman.cfg') - if filename is not None: - self.filename = filename - with open(filename) as user_config: - self._config.push(filename, user_config.read()) + schema_file = config_file = None + try: + schema_file = resource_stream('mailman.config', 'schema.cfg') + schema = ConfigSchema('schema.cfg', schema_file) + # If a configuration file was given, load it now too. First, load + # the absolute minimum default configuration, then if a + # configuration filename was given by the user, push it. + config_file = resource_stream('mailman.config', 'mailman.cfg') + self._config = schema.loadFile(config_file, 'mailman.cfg') + if filename is not None: + self.filename = filename + with open(filename) as user_config: + self._config.push(filename, user_config.read()) + finally: + if schema_file: + schema_file.close() + if config_file: + config_file.close() self._post_process() def push(self, config_name, config_string): diff --git a/src/mailman/queue/docs/rest.txt b/src/mailman/queue/docs/rest.txt new file mode 100644 index 000000000..62e61643f --- /dev/null +++ b/src/mailman/queue/docs/rest.txt @@ -0,0 +1,23 @@ +REST server +=========== + +Mailman is controllable through an administrative RESTful HTTP server. + + >>> from mailman.testing import helpers + >>> master = helpers.TestableMaster(helpers.wait_for_webservice) + >>> master.start('rest') + +The RESTful server can be used to access basic version information. + + >>> dump_json('http://localhost:8001/3.0/system') + http_etag: "..." + mailman_version: GNU Mailman 3.0... (...) + python_version: ... + resource_type_link: https://localhost:8001/3.0/#system + self_link: https://localhost:8001/3.0/system + + +Clean up +-------- + + >>> master.stop() diff --git a/src/mailman/queue/rest.py b/src/mailman/queue/rest.py index 0728e6fbd..148622d2b 100644 --- a/src/mailman/queue/rest.py +++ b/src/mailman/queue/rest.py @@ -35,15 +35,21 @@ from mailman.queue import Runner from mailman.rest.webservice import make_server +log = logging.getLogger('mailman.http') + + class RESTRunner(Runner): def run(self): + log.info('Starting REST server') try: make_server().serve_forever() except KeyboardInterrupt: + log.info('REST server interrupted') sys.exit(signal.SIGTERM) except select.error as (errcode, message): if errcode == errno.EINTR: + log.info('REST server exiting') sys.exit(signal.SIGTERM) raise except: diff --git a/src/mailman/rest/configuration.py b/src/mailman/rest/configuration.py index 0b142ca55..30bc184fe 100644 --- a/src/mailman/rest/configuration.py +++ b/src/mailman/rest/configuration.py @@ -63,6 +63,7 @@ class AdminWebServiceConfiguration: default_batch_size = 50 max_batch_size = 300 + # XXX What's this for? def createRequest(self, body_instream, environ): """See `IWebServiceConfiguration`.""" request = AdminWebServiceRequest(body_instream, environ) diff --git a/src/mailman/rest/urls.py b/src/mailman/rest/urls.py index a0f1b396a..384c21dba 100644 --- a/src/mailman/rest/urls.py +++ b/src/mailman/rest/urls.py @@ -45,6 +45,7 @@ class AbsoluteURLMapper: """Initialize with respect to a context and request.""" self.context = context self.request = request + # XXX Is this strictly necessary? self.webservice_config = AdminWebServiceConfiguration() self.version = self.webservice_config.service_version_uri_prefix self.schema = ('https' if self.webservice_config.use_https else 'http') @@ -53,13 +54,14 @@ class AbsoluteURLMapper: def __str__(self): """Return the semi-hard-coded URL to the service root.""" - path = self[self.context] + path = self._lookup(self.context) return '{0.schema}://{0.hostname}:{0.port}/{0.version}/{1}'.format( self, path) + # XXX Is this strictly necessary? __call__ = __str__ - def __getitem__(self, ob): + def _lookup(self, ob): """Return the path component for the object. :param ob: The object we're looking for. diff --git a/src/mailman/testing/helpers.py b/src/mailman/testing/helpers.py index 41d95602f..0f7d2ed25 100644 --- a/src/mailman/testing/helpers.py +++ b/src/mailman/testing/helpers.py @@ -26,6 +26,7 @@ __all__ = [ 'get_lmtp_client', 'get_queue_messages', 'make_testable_runner', + 'wait_for_webservice', ] @@ -122,9 +123,18 @@ def digest_mbox(mlist): class TestableMaster(Master): """A testable master loop watcher.""" - def __init__(self): + def __init__(self, start_check=None): + """Create a testable master loop watcher. + + :param start_check: Optional callable used to check whether everything + is running as the test expects. Called in `loop()` in the + subthread before the event is set. The callback should block + until the pass condition is set. + :type start_check: Callable taking no arguments, returning nothing. + """ super(TestableMaster, self).__init__( restartable=False, config_file=config.filename) + self.start_check = start_check self.event = threading.Event() self.thread = threading.Thread(target=self.loop) self._started_kids = None @@ -159,6 +169,9 @@ class TestableMaster(Master): # Keeping a copy of all the started child processes for use by the # testing environment, even after all have exited. self._started_kids = set(self._kids) + # If there are extra conditions to check, do it now. + if self.start_check is not None: + self.start_check() # Let the blocking thread know everything's running. self.event.set() super(TestableMaster, self).loop() @@ -255,3 +268,17 @@ def get_lmtp_client(): raise else: raise RuntimeError('Connection refused') + + + +def wait_for_webservice(): + """Wait for the REST server to start serving requests.""" + # Time out after approximately 3 seconds. + for count in range(30): + try: + socket.socket().connect((config.webservice.hostname, + int(config.webservice.port))) + except socket.error: + time.sleep(0.1) + else: + break |
