From 001ac4786fc7ac64e3e82c7f922f5b345cca5d55 Mon Sep 17 00:00:00 2001 From: James Hebden Date: Mon, 4 Dec 2017 12:48:41 +1100 Subject: [PATCH] Add get-health action to the Ceph mon charm * get-health - outputs `ceph health` output Including unit and functional tests for the above actions. Change-Id: Id4c0a89f2068a6f30025d4a165f84ad112b62cf7 Closes-Bug: #1720099 --- actions.yaml | 2 ++ actions/ceph_ops.py | 64 +++++++++++++++++++++++++++++++++- actions/get-health | 6 ++++ tests/basic_deployment.py | 8 +++++ unit_tests/__init__.py | 1 + unit_tests/test_actions_mon.py | 54 ++++++++++++++++++++++++++++ 6 files changed, 134 insertions(+), 1 deletion(-) create mode 100755 actions/get-health create mode 100644 unit_tests/test_actions_mon.py diff --git a/actions.yaml b/actions.yaml index 749a17f5..2d2f1729 100644 --- a/actions.yaml +++ b/actions.yaml @@ -2,6 +2,8 @@ pause-health: description: Pause ceph health operations across the entire ceph cluster resume-health: description: Resume ceph health operations across the entire ceph cluster +get-health: + description: Output the current cluster health reported by `ceph health` create-cache-tier: description: Create a new cache tier params: diff --git a/actions/ceph_ops.py b/actions/ceph_ops.py index d23ad017..86765dd0 100755 --- a/actions/ceph_ops.py +++ b/actions/ceph_ops.py @@ -13,11 +13,11 @@ # limitations under the License. from subprocess import CalledProcessError, check_output +import rados import sys sys.path.append('hooks') -import rados from charmhelpers.core.hookenv import log, action_get, action_fail from charmhelpers.contrib.storage.linux.ceph import pool_set, \ set_pool_quota, snapshot_pool, remove_pool_snapshot @@ -25,6 +25,7 @@ from charmhelpers.contrib.storage.linux.ceph import pool_set, \ # Connect to Ceph via Librados and return a connection def connect(): + """Creates a connection to Ceph using librados.""" try: cluster = rados.Rados(conffile='/etc/ceph/ceph.conf') cluster.connect() @@ -38,11 +39,13 @@ def connect(): def create_crush_rule(): + """Stub function.""" # Shell out pass def list_pools(): + """Return a list of all Ceph pools.""" try: cluster = connect() pool_list = cluster.list_pools() @@ -56,7 +59,31 @@ def list_pools(): action_fail(str(e)) +def get_health(): + """ + Returns the output of 'ceph health'. + + On error, 'unknown' is returned. + """ + try: + value = check_output(['ceph', 'health']) + return value + except CalledProcessError as e: + action_fail(e.message) + return 'Getting health failed, health unknown' + + def pool_get(): + """ + Returns a key from a pool using 'ceph osd pool get'. + + The key is provided via the 'key' action parameter and the + pool provided by the 'pool_name' parameter. These are used when + running 'ceph osd pool get ', the result of + which is returned. + + On failure, 'unknown' will be returned. + """ key = action_get("key") pool_name = action_get("pool_name") try: @@ -65,9 +92,18 @@ def pool_get(): return value except CalledProcessError as e: action_fail(str(e)) + return 'unknown' def set_pool(): + """ + Sets an arbitrary key key in a Ceph pool. + + Sets the key specified by the action parameter 'key' to the value + specified in the action parameter 'value' for the pool specified + by the action parameter 'pool_name' using the charmhelpers + 'pool_set' function. + """ key = action_get("key") value = action_get("value") pool_name = action_get("pool_name") @@ -75,6 +111,11 @@ def set_pool(): def pool_stats(): + """ + Returns statistics for a pool. + + The pool name is provided by the action parameter 'pool-name'. + """ try: pool_name = action_get("pool-name") cluster = connect() @@ -93,6 +134,13 @@ def pool_stats(): def delete_pool_snapshot(): + """ + Delete a pool snapshot. + + Deletes a snapshot from the pool provided by the action + parameter 'pool-name', with the snapshot name provided by + action parameter 'snapshot-name' + """ pool_name = action_get("pool-name") snapshot_name = action_get("snapshot-name") remove_pool_snapshot(service='ceph', @@ -102,6 +150,13 @@ def delete_pool_snapshot(): # Note only one or the other can be set def set_pool_max_bytes(): + """ + Sets the max bytes quota for a pool. + + Sets the pool quota maximum bytes for the pool specified by + the action parameter 'pool-name' to the value specified by + the action parameter 'max' + """ pool_name = action_get("pool-name") max_bytes = action_get("max") set_pool_quota(service='ceph', @@ -110,6 +165,13 @@ def set_pool_max_bytes(): def snapshot_ceph_pool(): + """ + Snapshots a Ceph pool. + + Snapshots the pool provided in action parameter 'pool-name' and + uses the parameter provided in the action parameter 'snapshot-name' + as the name for the snapshot. + """ pool_name = action_get("pool-name") snapshot_name = action_get("snapshot-name") snapshot_pool(service='ceph', diff --git a/actions/get-health b/actions/get-health new file mode 100755 index 00000000..09cf08c2 --- /dev/null +++ b/actions/get-health @@ -0,0 +1,6 @@ +#!/usr/bin/python + +from ceph_ops import get_health + +if __name__ == '__main__': + get_health() diff --git a/tests/basic_deployment.py b/tests/basic_deployment.py index 15397c74..2aeaa2a8 100644 --- a/tests/basic_deployment.py +++ b/tests/basic_deployment.py @@ -708,6 +708,14 @@ class CephBasicDeployment(OpenStackAmuletDeployment): if ret: amulet.raise_status(amulet.FAIL, msg=ret) + def test_414_get_health_action(self): + """Verify that getting health works""" + u.log.debug("Testing get-health") + + sentry_unit = self.ceph0_sentry + action_id = u.run_action(sentry_unit, 'get-health') + assert u.wait_on_action(action_id), "HEALTH_OK" + def test_499_ceph_cmds_exit_zero(self): """Check basic functionality of ceph cli commands against all ceph units.""" diff --git a/unit_tests/__init__.py b/unit_tests/__init__.py index 2e3f304c..70342765 100644 --- a/unit_tests/__init__.py +++ b/unit_tests/__init__.py @@ -16,3 +16,4 @@ import sys sys.path.append('hooks') sys.path.append('lib') sys.path.append('unit_tests') +sys.path.append('actions') diff --git a/unit_tests/test_actions_mon.py b/unit_tests/test_actions_mon.py new file mode 100644 index 00000000..a4425aa1 --- /dev/null +++ b/unit_tests/test_actions_mon.py @@ -0,0 +1,54 @@ +# Copyright 2016 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. + +from mock import mock +import sys + +from test_utils import CharmTestCase + +# python-apt is not installed as part of test-requirements but is imported by +# some charmhelpers modules so create a fake import. +mock_apt = mock.MagicMock() +sys.modules['apt'] = mock_apt +mock_apt.apt_pkg = mock.MagicMock() + +# mocking for rados +mock_rados = mock.MagicMock() +sys.modules['rados'] = mock_rados +mock_rados.connect = mock.MagicMock() + +# mocking for psutil +mock_psutil = mock.MagicMock() +sys.modules['psutil'] = mock_psutil +mock_psutil.disks = mock.MagicMock() + +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)) + # import health actions as actions + import ceph_ops as actions + + +class OpsTestCase(CharmTestCase): + + def setUp(self): + super(OpsTestCase, self).setUp( + actions, ["check_output", + "action_get", + "action_fail", + "open"]) + + def test_get_health(self): + actions.get_health() + cmd = ['ceph', 'health'] + self.check_output.assert_called_once_with(cmd)