From e8236aaf658262a33ce91ab44c0dbceda489ce87 Mon Sep 17 00:00:00 2001 From: Vincent Francoise Date: Fri, 29 Apr 2016 12:20:24 +0200 Subject: [PATCH] OpenStackClient plugin for action In this changeset, I implemented the 'action' commands. Partially Implements: blueprint openstackclient-plugin Change-Id: I3e454d4bb13a392a96787cce3eddcfa7aec4c9e8 --- setup.cfg | 6 + .../tests/v1/osc/test_action_shell.py | 156 ++++++++++++++++++ watcherclient/v1/osc/action_shell.py | 115 +++++++++++++ 3 files changed, 277 insertions(+) create mode 100644 watcherclient/tests/v1/osc/test_action_shell.py create mode 100644 watcherclient/v1/osc/action_shell.py diff --git a/setup.cfg b/setup.cfg index d3e7ba1..66e5cd2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -55,6 +55,9 @@ openstack.infra_optim.v1 = optimize_actionplan_update = watcherclient.v1.osc.action_plan_shell:UpdateActionPlan optimize_actionplan_start = watcherclient.v1.osc.action_plan_shell:StartActionPlan + optimize_action_show = watcherclient.v1.osc.action_shell:ShowAction + optimize_action_list = watcherclient.v1.osc.action_shell:ListAction + # The same as above but used by the 'watcher' command watcherclient.v1 = goal_show = watcherclient.v1.osc.goal_shell:ShowGoal @@ -82,6 +85,9 @@ watcherclient.v1 = actionplan_start = watcherclient.v1.osc.action_plan_shell:StartActionPlan actionplan_delete = watcherclient.v1.osc.action_plan_shell:DeleteActionPlan + action_show = watcherclient.v1.osc.action_shell:ShowAction + action_list = watcherclient.v1.osc.action_shell:ListAction + [pbr] autodoc_index_modules = True diff --git a/watcherclient/tests/v1/osc/test_action_shell.py b/watcherclient/tests/v1/osc/test_action_shell.py new file mode 100644 index 0000000..f167acd --- /dev/null +++ b/watcherclient/tests/v1/osc/test_action_shell.py @@ -0,0 +1,156 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2013 IBM Corp +# +# 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 datetime + +import mock +import six + +from watcherclient import exceptions +from watcherclient.tests.v1.osc import base +from watcherclient import v1 as resource +from watcherclient.v1 import resource_fields +from watcherclient import watcher as shell + +ACTION_1 = { + 'uuid': '770ef053-ecb3-48b0-85b5-d55a2dbc6588', + 'action_plan_uuid': 'f8e47706-efcf-49a4-a5c4-af604eb492f2', + 'state': 'PENDING', + 'action_type': 'migrate', + 'next_uuid': '239f02a5-9649-4e14-9d33-ac2bf67cb755', + 'input_parameters': {"test": 1}, + 'created_at': datetime.datetime.now().isoformat(), + 'updated_at': None, + 'deleted_at': None, +} + +ACTION_2 = { + 'uuid': '239f02a5-9649-4e14-9d33-ac2bf67cb755', + 'action_plan_uuid': 'f8e47706-efcf-49a4-a5c4-af604eb492f2', + 'state': 'PENDING', + 'action_type': 'migrate', + 'next_uuid': '67653274-eb24-c7ba-70f6-a84e73d80843', + 'input_parameters': {"test": 2}, + 'created_at': datetime.datetime.now().isoformat(), + 'updated_at': None, + 'deleted_at': None, +} + +ACTION_3 = { + 'uuid': '67653274-eb24-c7ba-70f6-a84e73d80843', + 'action_plan_uuid': 'a5199d0e-0702-4613-9234-5ae2af8dafea', + 'next_uuid': None, + 'state': 'PENDING', + 'action_type': 'sleep', + 'created_at': datetime.datetime.now().isoformat(), + 'updated_at': None, + 'deleted_at': None, +} + +ACTION_PLAN_1 = { + 'uuid': 'a5199d0e-0702-4613-9234-5ae2af8dafea', + 'action': '770ef053-ecb3-48b0-85b5-d55a2dbc6588', + 'state': 'RECOMMENDED', + 'created_at': datetime.datetime.now().isoformat(), + 'updated_at': None, + 'deleted_at': None, +} + + +class ActionShellTest(base.CommandTestCase): + + SHORT_LIST_FIELDS = resource_fields.ACTION_SHORT_LIST_FIELDS + SHORT_LIST_FIELD_LABELS = resource_fields.ACTION_SHORT_LIST_FIELD_LABELS + FIELDS = resource_fields.ACTION_FIELDS + FIELD_LABELS = resource_fields.ACTION_FIELD_LABELS + + def setUp(self): + super(self.__class__, self).setUp() + + p_action_manager = mock.patch.object(resource, 'ActionManager') + p_action_plan_manager = mock.patch.object( + resource, 'ActionPlanManager') + self.m_action_mgr_cls = p_action_manager.start() + self.m_action_plan_mgr_cls = p_action_plan_manager.start() + self.addCleanup(p_action_manager.stop) + self.addCleanup(p_action_plan_manager.stop) + + self.m_action_mgr = mock.Mock() + self.m_action_plan_mgr = mock.Mock() + + self.m_action_mgr_cls.return_value = self.m_action_mgr + self.m_action_plan_mgr_cls.return_value = self.m_action_plan_mgr + + self.stdout = six.StringIO() + self.cmd = shell.WatcherShell(stdout=self.stdout) + + def test_do_action_list(self): + action1 = resource.Action(mock.Mock(), ACTION_1) + action2 = resource.Action(mock.Mock(), ACTION_2) + self.m_action_mgr.list.return_value = [action1, action2] + + exit_code, results = self.run_cmd('action list') + + self.assertEqual(0, exit_code) + self.assertEqual( + [self.resource_as_dict(action1, self.SHORT_LIST_FIELDS, + self.SHORT_LIST_FIELD_LABELS), + self.resource_as_dict(action2, self.SHORT_LIST_FIELDS, + self.SHORT_LIST_FIELD_LABELS)], + results) + + self.m_action_mgr.list.assert_called_once_with(detail=False) + + def test_do_action_list_detail(self): + action1 = resource.Action(mock.Mock(), ACTION_1) + action2 = resource.Action(mock.Mock(), ACTION_2) + self.m_action_mgr.list.return_value = [action1, action2] + + exit_code, results = self.run_cmd('action list --detail') + + self.assertEqual(0, exit_code) + self.assertEqual( + [self.resource_as_dict(action1, self.FIELDS, + self.FIELD_LABELS), + self.resource_as_dict(action2, self.FIELDS, + self.FIELD_LABELS)], + results) + + self.m_action_mgr.list.assert_called_once_with(detail=True) + + def test_do_action_show_by_uuid(self): + action = resource.Action(mock.Mock(), ACTION_1) + self.m_action_mgr.get.return_value = action + self.m_action_plan_mgr.get.return_value = action + + exit_code, result = self.run_cmd( + 'action show 5869da81-4876-4687-a1ed-12cd64cf53d9') + + self.assertEqual(0, exit_code) + self.assertEqual( + self.resource_as_dict(action, self.FIELDS, self.FIELD_LABELS), + result) + self.m_action_mgr.get.assert_called_once_with( + '5869da81-4876-4687-a1ed-12cd64cf53d9') + + def test_do_action_show_by_not_uuid(self): + self.m_action_mgr.get.side_effect = exceptions.HTTPNotFound + + exit_code, result = self.run_cmd( + 'action show not_uuid', formatting=None) + + self.assertEqual(1, exit_code) + self.assertEqual('', result) diff --git a/watcherclient/v1/osc/action_shell.py b/watcherclient/v1/osc/action_shell.py new file mode 100644 index 0000000..897d7b6 --- /dev/null +++ b/watcherclient/v1/osc/action_shell.py @@ -0,0 +1,115 @@ +# -*- encoding: utf-8 -*- +# Copyright (c) 2016 b<>com +# +# 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 openstackclient.common import utils + +from watcherclient._i18n import _ +from watcherclient.common import command +from watcherclient.common import utils as common_utils +from watcherclient import exceptions +from watcherclient.v1 import resource_fields as res_fields + + +class ShowAction(command.ShowOne): + """Show detailed information about a given action.""" + + def get_parser(self, prog_name): + parser = super(ShowAction, self).get_parser(prog_name) + parser.add_argument( + 'action', + metavar='', + help=_('UUID of the action'), + ) + return parser + + def take_action(self, parsed_args): + client = getattr(self.app.client_manager, "infra-optim") + + try: + action = client.action.get(parsed_args.action) + except exceptions.HTTPNotFound as exc: + raise exceptions.CommandError(str(exc)) + + columns = res_fields.ACTION_FIELDS + column_headers = res_fields.ACTION_FIELD_LABELS + + return column_headers, utils.get_item_properties(action, columns) + + +class ListAction(command.Lister): + """List information on retrieved actions.""" + + def get_parser(self, prog_name): + parser = super(ListAction, self).get_parser(prog_name) + parser.add_argument( + '--action-plan', + metavar='', + help=_('UUID of the action plan used for filtering.')) + parser.add_argument( + '--audit', + metavar='', + help=_(' UUID of the audit used for filtering.')) + parser.add_argument( + '--detail', + dest='detail', + action='store_true', + default=False, + help=_("Show detailed information about actions.")) + parser.add_argument( + '--limit', + metavar='', + type=int, + help=_('Maximum number of actions to return per request, ' + '0 for no limit. Default is the maximum number used ' + 'by the Watcher API Service.')) + parser.add_argument( + '--sort-key', + metavar='', + help=_('Action field that will be used for sorting.')) + parser.add_argument( + '--sort-dir', + metavar='', + choices=['asc', 'desc'], + help=_('Sort direction: "asc" (the default) or "desc".')) + + return parser + + def take_action(self, parsed_args): + client = getattr(self.app.client_manager, "infra-optim") + + params = {} + if parsed_args.action_plan is not None: + params['action_plan'] = parsed_args.action_plan + if parsed_args.audit is not None: + params['audit'] = parsed_args.audit + if parsed_args.detail: + fields = res_fields.ACTION_FIELDS + field_labels = res_fields.ACTION_FIELD_LABELS + else: + fields = res_fields.ACTION_SHORT_LIST_FIELDS + field_labels = res_fields.ACTION_SHORT_LIST_FIELD_LABELS + + params.update( + common_utils.common_params_for_list( + parsed_args, fields, field_labels)) + + try: + data = client.action.list(**params) + except exceptions.HTTPNotFound as ex: + raise exceptions.CommandError(str(ex)) + + return (field_labels, + (utils.get_item_properties(item, fields) for item in data))