diff --git a/bin/glance-control b/bin/glance-control index 732a619baa..349a902cc9 100755 --- a/bin/glance-control +++ b/bin/glance-control @@ -111,6 +111,18 @@ def do_start(server, conf, args): fp.write('%d\n' % pid) fp.close() + def await_child(pid): + if conf.await_child: + bail_time = time.time() + conf.await_child + while time.time() < bail_time: + reported_pid, status = os.waitpid(pid, os.WNOHANG) + if reported_pid == pid: + global exitcode + # the exit code is encoded in 2nd least significant byte + exitcode = status >> 8 + break + time.sleep(0.05) + def launch(pid_file, conf_file=None): args = [server] print 'Starting %s' % server, @@ -136,6 +148,7 @@ def do_start(server, conf, args): sys.exit(0) else: write_pid_file(pid_file, pid) + await_child(pid) if not conf.pid_file: pid_file = '/var/run/glance/%s.pid' % server @@ -181,11 +194,19 @@ def do_stop(server, conf, args, graceful=False): if __name__ == '__main__': + exitcode = 0 + conf = config.GlanceConfigOpts(usage=USAGE) conf.register_cli_opt(cfg.StrOpt('pid-file', metavar='PATH', help='File to use as pid file. Default: ' '/var/run/glance/$server.pid')) + conf.register_cli_opt(cfg.IntOpt('await-child', + metavar='DELAY', + default=0, + help='Period to wait for service death ' + 'in order to report exit code ' + '(default is to not wait at all)')) args = conf() if len(args) < 2: @@ -235,3 +256,5 @@ if __name__ == '__main__': for server in servers: do_stop(server, conf, args, graceful=True) do_start(server, conf, args) + + sys.exit(exitcode) diff --git a/glance/tests/functional/__init__.py b/glance/tests/functional/__init__.py index bd2b908b7c..6819d3ceae 100644 --- a/glance/tests/functional/__init__.py +++ b/glance/tests/functional/__init__.py @@ -87,6 +87,7 @@ class Server(object): self.server_control = './bin/glance-control' self.exec_env = None self.deployment_flavor = '' + self.server_control_options = '' def write_conf(self, **kwargs): """ @@ -128,7 +129,7 @@ class Server(object): return self.conf_file_name - def start(self, **kwargs): + def start(self, expected_exitcode=0, **kwargs): """ Starts the server. @@ -140,9 +141,13 @@ class Server(object): self.write_conf(**kwargs) cmd = ("%(server_control)s %(server_name)s start " - "%(conf_file_name)s --pid-file=%(pid_file)s" + "%(conf_file_name)s --pid-file=%(pid_file)s " + "%(server_control_options)s" % self.__dict__) - return execute(cmd, no_venv=self.no_venv, exec_env=self.exec_env) + return execute(cmd, + no_venv=self.no_venv, + exec_env=self.exec_env, + expected_exitcode=expected_exitcode) def stop(self): """ @@ -439,7 +444,11 @@ class FunctionalTest(unittest.TestCase): if os.path.exists(f): os.unlink(f) - def start_server(self, server, expect_launch, **kwargs): + def start_server(self, + server, + expect_launch, + expected_exitcode=0, + **kwargs): """ Starts a server on an unused port. @@ -449,13 +458,15 @@ class FunctionalTest(unittest.TestCase): :param server: the server to launch :param expect_launch: true iff the server is expected to successfully start + :param expected_exitcode: expected exitcode from the launcher """ self.cleanup() # Start up the requested server - exitcode, out, err = server.start(**kwargs) + exitcode, out, err = server.start(expected_exitcode=expected_exitcode, + **kwargs) - self.assertEqual(0, exitcode, + self.assertEqual(expected_exitcode, exitcode, "Failed to spin up the requested server. " "Got: %s" % err) self.assertTrue(re.search("Starting glance-[a-z]+ with", out)) diff --git a/glance/tests/functional/test_api.py b/glance/tests/functional/test_api.py index 75297babc3..6b96b6181e 100644 --- a/glance/tests/functional/test_api.py +++ b/glance/tests/functional/test_api.py @@ -1248,8 +1248,10 @@ class TestApi(functional.FunctionalTest): """ self.cleanup() self.api_server.default_store = 'shouldnotexist' + self.api_server.server_control_options += ' --await-child=1' # ensure that the API server fails to launch self.start_server(self.api_server, expect_launch=False, + expected_exitcode=255, **self.__dict__.copy()) diff --git a/glance/tests/utils.py b/glance/tests/utils.py index 5b94bfa273..e421611711 100644 --- a/glance/tests/utils.py +++ b/glance/tests/utils.py @@ -168,7 +168,11 @@ def skip_if_disabled(func): return wrapped -def execute(cmd, raise_error=True, no_venv=False, exec_env=None): +def execute(cmd, + raise_error=True, + no_venv=False, + exec_env=None, + expected_exitcode=0): """ Executes a command in a subprocess. Returns a tuple of (exitcode, out, err), where out is the string output @@ -183,6 +187,7 @@ def execute(cmd, raise_error=True, no_venv=False, exec_env=None): variables; values may be callables, which will be passed the current value of the named environment variable + :param expected_exitcode: expected exitcode from the launcher """ env = os.environ.copy() @@ -219,7 +224,7 @@ def execute(cmd, raise_error=True, no_venv=False, exec_env=None): result = process.communicate() (out, err) = result exitcode = process.returncode - if process.returncode != 0 and raise_error: + if process.returncode != expected_exitcode and raise_error: msg = "Command %(cmd)s did not succeed. Returned an exit "\ "code of %(exitcode)d."\ "\n\nSTDOUT: %(out)s"\