diff --git a/actions.yaml b/actions.yaml index 09056bb4..9d4bf08b 100644 --- a/actions.yaml +++ b/actions.yaml @@ -443,3 +443,14 @@ pg-repair: description: "Repair inconsistent placement groups, if safe to do so." reset-osd-count-report: description: "Update report of osds present in osd tree. Used for monitoring." +list-entities: + description: "Returns a list of entities recognized by the Ceph cluster." + params: + format: + type: string + enum: + - json + - yaml + - text + default: text + description: "The output format, either json, yaml or text (default)" diff --git a/src/charm.py b/src/charm.py index 940b8779..0c9e4e8d 100755 --- a/src/charm.py +++ b/src/charm.py @@ -228,6 +228,8 @@ class CephMonCharm(ops_openstack.core.OSBaseCharm): ops_actions.get_health.get_health_action) self._observe_action(self.on.get_erasure_profile_action, ops_actions.get_erasure_profile.erasure_profile) + self._observe_action(self.on.list_entities_action, + ops_actions.list_entities.list_entities) fw.observe(self.on.install, self.on_install) fw.observe(self.on.config_changed, self.on_config) diff --git a/src/ops_actions/__init__.py b/src/ops_actions/__init__.py index 2513e9ee..1563577d 100644 --- a/src/ops_actions/__init__.py +++ b/src/ops_actions/__init__.py @@ -19,4 +19,5 @@ from . import ( # noqa: F401 create_erasure_profile, get_health, get_erasure_profile, + list_entities, ) diff --git a/src/ops_actions/list_entities.py b/src/ops_actions/list_entities.py new file mode 100644 index 00000000..4b22b701 --- /dev/null +++ b/src/ops_actions/list_entities.py @@ -0,0 +1,53 @@ +#! /usr/bin/env python3 +# +# Copyright 2024 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. + +"""Retrieve a list of entities recognized by the Ceph cluster.""" + +import json +import logging +import subprocess +import yaml + + +logger = logging.getLogger(__name__) + + +def list_entities(event): + try: + # NOTE(lmlg): Don't bother passing --format=json or the likes, + # since it sometimes contain escaped strings that are incompatible + # with python's json module. This method of fetching entities is + # simple enough and portable across Ceph versions. + out = subprocess.check_call(['sudo', 'ceph', 'auth', 'ls']) + ret = [] + + for line in out.decode('utf-8').split('\n'): + if line and not (line.startswith(' ') or line.startswith('\t') or + line.startswith('\n')): + ret.append(line) + + fmt = event.params.get('format', 'text') + if fmt == 'json': + msg = json.dumps(str(ret)) + elif fmt == 'yaml': + msg = yaml.safe_dump(str(ret)) + else: + msg = '\n'.join(ret) + + event.set_results({'message': msg}) + except Exception as e: + logger.warning(e) + event.fail('failed to list entities: {}'.format(str(e))) diff --git a/unit_tests/test_ceph_actions.py b/unit_tests/test_ceph_actions.py index 86e34773..f5cf1fb8 100644 --- a/unit_tests/test_ceph_actions.py +++ b/unit_tests/test_ceph_actions.py @@ -17,6 +17,7 @@ import subprocess import test_utils import ops_actions.copy_pool as copy_pool +import ops_actions.list_entities as list_entities with mock.patch('charmhelpers.contrib.hardening.harden.harden') as mock_dec: mock_dec.side_effect = (lambda *dargs, **dkwargs: lambda f: @@ -283,3 +284,26 @@ class GetErasureProfile(test_utils.CharmTestCase): event.set_results.assert_called_once_with(( {"message": None} )) + + +class ListEntities(test_utils.CharmTestCase): + """Run tests for action.""" + + def setUp(self): + self.harness = Harness(CephMonCharm) + self.harness.begin() + self.addCleanup(self.harness.cleanup) + + @mock.patch.object(list_entities.subprocess, 'check_call') + def test_list_entities(self, check_call): + check_call.return_value = b""" +client.admin + key: AQAOwwFmTR3TNxAAIsdYgastd0uKntPtEnoWug== +mgr.0 + key: AQAVwwFm/CmaJhAAdacns6DdFe4xZE1iwj8izg== +""" + event = test_utils.MockActionEvent({}) + self.harness.charm.on_list_entities_action(event) + event.set_results.assert_called_once_with( + {"message": "client.admin\nmgr.0"} + )