Merge "Rewrite update status machinery with the ops framework"
This commit is contained in:
commit
11b7a7340b
|
@ -44,13 +44,12 @@ from charmhelpers.core.hookenv import (
|
|||
leader_set, leader_get,
|
||||
is_leader,
|
||||
remote_unit,
|
||||
Hooks, UnregisteredHookError,
|
||||
Hooks,
|
||||
service_name,
|
||||
relations_of_type,
|
||||
relations,
|
||||
status_set,
|
||||
local_unit,
|
||||
application_version_set)
|
||||
)
|
||||
from charmhelpers.core.host import (
|
||||
service_pause,
|
||||
mkdir,
|
||||
|
@ -61,14 +60,12 @@ from charmhelpers.fetch import (
|
|||
apt_install,
|
||||
filter_installed_packages,
|
||||
add_source,
|
||||
get_upstream_version,
|
||||
)
|
||||
from charmhelpers.contrib.openstack.alternatives import install_alternative
|
||||
from charmhelpers.contrib.openstack.utils import (
|
||||
clear_unit_paused,
|
||||
clear_unit_upgrading,
|
||||
get_os_codename_install_source,
|
||||
is_unit_upgrading_set,
|
||||
set_unit_paused,
|
||||
set_unit_upgrading,
|
||||
)
|
||||
|
@ -82,19 +79,15 @@ from charmhelpers.core.templating import render
|
|||
from charmhelpers.contrib.storage.linux.ceph import (
|
||||
CephBrokerRq,
|
||||
CephConfContext,
|
||||
OSD_SETTING_EXCEPTIONS,
|
||||
enable_pg_autoscale,
|
||||
get_osd_settings,
|
||||
send_osd_settings,
|
||||
)
|
||||
from utils import (
|
||||
add_rbd_mirror_features,
|
||||
assert_charm_supports_ipv6,
|
||||
get_cluster_addr,
|
||||
get_networks,
|
||||
get_public_addr,
|
||||
get_rbd_features,
|
||||
has_rbd_mirrors,
|
||||
get_ceph_osd_releases,
|
||||
execute_post_osd_upgrade_steps,
|
||||
mgr_disable_module,
|
||||
|
@ -1291,94 +1284,6 @@ def is_unsupported_cmr(unit_name):
|
|||
return unsupported
|
||||
|
||||
|
||||
def assess_status(charm=None):
|
||||
'''Assess status of current unit'''
|
||||
application_version_set(get_upstream_version(VERSION_PACKAGE))
|
||||
if not config('permit-insecure-cmr'):
|
||||
units = [unit
|
||||
for rtype in relations()
|
||||
for relid in relation_ids(reltype=rtype)
|
||||
for unit in related_units(relid=relid)
|
||||
if is_cmr_unit(unit)]
|
||||
if units:
|
||||
status_set("blocked", "Unsupported CMR relation")
|
||||
return
|
||||
if is_unit_upgrading_set():
|
||||
status_set("blocked",
|
||||
"Ready for do-release-upgrade and reboot. "
|
||||
"Set complete when finished.")
|
||||
return
|
||||
|
||||
# Check that the no-bootstrap config option is set in conjunction with
|
||||
# having the bootstrap-source relation established
|
||||
if not config('no-bootstrap') and is_relation_made('bootstrap-source'):
|
||||
status_set('blocked', 'Cannot join the bootstrap-source relation when '
|
||||
'no-bootstrap is False')
|
||||
return
|
||||
|
||||
moncount = int(config('monitor-count'))
|
||||
units = get_peer_units()
|
||||
# not enough peers and mon_count > 1
|
||||
if len(units.keys()) < moncount:
|
||||
status_set('blocked', 'Insufficient peer units to bootstrap'
|
||||
' cluster (require {})'.format(moncount))
|
||||
return
|
||||
|
||||
# mon_count > 1, peers, but no ceph-public-address
|
||||
ready = sum(1 for unit_ready in units.values() if unit_ready)
|
||||
if ready < moncount:
|
||||
status_set('waiting', 'Peer units detected, waiting for addresses')
|
||||
return
|
||||
|
||||
configured_rbd_features = config('default-rbd-features')
|
||||
if has_rbd_mirrors() and configured_rbd_features:
|
||||
if add_rbd_mirror_features(
|
||||
configured_rbd_features) != configured_rbd_features:
|
||||
# The configured RBD features bitmap does not contain the features
|
||||
# required for RBD Mirroring
|
||||
status_set('blocked', 'Configuration mismatch: RBD Mirroring '
|
||||
'enabled but incorrect value set for '
|
||||
'``default-rbd-features``')
|
||||
return
|
||||
|
||||
try:
|
||||
get_osd_settings('client')
|
||||
except OSD_SETTING_EXCEPTIONS as e:
|
||||
status_set('blocked', str(e))
|
||||
return
|
||||
|
||||
if charm is not None and charm.metrics_endpoint.assess_alert_rule_errors():
|
||||
return
|
||||
|
||||
# active - bootstrapped + quorum status check
|
||||
if ceph.is_bootstrapped() and ceph.is_quorum():
|
||||
expected_osd_count = config('expected-osd-count') or 3
|
||||
if sufficient_osds(expected_osd_count):
|
||||
status_set('active', 'Unit is ready and clustered')
|
||||
elif not relation_ids('osd'):
|
||||
status_set('blocked', 'Missing relation: OSD')
|
||||
else:
|
||||
status_set(
|
||||
'waiting',
|
||||
'Monitor bootstrapped but waiting for number of'
|
||||
' OSDs to reach expected-osd-count ({})'
|
||||
.format(expected_osd_count)
|
||||
)
|
||||
else:
|
||||
# Unit should be running and clustered, but no quorum
|
||||
# TODO: should this be blocked or waiting?
|
||||
status_set('blocked', 'Unit not clustered (no quorum)')
|
||||
# If there's a pending lock for this unit,
|
||||
# can i get the lock?
|
||||
# reboot the ceph-mon process
|
||||
|
||||
|
||||
@hooks.hook('update-status')
|
||||
@harden()
|
||||
def update_status():
|
||||
log('Updating status.')
|
||||
|
||||
|
||||
@hooks.hook('pre-series-upgrade')
|
||||
def pre_series_upgrade():
|
||||
log("Running prepare series upgrade hook", "INFO")
|
||||
|
@ -1398,18 +1303,3 @@ def post_series_upgrade():
|
|||
# upgrading states.
|
||||
clear_unit_paused()
|
||||
clear_unit_upgrading()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
remote_block = False
|
||||
remote_unit_name = remote_unit()
|
||||
if remote_unit_name and is_cmr_unit(remote_unit_name):
|
||||
remote_block = not config('permit-insecure-cmr')
|
||||
if remote_block:
|
||||
log("Not running hook, CMR detected and not supported", "ERROR")
|
||||
else:
|
||||
try:
|
||||
hooks.execute(sys.argv)
|
||||
except UnregisteredHookError as e:
|
||||
log('Unknown hook {} - skipping.'.format(e))
|
||||
assess_status()
|
||||
|
|
|
@ -12,7 +12,6 @@ import pathlib
|
|||
from typing import Optional, Union, List, TYPE_CHECKING
|
||||
|
||||
import ops.model
|
||||
from ops.model import BlockedStatus
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import charm
|
||||
|
@ -81,12 +80,8 @@ class CephMetricsEndpointProvider(prometheus_scrape.MetricsEndpointProvider):
|
|||
# We're not related to prom, don't care about alert rules
|
||||
self._charm._stored.alert_rule_errors = None
|
||||
|
||||
def assess_alert_rule_errors(self):
|
||||
if self._charm._stored.alert_rule_errors:
|
||||
self._charm.unit.status = BlockedStatus(
|
||||
"invalid alert rules, check unit logs"
|
||||
)
|
||||
return True
|
||||
def have_alert_rule_errors(self):
|
||||
return bool(self._charm._stored.alert_rule_errors)
|
||||
|
||||
def _on_alert_rule_status_changed(self, event):
|
||||
logger.debug(
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
# Copyright 2022 Canonical Ltd.
|
||||
# See LICENSE file for licensing details.
|
||||
|
||||
"""Shared operator framework code
|
||||
|
||||
Provide helpers for querying current status of ceph-mon units
|
||||
"""
|
||||
import logging
|
||||
from typing import Mapping, List, Dict, TYPE_CHECKING
|
||||
|
||||
from ops import model, framework
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import charm
|
||||
|
||||
|
||||
class CephMonInfo(framework.Object):
|
||||
"""Provide status information about ceph-mon.
|
||||
|
||||
Information about
|
||||
- Relations
|
||||
- Peer information
|
||||
- CMR units
|
||||
"""
|
||||
|
||||
def __init__(self, charm: "charm.CephMonCharm"):
|
||||
super().__init__(charm, "moninfo")
|
||||
self.charm = charm
|
||||
|
||||
@property
|
||||
def relations(self) -> Mapping[str, List[model.Relation]]:
|
||||
return self.charm.model.relations
|
||||
|
||||
def get_peer_mons(self) -> Dict[model.Unit, model.RelationDataContent]:
|
||||
"""Retrieve information about ceph-mon peer units."""
|
||||
return self._get_related_unit_data("mon")
|
||||
|
||||
def get_osd_units(self) -> Dict[model.Unit, model.RelationDataContent]:
|
||||
"""Retrieve information about related osd units."""
|
||||
return self._get_related_unit_data("osd")
|
||||
|
||||
def _get_related_unit_data(
|
||||
self, reltype: str
|
||||
) -> Dict[model.Unit, model.RelationDataContent]:
|
||||
rel_units = [
|
||||
unit for rel in self.relations[reltype] for unit in rel.units
|
||||
]
|
||||
rel_data = {}
|
||||
for rel in self.relations[reltype]:
|
||||
for unit in rel_units:
|
||||
rel_data[unit] = rel.data.get(unit, {})
|
||||
return rel_data
|
||||
|
||||
def remote_units(self) -> List[model.Unit]:
|
||||
"""Retrieve related CMR units."""
|
||||
remotes = [
|
||||
unit
|
||||
for reltype in self.relations.values()
|
||||
for rel in reltype
|
||||
for unit in rel.units
|
||||
if unit.name.startswith("remote-")
|
||||
]
|
||||
return remotes
|
||||
|
||||
def sufficient_osds(self, minimum_osds: int = 3) -> bool:
|
||||
"""
|
||||
Determine if the minimum number of OSD's have been
|
||||
bootstrapped into the cluster.
|
||||
|
||||
:param expected_osds: The minimum number of OSD's required
|
||||
:return: boolean indicating whether the required number of
|
||||
OSD's where detected.
|
||||
"""
|
||||
osds = self.get_osd_units()
|
||||
bootstrapped_osds = sum(
|
||||
int(osd.get("bootstrapped-osds"))
|
||||
for osd in osds.values()
|
||||
if osd.get("bootstrapped-osds")
|
||||
)
|
||||
if bootstrapped_osds >= minimum_osds:
|
||||
return True
|
||||
return False
|
||||
|
||||
def have_osd_relation(self) -> bool:
|
||||
return bool(self.relations["osd"])
|
|
@ -0,0 +1,147 @@
|
|||
# Copyright 2022 Canonical Ltd.
|
||||
# See LICENSE file for licensing details.
|
||||
|
||||
"""Provide status checking for the ceph-mon charm"""
|
||||
|
||||
import logging
|
||||
from typing import Union, TYPE_CHECKING
|
||||
|
||||
from charmhelpers.core.hookenv import (
|
||||
application_version_set,
|
||||
is_relation_made,
|
||||
)
|
||||
from charmhelpers.fetch import get_upstream_version
|
||||
from ops import model
|
||||
|
||||
import utils
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import charm
|
||||
|
||||
from charmhelpers.contrib.storage.linux import ceph as ch_ceph
|
||||
|
||||
import charms_ceph.utils as ceph_utils
|
||||
import ceph_shared
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
VERSION_PACKAGE = "ceph-common"
|
||||
|
||||
|
||||
class StatusAssessor(ceph_shared.CephMonInfo):
|
||||
"""Status checking for ceph-mon charms
|
||||
|
||||
Takes a ceph-mon charm object as a client, registers checking methods for
|
||||
the charm object and updates status.
|
||||
"""
|
||||
|
||||
def __init__(self, charm: "charm.CephMonCharm"):
|
||||
super().__init__(charm)
|
||||
self.framework.observe(
|
||||
self.framework.on.commit, self.assess_status
|
||||
)
|
||||
self.register_checks()
|
||||
|
||||
def config(self, key) -> Union[str, int, float, bool, None]:
|
||||
return self.charm.model.config.get(key)
|
||||
|
||||
def check_insecure_cmr(self) -> model.StatusBase:
|
||||
if not self.config("permit-insecure-cmr") and self.remote_units():
|
||||
return model.BlockedStatus("Unsupported CMR relation")
|
||||
return model.ActiveStatus()
|
||||
|
||||
def check_bootstrap_source(self) -> model.StatusBase:
|
||||
if not self.config("no-bootstrap") and is_relation_made(
|
||||
"bootstrap-source"
|
||||
):
|
||||
return model.BlockedStatus(
|
||||
"Cannot join the bootstrap-source relation when "
|
||||
"no-bootstrap is False",
|
||||
)
|
||||
return model.ActiveStatus()
|
||||
|
||||
def check_moncount(self) -> model.StatusBase:
|
||||
moncount = self.config("monitor-count")
|
||||
if (
|
||||
len(self.get_peer_mons()) + 1 < moncount
|
||||
): # we're including ourselves
|
||||
return model.BlockedStatus(
|
||||
"Insufficient peer units to bootstrap"
|
||||
" cluster (require {})".format(moncount)
|
||||
)
|
||||
return model.ActiveStatus()
|
||||
|
||||
def check_ready_mons(self) -> model.StatusBase:
|
||||
moncount = self.config("monitor-count")
|
||||
mons = self.get_peer_mons()
|
||||
ready = sum(
|
||||
1 for mon in mons.values() if mon.get("ceph-public-address")
|
||||
)
|
||||
if ready + 1 < moncount: # "this" mon is ready presumably
|
||||
return model.WaitingStatus(
|
||||
"Peer units detected, waiting for addresses"
|
||||
)
|
||||
return model.ActiveStatus()
|
||||
|
||||
def check_rbd_features(self) -> model.StatusBase:
|
||||
configured_rbd_features = self.config("default-rbd-features")
|
||||
if utils.has_rbd_mirrors() and configured_rbd_features:
|
||||
if (
|
||||
utils.add_rbd_mirror_features(configured_rbd_features)
|
||||
!= configured_rbd_features
|
||||
):
|
||||
# The configured RBD features bitmap does not contain the
|
||||
# features required for RBD Mirroring
|
||||
return model.BlockedStatus(
|
||||
"Configuration mismatch: RBD Mirroring "
|
||||
"enabled but incorrect value set for "
|
||||
"``default-rbd-features``",
|
||||
)
|
||||
return model.ActiveStatus()
|
||||
|
||||
def check_get_osd_settings(self):
|
||||
try:
|
||||
ch_ceph.get_osd_settings("client")
|
||||
except ch_ceph.OSD_SETTING_EXCEPTIONS as e:
|
||||
return model.BlockedStatus(str(e))
|
||||
return model.ActiveStatus()
|
||||
|
||||
def check_alert_rule_errors(self):
|
||||
if self.charm.metrics_endpoint.have_alert_rule_errors():
|
||||
return model.BlockedStatus("invalid alert rules, check unit logs")
|
||||
return model.ActiveStatus()
|
||||
|
||||
def check_expected_osd_count(self):
|
||||
if ceph_utils.is_bootstrapped() and ceph_utils.is_quorum():
|
||||
expected_osd_count = self.config("expected-osd-count") or 3
|
||||
if self.sufficient_osds(expected_osd_count):
|
||||
return model.ActiveStatus("Unit is ready and clustered")
|
||||
elif not self.have_osd_relation():
|
||||
return model.BlockedStatus("Missing relation: OSD")
|
||||
else:
|
||||
return model.WaitingStatus(
|
||||
"Monitor bootstrapped but waiting for number of"
|
||||
" OSDs to reach expected-osd-count ({})".format(
|
||||
expected_osd_count
|
||||
)
|
||||
)
|
||||
else:
|
||||
return model.BlockedStatus("Unit not clustered (no quorum)")
|
||||
|
||||
def register_checks(self):
|
||||
checkers = [
|
||||
self.check_insecure_cmr,
|
||||
self.check_bootstrap_source,
|
||||
self.check_moncount,
|
||||
self.check_ready_mons,
|
||||
self.check_rbd_features,
|
||||
self.check_alert_rule_errors,
|
||||
self.check_expected_osd_count,
|
||||
]
|
||||
for check in checkers:
|
||||
self.charm.register_status_check(check)
|
||||
|
||||
def assess_status(self, _event):
|
||||
logger.debug("Running assess_status() for %s", self.charm)
|
||||
application_version_set(get_upstream_version(VERSION_PACKAGE))
|
||||
self.charm.update_status()
|
27
src/charm.py
27
src/charm.py
|
@ -3,6 +3,7 @@ import logging
|
|||
|
||||
from ops.main import main
|
||||
|
||||
import ceph_status
|
||||
import charms.operator_libs_linux.v0.apt as apt
|
||||
import charms.operator_libs_linux.v1.systemd as systemd
|
||||
|
||||
|
@ -40,77 +41,62 @@ class CephMonCharm(ops_openstack.core.OSBaseCharm):
|
|||
systemd.service_pause('ceph-create-keys')
|
||||
except systemd.SystemdError:
|
||||
pass
|
||||
hooks.assess_status(self)
|
||||
|
||||
def on_config(self, event):
|
||||
hooks.config_changed()
|
||||
hooks.assess_status(self)
|
||||
|
||||
def on_pre_series_upgrade(self, event):
|
||||
hooks.pre_series_upgrade()
|
||||
hooks.assess_status(self)
|
||||
|
||||
def on_upgrade(self, event):
|
||||
self.metrics_endpoint.update_alert_rules()
|
||||
hooks.upgrade_charm()
|
||||
hooks.assess_status(self)
|
||||
|
||||
def on_post_series_upgrade(self, event):
|
||||
hooks.post_series_upgrade()
|
||||
hooks.assess_status(self)
|
||||
|
||||
# Relations.
|
||||
def on_mon_relation_joined(self, event):
|
||||
hooks.mon_relation_joined()
|
||||
hooks.assess_status(self)
|
||||
|
||||
def on_bootstrap_source_relation_changed(self, event):
|
||||
hooks.bootstrap_source_relation_changed()
|
||||
hooks.assess_status(self)
|
||||
|
||||
def on_prometheus_relation_joined_or_changed(self, event):
|
||||
hooks.prometheus_relation()
|
||||
hooks.assess_status(self)
|
||||
|
||||
def on_prometheus_relation_departed(self, event):
|
||||
hooks.prometheus_left()
|
||||
hooks.assess_status(self)
|
||||
|
||||
def on_mon_relation(self, event):
|
||||
hooks.mon_relation()
|
||||
hooks.assess_status(self)
|
||||
|
||||
def on_osd_relation(self, event):
|
||||
hooks.osd_relation()
|
||||
hooks.assess_status(self)
|
||||
|
||||
def on_dashboard_relation_joined(self, event):
|
||||
hooks.dashboard_relation()
|
||||
hooks.assess_status(self)
|
||||
|
||||
def on_radosgw_relation(self, event):
|
||||
hooks.radosgw_relation()
|
||||
hooks.assess_status(self)
|
||||
|
||||
def on_rbd_mirror_relation(self, event):
|
||||
hooks.rbd_mirror_relation()
|
||||
hooks.assess_status(self)
|
||||
|
||||
def on_mds_relation(self, event):
|
||||
hooks.mds_relation_joined()
|
||||
hooks.assess_status(self)
|
||||
|
||||
def on_admin_relation(self, event):
|
||||
hooks.admin_relation_joined()
|
||||
hooks.assess_status(self)
|
||||
|
||||
def on_client_relation(self, event):
|
||||
hooks.client_relation()
|
||||
hooks.assess_status(self)
|
||||
|
||||
def on_nrpe_relation(self, event):
|
||||
hooks.update_nrpe_config()
|
||||
hooks.assess_status(self)
|
||||
|
||||
def on_commit(self, _event):
|
||||
self.ceph_status.assess_status()
|
||||
|
||||
# Actions.
|
||||
|
||||
|
@ -141,12 +127,15 @@ class CephMonCharm(ops_openstack.core.OSBaseCharm):
|
|||
self._stored.is_started = True
|
||||
|
||||
if self.is_blocked_insecure_cmr():
|
||||
logging.error("Not running hook, CMR detected and not supported")
|
||||
logging.error(
|
||||
"Not running hook, CMR detected and not supported")
|
||||
return
|
||||
|
||||
fw = self.framework
|
||||
|
||||
self.metrics_endpoint = ceph_metrics.CephMetricsEndpointProvider(self)
|
||||
self.ceph_status = ceph_status.StatusAssessor(self)
|
||||
|
||||
self._observe_action(self.on.change_osd_weight_action,
|
||||
ops_actions.change_osd_weight.change_osd_weight)
|
||||
self._observe_action(self.on.copy_pool_action,
|
||||
|
|
|
@ -122,7 +122,7 @@ class TestCephMetrics(unittest.TestCase):
|
|||
self.harness.add_resource("alert-rules", "not-a-rule")
|
||||
self.harness.charm.metrics_endpoint.update_alert_rules()
|
||||
self.assertTrue(
|
||||
self.harness.charm.metrics_endpoint.assess_alert_rule_errors()
|
||||
self.harness.charm.metrics_endpoint.have_alert_rule_errors()
|
||||
)
|
||||
|
||||
@patch("ceph_metrics.ceph_utils.is_bootstrapped", return_value=True)
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
#!/usr/bin/env python3
|
||||
# Copyright 2022 Canonical Ltd.
|
||||
# See LICENSE file for licensing details.
|
||||
|
||||
from unittest.mock import patch
|
||||
import unittest
|
||||
|
||||
from ops.testing import Harness
|
||||
|
||||
import ceph_shared
|
||||
import charm
|
||||
|
||||
|
||||
@patch("charm.hooks")
|
||||
class TestCephShared(unittest.TestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.harness = Harness(charm.CephMonCharm)
|
||||
self.addCleanup(self.harness.cleanup)
|
||||
|
||||
def test_init(self, _hooks):
|
||||
self.harness.begin()
|
||||
ceph_info = ceph_shared.CephMonInfo(self.harness.charm)
|
||||
self.assertTrue(ceph_info.relations)
|
||||
|
||||
def test_get_peer_mons(self, _hooks):
|
||||
self.harness.begin()
|
||||
self.harness.set_leader(True)
|
||||
ceph_info = ceph_shared.CephMonInfo(self.harness.charm)
|
||||
self.harness.add_relation_unit(
|
||||
self.harness.add_relation("mon", "ceph-mon"), "ceph-mon/0"
|
||||
)
|
||||
peer_mons = ceph_info.get_peer_mons()
|
||||
self.assertEqual(len(peer_mons), 1)
|
||||
peer = list(peer_mons.keys())[0]
|
||||
self.assertEqual(peer.name, "ceph-mon/0")
|
||||
|
||||
def test_not_sufficient_osds(self, _hooks):
|
||||
self.harness.begin()
|
||||
ceph_info = ceph_shared.CephMonInfo(self.harness.charm)
|
||||
rel_id = self.harness.add_relation("osd", "ceph-osd")
|
||||
self.harness.add_relation_unit(rel_id, "ceph-osd/0")
|
||||
have_enough = ceph_info.sufficient_osds(minimum_osds=77)
|
||||
self.assertFalse(have_enough)
|
||||
|
||||
def test_sufficient_osds(self, _hooks):
|
||||
self.harness.begin()
|
||||
ceph_info = ceph_shared.CephMonInfo(self.harness.charm)
|
||||
rel_id = self.harness.add_relation("osd", "ceph-osd")
|
||||
self.harness.add_relation_unit(rel_id, "ceph-osd/0")
|
||||
self.harness.update_relation_data(
|
||||
rel_id, "ceph-osd/0", {"bootstrapped-osds": "77"}
|
||||
)
|
||||
have_enough = ceph_info.sufficient_osds(minimum_osds=77)
|
||||
self.assertTrue(have_enough)
|
|
@ -0,0 +1,120 @@
|
|||
#!/usr/bin/env python3
|
||||
# Copyright 2022 Canonical Ltd.
|
||||
# See LICENSE file for licensing details.
|
||||
|
||||
from unittest.mock import patch
|
||||
import unittest
|
||||
|
||||
from ops import model
|
||||
from ops.testing import Harness
|
||||
|
||||
import ceph_status
|
||||
import charm
|
||||
|
||||
from charmhelpers.contrib.storage.linux import ceph as ch_ceph
|
||||
|
||||
|
||||
@patch("charm.hooks")
|
||||
class TestCephStatus(unittest.TestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.harness = Harness(charm.CephMonCharm)
|
||||
self.addCleanup(self.harness.cleanup)
|
||||
|
||||
def test_init(self, _hooks):
|
||||
self.harness.begin()
|
||||
status = ceph_status.StatusAssessor(self.harness.charm)
|
||||
self.assertTrue(status.charm.custom_status_checks)
|
||||
|
||||
def test_check_insecure_cmr(self, _hooks):
|
||||
self.harness.begin()
|
||||
status = ceph_status.StatusAssessor(self.harness.charm)
|
||||
result = status.check_insecure_cmr()
|
||||
self.assertIsInstance(result, model.ActiveStatus)
|
||||
self.harness.add_relation_unit(
|
||||
self.harness.add_relation("client", "remote"), "remote-foo/0"
|
||||
)
|
||||
result = status.check_insecure_cmr()
|
||||
self.assertIsInstance(result, model.BlockedStatus)
|
||||
|
||||
def test_check_moncount(self, _hooks):
|
||||
self.harness.begin()
|
||||
status = ceph_status.StatusAssessor(self.harness.charm)
|
||||
result = status.check_moncount()
|
||||
self.assertIsInstance(result, model.BlockedStatus)
|
||||
rel_id = self.harness.add_relation("mon", "ceph-mon")
|
||||
for n in (0, 1, 2):
|
||||
self.harness.add_relation_unit(rel_id, "ceph-mon/{}".format(n))
|
||||
result = status.check_moncount()
|
||||
self.assertIsInstance(result, model.ActiveStatus)
|
||||
|
||||
def test_check_ready_mons(self, _hooks):
|
||||
self.harness.begin()
|
||||
status = ceph_status.StatusAssessor(self.harness.charm)
|
||||
result = status.check_ready_mons()
|
||||
self.assertIsInstance(result, model.WaitingStatus)
|
||||
rel_id = self.harness.add_relation("mon", "ceph-mon")
|
||||
for n in (0, 1, 2):
|
||||
self.harness.add_relation_unit(rel_id, "ceph-mon/{}".format(n))
|
||||
self.harness.update_relation_data(
|
||||
rel_id, "ceph-mon/{}".format(n), {"ceph-public-address": "foo"}
|
||||
)
|
||||
result = status.check_ready_mons()
|
||||
self.assertIsInstance(result, model.ActiveStatus)
|
||||
|
||||
@patch("ceph_status.ch_ceph.get_osd_settings")
|
||||
def test_check_get_osd_settings(self, get_osd_settings, _hooks):
|
||||
self.harness.begin()
|
||||
status = ceph_status.StatusAssessor(self.harness.charm)
|
||||
result = status.check_get_osd_settings()
|
||||
self.assertIsInstance(result, model.ActiveStatus)
|
||||
get_osd_settings.side_effect = ch_ceph.OSDSettingConflict(
|
||||
"testexception"
|
||||
)
|
||||
result = status.check_get_osd_settings()
|
||||
self.assertIsInstance(result, model.BlockedStatus)
|
||||
|
||||
def test_check_alert_rule_errors(self, _hooks):
|
||||
self.harness.begin()
|
||||
status = ceph_status.StatusAssessor(self.harness.charm)
|
||||
with patch.object(
|
||||
self.harness.charm,
|
||||
"metrics_endpoint",
|
||||
create=True,
|
||||
) as metrics_endpoint:
|
||||
metrics_endpoint.have_alert_rule_errors.return_value = True
|
||||
result = status.check_alert_rule_errors()
|
||||
self.assertIsInstance(result, model.BlockedStatus)
|
||||
|
||||
metrics_endpoint.have_alert_rule_errors.return_value = False
|
||||
result = status.check_alert_rule_errors()
|
||||
self.assertIsInstance(result, model.ActiveStatus)
|
||||
|
||||
@patch("ceph_status.ceph_utils")
|
||||
def test_check_expected_osd_count(self, ceph_utils, _hooks):
|
||||
self.harness.begin()
|
||||
status = ceph_status.StatusAssessor(self.harness.charm)
|
||||
|
||||
# not bootstrapped
|
||||
ceph_utils.is_bootstrapped.return_value = False
|
||||
ceph_utils.is_quorum.return_value = False
|
||||
result = status.check_expected_osd_count()
|
||||
self.assertIsInstance(result, model.BlockedStatus)
|
||||
self.assertEqual(result.message, "Unit not clustered (no quorum)")
|
||||
|
||||
# bootstrapped, no osd rel
|
||||
ceph_utils.is_bootstrapped.return_value = True
|
||||
ceph_utils.is_quorum.return_value = True
|
||||
result = status.check_expected_osd_count()
|
||||
self.assertIsInstance(result, model.BlockedStatus)
|
||||
self.assertEqual(result.message, "Missing relation: OSD")
|
||||
|
||||
# bootstrapped, enough osds
|
||||
rel_id = self.harness.add_relation("osd", "ceph-osd")
|
||||
for n in (0, 1, 2):
|
||||
self.harness.add_relation_unit(rel_id, "ceph-osd/{}".format(n))
|
||||
self.harness.update_relation_data(
|
||||
rel_id, "ceph-osd/{}".format(n), {"bootstrapped-osds": "1"}
|
||||
)
|
||||
result = status.check_expected_osd_count()
|
||||
self.assertIsInstance(result, model.ActiveStatus)
|
|
@ -0,0 +1,62 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# Copyright 2022 Canonical Ltd.
|
||||
# See LICENSE file for licensing details.
|
||||
|
||||
from unittest.mock import patch
|
||||
import unittest
|
||||
|
||||
from ops.testing import Harness
|
||||
|
||||
import ceph_metrics # noqa: avoid circ. import
|
||||
import charm
|
||||
|
||||
|
||||
class TestCephCharm(unittest.TestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.harness = Harness(charm.CephMonCharm)
|
||||
self.harness.begin()
|
||||
self.addCleanup(self.harness.cleanup)
|
||||
|
||||
def test_init(self):
|
||||
self.assertTrue(self.harness.charm.framework)
|
||||
self.assertTrue(self.harness.charm.metrics_endpoint)
|
||||
self.assertTrue(self.harness.charm.ceph_status)
|
||||
|
||||
@patch("charm.hooks")
|
||||
def test_on_config_changed(self, hooks):
|
||||
self.harness.update_config({"permit-insecure-cmr": None})
|
||||
hooks.config_changed.assert_called()
|
||||
|
||||
@patch("charm.ops_openstack.core.apt_install")
|
||||
@patch("charm.ops_openstack.core.apt_update")
|
||||
@patch("charm.ops_openstack.core.add_source")
|
||||
@patch("charm.ops_openstack.core.OSBaseCharm.update_status")
|
||||
@patch("charm.hooks")
|
||||
@patch("charm.systemd")
|
||||
@patch("charm.apt")
|
||||
def test_on_install(
|
||||
self,
|
||||
_apt,
|
||||
_systemd,
|
||||
_hooks,
|
||||
_update_status,
|
||||
_add_source,
|
||||
apt_update,
|
||||
apt_install,
|
||||
):
|
||||
self.harness.update_config({"permit-insecure-cmr": None})
|
||||
self.harness.charm.on.install.emit()
|
||||
apt_install.assert_called_with(
|
||||
[
|
||||
"ceph",
|
||||
"gdisk",
|
||||
"radosgw",
|
||||
"lvm2",
|
||||
"parted",
|
||||
"smartmontools",
|
||||
],
|
||||
fatal=True,
|
||||
)
|
||||
apt_update.assert_called()
|
|
@ -1,251 +0,0 @@
|
|||
# Copyright 2016 Canonical Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import unittest.mock as mock
|
||||
import sys
|
||||
|
||||
import test_utils
|
||||
|
||||
import charmhelpers.contrib.storage.linux.ceph as ch_ceph
|
||||
|
||||
# python-apt is not installed as part of test-requirements but is imported by
|
||||
# some charmhelpers modules so create a fake import.
|
||||
mock_apt = mock.MagicMock()
|
||||
sys.modules['apt'] = mock_apt
|
||||
mock_apt.apt_pkg = mock.MagicMock()
|
||||
|
||||
with mock.patch('charmhelpers.contrib.hardening.harden.harden') as mock_dec:
|
||||
mock_dec.side_effect = (lambda *dargs, **dkwargs: lambda f:
|
||||
lambda *args, **kwargs: f(*args, **kwargs))
|
||||
import ceph_hooks as hooks
|
||||
|
||||
TO_PATCH = [
|
||||
'status_set',
|
||||
'config',
|
||||
'ceph',
|
||||
'is_relation_made',
|
||||
'relations',
|
||||
'relation_ids',
|
||||
'relation_get',
|
||||
'related_units',
|
||||
'local_unit',
|
||||
'application_version_set',
|
||||
'get_upstream_version',
|
||||
]
|
||||
|
||||
NO_PEERS = {
|
||||
'ceph-mon1': True
|
||||
}
|
||||
|
||||
ENOUGH_PEERS_INCOMPLETE = {
|
||||
'ceph-mon1': True,
|
||||
'ceph-mon2': False,
|
||||
'ceph-mon3': False,
|
||||
}
|
||||
|
||||
ENOUGH_PEERS_COMPLETE = {
|
||||
'ceph-mon1': True,
|
||||
'ceph-mon2': True,
|
||||
'ceph-mon3': True,
|
||||
}
|
||||
|
||||
|
||||
class ServiceStatusTestCase(test_utils.CharmTestCase):
|
||||
def setUp(self):
|
||||
super(ServiceStatusTestCase, self).setUp(hooks, TO_PATCH)
|
||||
self.config.side_effect = self.test_config.get
|
||||
self.test_config.set('monitor-count', 3)
|
||||
self.local_unit.return_value = 'ceph-mon1'
|
||||
self.get_upstream_version.return_value = '10.2.2'
|
||||
self.is_relation_made.return_value = False
|
||||
|
||||
@mock.patch.object(hooks, 'get_peer_units')
|
||||
def test_assess_status_no_peers(self, _peer_units):
|
||||
_peer_units.return_value = NO_PEERS
|
||||
hooks.assess_status()
|
||||
self.status_set.assert_called_with('blocked', mock.ANY)
|
||||
self.application_version_set.assert_called_with('10.2.2')
|
||||
|
||||
@mock.patch.object(hooks, 'get_peer_units')
|
||||
def test_assess_status_peers_incomplete(self, _peer_units):
|
||||
_peer_units.return_value = ENOUGH_PEERS_INCOMPLETE
|
||||
hooks.assess_status()
|
||||
self.status_set.assert_called_with('waiting', mock.ANY)
|
||||
self.application_version_set.assert_called_with('10.2.2')
|
||||
|
||||
@mock.patch.object(hooks, 'get_osd_settings')
|
||||
@mock.patch.object(hooks, 'has_rbd_mirrors')
|
||||
@mock.patch.object(hooks, 'sufficient_osds')
|
||||
@mock.patch.object(hooks, 'get_peer_units')
|
||||
def test_assess_status_peers_complete_active(self, _peer_units,
|
||||
_sufficient_osds,
|
||||
_has_rbd_mirrors,
|
||||
_get_osd_settings):
|
||||
_peer_units.return_value = ENOUGH_PEERS_COMPLETE
|
||||
_sufficient_osds.return_value = True
|
||||
self.ceph.is_bootstrapped.return_value = True
|
||||
self.ceph.is_quorum.return_value = True
|
||||
_has_rbd_mirrors.return_value = False
|
||||
_get_osd_settings.return_value = {}
|
||||
hooks.assess_status()
|
||||
self.status_set.assert_called_with('active', mock.ANY)
|
||||
self.application_version_set.assert_called_with('10.2.2')
|
||||
|
||||
@mock.patch.object(hooks, 'relation_ids')
|
||||
@mock.patch.object(hooks, 'get_osd_settings')
|
||||
@mock.patch.object(hooks, 'has_rbd_mirrors')
|
||||
@mock.patch.object(hooks, 'sufficient_osds')
|
||||
@mock.patch.object(hooks, 'get_peer_units')
|
||||
def test_assess_status_no_osd_relation(
|
||||
self,
|
||||
_peer_units,
|
||||
_sufficient_osds,
|
||||
_has_rbd_mirrors,
|
||||
_get_osd_settings,
|
||||
_relation_ids
|
||||
):
|
||||
_peer_units.return_value = ENOUGH_PEERS_COMPLETE
|
||||
_sufficient_osds.return_value = False
|
||||
_relation_ids.return_value = []
|
||||
self.ceph.is_bootstrapped.return_value = True
|
||||
self.ceph.is_quorum.return_value = True
|
||||
_has_rbd_mirrors.return_value = False
|
||||
_get_osd_settings.return_value = {}
|
||||
hooks.assess_status()
|
||||
self.status_set.assert_called_with('blocked', 'Missing relation: OSD')
|
||||
self.application_version_set.assert_called_with('10.2.2')
|
||||
|
||||
@mock.patch.object(hooks, 'relation_ids')
|
||||
@mock.patch.object(hooks, 'get_osd_settings')
|
||||
@mock.patch.object(hooks, 'has_rbd_mirrors')
|
||||
@mock.patch.object(hooks, 'sufficient_osds')
|
||||
@mock.patch.object(hooks, 'get_peer_units')
|
||||
def test_assess_status_osd_relation_but_insufficient_osds(
|
||||
self,
|
||||
_peer_units,
|
||||
_sufficient_osds,
|
||||
_has_rbd_mirrors,
|
||||
_get_osd_settings,
|
||||
_relation_ids
|
||||
):
|
||||
_peer_units.return_value = ENOUGH_PEERS_COMPLETE
|
||||
_sufficient_osds.return_value = False
|
||||
_relation_ids.return_value = ['osd:1']
|
||||
self.ceph.is_bootstrapped.return_value = True
|
||||
self.ceph.is_quorum.return_value = True
|
||||
_has_rbd_mirrors.return_value = False
|
||||
_get_osd_settings.return_value = {}
|
||||
hooks.assess_status()
|
||||
self.status_set.assert_called_with('waiting', mock.ANY)
|
||||
self.application_version_set.assert_called_with('10.2.2')
|
||||
|
||||
@mock.patch.object(hooks, 'get_osd_settings')
|
||||
@mock.patch.object(hooks, 'has_rbd_mirrors')
|
||||
@mock.patch.object(hooks, 'sufficient_osds')
|
||||
@mock.patch.object(hooks, 'get_peer_units')
|
||||
def test_assess_status_invalid_osd_settings(self, _peer_units,
|
||||
_sufficient_osds,
|
||||
_has_rbd_mirrors,
|
||||
_get_osd_settings):
|
||||
_peer_units.return_value = ENOUGH_PEERS_COMPLETE
|
||||
_sufficient_osds.return_value = True
|
||||
self.ceph.is_bootstrapped.return_value = True
|
||||
self.ceph.is_quorum.return_value = True
|
||||
_has_rbd_mirrors.return_value = False
|
||||
_get_osd_settings.side_effect = ch_ceph.OSDSettingConflict(
|
||||
'conflict in setting foo')
|
||||
hooks.assess_status()
|
||||
self.status_set.assert_called_with('blocked', mock.ANY)
|
||||
|
||||
@mock.patch.object(hooks, 'get_osd_settings')
|
||||
@mock.patch.object(hooks, 'has_rbd_mirrors')
|
||||
@mock.patch.object(hooks, 'sufficient_osds')
|
||||
@mock.patch.object(hooks, 'get_peer_units')
|
||||
def test_assess_status_peers_complete_down(self, _peer_units,
|
||||
_sufficient_osds,
|
||||
_has_rbd_mirrors,
|
||||
_get_osd_settings):
|
||||
_peer_units.return_value = ENOUGH_PEERS_COMPLETE
|
||||
_sufficient_osds.return_value = True
|
||||
self.ceph.is_bootstrapped.return_value = False
|
||||
self.ceph.is_quorum.return_value = False
|
||||
_has_rbd_mirrors.return_value = False
|
||||
_get_osd_settings.return_value = {}
|
||||
hooks.assess_status()
|
||||
self.status_set.assert_called_with('blocked', mock.ANY)
|
||||
self.application_version_set.assert_called_with('10.2.2')
|
||||
|
||||
@mock.patch.object(hooks, 'has_rbd_mirrors')
|
||||
@mock.patch.object(hooks, 'sufficient_osds')
|
||||
@mock.patch.object(hooks, 'get_peer_units')
|
||||
def test_assess_status_rbd_feature_mismatch(self, _peer_units,
|
||||
_sufficient_osds,
|
||||
_has_rbd_mirrors):
|
||||
_peer_units.return_value = ENOUGH_PEERS_COMPLETE
|
||||
_sufficient_osds.return_value = True
|
||||
self.ceph.is_bootstrapped.return_value = True
|
||||
self.ceph.is_quorum.return_value = True
|
||||
_has_rbd_mirrors.return_value = True
|
||||
self.test_config.set('default-rbd-features', 61)
|
||||
hooks.assess_status()
|
||||
self.status_set.assert_called_once_with('blocked', mock.ANY)
|
||||
|
||||
def test_get_peer_units_no_peers(self):
|
||||
self.relation_ids.return_value = ['mon:1']
|
||||
self.related_units.return_value = []
|
||||
self.assertEqual({'ceph-mon1': True},
|
||||
hooks.get_peer_units())
|
||||
|
||||
def test_get_peer_units_peers_incomplete(self):
|
||||
self.relation_ids.return_value = ['mon:1']
|
||||
self.related_units.return_value = ['ceph-mon2',
|
||||
'ceph-mon3']
|
||||
self.relation_get.return_value = None
|
||||
self.assertEqual({'ceph-mon1': True,
|
||||
'ceph-mon2': False,
|
||||
'ceph-mon3': False},
|
||||
hooks.get_peer_units())
|
||||
|
||||
def test_get_peer_units_peers_complete(self):
|
||||
self.relation_ids.return_value = ['mon:1']
|
||||
self.related_units.return_value = ['ceph-mon2',
|
||||
'ceph-mon3']
|
||||
self.relation_get.side_effect = ['ceph-mon2',
|
||||
'ceph-mon3']
|
||||
self.assertEqual({'ceph-mon1': True,
|
||||
'ceph-mon2': True,
|
||||
'ceph-mon3': True},
|
||||
hooks.get_peer_units())
|
||||
|
||||
def test_no_bootstrap_not_set(self):
|
||||
self.is_relation_made.return_value = True
|
||||
hooks.assess_status()
|
||||
self.status_set.assert_called_with('blocked', mock.ANY)
|
||||
self.application_version_set.assert_called_with('10.2.2')
|
||||
|
||||
def test_cmr_remote_unit(self):
|
||||
self.test_config.set('permit-insecure-cmr', False)
|
||||
self.relations.return_value = ['client']
|
||||
self.relation_ids.return_value = ['client:1']
|
||||
self.related_units.return_value = ['remote-1']
|
||||
hooks.assess_status()
|
||||
self.status_set.assert_called_with(
|
||||
'blocked',
|
||||
'Unsupported CMR relation')
|
||||
self.status_set.reset_mock()
|
||||
self.test_config.set('permit-insecure-cmr', True)
|
||||
hooks.assess_status()
|
||||
self.assertFalse(
|
||||
mock.call('blocked', 'Unsupported CMR relation') in
|
||||
self.status_set.call_args_list)
|
Loading…
Reference in New Issue