Add support for CephFS.

Extend ceph-proxy to implement ceph-mds interface, this allows the
ceph-fs to be related. The testing is made reusing the CephFSTests
testing class.

Usage example:

  juju add-relation ceph-proxy:mds ceph-fs:ceph-mds

Co-Authored-By: Felipe Reyes <felipe.reyes@canonical.com>
Closes-Bug: #1922195
Func-Test-PR: https://github.com/openstack-charmers/zaza-openstack-tests/pull/558
Change-Id: I437dbac9fe018eb2d0ffb87052d61a08aa014473
This commit is contained in:
Fulvio Galeazzi 2021-04-01 08:55:53 +00:00 committed by Felipe Reyes
parent 00b86b28f4
commit a2ffd3a497
13 changed files with 251 additions and 16 deletions

View File

@ -7,6 +7,10 @@ The ceph-proxy charm deploys a proxy that acts as a [ceph-mon][ceph-mon-charm]
application for an external Ceph cluster. It joins a non-charmed Ceph cluster
to a Juju model.
The charm works with traditional Ceph charm clients (e.g. cinder, glance,
nova-compute) but it also supports the [ceph-radosgw][ceph-radosgw-charm] and
[ceph-fs][ceph-fs-charm] charms.
# Usage
## Configuration
@ -66,7 +70,9 @@ For general charm questions refer to the [OpenStack Charm Guide][cg].
[ceph-upstream]: https://ceph.io
[cg]: https://docs.openstack.org/charm-guide
[ceph-mon-charm]: https://jaas.ai/ceph-mon
[juju-docs-actions]: https://jaas.ai/docs/actions
[ceph-mon-charm]: https://charmhub.io/ceph-mon
[ceph-fs-charm]: https://charmbui.io/ceph-fs
[ceph-radosgw-charm]: https://charmbui.io/ceph-radosgw
[juju-docs-actions]: https://charmbui.io/docs/actions
[juju-docs-config-apps]: https://juju.is/docs/configuring-applications
[lp-bugs-charm-ceph-proxy]: https://bugs.launchpad.net/charm-ceph-proxy/+filebug

View File

@ -351,6 +351,12 @@ def get_radosgw_key(name='radosgw.gateway'):
return get_named_key(name, _radosgw_caps)
def get_mds_key(name):
return get_named_entity_key(entity='mds',
name=name,
caps=mds_caps)
_default_caps = collections.OrderedDict([
('mon', ['allow r',
'allow command "osd blacklist"']),
@ -363,6 +369,12 @@ admin_caps = {
'osd': ['allow *']
}
mds_caps = collections.OrderedDict([
('osd', ['allow *']),
('mds', ['allow']),
('mon', ['allow rwx']),
])
osd_upgrade_caps = {
'mon': ['allow command "config-key"',
'allow command "osd tree"',
@ -390,15 +402,17 @@ def _config_user_key(name):
return k
def get_named_key(name, caps=None, pool_list=None):
def get_named_entity_key(name, caps=None, pool_list=None,
entity='client'):
"""Retrieve a specific named cephx key.
:param name: String Name of key to get.
:param pool_list: The list of pools to give access to
:param name: String Name of key to get. EXACT MATCH
:param caps: dict of cephx capabilities
:param pool_list: The list of pools to give access to
:param entity: String Name of type to get.
:returns: Returns a cephx key
"""
key_name = 'client.{}'.format(name)
key_name = '{}.{}'.format(entity, name)
try:
# Does the key already exist?
output = str(subprocess.check_output(
@ -424,7 +438,8 @@ def get_named_key(name, caps=None, pool_list=None):
return parse_key(output)
except subprocess.CalledProcessError:
# Couldn't get the key, time to create it!
log("Creating new key for {}".format(name), level=DEBUG)
log("Creating new key for {}".format(key_name), level=DEBUG)
caps = caps or _default_caps
cmd = [
"sudo",
@ -455,6 +470,17 @@ def get_named_key(name, caps=None, pool_list=None):
.strip()) # IGNORE:E1103
def get_named_key(name, caps=None, pool_list=None):
"""Retrieve a specific named cephx key.
:param name: String Name of key to get.
:param caps: dict of cephx capabilities
:param pool_list: The list of pools to give access to
:returns: Returns a cephx key
"""
return get_named_entity_key(name, caps, pool_list, entity='client')
def upgrade_key_caps(key, caps, pool_list=None):
""" Upgrade key to have capabilities caps """
if not is_leader():

View File

@ -31,6 +31,7 @@ import ceph
from charmhelpers.core.hookenv import (
log,
DEBUG,
INFO,
config,
is_leader,
relation_ids,
@ -137,6 +138,7 @@ def emit_cephconf():
notify_radosgws()
notify_client()
notify_cephfs_mds()
@hooks.hook('config-changed')
@ -160,6 +162,12 @@ def notify_client():
client_relation_joined(relid=relid, unit=unit)
def notify_cephfs_mds():
for relid in relation_ids('mds'):
for unit in related_units(relid):
mds_relation_joined(relid=relid, unit=unit)
@hooks.hook('radosgw-relation-changed')
@hooks.hook('radosgw-relation-joined')
def radosgw_relation(relid=None, unit=None):
@ -203,6 +211,41 @@ def radosgw_relation(relid=None, unit=None):
log('FSID or admin key not provided, please configure them')
@hooks.hook('mds-relation-joined')
@hooks.hook('mds-relation-changed')
def mds_relation_joined(relid=None, unit=None):
if not ready():
log('MDS: FSID or admin key not provided, please configure them',
level=INFO)
return
log('ceph-proxy config ok - providing mds client with keys')
if not unit:
unit = remote_unit()
mds_name = relation_get(attribute='mds-name',
rid=relid, unit=unit)
ceph_addrs = config('monitor-hosts')
data = {
'fsid': config('fsid'),
'auth': config('auth-supported'),
'ceph-public-address': ceph_addrs,
}
if mds_name:
data['{}_mds_key'.format(mds_name)] = (
ceph.get_mds_key(name=mds_name)
)
settings = relation_get(rid=relid, unit=unit) or {}
if 'broker_req' in settings:
rsp = process_requests(settings['broker_req'])
unit_id = unit.replace('/', '-')
unit_response_key = 'broker-rsp-' + unit_id
data[unit_response_key] = rsp
log('MDS: relation_set (%s): %s' % (relid, str(data)), level=DEBUG)
relation_set(relation_id=relid, relation_settings=data)
@hooks.hook('client-relation-joined')
def client_relation_joined(relid=None, unit=None):
if ready():

1
hooks/mds-relation-changed Symbolic link
View File

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

1
hooks/mds-relation-joined Symbolic link
View File

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

View File

@ -21,3 +21,5 @@ provides:
interface: ceph-client
radosgw:
interface: ceph-radosgw
mds:
interface: ceph-mds

View File

@ -25,6 +25,9 @@ machines:
'13':
'14':
'15':
'16':
'17':
'18':
applications:
@ -146,6 +149,19 @@ applications:
- '15'
channel: latest/edge
ubuntu: # used to test mounts
charm: ch:ubuntu
num_units: 2
to:
- '16'
- '17'
ceph-fs:
charm: ch:ceph-fs
channel: latest/edge
num_units: 1
to:
- '18'
relations:
@ -193,3 +209,6 @@ relations:
- - 'nova-compute:amqp'
- 'rabbitmq-server:amqp'
- - 'ceph-proxy:mds'
- 'ceph-fs:ceph-mds'

View File

@ -25,6 +25,9 @@ machines:
'13':
'14':
'15':
'16':
'17':
'18':
applications:
@ -146,6 +149,19 @@ applications:
- '15'
channel: latest/edge
ubuntu: # used to test mounts
charm: ch:ubuntu
num_units: 2
to:
- '16'
- '17'
ceph-fs:
charm: ch:ceph-fs
channel: latest/edge
num_units: 1
to:
- '18'
relations:
@ -193,3 +209,6 @@ relations:
- - 'nova-compute:amqp'
- 'rabbitmq-server:amqp'
- - 'ceph-proxy:mds'
- 'ceph-fs:ceph-mds'

View File

@ -25,6 +25,9 @@ machines:
'13':
'14':
'15':
'16':
'17':
'18':
applications:
@ -146,6 +149,19 @@ applications:
- '15'
channel: latest/edge
ubuntu: # used to test mounts
charm: ch:ubuntu
num_units: 2
to:
- '16'
- '17'
ceph-fs:
charm: ch:ceph-fs
channel: latest/edge
num_units: 1
to:
- '18'
relations:
@ -193,3 +209,6 @@ relations:
- - 'nova-compute:amqp'
- 'rabbitmq-server:amqp'
- - 'ceph-proxy:mds'
- 'ceph-fs:ceph-mds'

View File

@ -25,6 +25,9 @@ machines:
'13':
'14':
'15':
'16':
'17':
'18':
applications:
@ -146,6 +149,19 @@ applications:
- '15'
channel: latest/edge
ubuntu: # used to test mounts
charm: ch:ubuntu
num_units: 2
to:
- '16'
- '17'
ceph-fs:
charm: ch:ceph-fs
channel: latest/edge
num_units: 1
to:
- '18'
relations:
@ -193,3 +209,6 @@ relations:
- - 'nova-compute:amqp'
- 'rabbitmq-server:amqp'
- - 'ceph-proxy:mds'
- 'ceph-fs:ceph-mds'

View File

@ -25,6 +25,9 @@ machines:
'13':
'14':
'15':
'16':
'17':
'18':
applications:
@ -146,6 +149,19 @@ applications:
- '15'
channel: latest/edge
ubuntu: # used to test mounts
charm: ch:ubuntu
num_units: 2
to:
- '16'
- '17'
ceph-fs:
charm: ch:ceph-fs
channel: latest/edge
num_units: 1
to:
- '18'
relations:
@ -193,3 +209,6 @@ relations:
- - 'nova-compute:amqp'
- 'rabbitmq-server:amqp'
- - 'ceph-proxy:mds'
- 'ceph-fs:ceph-mds'

View File

@ -7,6 +7,7 @@ configure:
tests:
- zaza.openstack.charm_tests.ceph.tests.CephProxyTest
- zaza.openstack.charm_tests.ceph.fs.tests.CephFSWithCephProxyTests
- erasure-coded:
- zaza.openstack.charm_tests.ceph.tests.CephProxyTest
- zaza.openstack.charm_tests.ceph.tests.CheckPoolTypes
@ -33,22 +34,28 @@ smoke_bundles:
target_deploy_status:
ceph-proxy:
workload-status: blocked
workload-status-message: Ensure FSID and admin-key are set
workload-status-message-prefix: "Ensure FSID and admin-key are set"
ceph-radosgw:
workload-status: waiting
workload-status-message: "Incomplete relations: mon"
workload-status-message-prefix: "Incomplete relations: mon"
keystone:
workload-status: active
workload-status-message: "Unit is ready"
nova-compute:
workload-status: waiting
workload-status-message: "Incomplete relations: storage-backend"
workload-status-message-prefix: "Unit is ready"
cinder-ceph:
workload-status: waiting
workload-status-message: "Ceph broker request incomplete"
workload-status-message-prefix: "Ceph broker request incomplete"
ceph-fs:
workload-status: waiting
workload-status-message-prefix: "'ceph-mds' incomplete"
nova-compute:
workload-status: waiting
workload-status-message-prefix: "Incomplete relations: storage-backend"
glance:
workload-status: waiting
workload-status-message: "Incomplete relations: storage-backend"
workload-status-message-prefix: "Incomplete relations: storage-backend"
ubuntu:
workload-status: active
workload-status-message-prefix: ''
tests_options:
force_deploy:

View File

@ -74,10 +74,11 @@ class TestHooks(test_utils.CharmTestCase):
mock_apt_install.assert_called_with(packages=[])
@mock.patch('ceph.ceph_user')
@mock.patch.object(hooks, 'mds_relation_joined', autospec=True)
@mock.patch.object(hooks, 'radosgw_relation')
@mock.patch.object(hooks, 'client_relation_joined')
def test_emit_cephconf(self, mock_client_rel, mock_rgw_rel,
mock_ceph_user):
mock_mds_rel, mock_ceph_user):
mock_ceph_user.return_value = 'ceph-user'
self.test_config.set('monitor-hosts', '127.0.0.1:1234')
self.test_config.set('fsid', 'abc123')
@ -89,6 +90,8 @@ class TestHooks(test_utils.CharmTestCase):
'client': ['client:1'],
'rados:1': ['rados/1'],
'client:1': ['client/1'],
'mds': ['mds:2'],
'mds:2': ['mds/3'],
}
return x[k]
@ -127,6 +130,7 @@ class TestHooks(test_utils.CharmTestCase):
mock_rgw_rel.assert_called_with(relid='rados:1', unit='rados/1')
mock_client_rel.assert_called_with(relid='client:1', unit='client/1')
mock_mds_rel.assert_called_with(relid='mds:2', unit='mds/3')
@mock.patch.object(hooks.ceph, 'ceph_user')
@mock.patch('subprocess.check_output')
@ -162,6 +166,56 @@ class TestHooks(test_utils.CharmTestCase):
mock_package_install.assert_not_called()
mock_emit_cephconf.assert_any_call()
@mock.patch('subprocess.check_output', autospec=True)
@mock.patch('ceph.config', autospec=True)
@mock.patch('ceph.get_mds_key', autospec=True)
@mock.patch('ceph.ceph_user', autospec=True)
def test_mds_relation_joined(self, ceph_user, get_mds_key, ceph_config,
check_output):
my_mds_key = '1234-key'
mds_name = 'adjusted-mayfly'
rid = 'mds:1'
ceph_user.return_value = 'ceph'
get_mds_key.return_value = my_mds_key
ceph_config.side_effect = self.test_config.get
settings = {'ceph-public-address': '127.0.0.1:1234 [::1]:4321',
'auth': 'cephx',
'fsid': 'some-fsid'}
rel_data_get = {'broker_req': 'my-uuid',
'mds-name': mds_name}
rel_data_set = {'broker-rsp-client-0': 'foobar',
'%s_mds_key' % mds_name: my_mds_key}
rel_data_set.update(settings)
def fake_relation_get(attribute=None, rid=None, unit=None):
if attribute:
return rel_data_get[attribute]
else:
return rel_data_get
self.relation_get.side_effect = fake_relation_get
# unconfigured ceph-proxy
with mock.patch.object(hooks, 'log') as log:
hooks.mds_relation_joined()
log.assert_called_with(
'MDS: FSID or admin key not provided, please configure them',
level='INFO')
# Configure ceph-proxy with the ceph details.
self.test_config.set('monitor-hosts', settings['ceph-public-address'])
self.test_config.set('fsid', settings['fsid'])
self.test_config.set('admin-key', 'some-admin-key')
with mock.patch.object(hooks, 'process_requests') as process_requests:
process_requests.return_value = 'foobar'
hooks.mds_relation_joined(relid=rid)
process_requests.assert_called_with('my-uuid')
self.relation_set.assert_called_with(
relation_id=rid, relation_settings=rel_data_set)
@mock.patch('ceph_hooks.emit_cephconf')
@mock.patch('ceph_hooks.package_install')
def test_update_apt_source(self, mock_package_install, mock_emit_cephconf):