Refactor custom status checks

Refactor custom status checks to allow the charm specific class and
the inherited general charm type classes to specify status checks.

Change-Id: Ib40fa3b46cc629d5b3e5b199a70cff22451661ee
This commit is contained in:
Liam Young 2020-10-25 08:45:09 +00:00
parent 288e712c0a
commit ffbfa581c6
2 changed files with 113 additions and 13 deletions

View File

@ -51,6 +51,7 @@ class OSBaseCharm(CharmBase):
def __init__(self, framework): def __init__(self, framework):
super().__init__(framework) super().__init__(framework)
self.custom_status_checks = []
self._stored.set_default(is_started=False) self._stored.set_default(is_started=False)
self._stored.set_default(is_paused=False) self._stored.set_default(is_paused=False)
self._stored.set_default(series_upgrade=False) self._stored.set_default(series_upgrade=False)
@ -79,15 +80,47 @@ class OSBaseCharm(CharmBase):
def custom_status_check(self): def custom_status_check(self):
raise NotImplementedError raise NotImplementedError
def register_status_check(self, custom_check):
self.custom_status_checks.append(custom_check)
def update_status(self): def update_status(self):
"""Update the charms status
A charm, or plugin, can register checks to be run when calculating the
charms status. Each status method should have a unique name. The custom
check should return a StatusBase object. If the check returns an
ActiveStatus object then subsequent checks are run, if it returns
anything else then the charms status is set to the object the check
returned and no subsequent checks are run. If the check returns an
ActiveStatus with a specific message then this message will be
concatenated with the other active status messages.
Example::
class MyCharm(OSBaseCharm):
def __init__(self, framework):
super().__init__(framework)
super().register_status_check(self.mycharm_check)
def mycharm_check(self):
if self.model.config['plugin-check-fail'] == 'True':
return BlockedStatus(
'Plugin Custom check failed')
else:
return ActiveStatus()
"""
logging.info("Updating status") logging.info("Updating status")
try: active_messages = ['Unit is ready']
# Custom checks return True if the checked passed else False. for check in self.custom_status_checks:
# If the check failed the custom check will have set the status. _result = check()
if not self.custom_status_check(): if isinstance(_result, ActiveStatus):
if _result.message:
active_messages.append(_result.message)
else:
self.unit.status = _result
return return
except NotImplementedError:
pass
if self._stored.series_upgrade: if self._stored.series_upgrade:
self.unit.status = BlockedStatus( self.unit.status = BlockedStatus(
'Ready for do-release-upgrade and reboot. ' 'Ready for do-release-upgrade and reboot. '
@ -106,7 +139,16 @@ class OSBaseCharm(CharmBase):
'Missing relations: {}'.format(', '.join(missing_relations))) 'Missing relations: {}'.format(', '.join(missing_relations)))
return return
if self._stored.is_started: if self._stored.is_started:
self.unit.status = ActiveStatus('Unit is ready') _unique = []
# Reverse sort the list so that a shorter message that has the same
# start as a longer message comes first and can then be omitted.
# eg 'Unit is ready' comes after 'Unit is ready and clustered'
# and 'Unit is ready' is dropped.
for msg in sorted(list(set(active_messages)), reverse=True):
dupes = [m for m in _unique if m.startswith(msg)]
if not dupes:
_unique.append(msg)
self.unit.status = ActiveStatus(', '.join(_unique))
else: else:
self.unit.status = WaitingStatus('Charm configuration in progress') self.unit.status = WaitingStatus('Charm configuration in progress')
logging.info("Status updated") logging.info("Status updated")

View File

@ -29,7 +29,36 @@ from ops.model import (
import ops_openstack.core import ops_openstack.core
class OpenStackTestAPICharm(ops_openstack.core.OSBaseCharm): class OpenStackTestPlugin1(ops_openstack.core.OSBaseCharm):
def __init__(self, framework):
super().__init__(framework)
super().register_status_check(self.plugin1_status_check)
def plugin1_status_check(self):
if self.model.config.get('plugin1-check-fail', 'False') == 'True':
return BlockedStatus(
'Plugin1 Custom check failed')
else:
return ActiveStatus('Unit is ready and awesome')
class OpenStackTestPlugin2(ops_openstack.core.OSBaseCharm):
def __init__(self, framework):
super().__init__(framework)
super().register_status_check(self.plugin2_status_check)
def plugin2_status_check(self):
if self.model.config.get('plugin2-check-fail', 'False') == 'True':
return BlockedStatus(
'Plugin2 Custom check failed')
else:
return ActiveStatus('Unit is ready and super')
class OpenStackTestAPICharm(OpenStackTestPlugin1,
OpenStackTestPlugin2):
PACKAGES = ['keystone-common'] PACKAGES = ['keystone-common']
REQUIRED_RELATIONS = ['shared-db'] REQUIRED_RELATIONS = ['shared-db']
@ -38,13 +67,15 @@ class OpenStackTestAPICharm(ops_openstack.core.OSBaseCharm):
'/etc/f2.conf': ['apache2', 'ks-api'], '/etc/f2.conf': ['apache2', 'ks-api'],
'/etc/f3.conf': []} '/etc/f3.conf': []}
def __init__(self, framework):
super().__init__(framework)
super().register_status_check(self.custom_status_check)
def custom_status_check(self): def custom_status_check(self):
if self.model.config.get('custom-check-fail', 'False') == 'True': if self.model.config.get('custom-check-fail', 'False') == 'True':
self.unit.status = MaintenanceStatus( return MaintenanceStatus('Custom check failed')
'Custom check failed')
return False
else: else:
return True return ActiveStatus()
class CharmTestCase(unittest.TestCase): class CharmTestCase(unittest.TestCase):
@ -131,7 +162,7 @@ class TestOSBaseCharm(CharmTestCase):
self.harness.charm.on.update_status.emit() self.harness.charm.on.update_status.emit()
self.assertEqual( self.assertEqual(
self.harness.charm.unit.status.message, self.harness.charm.unit.status.message,
'Unit is ready') 'Unit is ready and super, Unit is ready and awesome')
self.assertIsInstance( self.assertIsInstance(
self.harness.charm.unit.status, self.harness.charm.unit.status,
ActiveStatus) ActiveStatus)
@ -195,6 +226,33 @@ class TestOSBaseCharm(CharmTestCase):
self.harness.charm.unit.status, self.harness.charm.unit.status,
BlockedStatus) BlockedStatus)
def test_update_status_plugin_check_fail(self):
self.harness.update_config(
key_values={
'plugin1-check-fail': 'True',
'plugin2-check-fail': 'False'})
self.harness.add_relation('shared-db', 'mysql')
self.harness.begin()
self.harness.charm._stored.is_started = True
self.harness.charm.on.update_status.emit()
self.assertEqual(
self.harness.charm.unit.status.message,
'Plugin1 Custom check failed')
self.assertIsInstance(
self.harness.charm.unit.status,
BlockedStatus)
self.harness.update_config(
key_values={
'plugin1-check-fail': 'False',
'plugin2-check-fail': 'True'})
self.harness.charm.on.update_status.emit()
self.assertEqual(
self.harness.charm.unit.status.message,
'Plugin2 Custom check failed')
self.assertIsInstance(
self.harness.charm.unit.status,
BlockedStatus)
def test_services(self): def test_services(self):
self.harness.begin() self.harness.begin()
self.assertEqual( self.assertEqual(