Fallback to a temp pid file in glance-control

Fixes bug 1046593.

Previously if the pid file was not writeable by the current user
then glance-control bombs out, *after* spawning the required service
but *without* caching the process ID anywhere.

Now we fall back to a temporary file, so that at least user can supply
that file to the corresponding glance-contol stop command.

Change-Id: I6c1daf36221f731fdb5a8bb59d3f91a595337af8
This commit is contained in:
Eoghan Glynn 2012-09-10 19:03:30 +00:00
parent 3bfd7bd56c
commit 69389a6442
3 changed files with 76 additions and 23 deletions

View File

@ -31,6 +31,7 @@ import resource
import signal
import subprocess
import sys
import tempfile
import time
# If ../glance/__init__.py exists, add ../ to Python search path, so that
@ -92,9 +93,9 @@ def pid_files(server, pid_file):
yield pid_file, pid
def do_start(verb, server, args):
if verb != 'Respawn':
for pid_file, pid in pid_files(server, CONF.pid_file):
def do_start(verb, pid_file, server, args):
if verb != 'Respawn' and pid_file == CONF.pid_file:
for pid_file, pid in pid_files(server, pid_file):
if os.path.exists('/proc/%s' % pid):
print ("%s appears to already be running: %s" %
(server, pid_file))
@ -114,14 +115,6 @@ def do_start(verb, server, args):
os.environ['PYTHON_EGG_CACHE'] = '/tmp'
def write_pid_file(pid_file, pid):
dir, file = os.path.split(pid_file)
if not os.path.exists(dir):
try:
os.makedirs(dir)
except OSError, err:
if err.errno == errno.EACCES:
sys.exit('Unable to create %s. Running as non-root?'
% dir)
fp = open(pid_file, 'w')
fp.write('%d\n' % pid)
fp.close()
@ -197,7 +190,6 @@ def do_start(verb, server, args):
break
time.sleep(0.05)
pid_file = get_pid_file(server, CONF.pid_file)
conf_file = None
if args and os.path.exists(args[0]):
@ -207,8 +199,26 @@ def do_start(verb, server, args):
def get_pid_file(pid, pid_file):
return (os.path.abspath(pid_file) if pid_file else
'/var/run/glance/%s.pid' % server)
pid_file = (os.path.abspath(pid_file) if pid_file else
'/var/run/glance/%s.pid' % server)
dir, file = os.path.split(pid_file)
if not os.path.exists(dir):
try:
os.makedirs(dir)
except OSError:
pass
if not os.access(dir, os.W_OK):
fallback = os.path.join(tempfile.mkdtemp(), '%s.pid' % server)
msg = ('Unable to create pid file %s. Running as non-root?\n'
'Falling back to a temp file, you can stop %s service using:\n'
' %s %s stop --pid-file %s'
% (pid_file, server, __file__, server, fallback))
print msg
pid_file = fallback
return pid_file
def do_stop(server, args, graceful=False):
@ -306,14 +316,13 @@ if __name__ == '__main__':
while children:
pid, status = os.wait()
if pid in children:
(server, args) = children.pop(pid)
pid_file = get_pid_file(server, CONF.pid_file)
(pid_file, server, args) = children.pop(pid)
running = os.path.exists(pid_file)
one_second_ago = time.time() - 1
bouncing = (running and
os.path.getmtime(pid_file) >= one_second_ago)
if running and not bouncing:
args = (server, args)
args = (pid_file, server, args)
new_pid = do_start('Respawn', *args)
children[new_pid] = args
else:
@ -321,9 +330,10 @@ if __name__ == '__main__':
print 'Supressed respawn as %s was %s.' % (server, rsn)
if command == 'start':
pid_file = get_pid_file(server, CONF.pid_file)
children = {}
for server in servers:
args = (server, args)
args = (pid_file, server, args)
pid = do_start('Start', *args)
children[pid] = args
@ -341,11 +351,13 @@ if __name__ == '__main__':
for server in servers:
do_stop(server, args)
for server in servers:
do_start('Restart', server, args)
pid_file = get_pid_file(server, CONF.pid_file)
do_start('Restart', pid_file, server, args)
if command == 'reload' or command == 'force-reload':
for server in servers:
do_stop(server, args, graceful=True)
do_start('Restart', server, args)
pid_file = get_pid_file(server, CONF.pid_file)
do_start('Restart', pid_file, server, args)
sys.exit(exitcode)

View File

@ -192,7 +192,8 @@ class ApiServer(Server):
Server object that starts/stops/manages the API server
"""
def __init__(self, test_dir, port, policy_file, delayed_delete=False):
def __init__(self, test_dir, port, policy_file, delayed_delete=False,
pid_file=None):
super(ApiServer, self).__init__(test_dir, port)
self.server_name = 'api'
self.default_store = 'file'
@ -201,8 +202,8 @@ class ApiServer(Server):
self.metadata_encryption_key = "012345678901234567890123456789ab"
self.image_dir = os.path.join(self.test_dir,
"images")
self.pid_file = os.path.join(self.test_dir,
"api.pid")
self.pid_file = pid_file or os.path.join(self.test_dir,
"api.pid")
self.scrubber_datadir = os.path.join(self.test_dir,
"scrubber")
self.log_file = os.path.join(self.test_dir, "api.log")

View File

@ -21,6 +21,7 @@ import os
import signal
import socket
import sys
import tempfile
import time
from glance.tests import functional
@ -64,6 +65,45 @@ class TestGlanceControl(functional.FunctionalTest):
self.assertTrue('Connection refused' in exc_value or
'ECONNREFUSED' in exc_value)
def _do_test_fallback_pidfile(self, pid_file):
self.cleanup()
self.api_server.pid_file = pid_file
exitcode, out, err = self.api_server.start(expect_exit=True,
**self.__dict__.copy())
lines = out.split('\n')
warn = ('Falling back to a temp file, '
'you can stop glance-api service using:')
self.assertTrue(warn in lines)
fallback = lines[lines.index(warn) + 1].split()[-1]
self.assertTrue(os.path.exists(fallback))
self.api_server.pid_file = fallback
self.assertTrue(os.path.exists('/proc/%s' % self.get_pid()))
self.stop_server(self.api_server, 'API server')
@skip_if_disabled
def test_fallback_pidfile_uncreateable_dir(self):
"""
We test that glance-control falls back to a temporary pid file
for non-existent pid file directory that cannot be created.
"""
parent = tempfile.mkdtemp()
os.chmod(parent, 0)
pid_file = os.path.join(parent, 'pids', 'api.pid')
self._do_test_fallback_pidfile(pid_file)
@skip_if_disabled
def test_fallback_pidfile_unwriteable_dir(self):
"""
We test that glance-control falls back to a temporary pid file
for unwriteable pid file directory.
"""
parent = tempfile.mkdtemp()
os.chmod(parent, 0)
pid_file = os.path.join(parent, 'api.pid')
self._do_test_fallback_pidfile(pid_file)
@skip_if_disabled
def test_respawn(self):
"""