Added assess_status() functionality - no tests yet
This adds the assess_status functionality, but without any tests yet. It also adds a register function to enable the release to be specified before an OpenStackCharm class is instantiated, thus providing the mechanism to choose the class to use for a release (or series of releases). This was missing from earlier commits.
This commit is contained in:
parent
7bb4d57eeb
commit
659403d17c
@ -8,6 +8,7 @@ import os
|
||||
import subprocess
|
||||
import contextlib
|
||||
import collections
|
||||
import itertools
|
||||
|
||||
import six
|
||||
|
||||
@ -34,6 +35,11 @@ _releases = {}
|
||||
# hook invocation.
|
||||
_singleton = None
|
||||
|
||||
# `_release_selector_function` holds a function that takes optionally takes a
|
||||
# release and commutes it to another release or just returns a release.
|
||||
# This is to enable the defining code to define which release is used.
|
||||
_release_selector_function = None
|
||||
|
||||
# List of releases that OpenStackCharm based charms know about
|
||||
KNOWN_RELEASES = [
|
||||
'diablo',
|
||||
@ -75,6 +81,10 @@ def get_charm_instance(release=None, *args, **kwargs):
|
||||
"Release {} is not supported by this charm. Earliest support is "
|
||||
"{} release".format(release, known_releases[0]))
|
||||
else:
|
||||
# check that the release is a valid release
|
||||
if release not in KNOWN_RELEASES:
|
||||
raise RuntimeError(
|
||||
"Release {} is not a known OpenStack release?".format(release))
|
||||
# try to find the release that is supported.
|
||||
for known_release in reversed(known_releases):
|
||||
if release >= known_release:
|
||||
@ -85,6 +95,30 @@ def get_charm_instance(release=None, *args, **kwargs):
|
||||
return cls(release=release, *args, **kwargs)
|
||||
|
||||
|
||||
def register_os_release_selector(f):
|
||||
"""Register a function that determines what the release is for the
|
||||
invocation run. This allows the charm to define HOW the release is
|
||||
determined.
|
||||
|
||||
Usage:
|
||||
|
||||
@register_os_release_selector
|
||||
def my_release_selector():
|
||||
return os_release_chooser()
|
||||
|
||||
The function should return a string which is an OS release.
|
||||
"""
|
||||
global _release_selector_function
|
||||
if _release_selector_function is None:
|
||||
# we can only do this once in a system invocation.
|
||||
_release_selector_function = f
|
||||
else:
|
||||
raise RuntimeError(
|
||||
"Only a single release_selector_function is supported."
|
||||
" Called with {}".format(f.__name__))
|
||||
return f
|
||||
|
||||
|
||||
class OpenStackCharmMeta(type):
|
||||
"""Metaclass to provide a classproperty of 'singleton' so that class
|
||||
methods in the derived OpenStackCharm() class can simply use cls.singleton
|
||||
@ -141,8 +175,17 @@ class OpenStackCharmMeta(type):
|
||||
|
||||
@property
|
||||
def singleton(cls):
|
||||
"""Either returns the already created charm, or create a new one.
|
||||
|
||||
This uses the _release_selector_function to choose the release is one
|
||||
has been registered, otherwise None is passed to get_charm_instance()
|
||||
"""
|
||||
global _singleton
|
||||
if _singleton is None:
|
||||
release = None
|
||||
# see if a _release_selector_function has been registered.
|
||||
if _release_selector_function is not None:
|
||||
release = _release_selector_function()
|
||||
_singleton = get_charm_instance()
|
||||
return _singleton
|
||||
|
||||
@ -187,6 +230,10 @@ class OpenStackCharm(object):
|
||||
# }
|
||||
restart_map = {}
|
||||
|
||||
# The list of required services that are checked for assess_status
|
||||
# e.g. required_relations = ['identity-service', 'shared-db']
|
||||
required_relations = []
|
||||
|
||||
# The command used to sync the database
|
||||
sync_cmd = []
|
||||
|
||||
@ -226,11 +273,9 @@ class OpenStackCharm(object):
|
||||
if packages:
|
||||
hookenv.status_set('maintenance', 'Installing packages')
|
||||
charmhelpers.fetch.apt_install(packages, fatal=True)
|
||||
# TODO need a call to assess_status(...) or equivalent so that we
|
||||
# can determine the workload status at the end of the handler. At
|
||||
# the end of install the 'status' is stuck in maintenance until the
|
||||
# next hook is run.
|
||||
self.set_state('{}-installed'.format(self.name))
|
||||
hookenv.status_set('maintenance',
|
||||
'Installation complete - awaiting next status')
|
||||
|
||||
def set_state(self, state, value=None):
|
||||
"""proxy for charms.reactive.bus.set_state()"""
|
||||
@ -379,3 +424,159 @@ class OpenStackCharm(object):
|
||||
# Restart services immediatly after db sync as
|
||||
# render_domain_config needs a working system
|
||||
self.restart_all()
|
||||
|
||||
def assess_status(self):
|
||||
"""Assess the status of the unit and set the status and a useful
|
||||
message as appropriate.
|
||||
|
||||
The 3 checks are:
|
||||
|
||||
1. Check if the unit has been paused (using
|
||||
os_utils.is_unit_paused_set().
|
||||
2. Check if the interfaces are all present (using the states that are
|
||||
set by each interface as it comes 'live'.
|
||||
3. Do a custom_assess_status_check() check.
|
||||
4. Check that services that should be running are running.
|
||||
|
||||
Each sub-function determins what checks are taking place.
|
||||
|
||||
If custom assess_status() functionality is required then the derived
|
||||
class should override any of the 4 check functions to alter the
|
||||
behaviour as required.
|
||||
|
||||
Note that if ports are NOT to be checked, then the derived class should
|
||||
override :meth:`ports_to_check()` and return an empty list.
|
||||
|
||||
SIDE EFFECT: this function calls status_set(state, message) to set the
|
||||
workload status in juju.
|
||||
"""
|
||||
for f in [self.check_if_paused,
|
||||
self.check_interfaces,
|
||||
self.custom_assess_status_check,
|
||||
self.check_services_running]:
|
||||
state, message = f()
|
||||
if state is not None:
|
||||
hookenv.status_set(state, message)
|
||||
return
|
||||
# No state was particularly set, so assume the unit is active
|
||||
hookenv.state_set('active', 'Unit is ready')
|
||||
|
||||
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.
|
||||
|
||||
Return (None, None) if the status is okay (i.e. the unit is active).
|
||||
Return ('active', message) do shortcut and force the unit to the active
|
||||
status.
|
||||
Return (other_status, message) to set the status to desired state.
|
||||
|
||||
:returns: None, None - no action in this function.
|
||||
"""
|
||||
return None, None
|
||||
|
||||
def check_if_paused(self):
|
||||
"""Check if the unit is paused and return either the paused status,
|
||||
message or None, None if the unit is not paused. If the unit is paused
|
||||
but a service is incorrectly running, then the function returns a
|
||||
broken status.
|
||||
|
||||
:returns: (status, message) or (None, None)
|
||||
"""
|
||||
return os_utils._ows_check_if_paused(
|
||||
services=self.services,
|
||||
ports=self.ports_to_check(self.api_ports))
|
||||
|
||||
def check_interfaces(self):
|
||||
"""Check that the required interfaces have both connected and availble
|
||||
states set.
|
||||
|
||||
This requires a convention from the OS interfaces that they set the
|
||||
'{relation_name}.connected' state on connection, and the
|
||||
'{relation_name}.available' state when the connection information is
|
||||
available and the interface is ready to go.
|
||||
|
||||
The interfaces (relations) that are checked are named in
|
||||
self.required_relations which is a list of strings representing the
|
||||
generic relation name. e.g. 'identity-service' rather than 'keystone'.
|
||||
|
||||
Returns (None, None) if the interfaces are okay, or a status, message
|
||||
if any of the interfaces are not ready.
|
||||
|
||||
Derived classes can augment/alter the checks done by overriding the
|
||||
companion method :property:`states_to_check` which converts a relation
|
||||
into the states to confirm existence, along with the error message.
|
||||
|
||||
:returns (status, message) or (None, None)
|
||||
"""
|
||||
states_to_check = self.states_to_check
|
||||
# bail if there is nothing to do.
|
||||
if not states_to_check:
|
||||
return None, None
|
||||
available_states = charms.reactive.bus.get_states().keys()
|
||||
status = None
|
||||
messages = []
|
||||
for relation, states in states_to_check.items():
|
||||
for state, err_status, err_msg in states:
|
||||
if state not in available_states:
|
||||
messages.append(err_msg)
|
||||
status = os_utils.workload_state_compare(status,
|
||||
err_status)
|
||||
# as soon as we error on a relation, skip to the next one.
|
||||
break
|
||||
if status is not None:
|
||||
return status, ", ".join(messages)
|
||||
# Everything is fine.
|
||||
return None, None
|
||||
|
||||
@property
|
||||
def states_to_check(self):
|
||||
"""Construct a default set of connected and available states for each
|
||||
of the relations passed, along with error messages and new status
|
||||
conditions if they are missing.
|
||||
|
||||
The method returns a {relation: [(state, err_status, err_msg), (...),]}
|
||||
This corresponds to the relation, the state to check for, the error
|
||||
status to set if that state is missing, and the message to show if the
|
||||
state is missing.
|
||||
|
||||
The list of tuples is evaulated in order for each relation, and stops
|
||||
after the first failure. This means that it doesn't check (say)
|
||||
available if connected is not available.
|
||||
"""
|
||||
states_to_check = {
|
||||
relation: [("{}.connected".format(relation),
|
||||
"blocked",
|
||||
"'{}' missing".format(relation)),
|
||||
("{}.available".format(relation),
|
||||
"waiting",
|
||||
"'{}' incomplete".format(relation))]
|
||||
for relation in self.required_relations}
|
||||
return states_to_check
|
||||
|
||||
def check_services_running(self):
|
||||
"""Check that the services that should be running are actually running.
|
||||
|
||||
This uses the self.services and self.api_ports to determine what should
|
||||
be checked.
|
||||
|
||||
:returns: (status, message) or (None, None).
|
||||
"""
|
||||
# This returns either a None, None or a status, message if the service
|
||||
# is not running or the ports are not open.
|
||||
return os_utils._ows_check_services_running(
|
||||
services=self.services,
|
||||
ports=self.ports_to_check(self.api_ports))
|
||||
|
||||
def ports_to_check(self, ports):
|
||||
"""Return a flattened, sorted, unique list of ports from self.api_ports
|
||||
|
||||
NOTE. To disable port checking, simply override this method in the
|
||||
derived class and return an empty [].
|
||||
|
||||
:param ports: {key: {subkey: value}}
|
||||
:returns: [value1, value2, ...]
|
||||
"""
|
||||
# NB self.api_ports = {key: {space: value}}
|
||||
# The chain .. map flattens all the values into a single list
|
||||
return sorted(set(itertools.chain(*map(lambda x: x.values(),
|
||||
self.api_ports.values()))))
|
||||
|
Loading…
Reference in New Issue
Block a user