summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBarry Warsaw2013-06-17 09:36:43 -0400
committerBarry Warsaw2013-06-17 09:36:43 -0400
commit41059ed20ec668baf41cceaf539f8017171e9651 (patch)
tree391b33289ecb1d02905a897ed581ac9538531f10
parentbffd71903475efad53ce6aa59c96a704a905a984 (diff)
downloadmailman-41059ed20ec668baf41cceaf539f8017171e9651.tar.gz
mailman-41059ed20ec668baf41cceaf539f8017171e9651.tar.zst
mailman-41059ed20ec668baf41cceaf539f8017171e9651.zip
-rw-r--r--src/mailman/bin/runner.py291
-rw-r--r--src/mailman/core/runner.py26
-rw-r--r--src/mailman/docs/NEWS.rst5
-rw-r--r--src/mailman/handlers/cook_headers.py4
-rw-r--r--src/mailman/interfaces/runner.py17
-rw-r--r--src/mailman/runners/lmtp.py3
-rw-r--r--src/mailman/runners/rest.py64
-rw-r--r--src/mailman/runners/tests/test_rest.py52
-rw-r--r--src/mailman/runners/tests/test_retry.py1
-rw-r--r--src/mailman/testing/layers.py27
10 files changed, 243 insertions, 247 deletions
diff --git a/src/mailman/bin/runner.py b/src/mailman/bin/runner.py
index 47133bff7..d6a1dd6db 100644
--- a/src/mailman/bin/runner.py
+++ b/src/mailman/bin/runner.py
@@ -29,115 +29,37 @@ import os
import sys
import signal
import logging
+import argparse
import traceback
from mailman.config import config
from mailman.core.i18n import _
-from mailman.core.logging import reopen
-from mailman.options import Options
+from mailman.core.initialize import initialize
from mailman.utilities.modules import find_name
+from mailman.version import MAILMAN_VERSION_FULL
log = None
-def r_callback(option, opt, value, parser):
+class ROptionAction(argparse.Action):
"""Callback for -r/--runner option."""
- dest = getattr(parser.values, option.dest)
- parts = value.split(':')
- if len(parts) == 1:
- runner = parts[0]
- rslice = rrange = 1
- elif len(parts) == 3:
- runner = parts[0]
- try:
- rslice = int(parts[1])
- rrange = int(parts[2])
- except ValueError:
- parser.print_help()
- print >> sys.stderr, _('Bad runner specification: $value')
- sys.exit(1)
- else:
- parser.print_help()
- print >> sys.stderr, _('Bad runner specification: $value')
- sys.exit(1)
- dest.append((runner, rslice, rrange))
-
-
-
-class ScriptOptions(Options):
- """Options for bin/runner."""
- usage = _("""\
-Start one or more runners.
-
-The runner named on the command line is started, and it can either run through
-its main loop once (for those runners that support this) or continuously. The
-latter is how the master runner starts all its subprocesses.
-
-When more than one runner is specified on the command line, they are each run
-in round-robin fashion. All runners must support running its main loop once.
-In other words, the first named runner is run once. When that runner is done,
-the next one is run to consume all the files in *its* directory, and so on.
-The number of total iterations can be given on the command line. This mode of
-operation is primarily for debugging purposes.
-
-Usage: %prog [options]
-
--r is required unless -l or -h is given, and its argument must be one of the
-names displayed by the -l switch.
-
-Normally, this script should be started from 'bin/mailman start'. Running it
-separately or with -o is generally useful only for debugging. When run this
-way, the environment variable $MAILMAN_UNDER_MASTER_CONTROL will be set which
-subtly changes some error handling behavior.
-""")
-
- def add_options(self):
- """See `Options`."""
- self.parser.add_option(
- '-r', '--runner',
- metavar='runner[:slice:range]', dest='runners',
- type='string', default=[],
- action='callback', callback=r_callback,
- help=_("""\
-Start the named runner, which must be one of the strings returned by the -l
-option.
-
-For runners that manage a queue directory, optional slice:range if given, is
-used to assign multiple runner processes to that queue. range is the total
-number of runners for the queue while slice is the number of this runner from
-[0..range). For runners that do not manage a queue, slice and range are
-ignored.
-
-When using the slice:range form, you must ensure that each runner for the
-queue is given the same range value. If slice:runner is not given, then 1:1
-is used.
-
-Multiple -r options may be given, in which case each runner will run once in
-round-robin fashion. The special runner 'All' is shorthand for running all
-named runners listed by the -l option."""))
- self.parser.add_option(
- '-o', '--once',
- default=False, action='store_true', help=_("""\
-Run each named runner exactly once through its main loop. Otherwise, each
-runner runs indefinitely, until the process receives signal. This is not
-compatible with runners that cannot be run once."""))
- self.parser.add_option(
- '-l', '--list',
- default=False, action='store_true',
- help=_('List the available runner names and exit.'))
- self.parser.add_option(
- '-v', '--verbose',
- default=0, action='count', help=_("""\
-Display more debugging information to the log file."""))
-
- def sanity_check(self):
- """See `Options`."""
- if self.arguments:
- self.parser.error(_('Unexpected arguments'))
- if not self.options.runners and not self.options.list:
- self.parser.error(_('No runner name given.'))
+ def __call__(self, parser, namespace, values, option_string=None):
+ parts = values.split(':')
+ if len(parts) == 1:
+ runner = parts[0]
+ rslice = rrange = 1
+ elif len(parts) == 3:
+ runner = parts[0]
+ try:
+ rslice = int(parts[1])
+ rrange = int(parts[2])
+ except ValueError:
+ parser.error(_('Bad runner specification: $value'))
+ else:
+ parser.error(_('Bad runner specification: $value'))
+ setattr(namespace, self.dest, (runner, rslice, rrange))
@@ -175,53 +97,90 @@ def make_runner(name, slice, range, once=False):
-def set_signals(loop):
- """Set up the signal handlers.
+def main():
+ global log
- Signals caught are: SIGTERM, SIGINT, SIGUSR1 and SIGHUP. The latter is
- used to re-open the log files. SIGTERM and SIGINT are treated exactly the
- same -- they cause the runner to exit with no restart from the master.
- SIGUSR1 also causes the runner to exit, but the master watcher will
- restart it in that case.
+ parser = argparse.ArgumentParser(
+ description=_("""\
+ Start a runner
- :param loop: A runner instance.
- :type loop: `IRunner`
- """
- def sigterm_handler(signum, frame):
- # Exit the runner cleanly
- loop.stop()
- loop.status = signal.SIGTERM
- log.info('%s runner caught SIGTERM. Stopping.', loop.name())
- signal.signal(signal.SIGTERM, sigterm_handler)
- def sigint_handler(signum, frame):
- # Exit the runner cleanly
- loop.stop()
- loop.status = signal.SIGINT
- log.info('%s runner caught SIGINT. Stopping.', loop.name())
- signal.signal(signal.SIGINT, sigint_handler)
- def sigusr1_handler(signum, frame):
- # Exit the runner cleanly
- loop.stop()
- loop.status = signal.SIGUSR1
- log.info('%s runner caught SIGUSR1. Stopping.', loop.name())
- signal.signal(signal.SIGUSR1, sigusr1_handler)
- # SIGHUP just tells us to rotate our log files.
- def sighup_handler(signum, frame):
- reopen()
- log.info('%s runner caught SIGHUP. Reopening logs.', loop.name())
- signal.signal(signal.SIGHUP, sighup_handler)
+ The runner named on the command line is started, and it can
+ either run through its main loop once (for those runners that
+ support this) or continuously. The latter is how the master
+ runner starts all its subprocesses.
+ -r is required unless -l or -h is given, and its argument must
+ be one of the names displayed by the -l switch.
-
-def main():
- global log
+ Normally, this script should be started from 'bin/mailman
+ start'. Running it separately or with -o is generally useful
+ only for debugging. When run this way, the environment variable
+ $MAILMAN_UNDER_MASTER_CONTROL will be set which subtly changes
+ some error handling behavior.
+ """))
+ parser.add_argument(
+ '--version',
+ action='version', version=MAILMAN_VERSION_FULL,
+ help=_('Print this version string and exit'))
+ parser.add_argument(
+ '-C', '--config',
+ help=_("""\
+ Configuration file to use. If not given, the environment variable
+ MAILMAN_CONFIG_FILE is consulted and used if set. If neither are
+ given, a default configuration file is loaded."""))
+ parser.add_argument(
+ '-r', '--runner',
+ metavar='runner[:slice:range]', dest='runner',
+ action=ROptionAction, default=None,
+ help=_("""\
+ Start the named runner, which must be one of the strings
+ returned by the -l option.
+
+ For runners that manage a queue directory, optional
+ `slice:range` if given is used to assign multiple runner
+ processes to that queue. range is the total number of runners
+ for the queue while slice is the number of this runner from
+ [0..range). For runners that do not manage a queue, slice and
+ range are ignored.
- options = ScriptOptions()
- options.initialize()
+ When using the `slice:range` form, you must ensure that each
+ runner for the queue is given the same range value. If
+ `slice:runner` is not given, then 1:1 is used.
+ """))
+ parser.add_argument(
+ '-o', '--once',
+ default=False, action='store_true', help=_("""\
+ Run the named runner exactly once through its main loop.
+ Otherwise, the runner runs indefinitely until the process
+ receives a signal. This is not compatible with runners that
+ cannot be run once."""))
+ parser.add_argument(
+ '-l', '--list',
+ default=False, action='store_true',
+ help=_('List the available runner names and exit.'))
+ parser.add_argument(
+ '-v', '--verbose',
+ default=False, action='store_true', help=_("""\
+ Display more debugging information to the log file."""))
+ args = parser.parse_args()
+ if args.runner is None and not args.list:
+ parser.error(_('No runner name given.'))
+
+ # Initialize the system. Honor the -C flag if given.
+ config_path = (None if args.config is None
+ else os.path.abspath(os.path.expanduser(args.config)))
+ initialize(config_path, args.verbose)
log = logging.getLogger('mailman.runner')
+ if args.verbose:
+ console = logging.StreamHandler(sys.stderr)
+ formatter = logging.Formatter(config.logging.root.format,
+ config.logging.root.datefmt)
+ console.setFormatter(formatter)
+ logging.getLogger().addHandler(console)
+ logging.getLogger().setLevel(logging.DEBUG)
- if options.options.list:
+ if args.list:
descriptions = {}
for section in config.runner_configs:
ignore, dot, shortname = section.name.rpartition('.')
@@ -234,56 +193,10 @@ def main():
print _('$name runs $classname')
sys.exit(0)
- # Fast track for one infinite runner.
- if len(options.options.runners) == 1 and not options.options.once:
- runner = make_runner(*options.options.runners[0])
- class Loop:
- status = 0
- def __init__(self, runner):
- self._runner = runner
- def name(self):
- return self._runner.__class__.__name__
- def stop(self):
- self._runner.stop()
- loop = Loop(runner)
- if runner.intercept_signals:
- set_signals(loop)
- # Now start up the main loop
- log.info('%s runner started.', loop.name())
- runner.run()
- log.info('%s runner exiting.', loop.name())
- else:
- # Anything else we have to handle a bit more specially.
- runners = []
- for runner, rslice, rrange in options.options.runners:
- runner = make_runner(runner, rslice, rrange, once=True)
- runners.append(runner)
- # This class is used to manage the main loop.
- class Loop:
- status = 0
- def __init__(self):
- self._isdone = False
- def name(self):
- return 'Main loop'
- def stop(self):
- self._isdone = True
- def isdone(self):
- return self._isdone
- loop = Loop()
- if runner.intercept_signals:
- set_signals(loop)
- log.info('Main runner loop started.')
- while not loop.isdone():
- for runner in runners:
- # In case the SIGTERM came in the middle of this iteration.
- if loop.isdone():
- break
- if options.options.verbose:
- log.info('Now doing a %s runner iteration',
- runner.__class__.__bases__[0].__name__)
- runner.run()
- if options.options.once:
- break
- log.info('Main runner loop exiting.')
- # All done
- sys.exit(loop.status)
+ runner = make_runner(*args.runner, once=args.once)
+ runner.set_signals()
+ # Now start up the main loop
+ log.info('%s runner started.', runner.name)
+ runner.run()
+ log.info('%s runner exiting.', runner.name)
+ sys.exit(runner.status)
diff --git a/src/mailman/core/runner.py b/src/mailman/core/runner.py
index a8412195d..347726b85 100644
--- a/src/mailman/core/runner.py
+++ b/src/mailman/core/runner.py
@@ -26,6 +26,7 @@ __all__ = [
import time
+import signal
import logging
import traceback
@@ -37,6 +38,7 @@ from zope.interface import implementer
from mailman.config import config
from mailman.core.i18n import _
+from mailman.core.logging import reopen
from mailman.core.switchboard import Switchboard
from mailman.interfaces.languages import ILanguageManager
from mailman.interfaces.listmanager import IListManager
@@ -46,12 +48,12 @@ from mailman.utilities.string import expand
dlog = logging.getLogger('mailman.debug')
elog = logging.getLogger('mailman.error')
+rlog = logging.getLogger('mailman.runner')
@implementer(IRunner)
class Runner:
- intercept_signals = True
is_queue_runner = True
def __init__(self, name, slice=None):
@@ -85,10 +87,32 @@ class Runner:
self.max_restarts = int(section.max_restarts)
self.start = as_boolean(section.start)
self._stop = False
+ self.status = 0
def __repr__(self):
return '<{0} at {1:#x}>'.format(self.__class__.__name__, id(self))
+ def signal_handler(self, signum, frame):
+ signame = {
+ signal.SIGTERM: 'SIGTERM',
+ signal.SIGINT: 'SIGINT',
+ signal.SIGUSR1: 'SIGUSR1',
+ }.get(signum, signum)
+ if signum in (signal.SIGTERM, signal.SIGINT, signal.SIGUSR1):
+ self.stop()
+ self.status = signum
+ rlog.info('%s runner caught %s. Stopping.', self.name, signame)
+ elif signum == signal.SIGHUP:
+ reopen()
+ rlog.info('%s runner caught SIGHUP. Reopening logs.', self.name)
+
+ def set_signals(self):
+ """See `IRunner`."""
+ signal.signal(signal.SIGHUP, self.signal_handler)
+ signal.signal(signal.SIGINT, self.signal_handler)
+ signal.signal(signal.SIGTERM, self.signal_handler)
+ signal.signal(signal.SIGUSR1, self.signal_handler)
+
def stop(self):
"""See `IRunner`."""
self._stop = True
diff --git a/src/mailman/docs/NEWS.rst b/src/mailman/docs/NEWS.rst
index 97478e2ce..8faeb3074 100644
--- a/src/mailman/docs/NEWS.rst
+++ b/src/mailman/docs/NEWS.rst
@@ -130,6 +130,9 @@ Commands
the Postfix `relay_domains` support.
* `bin/mailman start` was passing the wrong relative path to its runner
subprocesses when -C was given. (LP: #982551)
+ * `bin/runner` command has been simplified and its command line options
+ reduced. Now, only one `-r/--runner` option may be provided and the
+ round-robin feature has been removed.
Other
-----
@@ -144,6 +147,8 @@ Other
Bugs
----
* Fixed `send_goodbye_message()`. (LP: #1091321)
+ * Fixed REST server crash on `reopen` command. Identification and test
+ provided by Aurélien Bompard. (LP: #1184376)
3.0 beta 2 -- "Freeze"
diff --git a/src/mailman/handlers/cook_headers.py b/src/mailman/handlers/cook_headers.py
index 07235d6bd..6269f4c45 100644
--- a/src/mailman/handlers/cook_headers.py
+++ b/src/mailman/handlers/cook_headers.py
@@ -155,8 +155,8 @@ def process(mlist, msg, msgdata):
# above code?
# Also skip Cc if this is an anonymous list as list posting address
# is already in From and Reply-To in this case.
- if (mlist.personalize == Personalization.full and
- mlist.reply_goes_to_list != ReplyToMunging.point_to_list and
+ if (mlist.personalize is Personalization.full and
+ mlist.reply_goes_to_list is not ReplyToMunging.point_to_list and
not mlist.anonymous_list):
# Watch out for existing Cc headers, merge, and remove dups. Note
# that RFC 2822 says only zero or one Cc header is allowed.
diff --git a/src/mailman/interfaces/runner.py b/src/mailman/interfaces/runner.py
index c54415c7a..a8b76652c 100644
--- a/src/mailman/interfaces/runner.py
+++ b/src/mailman/interfaces/runner.py
@@ -63,15 +63,16 @@ class IRunner(Interface):
through the main loop.
""")
- # BAW 2013-05-30: but see LP: #1184376 for why this perhaps should be
- # removed entirely.
- intercept_signals = Attribute("""\
- Should the runner mechanism intercept signals?
+ def set_signals():
+ """Set up the signal handlers necessary to control the runner.
- In general, the runner catches SIGINT, SIGTERM, SIGUSR1, and SIGHUP to
- manage the process. Some runners need to manage their own signals,
- and set this attribute to False.
- """)
+ The runner should catch the following signals:
+ - SIGTERM and SIGINT: treated exactly the same, they cause the runner
+ to exit with no restart from the master.
+ - SIGUSR1: Also causes the runner to exit, but the master watcher will
+ retart it.
+ - SIGHUP: Re-open the log files.
+ """
def _one_iteration():
"""The work done in one iteration of the main loop.
diff --git a/src/mailman/runners/lmtp.py b/src/mailman/runners/lmtp.py
index 31d7723f5..2bc3af979 100644
--- a/src/mailman/runners/lmtp.py
+++ b/src/mailman/runners/lmtp.py
@@ -156,12 +156,13 @@ class LMTPRunner(Runner, smtpd.SMTPServer):
is_queue_runner = False
- def __init__(self, slice=None, numslices=1):
+ def __init__(self, name, slice=None):
localaddr = config.mta.lmtp_host, int(config.mta.lmtp_port)
# Do not call Runner's constructor because there's no QDIR to create
qlog.debug('LMTP server listening on %s:%s',
localaddr[0], localaddr[1])
smtpd.SMTPServer.__init__(self, localaddr, remoteaddr=None)
+ super(LMTPRunner, self).__init__(name, slice)
def handle_accept(self):
conn, addr = self.accept()
diff --git a/src/mailman/runners/rest.py b/src/mailman/runners/rest.py
index 252e41ef0..67987805d 100644
--- a/src/mailman/runners/rest.py
+++ b/src/mailman/runners/rest.py
@@ -25,11 +25,9 @@ __all__ = [
]
-import sys
-import errno
-import select
import signal
import logging
+import threading
from mailman.core.runner import Runner
from mailman.rest.wsgiapp import make_server
@@ -40,25 +38,47 @@ log = logging.getLogger('mailman.http')
class RESTRunner(Runner):
- #intercept_signals = False
+ # Don't install the standard signal handlers because as defined, they
+ # won't actually stop the TCPServer started by .serve_forever().
is_queue_runner = False
+ def __init__(self, name, slice=None):
+ """See `IRunner`."""
+ super(RESTRunner, self).__init__(name, slice)
+ # Both the REST server and the signal handlers must run in the main
+ # thread; the former because of SQLite requirements (objects created
+ # in one thread cannot be shared with the other threads), and the
+ # latter because of Python's signal handling semantics.
+ #
+ # Unfortunately, we cannot issue a TCPServer shutdown in the main
+ # thread, because that will cause a deadlock. Yay. So what we do is
+ # to use the signal handler to notify a shutdown thread that the
+ # shutdown should happen. That thread will wake up and stop the main
+ # server.
+ self._server = make_server()
+ self._event = threading.Event()
+ def stopper(event, server):
+ event.wait()
+ server.shutdown()
+ self._thread = threading.Thread(
+ target=stopper, args=(self._event, self._server))
+ self._thread.start()
+
def run(self):
- log.info('Starting REST server')
- # Handle SIGTERM the same way as SIGINT.
- def stop_server(signum, frame):
- log.info('REST server shutdown')
- sys.exit(signal.SIGTERM)
- signal.signal(signal.SIGTERM, stop_server)
- try:
- make_server().serve_forever()
- except KeyboardInterrupt:
- log.info('REST server interrupted')
- sys.exit(signal.SIGINT)
- except select.error as (errcode, message):
- if errcode == errno.EINTR:
- log.info('REST server exiting')
- sys.exit(errno.EINTR)
- raise
- except:
- raise
+ """See `IRunner`."""
+ self._server.serve_forever()
+
+ def signal_handler(self, signum, frame):
+ super(RESTRunner, self).signal_handler(signum, frame)
+ if signum in (signal.SIGTERM, signal.SIGINT, signal.SIGUSR1):
+ # Set the flag that will terminate the TCPserver loop.
+ self._event.set()
+
+ def _one_iteration(self):
+ # Just keep going
+ if self._thread.is_alive():
+ self._thread.join(timeout=0.1)
+ return 1
+
+ def _snooze(self, filecnt):
+ pass
diff --git a/src/mailman/runners/tests/test_rest.py b/src/mailman/runners/tests/test_rest.py
new file mode 100644
index 000000000..a7b51f720
--- /dev/null
+++ b/src/mailman/runners/tests/test_rest.py
@@ -0,0 +1,52 @@
+# Copyright (C) 2013 by the Free Software Foundation, Inc.
+#
+# This file is part of GNU Mailman.
+#
+# GNU Mailman is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
+
+"""Test the REST runner."""
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+__metaclass__ = type
+__all__ = [
+ 'TestRESTRunner',
+ ]
+
+
+import os
+import signal
+import unittest
+
+from mailman.testing.helpers import call_api, wait_for_webservice
+from mailman.testing.layers import RESTLayer
+
+
+
+class TestRESTRunner(unittest.TestCase):
+ """Test the REST runner."""
+
+ layer = RESTLayer
+
+ def test_sighup_restart(self):
+ # The REST runner must survive a SIGHUP.
+ wait_for_webservice()
+ for pid in self.layer.server.runner_pids:
+ os.kill(pid, signal.SIGHUP)
+ wait_for_webservice()
+ # This should not raise an exception. The best way to assert this is
+ # to ensure that the response is valid.
+ response, json = call_api('http://localhost:9001/3.0/system')
+ self.assertEqual(json['content-location'],
+ 'http://localhost:9001/3.0/system')
diff --git a/src/mailman/runners/tests/test_retry.py b/src/mailman/runners/tests/test_retry.py
index 5869f42a2..99e87235e 100644
--- a/src/mailman/runners/tests/test_retry.py
+++ b/src/mailman/runners/tests/test_retry.py
@@ -21,6 +21,7 @@ from __future__ import absolute_import, unicode_literals
__metaclass__ = type
__all__ = [
+ 'TestRetryRunner',
]
diff --git a/src/mailman/testing/layers.py b/src/mailman/testing/layers.py
index bee116b6c..e47d5c9e0 100644
--- a/src/mailman/testing/layers.py
+++ b/src/mailman/testing/layers.py
@@ -45,11 +45,9 @@ import logging
import datetime
import tempfile
-from base64 import b64encode
-from lazr.config import as_boolean, as_timedelta
+from lazr.config import as_boolean
from pkg_resources import resource_string
from textwrap import dedent
-from urllib2 import Request, URLError, urlopen
from zope.component import getUtility
from mailman.config import config
@@ -59,7 +57,7 @@ from mailman.core.logging import get_handler
from mailman.database.transaction import transaction
from mailman.interfaces.domain import IDomainManager
from mailman.testing.helpers import (
- TestableMaster, get_lmtp_client, reset_the_world)
+ TestableMaster, get_lmtp_client, reset_the_world, wait_for_webservice)
from mailman.testing.mta import ConnectionCountingController
from mailman.utilities.string import expand
@@ -315,29 +313,10 @@ class RESTLayer(SMTPLayer):
server = None
- @staticmethod
- def _wait_for_rest_server():
- until = datetime.datetime.now() + as_timedelta(config.devmode.wait)
- while datetime.datetime.now() < until:
- try:
- request = Request('http://localhost:9001/3.0/system')
- basic_auth = '{0}:{1}'.format(config.webservice.admin_user,
- config.webservice.admin_pass)
- request.add_header('Authorization',
- 'Basic ' + b64encode(basic_auth))
- fp = urlopen(request)
- except URLError:
- pass
- else:
- fp.close()
- break
- else:
- raise RuntimeError('REST server did not start up')
-
@classmethod
def setUp(cls):
assert cls.server is None, 'Layer already set up'
- cls.server = TestableMaster(cls._wait_for_rest_server)
+ cls.server = TestableMaster(wait_for_webservice)
cls.server.start('rest')
@classmethod