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):
super().__init__(framework)
self.custom_status_checks = []
self._stored.set_default(is_started=False)
self._stored.set_default(is_paused=False)
self._stored.set_default(series_upgrade=False)
@ -79,15 +80,47 @@ class OSBaseCharm(CharmBase):
def custom_status_check(self):
raise NotImplementedError
def register_status_check(self, custom_check):
self.custom_status_checks.append(custom_check)
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")
try:
# Custom checks return True if the checked passed else False.
# If the check failed the custom check will have set the status.
if not self.custom_status_check():
active_messages = ['Unit is ready']
for check in self.custom_status_checks:
_result = check()
if isinstance(_result, ActiveStatus):
if _result.message:
active_messages.append(_result.message)
else:
self.unit.status = _result
return
except NotImplementedError:
pass
if self._stored.series_upgrade:
self.unit.status = BlockedStatus(
'Ready for do-release-upgrade and reboot. '
@ -106,7 +139,16 @@ class OSBaseCharm(CharmBase):
'Missing relations: {}'.format(', '.join(missing_relations)))
return
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:
self.unit.status = WaitingStatus('Charm configuration in progress')
logging.info("Status updated")

View File

@ -29,7 +29,36 @@ from ops.model import (
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']
REQUIRED_RELATIONS = ['shared-db']
@ -38,13 +67,15 @@ class OpenStackTestAPICharm(ops_openstack.core.OSBaseCharm):
'/etc/f2.conf': ['apache2', 'ks-api'],
'/etc/f3.conf': []}
def __init__(self, framework):
super().__init__(framework)
super().register_status_check(self.custom_status_check)
def custom_status_check(self):
if self.model.config.get('custom-check-fail', 'False') == 'True':
self.unit.status = MaintenanceStatus(
'Custom check failed')
return False
return MaintenanceStatus('Custom check failed')
else:
return True
return ActiveStatus()
class CharmTestCase(unittest.TestCase):
@ -131,7 +162,7 @@ class TestOSBaseCharm(CharmTestCase):
self.harness.charm.on.update_status.emit()
self.assertEqual(
self.harness.charm.unit.status.message,
'Unit is ready')
'Unit is ready and super, Unit is ready and awesome')
self.assertIsInstance(
self.harness.charm.unit.status,
ActiveStatus)
@ -195,6 +226,33 @@ class TestOSBaseCharm(CharmTestCase):
self.harness.charm.unit.status,
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):
self.harness.begin()
self.assertEqual(