Add operator-native ceph-client library

Change-Id: Id9caf3b385094b9bc4010893034185d0a47c45d4
This commit is contained in:
Chris MacNaughton 2022-09-27 14:14:46 -04:00
parent 11b7a7340b
commit 7703ba5c28
7 changed files with 527 additions and 334 deletions

208
src/ceph_client.py Normal file
View File

@ -0,0 +1,208 @@
"""Ceph client library
"""
import json
import logging
from ops.framework import Object
from ops.framework import StoredState
from charmhelpers.contrib.storage.linux.ceph import (
send_osd_settings,
)
import charms_ceph.utils as ceph
from utils import (
get_public_addr,
get_rbd_features,
)
logger = logging.getLogger(__name__)
class CephClientProvides(Object):
"""
Encapsulate the Provides side of the Ceph Client relation.
Hook events observed:
- relation-joined
- relation-changed
"""
charm = None
_stored = StoredState()
def __init__(self, charm, relation_name='client'):
super().__init__(charm, relation_name)
self._stored.set_default(processed=[])
self.charm = charm
self.this_unit = self.model.unit
self.relation_name = relation_name
self.framework.observe(
charm.on[self.relation_name].relation_joined,
self._on_relation_changed
)
self.framework.observe(
charm.on[self.relation_name].relation_changed,
self._on_relation_changed
)
def notify_all(self):
send_osd_settings()
if not self.charm.ready_for_service():
return
for relation in self.model.relations[self.relation_name]:
for unit in relation.units:
self._handle_client_relation(relation, unit)
def _on_relation_changed(self, event):
"""Prepare relation for data from requiring side."""
send_osd_settings()
if not self.charm.ready_for_service():
return
self._handle_client_relation(event.relation, event.unit)
def _get_ceph_info_from_configs(self):
"""Create dictionary of ceph information required to set client relation.
:returns: Dictionary of ceph configurations needed for client relation
:rtype: dict
"""
public_addr = get_public_addr()
rbd_features = get_rbd_features()
data = {
'auth': 'cephx',
'ceph-public-address': public_addr
}
if rbd_features:
data['rbd-features'] = rbd_features
return data
def _get_custom_relation_init_data(self):
"""Information required for specialised relation.
:returns: Ceph configurations needed for specialised relation
:rtype: dict
"""
return {}
def _get_client_application_name(self, relation, unit):
"""Retrieve client application name from relation data."""
return relation.data[unit].get(
'application-name',
relation.app)
def _handle_client_relation(self, relation, unit):
"""Handle broker request and set the relation data
:param relation: Operator relation
:type relation: Relation
:param unit: Unit to handle
:type unit: Unit
"""
# if is_unsupported_cmr(unit):
# return
logger.debug(
'mon cluster in quorum and osds bootstrapped '
'- providing client with keys, processing broker requests')
service_name = self._get_client_application_name(relation, unit)
data = self._get_ceph_info_from_configs()
data.update(self._get_custom_relation_init_data())
data.update({'key': ceph.get_named_key(service_name)})
data.update(
self._handle_broker_request(
relation, unit, add_legacy_response=True))
for k, v in data.items():
relation.data[self.this_unit][k] = str(v)
def _req_already_treated(self, request_id):
"""Check if broker request already handled.
The local relation data holds all the broker request/responses that
are handled as a dictionary. There will be a single entry for each
unit that makes broker request in the form of broker-rsp-<unit name>:
{reqeust-id: <id>, ..}. Verify if request_id exists in the relation
data broker response for the requested unit.
:param request_id: Request ID
:type request_id: str
:returns: Whether request is already handled
:rtype: bool
"""
return request_id in self._stored.processed
def _handle_broker_request(
self, relation, unit, add_legacy_response=False):
"""Retrieve broker request from relation, process, return response data.
:param event: Operator event for the relation
:type relid: Event
:param add_legacy_response: (Optional) Adds the legacy ``broker_rsp``
key to the response in addition to the
new way.
:type add_legacy_response: bool
:returns: Dictionary of response data ready for use with relation_set.
:rtype: dict
"""
def _get_broker_req_id(request):
try:
if isinstance(request, str):
try:
req_key = json.loads(request)['request-id']
except (TypeError, json.decoder.JSONDecodeError):
logger.warning(
'Not able to decode request '
'id for broker request {}'.
format(request))
req_key = None
else:
req_key = request['request-id']
except KeyError:
logger.warning(
'Not able to decode request id for broker request {}'.
format(request))
req_key = None
return req_key
response = {}
settings = relation.data[unit]
if 'broker_req' in settings:
broker_req_id = _get_broker_req_id(settings['broker_req'])
if broker_req_id is None:
return {}
if not ceph.is_leader():
logger.debug(
"Not leader - ignoring broker request {}".format(
broker_req_id))
return {}
if self._req_already_treated(broker_req_id):
logger.debug(
"Ignoring already executed broker request {}".format(
broker_req_id))
return {}
rsp = self.charm.process_broker_request(
broker_req_id, settings['broker_req'])
unit_id = settings.get(
'unit-name', unit.name).replace('/', '-')
unit_response_key = 'broker-rsp-' + unit_id
response.update({unit_response_key: rsp})
if add_legacy_response:
response.update({'broker_rsp': rsp})
processed = self._stored.processed
processed.append(broker_req_id)
self._stored.processed = processed
else:
logger.warn('broker_req not in settings: {}'.format(settings))
return response

View File

@ -252,6 +252,12 @@ def update_host_osd_count_report(reset=False):
@hooks.hook('config-changed')
@harden()
def config_changed():
'''
Handle config-changed
:returns: Whether or not relations should be notified after completion.
:rtype: bool
'''
# Get the cfg object so we can see if the no-bootstrap value has changed
# and triggered this hook invocation
cfg = config()
@ -341,8 +347,7 @@ def config_changed():
try_disable_insecure_reclaim()
for relid in relation_ids('dashboard'):
dashboard_relation(relid)
# Update client relations
notify_client()
return True
def get_mon_hosts():
@ -393,6 +398,10 @@ def bootstrap_source_relation_changed():
the ceph-mon charm. This relation is used to exchange the remote
ceph-public-addresses which are used for the mon's, the fsid, and the
monitor-secret.
:returns: Whether or not relations should be notified after completion.
:rtype: bool
''
"""
if not config('no-bootstrap'):
status_set('blocked', 'Cannot join the bootstrap-source relation when '
@ -445,7 +454,7 @@ def bootstrap_source_relation_changed():
# The leader unit needs to bootstrap itself as it won't receive the
# leader-settings-changed hook elsewhere.
if curr_fsid:
mon_relation()
return mon_relation()
@hooks.hook('prometheus-relation-joined',
@ -488,6 +497,12 @@ def prometheus_left():
'leader-settings-changed',
'bootstrap-source-relation-departed')
def mon_relation():
'''
Handle the mon relation
:returns: Whether or not relations should be notified after completion.
:rtype: bool
'''
if leader_get('monitor-secret') is None:
log('still waiting for leader to setup keys')
status_set('waiting', 'Waiting for leader to setup keys')
@ -503,9 +518,11 @@ def mon_relation():
# the unit handling the broker request will update a nonce on the
# mon relation.
notify_relations()
return True
else:
if attempt_mon_cluster_bootstrap():
notify_relations()
return True
else:
log('Not enough mons ({}), punting.'
.format(len(get_mon_hosts())))
@ -578,7 +595,6 @@ def attempt_mon_cluster_bootstrap():
def notify_relations():
notify_osds()
notify_radosgws()
notify_client()
notify_rbd_mirrors()
notify_prometheus()
@ -613,79 +629,6 @@ def notify_rbd_mirrors():
rbd_mirror_relation(relid=relid, unit=unit, recurse=False)
def _get_ceph_info_from_configs():
"""Create dictionary of ceph information required to set client relation.
:returns: Dictionary of ceph configurations needed for client relation
:rtpe: dict
"""
public_addr = get_public_addr()
rbd_features = get_rbd_features()
data = {
'auth': 'cephx',
'ceph-public-address': public_addr
}
if rbd_features:
data['rbd-features'] = rbd_features
return data
def _handle_client_relation(relid, unit, data=None):
"""Handle broker request and set the relation data
:param relid: Relation ID
:type relid: str
:param unit: Unit name
:type unit: str
:param data: Initial relation data
:type data: dict
"""
if data is None:
data = {}
if is_unsupported_cmr(unit):
return
data.update(
handle_broker_request(relid, unit, add_legacy_response=True))
relation_set(relation_id=relid, relation_settings=data)
def notify_client():
send_osd_settings()
if not ready_for_service():
log("mon cluster is not in quorum, skipping notify_client",
level=WARNING)
return
for relid in relation_ids('client'):
data = _get_ceph_info_from_configs()
service_name = None
# Loop through all related units until client application name is found
# This is done in seperate loop to avoid calling ceph to retreive named
# key for every unit
for unit in related_units(relid):
service_name = get_client_application_name(relid, unit)
if service_name:
data.update({'key': ceph.get_named_key(service_name)})
break
if not service_name:
log('Unable to determine remote service name, deferring processing'
' of broker requests for relation {} '.format(relid))
# continue with next relid
continue
for unit in related_units(relid):
_handle_client_relation(relid, unit, data)
for relid in relation_ids('admin'):
admin_relation_joined(relid)
for relid in relation_ids('mds'):
for unit in related_units(relid):
mds_relation_joined(relid=relid, unit=unit)
def req_already_treated(request_id, relid, req_unit):
"""Check if broker request already handled.
@ -911,7 +854,6 @@ def osd_relation(relid=None, unit=None):
# NOTE: radosgw key provision is gated on presence of OSD
# units so ensure that any deferred hooks are processed
notify_radosgws()
notify_client()
notify_rbd_mirrors()
send_osd_settings()
@ -1036,6 +978,16 @@ def radosgw_relation(relid=None, unit=None):
@hooks.hook('rbd-mirror-relation-joined')
@hooks.hook('rbd-mirror-relation-changed')
def rbd_mirror_relation(relid=None, unit=None, recurse=True):
'''
Handle the rbd mirror relation
:param recurse: Whether we should call out to update relation functions or
not. Mainly used to handle recursion when called from
notify_rbd_mirrors()
:type recurse: bool
:returns: Whether or not relations should be notified after completion.
:rtype: bool
'''
if ready_for_service():
log('mon cluster in quorum and osds bootstrapped '
'- providing rbd-mirror client with keys')
@ -1071,7 +1023,7 @@ def rbd_mirror_relation(relid=None, unit=None, recurse=True):
# make sure clients are updated with the appropriate RBD features
# bitmap.
if recurse:
notify_client()
return True
@hooks.hook('mds-relation-changed')
@ -1117,26 +1069,6 @@ def admin_relation_joined(relid=None):
relation_settings=data)
@hooks.hook('client-relation-changed')
@hooks.hook('client-relation-joined')
def client_relation(relid=None, unit=None):
send_osd_settings()
if ready_for_service():
log('mon cluster in quorum and osds bootstrapped '
'- providing client with keys, processing broker requests')
if not unit:
unit = remote_unit()
service_name = get_client_application_name(relid, unit)
if not service_name:
log('Unable to determine remote service name, deferring '
'processing of broker requests for relation {} '
'remote unit {}'.format(relid, unit))
return
data = _get_ceph_info_from_configs()
data.update({'key': ceph.get_named_key(service_name)})
_handle_client_relation(relid, unit, data)
@hooks.hook('upgrade-charm.real')
@harden()
def upgrade_charm():

View File

@ -7,15 +7,35 @@ import ceph_status
import charms.operator_libs_linux.v0.apt as apt
import charms.operator_libs_linux.v1.systemd as systemd
from ops.charm import CharmEvents
from ops.framework import EventBase, EventSource
import ops_openstack.core
import charms_ceph.utils as ceph
from charms_ceph.broker import (
process_requests
)
import ceph_hooks as hooks
import ceph_client
import ceph_metrics
import ops_actions
logger = logging.getLogger(__name__)
class NotifyClientEvent(EventBase):
def __init__(self, handle):
super().__init__(handle)
class CephCharmEvents(CharmEvents):
"""Custom charm events."""
notify_clients = EventSource(NotifyClientEvent)
class CephMonCharm(ops_openstack.core.OSBaseCharm):
release = 'quincy'
@ -25,6 +45,8 @@ class CephMonCharm(ops_openstack.core.OSBaseCharm):
'radosgw', 'lvm2', 'parted', 'smartmontools',
]
on = CephCharmEvents()
# General charm control callbacks.
# TODO: Figure out how to do hardening in an operator-framework
@ -43,7 +65,8 @@ class CephMonCharm(ops_openstack.core.OSBaseCharm):
pass
def on_config(self, event):
hooks.config_changed()
if hooks.config_changed():
self.on.notify_clients.emit()
def on_pre_series_upgrade(self, event):
hooks.pre_series_upgrade()
@ -51,6 +74,7 @@ class CephMonCharm(ops_openstack.core.OSBaseCharm):
def on_upgrade(self, event):
self.metrics_endpoint.update_alert_rules()
hooks.upgrade_charm()
self.on.notify_clients.emit()
def on_post_series_upgrade(self, event):
hooks.post_series_upgrade()
@ -60,7 +84,8 @@ class CephMonCharm(ops_openstack.core.OSBaseCharm):
hooks.mon_relation_joined()
def on_bootstrap_source_relation_changed(self, event):
hooks.bootstrap_source_relation_changed()
if hooks.bootstrap_source_relation_changed():
self.on.notify_clients.emit()
def on_prometheus_relation_joined_or_changed(self, event):
hooks.prometheus_relation()
@ -69,10 +94,12 @@ class CephMonCharm(ops_openstack.core.OSBaseCharm):
hooks.prometheus_left()
def on_mon_relation(self, event):
hooks.mon_relation()
if hooks.mon_relation():
self.on.notify_clients.emit()
def on_osd_relation(self, event):
hooks.osd_relation()
self.on.notify_clients.emit()
def on_dashboard_relation_joined(self, event):
hooks.dashboard_relation()
@ -81,7 +108,8 @@ class CephMonCharm(ops_openstack.core.OSBaseCharm):
hooks.radosgw_relation()
def on_rbd_mirror_relation(self, event):
hooks.rbd_mirror_relation()
if hooks.rbd_mirror_relation():
self.on.notify_clients.emit()
def on_mds_relation(self, event):
hooks.mds_relation_joined()
@ -89,9 +117,6 @@ class CephMonCharm(ops_openstack.core.OSBaseCharm):
def on_admin_relation(self, event):
hooks.admin_relation_joined()
def on_client_relation(self, event):
hooks.client_relation()
def on_nrpe_relation(self, event):
hooks.update_nrpe_config()
@ -122,6 +147,16 @@ class CephMonCharm(ops_openstack.core.OSBaseCharm):
remote_block = not self.config['permit-insecure-cmr']
return remote_block
def notify_clients(self, _event):
self.clients.notify_all()
for relation in self.model.relations['admin']:
hooks.admin_relation_joined(str(relation.id))
for relation in self.model.relations['mds']:
for unit in relation.units:
hooks.mds_relation_joined(
relid=str(relation.id), unit=unit.name)
def __init__(self, *args):
super().__init__(*args)
self._stored.is_started = True
@ -133,6 +168,7 @@ class CephMonCharm(ops_openstack.core.OSBaseCharm):
fw = self.framework
self.clients = ceph_client.CephClientProvides(self)
self.metrics_endpoint = ceph_metrics.CephMetricsEndpointProvider(self)
self.ceph_status = ceph_status.StatusAssessor(self)
@ -190,16 +226,36 @@ class CephMonCharm(ops_openstack.core.OSBaseCharm):
fw.observe(self.on.admin_relation_joined,
self.on_admin_relation)
fw.observe(self.on.client_relation_changed,
self.on_client_relation)
fw.observe(self.on.client_relation_joined,
self.on_client_relation)
fw.observe(self.on.nrpe_external_master_relation_joined,
self.on_nrpe_relation)
fw.observe(self.on.nrpe_external_master_relation_changed,
self.on_nrpe_relation)
fw.observe(self.on.notify_clients, self.notify_clients)
def ready_for_service(self):
return hooks.ready_for_service()
def process_broker_request(self, broker_req_id, requests, recurse=True):
broker_result = process_requests(requests)
if hooks.relation_ids('rbd-mirror'):
# NOTE(fnordahl): juju relation level data candidate
# notify mons to flag that the other mon units should update
# their ``rbd-mirror`` relations with information about new
# pools.
logger.debug('Notifying peers after processing broker'
'request {}.'.format(broker_req_id))
hooks.notify_mons()
# notify_rbd_mirrors is the only case where this is False
if recurse:
# update ``rbd-mirror`` relations for this unit with
# information about new pools.
logger.debug(
'Notifying this units rbd-mirror relations after '
'processing broker request {}.'.format(broker_req_id))
hooks.notify_rbd_mirrors()
return broker_result
if __name__ == '__main__':
main(CephMonCharm)

View File

@ -0,0 +1,53 @@
# Copyright 2022 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
from ops.testing import Harness
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))
# src.charm imports ceph_hooks, so we need to workaround the inclusion
# of the 'harden' decorator.
from src.charm import CephMonCharm
relation_id = int
def add_ceph_client_relation(harness: Harness[CephMonCharm]) -> relation_id:
rel_id = harness.add_relation(
'client',
'glance')
harness.add_relation_unit(
rel_id,
'glance/0')
harness.update_relation_data(
rel_id,
'glance/0',
{'ingress-address': '10.0.0.3'})
return rel_id
def add_ceph_mds_relation(harness: Harness[CephMonCharm]) -> relation_id:
rel_id = harness.add_relation(
'mds',
'ceph-fs')
harness.add_relation_unit(
rel_id,
'ceph-fs/0')
harness.update_relation_data(
rel_id,
'ceph-fs/0',
{'ingress-address': '10.0.0.3'})
return rel_id

View File

@ -0,0 +1,158 @@
# Copyright 2022 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.
"""Tests for reweight_osd action."""
# import json
import unittest.mock as mock
from test_utils import CharmTestCase
from ops.testing import Harness
from manage_test_relations import (
add_ceph_client_relation,
add_ceph_mds_relation,
)
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))
# src.charm imports ceph_hooks, so we need to workaround the inclusion
# of the 'harden' decorator.
from src.charm import CephMonCharm
class CephClientTestCase(CharmTestCase):
"""Run tests for action."""
def setUp(self):
self.harness = Harness(CephMonCharm)
self.addCleanup(self.harness.cleanup)
@mock.patch("src.charm.ceph_client.ceph.get_named_key")
@mock.patch("src.charm.ceph_client.get_rbd_features")
@mock.patch("src.charm.ceph_client.get_public_addr")
@mock.patch.object(CephMonCharm, "ready_for_service")
@mock.patch("src.charm.ceph_client.send_osd_settings")
def test_client_relation(
self, _send_osd_settings, mock_ready_for_service,
mock_get_public_addr, mock_get_rbd_features, mock_get_named_key):
mock_get_public_addr.return_value = '127.0.0.1'
mock_ready_for_service.return_value = True
mock_get_rbd_features.return_value = 42
mock_get_named_key.return_value = 'test key'
self.harness.begin()
self.harness.set_leader()
rel_id = add_ceph_client_relation(self.harness)
unit_rel_data = self.harness.get_relation_data(
rel_id,
'ceph-mon/0')
self.assertEqual(
unit_rel_data,
{
'auth': 'cephx',
'ceph-public-address': '127.0.0.1',
'key': 'test key',
'rbd-features': '42',
})
@mock.patch("src.charm.ceph_client.ceph.is_leader")
@mock.patch.object(CephMonCharm, "process_broker_request")
@mock.patch("src.charm.ceph_client.ceph.get_named_key")
@mock.patch("src.charm.ceph_client.get_rbd_features")
@mock.patch("src.charm.ceph_client.get_public_addr")
@mock.patch.object(CephMonCharm, "ready_for_service")
@mock.patch("src.charm.ceph_client.send_osd_settings")
def test_client_relation_broker(
self, _send_osd_settings, mock_ready_for_service,
mock_get_public_addr, mock_get_rbd_features, mock_get_named_key,
mock_process_broker_request, mock_is_leader):
mock_get_public_addr.return_value = '127.0.0.1'
mock_ready_for_service.return_value = True
mock_get_rbd_features.return_value = 42
mock_get_named_key.return_value = 'test key'
mock_process_broker_request.return_value = 'AOK'
mock_is_leader.return_value = True
self.harness.begin()
self.harness.set_leader()
rel_id = add_ceph_client_relation(self.harness)
self.harness.update_relation_data(
rel_id,
'glance/0',
{'broker_req': '{"request-id": "req"}'})
mock_process_broker_request.assert_called_once_with(
'req', '{"request-id": "req"}'
)
unit_rel_data = self.harness.get_relation_data(
rel_id,
'ceph-mon/0')
self.assertEqual(
unit_rel_data,
{
'auth': 'cephx',
'ceph-public-address': '127.0.0.1',
'key': 'test key',
'rbd-features': '42',
'broker-rsp-glance-0': 'AOK',
'broker_rsp': 'AOK'
})
mock_process_broker_request.reset_mock()
self.harness.update_relation_data(
rel_id,
'glance/0',
{'broker_req': '{"request-id": "req"}'})
mock_process_broker_request.assert_not_called()
@mock.patch("src.charm.hooks.mds_relation_joined")
@mock.patch("src.charm.ceph_client.ceph.get_named_key")
@mock.patch("src.charm.ceph_client.get_rbd_features")
@mock.patch("src.charm.ceph_client.get_public_addr")
@mock.patch.object(CephMonCharm, "ready_for_service")
@mock.patch("src.charm.ceph_client.send_osd_settings")
def test_notify_clients(
self, _send_osd_settings, mock_ready_for_service,
mock_get_public_addr, mock_get_rbd_features, mock_get_named_key,
mock_mds_relation_joined):
mock_get_public_addr.return_value = '127.0.0.1'
mock_ready_for_service.return_value = True
mock_get_rbd_features.return_value = None
mock_get_named_key.return_value = 'test key'
self.harness.begin()
self.harness.set_leader()
rel_id = add_ceph_client_relation(self.harness)
add_ceph_mds_relation(self.harness)
unit_rel_data = self.harness.get_relation_data(
rel_id,
'ceph-mon/0')
self.assertEqual(
unit_rel_data,
{
'auth': 'cephx',
'ceph-public-address': '127.0.0.1',
'key': 'test key',
})
mock_get_rbd_features.return_value = 42
self.harness.charm.on.notify_clients.emit()
unit_rel_data = self.harness.get_relation_data(
rel_id,
'ceph-mon/0')
self.assertEqual(
unit_rel_data,
{
'auth': 'cephx',
'ceph-public-address': '127.0.0.1',
'key': 'test key',
'rbd-features': '42',
})
mock_mds_relation_joined.assert_called_with(
relid='1', unit='ceph-fs/0')

View File

@ -214,12 +214,10 @@ class CephHooksTestCase(test_utils.CharmTestCase):
@patch.object(ceph_hooks, 'service_pause')
@patch.object(ceph_hooks, 'notify_radosgws')
@patch.object(ceph_hooks, 'ceph')
@patch.object(ceph_hooks, 'notify_client')
@patch.object(ceph_hooks, 'config')
def test_upgrade_charm_with_nrpe_relation_installs_dependencies(
self,
mock_config,
mock_notify_client,
mock_ceph,
mock_notify_radosgws,
mock_service_pause,
@ -242,88 +240,11 @@ class CephHooksTestCase(test_utils.CharmTestCase):
ceph_hooks.upgrade_charm()
mocks["apt_install"].assert_called_with(
["python-dbus", "lockfile-progs"])
mock_notify_client.assert_called_once_with()
mock_notify_radosgws.assert_called_once_with()
mock_ceph.update_monfs.assert_called_once_with()
mock_notify_prometheus.assert_called_once_with()
mock_service_pause.assert_called_with('ceph-create-keys')
@patch.object(ceph_hooks, 'relation_get')
@patch.object(ceph_hooks, 'mds_relation_joined')
@patch.object(ceph_hooks, 'admin_relation_joined')
@patch.object(ceph_hooks, 'relation_set')
@patch.object(ceph_hooks, 'handle_broker_request')
@patch.object(ceph_hooks, 'config')
@patch.object(ceph_hooks, 'related_units')
@patch.object(ceph_hooks.ceph, 'get_named_key')
@patch.object(ceph_hooks.hookenv, 'remote_service_name')
@patch.object(ceph_hooks, 'relation_ids')
@patch.object(ceph_hooks.ceph, 'is_leader')
@patch.object(ceph_hooks, 'get_rbd_features')
@patch.object(ceph_hooks, 'get_public_addr')
@patch.object(ceph_hooks, 'ready_for_service')
@patch.object(ceph_hooks, 'send_osd_settings')
def test_notify_client(self,
_send_osd_settings,
_ready_for_service,
_get_public_addr,
_get_rbd_features,
_is_leader,
_relation_ids,
_remote_service_name,
_get_named_key,
_related_units,
_config,
_handle_broker_request,
_relation_set,
_admin_relation_joined,
_mds_relation_joined,
_relation_get):
_relation_ids.return_value = ['arelid']
_related_units.return_value = ['aunit/0']
_relation_get.return_value = {'application-name': 'aunit'}
_remote_service_name.return_value = 'aunit'
_is_leader.return_value = True
config = copy.deepcopy(CHARM_CONFIG)
_config.side_effect = lambda key: config[key]
_handle_broker_request.return_value = {}
_get_rbd_features.return_value = None
ceph_hooks.notify_client()
_send_osd_settings.assert_called_once_with()
_ready_for_service.assert_called_once_with()
_get_public_addr.assert_called_once_with()
_get_named_key.assert_called_once_with('aunit')
_handle_broker_request.assert_called_once_with(
'arelid', 'aunit/0', add_legacy_response=True)
_relation_set.assert_called_once_with(
relation_id='arelid',
relation_settings={
'key': _get_named_key(),
'auth': 'cephx',
'ceph-public-address': _get_public_addr()
})
_relation_ids.assert_has_calls([
call('admin'),
call('mds'),
])
_admin_relation_joined.assert_called_once_with('arelid')
_mds_relation_joined.assert_called_once_with(relid='arelid',
unit='aunit/0')
_get_rbd_features.return_value = 42
_relation_set.reset_mock()
ceph_hooks.notify_client()
_relation_set.assert_called_once_with(
relation_id='arelid',
relation_settings={
'key': _get_named_key(),
'auth': 'cephx',
'ceph-public-address': _get_public_addr(),
'rbd-features': 42,
})
@patch.object(ceph_hooks, 'rbd_mirror_relation')
@patch.object(ceph_hooks, 'related_units')
@patch.object(ceph_hooks, 'relation_ids')
@ -389,7 +310,6 @@ class CephHooksTestCase(test_utils.CharmTestCase):
ceph_hooks.get_client_application_name('rel:1', None),
'glance')
@patch.object(ceph_hooks, 'notify_client')
@patch.object(ceph_hooks.ceph, 'list_pools')
@patch.object(ceph_hooks, 'mgr_enable_module')
@patch.object(ceph_hooks, 'emit_cephconf')
@ -406,15 +326,13 @@ class CephHooksTestCase(test_utils.CharmTestCase):
create_sysctl,
emit_ceph_conf,
mgr_enable_module,
list_pools,
notify_client):
list_pools):
relations_of_type.return_value = False
self.test_config.set('pg-autotune', 'false')
self.test_config.set('balancer-mode', '')
ceph_hooks.config_changed()
mgr_enable_module.assert_not_called()
@patch.object(ceph_hooks, 'notify_client')
@patch.object(ceph_hooks.ceph, 'monitor_key_set')
@patch.object(ceph_hooks.ceph, 'list_pools')
@patch.object(ceph_hooks, 'mgr_enable_module')
@ -435,8 +353,7 @@ class CephHooksTestCase(test_utils.CharmTestCase):
emit_ceph_conf,
mgr_enable_module,
list_pools,
monitor_key_set,
notify_client):
monitor_key_set):
relations_of_type.return_value = False
cmp_pkgrevno.return_value = 1
self.test_config.set('pg-autotune', 'true')
@ -445,7 +362,6 @@ class CephHooksTestCase(test_utils.CharmTestCase):
mgr_enable_module.assert_called_once_with('pg_autoscaler')
monitor_key_set.assert_called_once_with('admin', 'autotune', 'true')
@patch.object(ceph_hooks, 'notify_client')
@patch.object(ceph_hooks.ceph, 'list_pools')
@patch.object(ceph_hooks, 'mgr_enable_module')
@patch.object(ceph_hooks, 'emit_cephconf')
@ -464,8 +380,7 @@ class CephHooksTestCase(test_utils.CharmTestCase):
create_sysctl,
emit_ceph_conf,
mgr_enable_module,
list_pools,
notify_client):
list_pools):
relations_of_type.return_value = False
cmp_pkgrevno.return_value = 1
self.test_config.set('pg-autotune', 'auto')
@ -593,130 +508,6 @@ class RelatedUnitsTestCase(unittest.TestCase):
call('osd:23')
])
@patch.object(ceph_hooks, 'send_osd_settings')
@patch.object(ceph_hooks, 'get_rbd_features')
@patch.object(ceph_hooks, 'relation_set')
@patch.object(ceph_hooks, 'handle_broker_request')
@patch.object(ceph_hooks, 'config')
@patch.object(ceph_hooks.ceph, 'get_named_key')
@patch.object(ceph_hooks, 'get_public_addr')
@patch.object(ceph_hooks, 'get_client_application_name')
@patch.object(ceph_hooks, 'ready_for_service')
def test_client_relation(self,
_ready_for_service,
_get_client_application_name,
_get_public_addr,
_get_named_key,
_config,
_handle_broker_request,
_relation_set,
_get_rbd_features,
_send_osd_settings):
_get_client_application_name.return_value = 'glance'
config = copy.deepcopy(CHARM_CONFIG)
_config.side_effect = lambda key: config[key]
_handle_broker_request.return_value = {}
_get_rbd_features.return_value = None
ceph_hooks.client_relation(relid='rel1', unit='glance/0')
_ready_for_service.assert_called_once_with()
_send_osd_settings.assert_called_once_with()
_get_public_addr.assert_called_once_with()
_get_named_key.assert_called_once_with('glance')
_handle_broker_request.assert_called_once_with(
'rel1', 'glance/0', add_legacy_response=True)
_relation_set.assert_called_once_with(
relation_id='rel1',
relation_settings={
'key': _get_named_key(),
'auth': 'cephx',
'ceph-public-address': _get_public_addr()
})
_get_rbd_features.return_value = 42
_relation_set.reset_mock()
ceph_hooks.client_relation(relid='rel1', unit='glance/0')
_relation_set.assert_called_once_with(
relation_id='rel1',
relation_settings={
'key': _get_named_key(),
'auth': 'cephx',
'ceph-public-address': _get_public_addr(),
'rbd-features': 42,
})
@patch.object(ceph_hooks, 'req_already_treated')
@patch.object(ceph_hooks, 'send_osd_settings')
@patch.object(ceph_hooks, 'get_rbd_features')
@patch.object(ceph_hooks, 'config')
@patch.object(ceph_hooks.ceph, 'get_named_key')
@patch.object(ceph_hooks, 'get_public_addr')
@patch.object(ceph_hooks.hookenv, 'remote_service_name')
@patch.object(ceph_hooks, 'relation_ids', return_value=[])
@patch.object(ceph_hooks, 'ready_for_service')
@patch.object(ceph_hooks.ceph, 'is_quorum')
@patch.object(ceph_hooks, 'remote_unit')
@patch.object(ceph_hooks, 'relation_get')
@patch.object(ceph_hooks.ceph, 'is_leader')
@patch.object(ceph_hooks, 'process_requests')
@patch.object(ceph_hooks, 'relation_set')
def test_client_relation_non_rel_hook(self, relation_set,
process_requests,
is_leader,
relation_get,
remote_unit,
is_quorum,
ready_for_service,
relation_ids,
remote_service_name,
get_public_addr,
get_named_key,
_config,
_get_rbd_features,
_send_osd_settings,
req_already_treated):
# Check for LP #1738154
ready_for_service.return_value = True
process_requests.return_value = 'AOK'
is_leader.return_value = True
relation_get.return_value = {'broker_req': '{"request-id": "req"}'}
remote_unit.return_value = None
is_quorum.return_value = True
config = copy.deepcopy(CHARM_CONFIG)
_config.side_effect = lambda key: config[key]
_get_rbd_features.return_value = None
req_already_treated.return_value = False
ceph_hooks.client_relation(relid='rel1', unit='glance/0')
_send_osd_settings.assert_called_once_with()
relation_set.assert_called_once_with(
relation_id='rel1',
relation_settings={
'key': get_named_key(),
'auth': 'cephx',
'ceph-public-address': get_public_addr(),
'broker-rsp-glance-0': 'AOK',
'broker_rsp': 'AOK'})
relation_set.reset_mock()
remote_unit.return_value = 'glance/0'
ceph_hooks.client_relation()
relation_set.assert_called_once_with(
relation_id=None,
relation_settings={
'key': get_named_key(),
'auth': 'cephx',
'ceph-public-address': get_public_addr(),
'broker-rsp-glance-0': 'AOK',
'broker_rsp': 'AOK'})
# Verify relation_set when broker request is already treated
relation_set.reset_mock()
req_already_treated.return_value = True
ceph_hooks.client_relation(relid='rel1', unit='glance/0')
relation_set.assert_called_once_with(
relation_id='rel1',
relation_settings={
'key': get_named_key(),
'auth': 'cephx',
'ceph-public-address': get_public_addr()})
@patch.object(ceph_hooks, 'req_already_treated')
@patch.object(ceph_hooks, 'relation_ids')
@patch.object(ceph_hooks, 'notify_mons')
@ -880,7 +671,6 @@ class BootstrapSourceTestCase(test_utils.CharmTestCase):
self.assertRaises(AssertionError,
ceph_hooks.bootstrap_source_relation_changed)
@patch.object(ceph_hooks, 'notify_client')
@patch.object(ceph_hooks.ceph, 'is_bootstrapped')
@patch.object(ceph_hooks, 'emit_cephconf')
@patch.object(ceph_hooks, 'leader_get')
@ -897,8 +687,7 @@ class BootstrapSourceTestCase(test_utils.CharmTestCase):
_is_leader,
_leader_get,
_emit_cephconf,
_is_bootstrapped,
_notify_client):
_is_bootstrapped):
config = copy.deepcopy(CHARM_CONFIG)
_config.side_effect = \
lambda key=None: config.get(key, None) if key else config
@ -915,7 +704,6 @@ class BootstrapSourceTestCase(test_utils.CharmTestCase):
])
_emit_cephconf.assert_called_once_with()
_is_bootstrapped.assert_called_once_with()
_notify_client.assert_called_once_with()
@patch.object(ceph_hooks, 'emit_cephconf')
@patch.object(ceph_hooks, 'create_sysctl')
@ -1086,9 +874,7 @@ class RBDMirrorRelationTestCase(test_utils.CharmTestCase):
self.ceph.list_pools_detail.return_value = {'pool': {}}
@patch.object(ceph_hooks, 'retrieve_client_broker_requests')
@patch.object(ceph_hooks, 'notify_client')
def test_rbd_mirror_relation(self,
_notify_client,
_retrieve_client_broker_requests):
self.handle_broker_request.return_value = {}
base_relation_settings = {
@ -1112,8 +898,6 @@ class RBDMirrorRelationTestCase(test_utils.CharmTestCase):
relation_settings=base_relation_settings)
self.test_relation.set(
{'unique_id': None})
_notify_client.assert_called_once_with()
_notify_client.reset_mock()
ceph_hooks.rbd_mirror_relation('rbd-mirror:52', 'ceph-rbd-mirror/0',
recurse=False)
self.relation_set.assert_called_with(
@ -1121,7 +905,6 @@ class RBDMirrorRelationTestCase(test_utils.CharmTestCase):
relation_settings=base_relation_settings)
self.test_relation.set(
{'unique_id': json.dumps('otherSideIsReactiveEndpoint')})
self.assertFalse(_notify_client.called)
ceph_hooks.rbd_mirror_relation('rbd-mirror:53', 'ceph-rbd-mirror/0')
self.ceph.get_rbd_mirror_key.assert_called_once_with(
'rbd-mirror.otherSideIsReactiveEndpoint')

View File

@ -24,11 +24,13 @@ class TestCephCharm(unittest.TestCase):
self.assertTrue(self.harness.charm.metrics_endpoint)
self.assertTrue(self.harness.charm.ceph_status)
@patch.object(charm.ceph_client.CephClientProvides, 'notify_all')
@patch("charm.hooks")
def test_on_config_changed(self, hooks):
def test_on_config_changed(self, hooks, _notify_all):
self.harness.update_config({"permit-insecure-cmr": None})
hooks.config_changed.assert_called()
@patch.object(charm.ceph_client.CephClientProvides, 'notify_all')
@patch("charm.ops_openstack.core.apt_install")
@patch("charm.ops_openstack.core.apt_update")
@patch("charm.ops_openstack.core.add_source")
@ -45,6 +47,7 @@ class TestCephCharm(unittest.TestCase):
_add_source,
apt_update,
apt_install,
_notify_all
):
self.harness.update_config({"permit-insecure-cmr": None})
self.harness.charm.on.install.emit()