Enable image features when ``rbd-mirror`` relation present

The RBD Mirroring feature requires ``journaling`` and
``exclusive-lock`` image features to be enabled.

Set the appropriate value so new images get these features
automatically.

Change-Id: Ie36c23b27fb7238814993756cb1d72e86309ab02
This commit is contained in:
Frode Nordahl 2019-03-01 22:21:08 +01:00
parent 463176b276
commit e899641dae
5 changed files with 164 additions and 24 deletions

View File

@ -212,14 +212,24 @@ options:
type: int
default:
description: |
Restrict the rbd features used to the specified level. If set, this will
inform clients that they should set the config value `rbd default
features`, for example:
.
rbd default features = 1
.
This needs to be set to 1 when deploying a cloud with the nova-lxd
hypervisor.
Default RBD Features to use when creating new images. The value of this
configuration option will be shared with consumers of the ``ceph-client``
interface and client charms may choose to add this to the Ceph
configuration file on the units they manage.
Example:
rbd default features = 1
NOTE: If you have clients using the kernel RBD driver you must set this
configuration option to a value corrensponding to the features the driver
in your kernel supports. The kernel RBD driver tends to be multiple
cycles behind the userspace driver available for libvirt/qemu. Nova LXD
is among the clients depending on the kernel RBD driver.
NOTE: If you want to use the RBD Mirroring feature you must either let
this configuration option be the default or make sure the value you set
includes the ``exclusive-lock`` and ``journaling`` features.
no-bootstrap:
type: boolean
default: False

View File

@ -81,10 +81,11 @@ from charmhelpers.core.templating import render
from charmhelpers.contrib.storage.linux.ceph import (
CephConfContext)
from utils import (
assert_charm_supports_ipv6,
get_cluster_addr,
get_networks,
get_public_addr,
get_cluster_addr,
assert_charm_supports_ipv6
get_rbd_features,
)
from charmhelpers.contrib.charmsupport import nrpe
@ -184,8 +185,9 @@ def get_ceph_context():
cephcontext['public_addr'] = get_public_addr()
cephcontext['cluster_addr'] = get_cluster_addr()
if config('default-rbd-features'):
cephcontext['rbd_features'] = config('default-rbd-features')
rbd_features = get_rbd_features()
if rbd_features:
cephcontext['rbd_features'] = rbd_features
if config('disable-pg-max-object-skew'):
cephcontext['disable_object_skew'] = config(

View File

@ -12,27 +12,30 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import socket
import re
import socket
import subprocess
from charmhelpers.core.hookenv import (
unit_get,
DEBUG,
cached,
config,
status_set,
network_get_primary_address,
goal_state,
log,
DEBUG,
network_get_primary_address,
related_units,
relation_ids,
status_set,
unit_get,
)
from charmhelpers.fetch import (
apt_install,
filter_installed_packages
)
from charmhelpers.core.host import (
lsb_release,
CompareHostReleases,
)
from charmhelpers.contrib.network.ip import (
get_address_in_network,
get_ipv6_addr
@ -152,3 +155,56 @@ def assert_charm_supports_ipv6():
if CompareHostReleases(_release) < "trusty":
raise Exception("IPv6 is not supported in the charms for Ubuntu "
"versions less than Trusty 14.04")
def has_rbd_mirrors():
"""Determine if we have or will have ``rbd-mirror`` charms related.
:returns: True or False
:rtype: bool
"""
try:
# NOTE(fnordahl): This optimization will not be useful until we get a
# resolution on LP: #1818245
raise NotImplementedError
gs = goal_state()
return 'rbd-mirror' in gs.get('relations', {})
except NotImplementedError:
for relid in relation_ids('rbd-mirror'):
if related_units(relid):
return True
def get_default_rbd_features():
"""Get default value for ``rbd_default_features``.
This is retrieved by asking the installed Ceph binary to show its runtime
config when using a empty configuration file.
:returns: Installed Ceph's Default vaule for ``rbd_default_features``
:rtype: int
:raises: subprocess.CalledProcessError
"""
output = subprocess.check_output(
['ceph', '-c', '/dev/null', '--show-config'],
universal_newlines=True)
for line in output.splitlines():
if 'rbd_default_features' in line:
return int(line.split('=')[1].lstrip().rstrip())
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)

View File

@ -57,6 +57,7 @@ class CephHooksTestCase(unittest.TestCase):
def setUp(self):
super(CephHooksTestCase, self).setUp()
@patch.object(ceph_hooks, 'get_rbd_features', return_value=None)
@patch.object(ceph_hooks, 'get_public_addr', lambda *args: "10.0.0.1")
@patch.object(ceph_hooks, 'get_cluster_addr', lambda *args: "10.1.0.1")
@patch.object(ceph_hooks, 'cmp_pkgrevno', lambda *args: 1)
@ -66,7 +67,8 @@ class CephHooksTestCase(unittest.TestCase):
@patch.object(ceph_hooks, 'leader_get', lambda *args: '1234')
@patch.object(ceph, 'config')
@patch.object(ceph_hooks, 'config')
def test_get_ceph_context(self, mock_config, mock_config2):
def test_get_ceph_context(self, mock_config, mock_config2,
_get_rbd_features):
config = copy.deepcopy(CHARM_CONFIG)
mock_config.side_effect = lambda key: config[key]
mock_config2.side_effect = lambda key: config[key]
@ -84,6 +86,7 @@ class CephHooksTestCase(unittest.TestCase):
'use_syslog': 'true'}
self.assertEqual(ctxt, expected)
@patch.object(ceph_hooks, 'get_rbd_features', return_value=1)
@patch.object(ceph_hooks, 'get_public_addr', lambda *args: "10.0.0.1")
@patch.object(ceph_hooks, 'get_cluster_addr', lambda *args: "10.1.0.1")
@patch.object(ceph_hooks, 'cmp_pkgrevno',
@ -94,9 +97,9 @@ class CephHooksTestCase(unittest.TestCase):
@patch.object(ceph_hooks, 'leader_get', lambda *args: '1234')
@patch.object(ceph, 'config')
@patch.object(ceph_hooks, 'config')
def test_get_ceph_context_rbd_features(self, mock_config, mock_config2):
def test_get_ceph_context_rbd_features(self, mock_config, mock_config2,
_get_rbd_features):
config = copy.deepcopy(CHARM_CONFIG)
config['default-rbd-features'] = 1
mock_config.side_effect = lambda key: config[key]
mock_config2.side_effect = lambda key: config[key]
ctxt = ceph_hooks.get_ceph_context()
@ -114,6 +117,7 @@ class CephHooksTestCase(unittest.TestCase):
'rbd_features': 1}
self.assertEqual(ctxt, expected)
@patch.object(ceph_hooks, 'get_rbd_features', return_value=None)
@patch.object(ceph_hooks, 'get_public_addr', lambda *args: "10.0.0.1")
@patch.object(ceph_hooks, 'get_cluster_addr', lambda *args: "10.1.0.1")
@patch.object(ceph_hooks, 'cmp_pkgrevno', lambda *args: 1)
@ -123,7 +127,8 @@ class CephHooksTestCase(unittest.TestCase):
@patch.object(ceph_hooks, 'leader_get', lambda *args: '1234')
@patch.object(ceph, 'config')
@patch.object(ceph_hooks, 'config')
def test_get_ceph_context_w_config_flags(self, mock_config, mock_config2):
def test_get_ceph_context_w_config_flags(self, mock_config, mock_config2,
_get_rbd_features):
config = copy.deepcopy(CHARM_CONFIG)
config['config-flags'] = '{"mon": {"mon sync max retries": 10}}'
mock_config.side_effect = lambda key: config[key]
@ -143,6 +148,7 @@ class CephHooksTestCase(unittest.TestCase):
'use_syslog': 'true'}
self.assertEqual(ctxt, expected)
@patch.object(ceph_hooks, 'get_rbd_features', return_value=None)
@patch.object(ceph_hooks, 'get_public_addr', lambda *args: "10.0.0.1")
@patch.object(ceph_hooks, 'get_cluster_addr', lambda *args: "10.1.0.1")
@patch.object(ceph_hooks, 'cmp_pkgrevno', lambda *args: 1)
@ -153,7 +159,8 @@ class CephHooksTestCase(unittest.TestCase):
@patch.object(ceph, 'config')
@patch.object(ceph_hooks, 'config')
def test_get_ceph_context_w_config_flags_invalid(self, mock_config,
mock_config2):
mock_config2,
_get_rbd_features):
config = copy.deepcopy(CHARM_CONFIG)
config['config-flags'] = ('{"mon": {"mon sync max retries": 10},'
'"foo": "bar"}')

View File

@ -0,0 +1,65 @@
# Copyright 2019 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 mock
import test_utils
from hooks import utils
class CephUtilsTestCase(test_utils.CharmTestCase):
def setUp(self):
super().setUp()
@mock.patch.object(utils, 'related_units')
@mock.patch.object(utils, 'relation_ids')
def test_has_rbd_mirrors(self, _relation_ids, _related_units):
# NOTE(fnordahl): This optimization will not be useful until we get a
# resolution on LP: #1818245
# _goal_state.return_value = {'relations': {'rbd-mirror': None}}
# self.assertTrue(utils.has_rbd_mirrors())
# _goal_state.assert_called_once_with()
# _goal_state.side_effect = NotImplementedError
_relation_ids.return_value = ['arelid']
_related_units.return_value = ['aunit/0']
self.assertTrue(utils.has_rbd_mirrors())
_relation_ids.assert_called_once_with('rbd-mirror')
_related_units.assert_called_once_with('arelid')
@mock.patch.object(utils.subprocess, 'check_output')
def test_get_default_rbd_features(self, _check_output):
_check_output.return_value = ('a = b\nrbd_default_features = 61\n'
'c = d\n')
self.assertEquals(
utils.get_default_rbd_features(),
61)
_check_output.assert_called_once_with(
['ceph', '-c', '/dev/null', '--show-config'],
universal_newlines=True)
@mock.patch.object(utils, 'get_default_rbd_features')
@mock.patch.object(utils, 'has_rbd_mirrors')
@mock.patch.object(utils, 'config')
def test_get_rbd_features(self, _config, _has_rbd_mirrors,
_get_default_rbd_features):
_config.side_effect = \
lambda key: {'default-rbd-features': 42}.get(key, None)
self.assertEquals(utils.get_rbd_features(), 42)
_has_rbd_mirrors.return_value = True
_get_default_rbd_features.return_value = 61
_config.side_effect = lambda key: {}.get(key, None)
self.assertEquals(utils.get_rbd_features(), 125)
_has_rbd_mirrors.return_value = False
self.assertEquals(utils.get_rbd_features(), None)