diff --git a/actions.yaml b/actions.yaml index e2a610a..cd2a55f 100644 --- a/actions.yaml +++ b/actions.yaml @@ -31,3 +31,5 @@ openstack-upgrade: description: | Perform openstack upgrades. Config option action-managed-upgrade must be set to True. +diskusage: + description: Run swift-recon -d on the specified unit. Returns values in GB. diff --git a/actions/actions.py b/actions/actions.py index 056a1f4..edd3462 100755 --- a/actions/actions.py +++ b/actions/actions.py @@ -22,7 +22,11 @@ import yaml sys.path.append('hooks/') from charmhelpers.core.host import service_pause, service_resume -from charmhelpers.core.hookenv import action_fail +from charmhelpers.core.hookenv import ( + action_fail, + action_set, +) + from charmhelpers.contrib.openstack.utils import ( set_unit_paused, clear_unit_paused, @@ -30,6 +34,11 @@ from charmhelpers.contrib.openstack.utils import ( from lib.swift_utils import assess_status, services from swift_hooks import CONFIGS +from subprocess import ( + check_output, + CalledProcessError, +) + def get_action_parser(actions_yaml_path, action_name, get_services=services): @@ -73,9 +82,32 @@ def resume(args): assess_status(CONFIGS, args.services) +def diskusage(args): + """Runs 'swift-recon -d' and returns values in GB. + @raises CalledProcessError on check_output failure + @raises Exception on any other failure + """ + try: + raw_output = check_output(['swift-recon', '-d']) + recon_result = list(line.strip().split(' ') + for line in raw_output.splitlines() + if 'Disk' in line) + for line in recon_result: + if 'space' in line: + line[4] = str(int(line[4]) / 1024 / 1024 / 1024) + 'GB' + line[6] = str(int(line[6]) / 1024 / 1024 / 1024) + 'GB' + result = [' '.join(x) for x in recon_result] + action_set({'output': result}) + except CalledProcessError as e: + action_set({'output': e.output}) + action_fail('Failed to run swift-recon -d') + except: + raise + + # A dictionary of all the defined actions to callables (which take # parsed arguments). -ACTIONS = {"pause": pause, "resume": resume} +ACTIONS = {"pause": pause, "resume": resume, 'diskusage': diskusage} def main(argv): diff --git a/actions/diskusage b/actions/diskusage new file mode 120000 index 0000000..405a394 --- /dev/null +++ b/actions/diskusage @@ -0,0 +1 @@ +actions.py \ No newline at end of file diff --git a/tests/basic_deployment.py b/tests/basic_deployment.py index d004416..793a50a 100644 --- a/tests/basic_deployment.py +++ b/tests/basic_deployment.py @@ -753,3 +753,11 @@ class SwiftProxyBasicDeployment(OpenStackAmuletDeployment): u.log.debug('Checking pause/resume actions...') self._test_pause() self._test_resume() + + def test_903_disk_usage_action(self): + """diskusage action can be run""" + u.log.info("Testing diskusage action") + action_id = u.run_action(self.swift_proxy_sentry, "diskusage") + assert u.wait_on_action(action_id), "diskusage action failed." + + u.log.info('OK') diff --git a/unit_tests/test_actions.py b/unit_tests/test_actions.py index 2e7ede4..8c8d7a7 100644 --- a/unit_tests/test_actions.py +++ b/unit_tests/test_actions.py @@ -247,3 +247,66 @@ class AddUserTestCase(CharmTestCase): self.action_fail.assert_called_once_with( 'Adding user test failed with: ""') + + +class DiskUsageTestCase(CharmTestCase): + + TEST_RECON_OUTPUT = '===================================================' \ + '============================\n--> Starting ' \ + 'reconnaissance on 9 hosts\n========================' \ + '===================================================' \ + '====\n[2017-11-03 21:50:30] Checking disk usage now' \ + '\nDistribution Graph:\n 40% 108 ******************' \ + '***************************************************' \ + '\n 41% 15 *********\n 42% 50 ******************' \ + '*************\n 43% 5 ***\n 44% 1 \n 45% ' \ + '1 \nDisk usage: space used: 89358060716032 of ' \ + '215829411840000\nDisk usage: space free: ' \ + '126471351123968 of 215829411840000\nDisk usage: ' \ + 'lowest: 40.64%, highest: 45.63%, avg: ' \ + '41.4021703318%\n===================================' \ + '============================================\n' + + TEST_RESULT = ['Disk usage: space used: 83221GB of 201006GB', + 'Disk usage: space free: 117785GB of 201006GB', + 'Disk usage: lowest: 40.64%, highest: 45.63%, avg: ' + '41.4021703318%'] + + def setUp(self): + super(DiskUsageTestCase, self).setUp( + actions.actions, ["check_output", "action_set", "action_fail"]) + + def test_success(self): + """Ensure that the action_set is called on success.""" + self.check_output.return_value = 'Swift recon ran OK' + actions.actions.diskusage([]) + self.check_output.assert_called_once_with(['swift-recon', '-d']) + + self.action_set.assert_called() + self.action_fail.not_called() + + def test_check_output_failure(self): + """Ensure that action_fail and action_set are called on + check_output failure.""" + self.check_output.side_effect = actions.actions.CalledProcessError( + 1, "Failure") + + actions.actions.diskusage([]) + self.check_output.assert_called_once_with(['swift-recon', '-d']) + + self.action_set.assert_called() + self.action_fail.assert_called() + + def test_failure(self): + """Ensure that action_fail is called on any other failure.""" + self.check_output.side_effect = Exception("Failure") + with self.assertRaises(Exception): + actions.actions.diskusage([]) + self.check_output.assert_called_once_with(['swift-recon', '-d']) + + def test_recon_result(self): + """Ensure the data ultimately returned is the right format + """ + self.check_output.return_value = self.TEST_RECON_OUTPUT + actions.actions.diskusage([]) + self.action_set.assert_called_once_with({'output': self.TEST_RESULT})