[1chb1n,r=james-page] Resync helpers and update amulet tests to avoid config-changed restart races.

This commit is contained in:
James Page
2015-09-09 08:45:33 +01:00
5 changed files with 117 additions and 64 deletions

View File

@@ -9,7 +9,7 @@ lint:
test: test:
@# Bundletester expects unit tests here. @# Bundletester expects unit tests here.
@echo Starting unit tests... @echo Starting unit tests...
@$(PYTHON) /usr/bin/nosetests --nologcapture --with-coverage unit_tests @$(PYTHON) /usr/bin/nosetests -v --nologcapture --with-coverage unit_tests
functional_test: functional_test:
@echo Starting Amulet tests... @echo Starting Amulet tests...

View File

@@ -4,11 +4,13 @@ set -ex
sudo add-apt-repository --yes ppa:juju/stable sudo add-apt-repository --yes ppa:juju/stable
sudo apt-get update --yes sudo apt-get update --yes
sudo apt-get install --yes python-amulet \ sudo apt-get install --yes amulet \
python-cinderclient \ python-cinderclient \
python-distro-info \ python-distro-info \
python-glanceclient \ python-glanceclient \
python-heatclient \ python-heatclient \
python-keystoneclient \ python-keystoneclient \
python-neutronclient \
python-novaclient \ python-novaclient \
python-pika \
python-swiftclient python-swiftclient

View File

@@ -456,28 +456,30 @@ class KeystoneBasicDeployment(OpenStackAmuletDeployment):
set_default = {'use-syslog': 'False'} set_default = {'use-syslog': 'False'}
set_alternate = {'use-syslog': 'True'} set_alternate = {'use-syslog': 'True'}
# Config file affected by juju set config change # Services which are expected to restart upon config change,
conf_file = '/etc/keystone/keystone.conf' # and corresponding config files affected by the change
services = {'keystone-all': '/etc/keystone/keystone.conf'}
# Services which are expected to restart upon config change
services = ['keystone-all']
# Make config change, check for service restarts # Make config change, check for service restarts
u.log.debug('Making config change on {}...'.format(juju_service)) u.log.debug('Making config change on {}...'.format(juju_service))
mtime = u.get_sentry_time(sentry)
self.d.configure(juju_service, set_alternate) self.d.configure(juju_service, set_alternate)
sleep_time = 30 sleep_time = 30
for s in services: for s, conf_file in services.iteritems():
u.log.debug("Checking that service restarted: {}".format(s)) u.log.debug("Checking that service restarted: {}".format(s))
if not u.service_restarted(sentry, s, if not u.validate_service_config_changed(sentry, mtime, s,
conf_file, sleep_time=sleep_time): conf_file,
sleep_time=sleep_time):
self.d.configure(juju_service, set_default) self.d.configure(juju_service, set_default)
msg = "service {} didn't restart after config change".format(s) msg = "service {} didn't restart after config change".format(s)
amulet.raise_status(amulet.FAIL, msg=msg) amulet.raise_status(amulet.FAIL, msg=msg)
sleep_time = 0
self.d.configure(juju_service, set_default) self.d.configure(juju_service, set_default)
u.log.debug('OK')
def test_901_pause_resume(self): def test_901_pause_resume(self):
"""Test pause and resume actions.""" """Test pause and resume actions."""
unit_name = "keystone/0" unit_name = "keystone/0"

View File

@@ -114,7 +114,7 @@ class AmuletUtils(object):
# /!\ DEPRECATION WARNING (beisner): # /!\ DEPRECATION WARNING (beisner):
# New and existing tests should be rewritten to use # New and existing tests should be rewritten to use
# validate_services_by_name() as it is aware of init systems. # validate_services_by_name() as it is aware of init systems.
self.log.warn('/!\\ DEPRECATION WARNING: use ' self.log.warn('DEPRECATION WARNING: use '
'validate_services_by_name instead of validate_services ' 'validate_services_by_name instead of validate_services '
'due to init system differences.') 'due to init system differences.')
@@ -269,33 +269,52 @@ class AmuletUtils(object):
"""Get last modification time of directory.""" """Get last modification time of directory."""
return sentry_unit.directory_stat(directory)['mtime'] return sentry_unit.directory_stat(directory)['mtime']
def _get_proc_start_time(self, sentry_unit, service, pgrep_full=False): def _get_proc_start_time(self, sentry_unit, service, pgrep_full=None):
"""Get process' start time. """Get start time of a process based on the last modification time
of the /proc/pid directory.
Determine start time of the process based on the last modification :sentry_unit: The sentry unit to check for the service on
time of the /proc/pid directory. If pgrep_full is True, the process :service: service name to look for in process table
name is matched against the full command line. :pgrep_full: [Deprecated] Use full command line search mode with pgrep
""" :returns: epoch time of service process start
if pgrep_full: :param commands: list of bash commands
cmd = 'pgrep -o -f {}'.format(service) :param sentry_units: list of sentry unit pointers
else: :returns: None if successful; Failure message otherwise
cmd = 'pgrep -o {}'.format(service) """
cmd = cmd + ' | grep -v pgrep || exit 0' if pgrep_full is not None:
cmd_out = sentry_unit.run(cmd) # /!\ DEPRECATION WARNING (beisner):
self.log.debug('CMDout: ' + str(cmd_out)) # No longer implemented, as pidof is now used instead of pgrep.
if cmd_out[0]: # https://bugs.launchpad.net/charm-helpers/+bug/1474030
self.log.debug('Pid for %s %s' % (service, str(cmd_out[0]))) self.log.warn('DEPRECATION WARNING: pgrep_full bool is no '
proc_dir = '/proc/{}'.format(cmd_out[0].strip()) 'longer implemented re: lp 1474030.')
return self._get_dir_mtime(sentry_unit, proc_dir)
pid_list = self.get_process_id_list(sentry_unit, service)
pid = pid_list[0]
proc_dir = '/proc/{}'.format(pid)
self.log.debug('Pid for {} on {}: {}'.format(
service, sentry_unit.info['unit_name'], pid))
return self._get_dir_mtime(sentry_unit, proc_dir)
def service_restarted(self, sentry_unit, service, filename, def service_restarted(self, sentry_unit, service, filename,
pgrep_full=False, sleep_time=20): pgrep_full=None, sleep_time=20):
"""Check if service was restarted. """Check if service was restarted.
Compare a service's start time vs a file's last modification time Compare a service's start time vs a file's last modification time
(such as a config file for that service) to determine if the service (such as a config file for that service) to determine if the service
has been restarted. has been restarted.
""" """
# /!\ DEPRECATION WARNING (beisner):
# This method is prone to races in that no before-time is known.
# Use validate_service_config_changed instead.
# NOTE(beisner) pgrep_full is no longer implemented, as pidof is now
# used instead of pgrep. pgrep_full is still passed through to ensure
# deprecation WARNS. lp1474030
self.log.warn('DEPRECATION WARNING: use '
'validate_service_config_changed instead of '
'service_restarted due to known races.')
time.sleep(sleep_time) time.sleep(sleep_time)
if (self._get_proc_start_time(sentry_unit, service, pgrep_full) >= if (self._get_proc_start_time(sentry_unit, service, pgrep_full) >=
self._get_file_mtime(sentry_unit, filename)): self._get_file_mtime(sentry_unit, filename)):
@@ -304,15 +323,15 @@ class AmuletUtils(object):
return False return False
def service_restarted_since(self, sentry_unit, mtime, service, def service_restarted_since(self, sentry_unit, mtime, service,
pgrep_full=False, sleep_time=20, pgrep_full=None, sleep_time=20,
retry_count=2): retry_count=2, retry_sleep_time=30):
"""Check if service was been started after a given time. """Check if service was been started after a given time.
Args: Args:
sentry_unit (sentry): The sentry unit to check for the service on sentry_unit (sentry): The sentry unit to check for the service on
mtime (float): The epoch time to check against mtime (float): The epoch time to check against
service (string): service name to look for in process table service (string): service name to look for in process table
pgrep_full (boolean): Use full command line search mode with pgrep pgrep_full: [Deprecated] Use full command line search mode with pgrep
sleep_time (int): Seconds to sleep before looking for process sleep_time (int): Seconds to sleep before looking for process
retry_count (int): If service is not found, how many times to retry retry_count (int): If service is not found, how many times to retry
@@ -321,30 +340,44 @@ class AmuletUtils(object):
False if service is older than mtime or if service was False if service is older than mtime or if service was
not found. not found.
""" """
self.log.debug('Checking %s restarted since %s' % (service, mtime)) # NOTE(beisner) pgrep_full is no longer implemented, as pidof is now
# used instead of pgrep. pgrep_full is still passed through to ensure
# deprecation WARNS. lp1474030
unit_name = sentry_unit.info['unit_name']
self.log.debug('Checking that %s service restarted since %s on '
'%s' % (service, mtime, unit_name))
time.sleep(sleep_time) time.sleep(sleep_time)
proc_start_time = self._get_proc_start_time(sentry_unit, service, proc_start_time = None
pgrep_full) tries = 0
while retry_count > 0 and not proc_start_time: while tries <= retry_count and not proc_start_time:
self.log.debug('No pid file found for service %s, will retry %i ' try:
'more times' % (service, retry_count)) proc_start_time = self._get_proc_start_time(sentry_unit,
time.sleep(30) service,
proc_start_time = self._get_proc_start_time(sentry_unit, service, pgrep_full)
pgrep_full) self.log.debug('Attempt {} to get {} proc start time on {} '
retry_count = retry_count - 1 'OK'.format(tries, service, unit_name))
except IOError:
# NOTE(beisner) - race avoidance, proc may not exist yet.
# https://bugs.launchpad.net/charm-helpers/+bug/1474030
self.log.debug('Attempt {} to get {} proc start time on {} '
'failed'.format(tries, service, unit_name))
time.sleep(retry_sleep_time)
tries += 1
if not proc_start_time: if not proc_start_time:
self.log.warn('No proc start time found, assuming service did ' self.log.warn('No proc start time found, assuming service did '
'not start') 'not start')
return False return False
if proc_start_time >= mtime: if proc_start_time >= mtime:
self.log.debug('proc start time is newer than provided mtime' self.log.debug('Proc start time is newer than provided mtime'
'(%s >= %s)' % (proc_start_time, mtime)) '(%s >= %s) on %s (OK)' % (proc_start_time,
mtime, unit_name))
return True return True
else: else:
self.log.warn('proc start time (%s) is older than provided mtime ' self.log.warn('Proc start time (%s) is older than provided mtime '
'(%s), service did not restart' % (proc_start_time, '(%s) on %s, service did not '
mtime)) 'restart' % (proc_start_time, mtime, unit_name))
return False return False
def config_updated_since(self, sentry_unit, filename, mtime, def config_updated_since(self, sentry_unit, filename, mtime,
@@ -361,12 +394,15 @@ class AmuletUtils(object):
bool: True if file was modified more recently than mtime, False if bool: True if file was modified more recently than mtime, False if
file was modified before mtime, file was modified before mtime,
""" """
self.log.debug('Checking %s updated since %s' % (filename, mtime)) self.log.debug('Checking that %s file updated since '
'%s' % (filename, mtime))
unit_name = sentry_unit.info['unit_name']
time.sleep(sleep_time) time.sleep(sleep_time)
file_mtime = self._get_file_mtime(sentry_unit, filename) file_mtime = self._get_file_mtime(sentry_unit, filename)
if file_mtime >= mtime: if file_mtime >= mtime:
self.log.debug('File mtime is newer than provided mtime ' self.log.debug('File mtime is newer than provided mtime '
'(%s >= %s)' % (file_mtime, mtime)) '(%s >= %s) on %s (OK)' % (file_mtime, mtime,
unit_name))
return True return True
else: else:
self.log.warn('File mtime %s is older than provided mtime %s' self.log.warn('File mtime %s is older than provided mtime %s'
@@ -374,8 +410,9 @@ class AmuletUtils(object):
return False return False
def validate_service_config_changed(self, sentry_unit, mtime, service, def validate_service_config_changed(self, sentry_unit, mtime, service,
filename, pgrep_full=False, filename, pgrep_full=None,
sleep_time=20, retry_count=2): sleep_time=20, retry_count=2,
retry_sleep_time=30):
"""Check service and file were updated after mtime """Check service and file were updated after mtime
Args: Args:
@@ -383,9 +420,10 @@ class AmuletUtils(object):
mtime (float): The epoch time to check against mtime (float): The epoch time to check against
service (string): service name to look for in process table service (string): service name to look for in process table
filename (string): The file to check mtime of filename (string): The file to check mtime of
pgrep_full (boolean): Use full command line search mode with pgrep pgrep_full: [Deprecated] Use full command line search mode with pgrep
sleep_time (int): Seconds to sleep before looking for process sleep_time (int): Initial sleep in seconds to pass to test helpers
retry_count (int): If service is not found, how many times to retry retry_count (int): If service is not found, how many times to retry
retry_sleep_time (int): Time in seconds to wait between retries
Typical Usage: Typical Usage:
u = OpenStackAmuletUtils(ERROR) u = OpenStackAmuletUtils(ERROR)
@@ -402,15 +440,25 @@ class AmuletUtils(object):
mtime, False if service is older than mtime or if service was mtime, False if service is older than mtime or if service was
not found or if filename was modified before mtime. not found or if filename was modified before mtime.
""" """
self.log.debug('Checking %s restarted since %s' % (service, mtime))
time.sleep(sleep_time) # NOTE(beisner) pgrep_full is no longer implemented, as pidof is now
service_restart = self.service_restarted_since(sentry_unit, mtime, # used instead of pgrep. pgrep_full is still passed through to ensure
service, # deprecation WARNS. lp1474030
pgrep_full=pgrep_full,
sleep_time=0, service_restart = self.service_restarted_since(
retry_count=retry_count) sentry_unit, mtime,
config_update = self.config_updated_since(sentry_unit, filename, mtime, service,
sleep_time=0) pgrep_full=pgrep_full,
sleep_time=sleep_time,
retry_count=retry_count,
retry_sleep_time=retry_sleep_time)
config_update = self.config_updated_since(
sentry_unit,
filename,
mtime,
sleep_time=0)
return service_restart and config_update return service_restart and config_update
def get_sentry_time(self, sentry_unit): def get_sentry_time(self, sentry_unit):

View File

@@ -8,11 +8,12 @@ sources:
- ppa:juju/stable - ppa:juju/stable
packages: packages:
- amulet - amulet
- python-amulet
- python-cinderclient - python-cinderclient
- python-distro-info - python-distro-info
- python-glanceclient - python-glanceclient
- python-heatclient - python-heatclient
- python-keystoneclient - python-keystoneclient
- python-neutronclient
- python-novaclient - python-novaclient
- python-pika
- python-swiftclient - python-swiftclient