Merge "Implement new relation: 'ceph-replication-device'"

This commit is contained in:
Zuul 2021-01-08 09:30:47 +00:00 committed by Gerrit Code Review
commit eb2757c98c
10 changed files with 289 additions and 15 deletions

View File

@ -0,0 +1 @@
cinder_hooks.py

View File

@ -0,0 +1 @@
cinder_hooks.py

View File

@ -0,0 +1 @@
cinder_hooks.py

View File

@ -17,11 +17,15 @@ from charmhelpers.core.hookenv import (
service_name,
is_relation_made,
leader_get,
log,
relation_get,
relation_ids,
related_units,
DEBUG,
)
from charmhelpers.contrib.openstack.context import (
CephContext,
OSContextGenerator,
)
@ -30,6 +34,10 @@ from charmhelpers.contrib.openstack.utils import (
CompareOpenStackReleases,
)
from charmhelpers.contrib.network.ip import (
format_ipv6_addr,
)
CHARM_CEPH_CONF = '/var/lib/charm/{}/ceph.conf'
@ -99,3 +107,51 @@ class CephSubordinateContext(OSContextGenerator):
config('rbd-flatten-volume-from-snapshot')))
return {'cinder': {'/etc/cinder/cinder.conf': {'sections': section}}}
class CephReplicationDeviceContext(CephContext):
"""Generates context for /etc/ceph/ceph.conf templates."""
interfaces = ['ceph-replication-device']
def __call__(self):
if not relation_ids('ceph-replication-device'):
return {}
log('Generating template context for ceph-replication-device',
level=DEBUG)
mon_hosts = []
ctxt = {
'use_syslog': str(config('use-syslog')).lower()
}
for rid in relation_ids('ceph-replication-device'):
for unit in related_units(rid):
if not ctxt.get('auth'):
ctxt['auth'] = relation_get('auth', rid=rid, unit=unit)
if not ctxt.get('key'):
ctxt['key'] = relation_get('key', rid=rid, unit=unit)
ceph_addrs = relation_get('ceph-public-address', rid=rid,
unit=unit)
if ceph_addrs:
for addr in ceph_addrs.split(' '):
mon_hosts.append(format_ipv6_addr(addr) or addr)
else:
priv_addr = relation_get('private-address', rid=rid,
unit=unit)
mon_hosts.append(format_ipv6_addr(priv_addr) or priv_addr)
ctxt['mon_hosts'] = ' '.join(sorted(mon_hosts))
if not self.context_complete(ctxt):
return {}
return ctxt
class CinderCephContext(CephContext):
def __call__(self):
ctxt = super(CinderCephContext, self).__call__()
# NOTE: If "rbd-mirroring-mode" is set to "image" we are going
# to ignore default 'rbd_features' that are set in the context
if config('rbd-mirror-mode') == "image":
ctxt.pop('rbd_features', None)
return ctxt

View File

@ -62,6 +62,7 @@ from charmhelpers.core.hookenv import (
log,
relation_ids,
relation_set,
application_name,
service_name,
status_set,
UnregisteredHookError,
@ -76,6 +77,7 @@ from charmhelpers.payload.execd import execd_preinstall
from cinder_contexts import (
ceph_config_file,
CephSubordinateContext,
CephReplicationDeviceContext,
)
from cinder_utils import (
CEPH_CONF,
@ -85,6 +87,7 @@ from cinder_utils import (
restart_map,
scrub_old_style_ceph,
VERSION_PACKAGE,
ceph_replication_device_config_file,
)
@ -109,6 +112,13 @@ def ceph_joined():
send_application_name()
@hooks.hook('ceph-replication-device-relation-joined')
def ceph_replication_device_joined():
data = {'application-name': '{}-replication-device'.format(
application_name())}
relation_set(relation_settings=data)
def get_ceph_request():
rq = CephBrokerRq()
service = service_name()
@ -249,6 +259,26 @@ def ceph_changed():
level=DEBUG)
@hooks.hook('ceph-replication-device-relation-changed')
@restart_on_change(restart_map())
def ceph_replication_device_changed():
if 'ceph-replication-device' not in CONFIGS.complete_contexts():
log('ceph-replication-device relation incomplete.')
return
app_name = '{}-replication-device'.format(application_name())
if not ensure_ceph_keyring(service=app_name,
relation='ceph-replication-device',
user='cinder', group='cinder'):
log('Could not create ceph keyring.')
return
CONFIGS.write_all()
for rid in relation_ids('storage-backend'):
storage_backend(rid)
@hooks.hook('ceph-relation-broken')
def ceph_broken():
service = service_name()
@ -257,6 +287,13 @@ def ceph_broken():
remove_alternative(os.path.basename(CEPH_CONF), ceph_config_file())
@hooks.hook('ceph-replication-device-relation-broken')
def ceph_replication_device_broken():
app_name = '{}-replication-device'.format(application_name())
delete_keyring(service=app_name)
CONFIGS.write_all()
@hooks.hook('config-changed')
@restart_on_change(restart_map())
def write_and_restart():
@ -274,13 +311,28 @@ def write_and_restart():
def storage_backend(rel_id=None):
if 'ceph' not in CONFIGS.complete_contexts():
log('ceph relation incomplete. Peer not ready?')
else:
relation_set(
relation_id=rel_id,
backend_name=service_name(),
subordinate_configuration=json.dumps(CephSubordinateContext()()),
stateless=True,
)
return
subordinate_config = CephSubordinateContext()()
if 'ceph-replication-device' in CONFIGS.complete_contexts():
replication_device = {
'backend_id': 'ceph',
'conf': ceph_replication_device_config_file(),
'user': '{}-replication-device'.format(application_name())
}
replication_device_str = ','.join(
['{}:{}'.format(k, v) for k, v in replication_device.items()])
subordinate_config['cinder'][
'/etc/cinder/cinder.conf']['sections'][application_name()].append(
('replication_device', replication_device_str))
relation_set(
relation_id=rel_id,
backend_name=service_name(),
subordinate_configuration=json.dumps(subordinate_config),
stateless=True,
)
@hooks.hook('storage-backend-relation-changed')
@ -328,12 +380,42 @@ def ceph_access_joined(relation_id=None):
# NOTE(jamespage): get key from ceph using a context
ceph_keys = CephContext()()
relation_set(
relation_id=relation_id,
relation_settings={'key': ceph_keys.get('key'),
'secret-uuid': leader_get('secret-uuid')}
)
if 'ceph-replication-device' not in CONFIGS.complete_contexts():
relation_data = {
'key': ceph_keys.get('key'),
'secret-uuid': leader_get('secret-uuid')
}
relation_set(
relation_id=relation_id,
relation_settings=relation_data
)
else:
replication_secret_uuid = leader_get('replication-device-secret-uuid')
if not replication_secret_uuid:
if is_leader():
leader_set(
{'replication-device-secret-uuid': str(uuid.uuid4())})
else:
log('Deferring keyrings provision until '
'leader seeds replication device uuid')
return
ceph_replication_keys = CephReplicationDeviceContext()()
keyrings = [
{
'name': application_name(),
'key': ceph_keys.get('key'),
'secret-uuid': leader_get('secret-uuid')
},
{
'name': '{}-replication-device'.format(application_name()),
'key': ceph_replication_keys.get('key'),
'secret-uuid': leader_get('replication-device-secret-uuid')
}
]
relation_set(
relation_id=relation_id,
keyrings=json.dumps(keyrings)
)
@hooks.hook('pre-series-upgrade')

View File

@ -18,7 +18,6 @@ from collections import OrderedDict
from tempfile import NamedTemporaryFile
from charmhelpers.contrib.openstack import (
context,
templating,
)
from charmhelpers.contrib.openstack.alternatives import install_alternative
@ -26,6 +25,7 @@ from charmhelpers.contrib.openstack.utils import get_os_codename_package
from charmhelpers.core.hookenv import (
hook_name,
relation_ids,
application_name,
service_name,
)
from charmhelpers.core.host import mkdir
@ -58,6 +58,11 @@ def ceph_config_file():
return CHARM_CEPH_CONF.format(service_name())
def ceph_replication_device_config_file():
return CHARM_CEPH_CONF.format(
'{}-replication-device'.format(application_name()))
def register_configs():
"""
Register config files with their respective contexts.
@ -87,12 +92,26 @@ def register_configs():
install_alternative(os.path.basename(CEPH_CONF),
CEPH_CONF, ceph_config_file())
CONFIG_FILES[ceph_config_file()] = {
'hook_contexts': [context.CephContext(),
'hook_contexts': [cinder_contexts.CinderCephContext(),
cinder_contexts.CephAccessContext()],
'services': ['cinder-volume'],
}
confs.append(ceph_config_file())
relation_present = relation_ids('ceph-replication-device') and \
hook_name() != 'ceph-replication-device-relation-broken'
if relation_present:
mkdir(os.path.dirname(ceph_replication_device_config_file()))
if not os.path.exists(ceph_replication_device_config_file()):
open(ceph_replication_device_config_file(), 'wt').close()
CONFIG_FILES[ceph_replication_device_config_file()] = {
'hook_contexts': [cinder_contexts.CephReplicationDeviceContext()],
'services': ['cinder-volume'],
}
confs.append(ceph_replication_device_config_file())
for conf in confs:
configs.register(conf, CONFIG_FILES[conf]['hook_contexts'])

View File

@ -30,3 +30,5 @@ requires:
scope: container
ceph:
interface: ceph-client
ceph-replication-device:
interface: ceph-client

View File

@ -24,6 +24,7 @@ TO_PATCH = [
'service_name',
'get_os_codename_package',
'leader_get',
'relation_get',
'relation_ids',
'related_units',
]
@ -241,3 +242,29 @@ class TestCinderContext(CharmTestCase):
contexts.CephAccessContext()(),
{'complete': True}
)
def test_ceph_replication_device(self):
'''Test ceph context with ceph-replication-device relation'''
self.relation_get.side_effect = ['foo', 'bar',
'ceph-mon-0', 'ceph-mon-1']
self.relation_ids.return_value = ['ceph-replication-device:1']
self.related_units.return_value = ['ceph-mon/0', 'ceph-mon/1']
ctxt = contexts.CephReplicationDeviceContext()
ctxt_dict = ctxt()
self.assertEqual(
ctxt.context_complete(ctxt_dict),
True
)
self.assertEqual(
ctxt_dict,
{
'use_syslog': 'false',
'auth': 'foo',
'key': 'bar',
'mon_hosts': 'ceph-mon-0 ceph-mon-1'
}
)
self.assertEquals(
contexts.CephReplicationDeviceContext.interfaces,
['ceph-replication-device']
)

View File

@ -38,7 +38,9 @@ TO_PATCH = [
'CONFIGS',
'CEPH_CONF',
'ceph_config_file',
'ceph_replication_device_config_file',
# charmhelpers.core.hookenv
'application_name',
'config',
'relation_ids',
'relation_set',
@ -327,6 +329,53 @@ class TestCinderHooks(CharmTestCase):
stateless=True,
)
@patch('charmhelpers.core.hookenv.config')
def test_storage_backend_replication_device(self, mock_config):
self.application_name.return_value = 'test'
app_name = '{}-replication-device'.format(self.application_name())
self.service_name.return_value = app_name
self.CONFIGS.complete_contexts.return_value = [
'ceph', 'ceph-replication-device']
def func():
return {
'cinder': {
'/etc/cinder/cinder.conf': {
'sections': {
'test': []
}
}
}
}
self.CephSubordinateContext.return_value = func
hooks.hooks.execute(['hooks/storage-backend-relation-joined'])
replication_device = {
'backend_id': 'ceph',
'conf': self.ceph_replication_device_config_file(),
'user': 'test-replication-device'
}
replication_device_str = ','.join(
['{}:{}'.format(k, v) for k, v in replication_device.items()])
expected_config = {
'cinder': {
'/etc/cinder/cinder.conf': {
'sections': {
'test': [
('replication_device', replication_device_str)
]
}
}
}
}
self.relation_set.assert_called_with(
relation_id=None,
backend_name='test-replication-device',
subordinate_configuration=json.dumps(expected_config),
stateless=True,
)
@patch.object(hooks, 'ceph_access_joined')
@patch.object(hooks, 'storage_backend')
def test_leader_settings_changed(self,
@ -441,3 +490,38 @@ class TestCinderHooks(CharmTestCase):
hooks.assess_status()
self.status_set.assert_called_once_with(
'blocked', 'Invalid configuration: fake message')
@patch('charmhelpers.core.hookenv.config')
@patch.object(hooks, 'storage_backend')
def test_ceph_replication_device_changed(self,
storage_backend,
mock_config):
self.CONFIGS.complete_contexts.return_value = [
'ceph-replication-device']
self.ensure_ceph_keyring.return_value = True
self.relation_ids.return_value = ['storage-backend:1']
app_name = '{}-replication-device'.format(self.application_name())
hooks.hooks.execute(['hooks/ceph-replication-device-relation-changed'])
self.ensure_ceph_keyring.assert_called_with(
service=app_name,
relation='ceph-replication-device',
user='cinder',
group='cinder')
self.assertTrue(self.CONFIGS.write_all.called)
storage_backend.assert_called_with('storage-backend:1')
@patch('charmhelpers.core.hookenv.config')
def test_ceph_replication_device_broken(self, mock_config):
app_name = '{}-replication-device'.format(self.application_name())
self.service_name.return_value = app_name
hooks.hooks.execute(['hooks/ceph-replication-device-relation-broken'])
self.delete_keyring.assert_called_with(
service=self.service_name.return_value)
self.assertTrue(self.CONFIGS.write_all.called)
@patch('charmhelpers.core.hookenv.config')
def test_ceph_replication_device_joined(self, mock_config):
data = {'application-name': '{}-replication-device'.format(
self.application_name())}
hooks.hooks.execute(['hooks/ceph-replication-device-relation-joined'])
self.relation_set.assert_called_with(relation_settings=data)

View File

@ -24,6 +24,7 @@ TO_PATCH = [
# helpers.core.hookenv
'relation_ids',
'service_name',
'application_name',
# storage_utils
'get_os_codename_package',
'templating',