Block when RBD Mirroring enabled with incorrect RBD features

If the ``default-rbd-features`` configuration option is not set
the correct feature bitmap will be computed automatically.

However, if the user has explicitly set the configuration option
we will honour that, but block if it does not contain the required
bits for RBD Mirroring.

Change-Id: I84ab445780d2208dc87c36b1eb8171b27a992a1e
This commit is contained in:
Frode Nordahl 2019-03-02 14:15:40 +01:00
parent e899641dae
commit 443afd96cc
4 changed files with 59 additions and 7 deletions

View File

@ -81,11 +81,13 @@ from charmhelpers.core.templating import render
from charmhelpers.contrib.storage.linux.ceph import (
CephConfContext)
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,
)
from charmhelpers.contrib.charmsupport import nrpe
@ -878,6 +880,17 @@ def assess_status():
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
# active - bootstrapped + quorum status check
if ceph.is_bootstrapped() and ceph.is_quorum():
expected_osd_count = config('expected-osd-count') or 3

View File

@ -193,18 +193,27 @@ def get_default_rbd_features():
return int(line.split('=')[1].lstrip().rstrip())
def add_rbd_mirror_features(rbd_features):
"""Take a RBD Features bitmap and add the features required for Mirroring.
:param rbd_features: Input bitmap
:type rbd_features: int
:returns: Bitmap bitwise OR'ed with the features required for Mirroring.
:rtype: int
"""
RBD_FEATURE_EXCLUSIVE_LOCK = 4
RBD_FEATURE_JOURNALING = 64
return rbd_features | RBD_FEATURE_EXCLUSIVE_LOCK | RBD_FEATURE_JOURNALING
def get_rbd_features():
"""Determine if we should set, and what the rbd default features should be.
:returns: None or the apropriate value to use
:rtype: Option[int, None]
"""
RBD_FEATURE_EXCLUSIVE_LOCK = 4
RBD_FEATURE_JOURNALING = 64
rbd_feature_config = config('default-rbd-features')
if rbd_feature_config:
return int(rbd_feature_config)
elif has_rbd_mirrors():
return (get_default_rbd_features() |
RBD_FEATURE_EXCLUSIVE_LOCK | RBD_FEATURE_JOURNALING)
return add_rbd_mirror_features(get_default_rbd_features())

View File

@ -49,6 +49,15 @@ class CephUtilsTestCase(test_utils.CharmTestCase):
['ceph', '-c', '/dev/null', '--show-config'],
universal_newlines=True)
def test_add_mirror_rbd_features(self):
DEFAULT_FEATURES = 61
RBD_FEATURE_EXCLUSIVE_LOCK = 4
RBD_FEATURE_JOURNALING = 64
COMBINED_FEATURES = (DEFAULT_FEATURES | RBD_FEATURE_EXCLUSIVE_LOCK |
RBD_FEATURE_JOURNALING)
self.assertEqual(utils.add_rbd_mirror_features(DEFAULT_FEATURES),
COMBINED_FEATURES)
@mock.patch.object(utils, 'get_default_rbd_features')
@mock.patch.object(utils, 'has_rbd_mirrors')
@mock.patch.object(utils, 'config')

View File

@ -81,30 +81,51 @@ class ServiceStatusTestCase(test_utils.CharmTestCase):
self.status_set.assert_called_with('waiting', 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_peers_complete_active(self, _peer_units,
_sufficient_osds):
_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 = False
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, '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):
_sufficient_osds,
_has_rbd_mirrors):
_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
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 = []