Change assess_status() method to be deferred and run once.

This change makes the assess_status() method to be deferred until the
end of the hook execution.  This is to ensure that the (expensive)
assess_status() functionality is only run once, even though it may be
called multiple times from reactive handlers that need to update the
status on the charm.

This uses the hookenv.atexit() function to queue an function that calls
_assess_status() on the charm singleton after all the reactive handlers
have run.  If no handler calls the assess_status() method then the
_assess_status() 'real' method won't be called for that hook invocation.

Change-Id: I5d405446761a646585dfa1c446009e4374c01000
This commit is contained in:
Alex Kavanagh
2017-05-11 12:26:05 -04:00
parent e67bce05d4
commit 1e3095124e
3 changed files with 42 additions and 7 deletions

View File

@@ -282,14 +282,22 @@ state, and override the `assess_status()` method to a NOP.
The `assess_status()` method on `OpenStackCharm` provides a helper to enable
the charm author to provide workload status. By default:
* The actual assessment of status is deferred until the all of the reactive
handlers have had a chance to execute (according to their conditions), just
before the charm hook exits. The real `assess_status()` method is actually
`_assess_status()` and the `assess_status()` method simply sets up an
`atexit()` hook to defer the operation. This means that you can call
`assess_status()` multiple times BUT it will actually only be invoked at the
end of the charm hook execution. If you _need_ to actually run
assess_status() at the point in the handler, then call `_assess_status()`.
* The install method provides the maintenance status.
* The `layer-openstack` layer provides a hook for `update-status` which
calls the `assess_status()` function on the charm class.
* The `assess_status()` method uses various attributes of the class to provide
* The `_assess_status()` method uses various attributes of the class to provide
a default mechanism for assessing the workload status of the charm/unit.
The latter is extremely useful for determining the workload status. The
`assess_status()` method does the following checks:
`_assess_status()` method does the following checks:
1. The unit checks if it is paused. (Not yet available as a feature).
2. The unit checks the relations to see if they are connected and available.
@@ -340,7 +348,7 @@ class. The return value from the method is:
### Not checking services are running
By default, the `assess_status()` method checks that the services declared in
By default, the `_assess_status()` method checks that the services declared in
the class attribute `services` (list of strings) are checked to ensure that
they are running. Additionally, the ports declared in the class attribute
`api_ports` are also checked for being _listened on_.

View File

@@ -568,6 +568,7 @@ class OpenStackCharm(object):
self.__adapters_instance = None
self.__interfaces = interfaces or []
self.__options = None
self.__run_assess_status = False
@property
def adapters_instance(self):
@@ -901,7 +902,7 @@ class OpenStackCharm(object):
"""
pass
def assess_status(self):
def _assess_status(self):
"""Assess the status of the unit and set the status and a useful
message as appropriate.
@@ -939,6 +940,20 @@ class OpenStackCharm(object):
# No state was particularly set, so assume the unit is active
hookenv.status_set('active', 'Unit is ready')
def assess_status(self):
"""This is a deferring version of _assess_status that only runs during
exit. This method can be called multiple times, but it will ensure that
the _assess_status() is only called once at the end of the charm after
all handlers have completed.
"""
if not self.__run_assess_status:
self.__run_assess_status = True
def atexit_assess_status():
hookenv.log("Running _assess_status()", level=hookenv.DEBUG)
self._assess_status()
hookenv.atexit(atexit_assess_status)
def custom_assess_status_check(self):
"""Override this function in a derived class if there are any other
status checks that need to be done that aren't about relations, etc.

View File

@@ -1442,6 +1442,18 @@ class TestMyOpenStackCharm(BaseOpenStackCharmTest):
for call in self.render.call_args_list:
self.assertTrue(call[1]['context'])
def test_deferred_assess_status(self):
self.patch_object(chm.hookenv, 'atexit')
s = self.target.singleton
self.patch_target('_assess_status')
s.assess_status()
self._assess_status.assert_not_called()
self.atexit.assert_called_once_with(mock.ANY)
self.atexit.reset_mock()
s.assess_status()
self.atexit.assert_not_called()
self._assess_status.assert_not_called()
def test_assess_status_active(self):
self.patch_object(chm.hookenv, 'status_set')
self.patch_object(chm.hookenv, 'application_version_set')
@@ -1451,7 +1463,7 @@ class TestMyOpenStackCharm(BaseOpenStackCharmTest):
self.patch_target('custom_assess_status_check',
return_value=(None, None))
self.patch_target('check_services_running', return_value=(None, None))
self.target.assess_status()
self.target._assess_status()
self.status_set.assert_called_once_with('active', 'Unit is ready')
self.application_version_set.assert_called_once_with(mock.ANY)
# check all the check functions got called
@@ -1466,7 +1478,7 @@ class TestMyOpenStackCharm(BaseOpenStackCharmTest):
# patch out _ows_check_if_paused
self.patch_object(chm.os_utils, '_ows_check_if_paused',
return_value=('paused', '123'))
self.target.assess_status()
self.target._assess_status()
self.status_set.assert_called_once_with('paused', '123')
self.application_version_set.assert_called_once_with(mock.ANY)
self._ows_check_if_paused.assert_called_once_with(
@@ -1525,7 +1537,7 @@ class TestMyOpenStackCharm(BaseOpenStackCharmTest):
self.assertEqual(self.target.check_interfaces(),
('blocked', "'rel1' incomplete, 'rel2' missing"))
# check that the assess_status give the same result
self.target.assess_status()
self.target._assess_status()
self.status_set.assert_called_once_with(
'blocked', "'rel1' incomplete, 'rel2' missing")