Add --await-child option to glance-control.
Fixes bug 817032 Previously an immediate non-zero exit status from service launch was not reflected in the exit status returned from glance-control. Now the parent glance-control process configurably waits for the child to exit ungracefully and if this occurs, it inherits the non-zero status code from the child. Change-Id: Ibbe92a5bf40d095951a572d78ae07026d8a9313d
This commit is contained in:
parent
16b682d273
commit
593e8c2fa7
@ -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)
|
||||
|
@ -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))
|
||||
|
@ -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())
|
||||
|
@ -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"\
|
||||
|
Loading…
Reference in New Issue
Block a user