From 4fc32cc401bf43107e907369d7ec38acd95f99eb Mon Sep 17 00:00:00 2001 From: Liam Young Date: Mon, 30 Mar 2020 13:02:39 +0000 Subject: [PATCH] Add apply_osd_settings Add apply_osd_settings for applying osd settings to running osd daemons. Change-Id: If8fa9020d9cbe2c8fcf981abd737653173926341 --- charms_ceph/utils.py | 54 ++++++++++++++++++++ unit_tests/test_utils.py | 103 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 157 insertions(+) diff --git a/charms_ceph/utils.py b/charms_ceph/utils.py index c4b7579..d9883c7 100644 --- a/charms_ceph/utils.py +++ b/charms_ceph/utils.py @@ -3067,3 +3067,57 @@ def osd_noout(enable): except subprocess.CalledProcessError as e: log(e) raise + + +class OSDConfigSetError(Exception): + """Error occured applying OSD settings.""" + pass + + +def apply_osd_settings(settings): + """Applies the provided osd settings + + Apply the provided settings to all local OSD unless settings are already + present. Settings stop being applied on encountering an error. + + :param settings: dict. Dictionary of settings to apply. + :returns: bool. True if commands ran succesfully. + :raises: OSDConfigSetError + """ + current_settings = {} + base_cmd = 'ceph daemon osd.{osd_id} config --format=json' + get_cmd = base_cmd + ' get {key}' + set_cmd = base_cmd + ' set {key} {value}' + + def _get_cli_key(key): + return(key.replace(' ', '_')) + # Retrieve the current values to check keys are correct and to make this a + # noop if setting are already applied. + for osd_id in get_local_osd_ids(): + for key, value in sorted(settings.items()): + cli_key = _get_cli_key(key) + cmd = get_cmd.format(osd_id=osd_id, key=cli_key) + out = json.loads( + subprocess.check_output(cmd.split()).decode('UTF-8')) + if 'error' in out: + log("Error retrieving osd setting: {}".format(out['error']), + level=ERROR) + return False + current_settings[key] = out[cli_key] + settings_diff = { + k: v + for k, v in settings.items() + if str(v) != str(current_settings[k])} + for key, value in sorted(settings_diff.items()): + log("Setting {} to {}".format(key, value), level=DEBUG) + cmd = set_cmd.format( + osd_id=osd_id, + key=_get_cli_key(key), + value=value) + out = json.loads( + subprocess.check_output(cmd.split()).decode('UTF-8')) + if 'error' in out: + log("Error applying osd setting: {}".format(out['error']), + level=ERROR) + raise OSDConfigSetError + return True diff --git a/unit_tests/test_utils.py b/unit_tests/test_utils.py index 960bc3c..23f4416 100644 --- a/unit_tests/test_utils.py +++ b/unit_tests/test_utils.py @@ -1101,6 +1101,109 @@ class CephTestCase(unittest.TestCase): }) +class CephApplyOSDSettingsTestCase(unittest.TestCase): + + def setUp(self): + super(CephApplyOSDSettingsTestCase, self).setUp() + self.base_cmd = 'ceph daemon osd.{osd_id} config --format=json' + self.get_cmd = self.base_cmd + ' get {key}' + self.set_cmd = self.base_cmd + ' set {key} {value}' + self.grace = 'osd_heartbeat_grace' + self.interval = 'osd_heartbeat_interval' + + @patch.object(utils, 'get_local_osd_ids') + @patch.object(utils.subprocess, 'check_output') + def test_apply_osd_settings(self, _check_output, _get_local_osd_ids): + _get_local_osd_ids.return_value = ['0'] + output = { + self.get_cmd.format(osd_id=0, key=self.grace): + b'{"osd_heartbeat_grace":"19"}', + self.set_cmd.format(osd_id=0, key=self.grace, value='21'): + b"""{"success":"osd_heartbeat_grace = '21'"}"""} + _check_output.side_effect = lambda x: output[' '.join(x)] + self.assertTrue( + utils.apply_osd_settings({'osd heartbeat grace': '21'})) + check_output_calls = [ + call(['ceph', 'daemon', 'osd.0', 'config', '--format=json', 'get', + 'osd_heartbeat_grace']), + call(['ceph', 'daemon', 'osd.0', 'config', '--format=json', 'set', + 'osd_heartbeat_grace', '21'])] + _check_output.assert_has_calls(check_output_calls) + self.assertTrue(_check_output.call_count == len(check_output_calls)) + + @patch.object(utils, 'get_local_osd_ids') + @patch.object(utils.subprocess, 'check_output') + def test_apply_osd_settings_noop_on_one_osd(self, _check_output, + _get_local_osd_ids): + _get_local_osd_ids.return_value = ['0', '1'] + output = { + self.get_cmd.format(osd_id=0, key=self.grace): + b'{"osd_heartbeat_grace":"21"}', + self.get_cmd.format(osd_id=1, key=self.grace): + b'{"osd_heartbeat_grace":"20"}', + self.set_cmd.format(osd_id=1, key=self.grace, value='21'): + b"""{"success":"osd_heartbeat_interval = '2'"}"""} + _check_output.side_effect = lambda x: output[' '.join(x)] + self.assertTrue( + utils.apply_osd_settings({'osd heartbeat grace': '21'})) + check_output_calls = [ + call(['ceph', 'daemon', 'osd.0', 'config', '--format=json', 'get', + 'osd_heartbeat_grace']), + call(['ceph', 'daemon', 'osd.1', 'config', '--format=json', 'get', + 'osd_heartbeat_grace']), + call(['ceph', 'daemon', 'osd.1', 'config', '--format=json', 'set', + 'osd_heartbeat_grace', '21'])] + _check_output.assert_has_calls(check_output_calls) + self.assertTrue(_check_output.call_count == len(check_output_calls)) + + @patch.object(utils, 'get_local_osd_ids') + @patch.object(utils.subprocess, 'check_output') + def _test_apply_osd_settings_error(self, _check_output, + _get_local_osd_ids): + _get_local_osd_ids.return_value = ['0'] + output = { + self.get_cmd.format(osd_id=0, key=self.grace): + b"""{"error":"error setting 'osd_heartbeat_grace'"}"""} + _check_output.side_effect = lambda x: output[' '.join(x)] + self.assertFalse( + utils.apply_osd_settings({'osd heartbeat grace': '21'})) + check_output_calls = [ + call(['ceph', 'daemon', 'osd.0', 'config', '--format=json', 'get', + 'osd_heartbeat_grace'])] + _check_output.assert_has_calls(check_output_calls) + self.assertTrue(_check_output.call_count == len(check_output_calls)) + + @patch.object(utils, 'get_local_osd_ids') + @patch.object(utils.subprocess, 'check_output') + def test_apply_osd_settings_error(self, _check_output, + _get_local_osd_ids): + _get_local_osd_ids.return_value = ['0', '1'] + output = { + self.get_cmd.format(osd_id=0, key=self.grace): + b'{"osd_heartbeat_grace":"19"}', + self.get_cmd.format(osd_id=0, key=self.interval): + b'{"osd_heartbeat_interval":"3"}', + self.set_cmd.format(osd_id=0, key=self.interval, value='2'): + b"""{"success":"osd_heartbeat_interval = '2'"}""", + self.set_cmd.format(osd_id=0, key=self.grace, value='21'): + b"""{"error":"error setting 'osd_heartbeat_grace'"}"""} + _check_output.side_effect = lambda x: output[' '.join(x)] + check_output_calls = [ + call(['ceph', 'daemon', 'osd.0', 'config', '--format=json', 'get', + 'osd_heartbeat_grace']), + call(['ceph', 'daemon', 'osd.0', 'config', '--format=json', 'get', + 'osd_heartbeat_interval']), + call(['ceph', 'daemon', 'osd.0', 'config', '--format=json', 'set', + 'osd_heartbeat_grace', '21'])] + with self.assertRaises(utils.OSDConfigSetError): + utils.apply_osd_settings({ + 'osd heartbeat grace': '21', + 'osd heartbeat interval': '2'}) + _check_output.assert_has_calls(check_output_calls) + self.assertTrue( + _check_output.call_count == len(check_output_calls)) + + class CephVolumeSizeCalculatorTestCase(unittest.TestCase): @patch.object(utils, 'get_conf')