Fix for multiple status-set - related to bug 1588462

This change fixes the obvious race for a status_set() between
check_optional_interfaces() and assess_status() as the later calls the former
which calls status_set(), returns the status, which is then potentially set
again by the assess_status() function.  This cleans up the code so that only a
single status_set() is performed when calling assess_status().

Change-Id: I05e071d635819c516ab76684842a25a9c2f81d83
Related-Bug:#1588462
This commit is contained in:
Alex Kavanagh
2016-06-16 10:55:05 +00:00
parent a55b96215f
commit 0322be6cc6
2 changed files with 39 additions and 13 deletions

View File

@@ -24,7 +24,6 @@ from charmhelpers.core.hookenv import (
unit_private_ip,
is_relation_made,
relation_ids,
status_get,
)
from charmhelpers.core.templating import render
from charmhelpers.fetch import (
@@ -48,7 +47,6 @@ from charmhelpers.contrib.openstack.utils import (
git_src_dir,
git_pip_venv_dir,
get_hostname,
set_os_workload_status,
make_assess_status_func,
pause_unit,
resume_unit,
@@ -1202,10 +1200,29 @@ def git_post_install(projects_yaml):
neutron_vpn_agent_context, perms=0o644)
def check_optional_relations(configs):
required_interfaces = {}
def get_optional_interfaces():
"""Return the optional interfaces that should be checked if the relavent
relations have appeared.
:returns: {general_interface: [specific_int1, specific_int2, ...], ...}
"""
optional_interfaces = {}
if relation_ids('ha'):
optional_interfaces['ha'] = ['cluster']
return optional_interfaces
def check_optional_relations(configs):
"""Check that if we have a relation_id for high availability that we can
get the hacluster config. If we can't then we are blocked.
This function is called from assess_status/set_os_workload_status as the
charm_func and needs to return either "unknown", "" if there is no problem
or the status, message if there is a problem.
:param configs: an OSConfigRender() instance.
:return 2-tuple: (string, string) = (status, message)
"""
if relation_ids('ha'):
required_interfaces['ha'] = ['cluster']
try:
get_hacluster_config()
except:
@@ -1213,11 +1230,9 @@ def check_optional_relations(configs):
'hacluster missing configuration: '
'vip, vip_iface, vip_cidr')
if required_interfaces:
set_os_workload_status(configs, required_interfaces)
return status_get()
else:
return 'unknown', 'No optional relations'
# return 'unknown' as the lowest priority to not clobber an existing
# status.
return 'unknown', ''
def assess_status(configs):
@@ -1241,15 +1256,21 @@ def assess_status_func(configs):
Used directly by assess_status() and also for pausing and resuming
the unit.
NOTE: REQUIRED_INTERFACES is augmented with the optional interfaces
depending on the current config before being passed to the
make_assess_status_func() function.
NOTE(ajkavanagh) ports are not checked due to race hazards with services
that don't behave sychronously w.r.t their service scripts. e.g.
apache2.
@param configs: a templating.OSConfigRenderer() object
@return f() -> None : a function that assesses the unit's workload status
"""
required_interfaces = REQUIRED_INTERFACES.copy()
required_interfaces.update(get_optional_interfaces())
active_services = [s for s in services() if s not in STOPPED_SERVICES]
return make_assess_status_func(
configs, REQUIRED_INTERFACES,
configs, required_interfaces,
charm_func=check_optional_relations,
services=active_services, ports=None)

View File

@@ -1177,6 +1177,7 @@ class TestNeutronAgentReallocation(CharmTestCase):
asf.assert_called_once_with('test-config')
callee.assert_called_once_with()
@patch.object(neutron_utils, 'get_optional_interfaces')
@patch.object(neutron_utils, 'check_optional_relations')
@patch.object(neutron_utils, 'REQUIRED_INTERFACES')
@patch.object(neutron_utils, 'services')
@@ -1185,12 +1186,16 @@ class TestNeutronAgentReallocation(CharmTestCase):
make_assess_status_func,
services,
REQUIRED_INTERFACES,
check_optional_relations):
check_optional_relations,
get_optional_interfaces):
services.return_value = ['s1']
REQUIRED_INTERFACES.copy.return_value = {'int': ['test 1']}
get_optional_interfaces.return_value = {'opt': ['test 2']}
neutron_utils.assess_status_func('test-config')
# ports=None whilst port checks are disabled.
make_assess_status_func.assert_called_once_with(
'test-config', REQUIRED_INTERFACES,
'test-config',
{'int': ['test 1'], 'opt': ['test 2']},
charm_func=check_optional_relations, services=['s1'], ports=None)
def test_pause_unit_helper(self):