From 297ca1dfc55a1b33209c942751d3368b633faa67 Mon Sep 17 00:00:00 2001 From: chenke Date: Fri, 16 Aug 2019 16:56:29 +0800 Subject: [PATCH] Implement watcher datamodel list in watcherclient 1. Add data_model.py and data_model_shell.py for accept and process request. 2. Add relative unittest. Partically Implements: blueprint show-datamodel-api Change-Id: I5e080453acaedf7e20734d67922b64511dd5f7fd --- setup.cfg | 4 + .../tests/unit/v1/test_data_model.py | 75 ++++++++++ .../tests/unit/v1/test_data_model_shell.py | 129 ++++++++++++++++++ watcherclient/v1/__init__.py | 6 +- watcherclient/v1/client.py | 1 + watcherclient/v1/data_model.py | 56 ++++++++ watcherclient/v1/data_model_shell.py | 77 +++++++++++ watcherclient/v1/resource_fields.py | 22 +++ 8 files changed, 369 insertions(+), 1 deletion(-) create mode 100644 watcherclient/tests/unit/v1/test_data_model.py create mode 100644 watcherclient/tests/unit/v1/test_data_model_shell.py create mode 100644 watcherclient/v1/data_model.py create mode 100644 watcherclient/v1/data_model_shell.py diff --git a/setup.cfg b/setup.cfg index 494caea..94e9f1c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -67,6 +67,8 @@ openstack.infra_optim.v1 = optimize_service_show = watcherclient.v1.service_shell:ShowService optimize_service_list = watcherclient.v1.service_shell:ListService + optimize_datamodel_list = watcherclient.v1.data_model_shell:ListDataModel + # The same as above but used by the 'watcher' command watcherclient.v1 = goal_show = watcherclient.v1.goal_shell:ShowGoal @@ -104,6 +106,8 @@ watcherclient.v1 = service_show = watcherclient.v1.service_shell:ShowService service_list = watcherclient.v1.service_shell:ListService + datamodel_list = watcherclient.v1.data_model_shell:ListDataModel + [pbr] autodoc_index_modules = True autodoc_exclude_modules = diff --git a/watcherclient/tests/unit/v1/test_data_model.py b/watcherclient/tests/unit/v1/test_data_model.py new file mode 100644 index 0000000..0991fc4 --- /dev/null +++ b/watcherclient/tests/unit/v1/test_data_model.py @@ -0,0 +1,75 @@ +# Copyright 2019 ZTE corporation. +# All Rights Reserved. +# +# 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 testtools + +from watcherclient.tests.unit import utils +import watcherclient.v1.data_model + +DATA_MODEL = { + 'context': [{ + "server_uuid": "1bf91464-9b41-428d-a11e-af691e5563bb", + "server_name": "fake-name", + "server_state": "active", + "node_uuid": "253e5dd0-9384-41ab-af13-4f2c2ce26112", + "node_hostname": "localhost.localdomain", + }] +} + +AUDIT = "81332bfc-36f8-444d-99e2-b7285d602528" + +fake_responses = { + '/v1/data_model/?data_model_type=compute': + { + 'GET': ( + {}, + DATA_MODEL, + ), + }, + '/v1/data_model/?audit_uuid=%s&data_model_type=compute' % AUDIT: + { + 'GET': ( + {}, + DATA_MODEL, + ), + }, +} + + +class DataModelManagerTest(testtools.TestCase): + + def setUp(self): + super(DataModelManagerTest, self).setUp() + self.api = utils.FakeAPI(fake_responses) + self.mgr = watcherclient.v1.data_model.DataModelManager(self.api) + + def test_data_model_list(self): + data_model = self.mgr.list() + expect = [ + ('GET', '/v1/data_model/?data_model_type=compute', {}, None), + ] + self.assertEqual(expect, self.api.calls) + self.assertEqual(1, len(data_model.context)) + + def test_data_model_list_audit(self): + data_model = self.mgr.list( + audit='%s' % AUDIT) + expect = [ + ('GET', '/v1/data_model/?' + 'audit_uuid=81332bfc-36f8-444d-99e2-b7285d602528' + '&data_model_type=compute', + {}, None), + ] + self.assertEqual(expect, self.api.calls) + self.assertEqual(1, len(data_model.context)) diff --git a/watcherclient/tests/unit/v1/test_data_model_shell.py b/watcherclient/tests/unit/v1/test_data_model_shell.py new file mode 100644 index 0000000..ad8a8c6 --- /dev/null +++ b/watcherclient/tests/unit/v1/test_data_model_shell.py @@ -0,0 +1,129 @@ +# Copyright 2019 ZTE Corporation. +# +# 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 mock +import six + +from watcherclient import shell +from watcherclient.tests.unit.v1 import base +from watcherclient import v1 as resource +from watcherclient.v1 import resource_fields + + +DATA_MODEL = { + 'context': [{ + "server_uuid": "1bf91464-9b41-428d-a11e-af691e5563bb", + "server_name": "fake-name", + "server_state": "active", + "server_vcpus": "1", + "server_memory": "512", + "server_disk": "1", + "node_uuid": "253e5dd0-9384-41ab-af13-4f2c2ce26112", + "node_hostname": "localhost.localdomain", + "node_vcpus": "4", + "node_vcpu_ratio": "16.0", + "node_memory": "16383", + "node_memory_ratio": "1.5", + "node_disk": "37", + "node_disk_ratio": "1.0", + "node_state": "up", + }] +} + +LIST_RESULT = [{ + "Server UUID": "1bf91464-9b41-428d-a11e-af691e5563bb", + "Server Name": "fake-name", + "Server Vcpus": "1", + "Server Memory": "512", + "Server Disk": "1", + "Server State": "active", + "Node UUID": "253e5dd0-9384-41ab-af13-4f2c2ce26112", + "Node Host Name": "localhost.localdomain", + "Node Vcpus": "4", + "Node Vcpu Ratio": "16.0", + "Node Memory": "16383", + "Node Memory Ratio": "1.5", + "Node Disk": "37", + "Node Disk Ratio": "1.0", + "Node State": "up", +}] + +SHORT_LIST_RESULT = [{ + "Server UUID": "1bf91464-9b41-428d-a11e-af691e5563bb", + "Server Name": "fake-name", + "Server State": "active", + "Node UUID": "253e5dd0-9384-41ab-af13-4f2c2ce26112", + "Node Host Name": "localhost.localdomain", +}] + + +class DataModelShellTest(base.CommandTestCase): + + SHORT_LIST_FIELDS = resource_fields.COMPUTE_MODEL_SHORT_LIST_FIELDS + SHORT_LIST_FIELD_LABELS = ( + resource_fields.COMPUTE_MODEL_SHORT_LIST_FIELD_LABELS) + FIELDS = resource_fields.COMPUTE_MODEL_LIST_FIELDS + FIELD_LABELS = resource_fields.COMPUTE_MODEL_LIST_FIELD_LABELS + + def setUp(self): + super(self.__class__, self).setUp() + + p_data_model_manager = mock.patch.object( + resource, 'DataModelManager') + + self.m_data_model_mgr_cls = p_data_model_manager.start() + + self.addCleanup(p_data_model_manager.stop) + + self.m_data_model_mgr = mock.Mock() + + self.m_data_model_mgr_cls.return_value = self.m_data_model_mgr + + self.stdout = six.StringIO() + self.cmd = shell.WatcherShell(stdout=self.stdout) + + def test_do_data_model_list(self): + data_model = resource.DataModel(mock.Mock(), DATA_MODEL) + self.m_data_model_mgr.list.return_value = data_model + + exit_code, results = self.run_cmd('datamodel list') + + self.assertEqual(0, exit_code) + expect_values = sorted(SHORT_LIST_RESULT[0].values()) + result_values = sorted(results[0].values()) + self.assertEqual(expect_values, result_values) + + def test_do_data_model_list_detail(self): + data_model = resource.DataModel(mock.Mock(), DATA_MODEL) + self.m_data_model_mgr.list.return_value = data_model + + exit_code, results = self.run_cmd('datamodel list --detail') + + self.assertEqual(0, exit_code) + expect_values = sorted(LIST_RESULT[0].values()) + result_values = sorted(results[0].values()) + self.assertEqual(expect_values, result_values) + + def test_do_data_model_list_filter_by_audit(self): + data_model = resource.DataModel(mock.Mock(), DATA_MODEL) + self.m_data_model_mgr.list.return_value = data_model + + exit_code, results = self.run_cmd( + 'datamodel list --audit ' + '770ef053-ecb3-48b0-85b5-d55a2dbc6588') + + self.assertEqual(0, exit_code) + expect_values = sorted(SHORT_LIST_RESULT[0].values()) + result_values = sorted(results[0].values()) + self.assertEqual(expect_values, result_values) diff --git a/watcherclient/v1/__init__.py b/watcherclient/v1/__init__.py index 075f37c..d86d683 100644 --- a/watcherclient/v1/__init__.py +++ b/watcherclient/v1/__init__.py @@ -17,6 +17,7 @@ from watcherclient.v1 import action from watcherclient.v1 import action_plan from watcherclient.v1 import audit from watcherclient.v1 import audit_template +from watcherclient.v1 import data_model from watcherclient.v1 import goal from watcherclient.v1 import scoring_engine from watcherclient.v1 import service @@ -38,9 +39,12 @@ Service = service.Service ServiceManager = service.ServiceManager Strategy = strategy.Strategy StrategyManager = strategy.StrategyManager +DataModel = data_model.DataModel +DataModelManager = data_model.DataModelManager __all__ = ( "Action", "ActionManager", "ActionPlan", "ActionPlanManager", "Audit", "AuditManager", "AuditTemplate", "AuditTemplateManager", "Goal", "GoalManager", "ScoringEngine", "ScoringEngineManager", - "Service", "ServiceManager", "Strategy", "StrategyManager") + "Service", "ServiceManager", "Strategy", "StrategyManager", + "DataModel", "DataModelManager") diff --git a/watcherclient/v1/client.py b/watcherclient/v1/client.py index 5846374..c76ecf1 100644 --- a/watcherclient/v1/client.py +++ b/watcherclient/v1/client.py @@ -56,3 +56,4 @@ class Client(object): self.scoring_engine = v1.ScoringEngineManager(self.http_client) self.service = v1.ServiceManager(self.http_client) self.strategy = v1.StrategyManager(self.http_client) + self.data_model = v1.DataModelManager(self.http_client) diff --git a/watcherclient/v1/data_model.py b/watcherclient/v1/data_model.py new file mode 100644 index 0000000..c44e5b2 --- /dev/null +++ b/watcherclient/v1/data_model.py @@ -0,0 +1,56 @@ +# Copyright 2019 ZTE Corporation. +# +# 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 watcherclient.common import base +from watcherclient.common import utils + + +class DataModel(base.Resource): + def __repr__(self): + return "" % self._info + + +class DataModelManager(base.Manager): + resource_class = DataModel + + @staticmethod + def _path(filters=None): + if filters: + path = '/v1/data_model/%s' % filters + else: + path = '/v1/data_model' + return path + + def list(self, data_model_type='compute', audit=None): + """Retrieve a list of data model. + + :param data_model_type: The type of data model user wants to list. + Supported values: compute. + Future support values: storage, baremetal. + The default value is compute. + :param audit: The UUID of the audit, used to filter data model + by the scope in audit. + + :returns: A list of data model. + + """ + path = '' + filters = utils.common_filters() + + if audit: + filters.append('audit_uuid=%s' % audit) + filters.append('data_model_type=%s' % data_model_type) + + path += '?' + '&'.join(filters) + + return self._list(self._path(path))[0] diff --git a/watcherclient/v1/data_model_shell.py b/watcherclient/v1/data_model_shell.py new file mode 100644 index 0000000..21d82ba --- /dev/null +++ b/watcherclient/v1/data_model_shell.py @@ -0,0 +1,77 @@ +# Copyright 2019 ZTE Corporation. +# +# 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 watcherclient._i18n import _ +from watcherclient.common import command +from watcherclient import exceptions +from watcherclient.v1 import resource_fields as res_fields + + +class ListDataModel(command.Lister): + """List information on retrieved data model.""" + + def get_parser(self, prog_name): + parser = super(ListDataModel, self).get_parser(prog_name) + parser.add_argument( + '--type', + metavar='', + dest='type', + help=_('Type of Datamodel user want to list. ' + 'Supported values: compute. ' + 'Future support values: storage, baremetal. ' + 'Default type is compute.')) + parser.add_argument( + '--audit', + metavar='', + dest='audit', + help=_('UUID of the audit')) + parser.add_argument( + '--detail', + dest='detail', + action='store_true', + default=False, + help=_("Show detailed information about data model.")) + return parser + + def get_tuple(self, dic, fields): + ret_tup = [] + for item in fields: + ret_tup.append(dic.get(item)) + return tuple(ret_tup) + + def take_action(self, parsed_args): + client = getattr(self.app.client_manager, "infra-optim") + allowed_type = ['compute', 'storage', 'baremetal'] + params = {} + if parsed_args.audit: + params["audit"] = parsed_args.audit + if parsed_args.type: + if parsed_args.type not in allowed_type: + raise exceptions.CommandError( + 'Type %s error, ' + 'Please check the valid type!' % parsed_args.type) + params["data_model_type"] = parsed_args.type + try: + data_model = client.data_model.list(**params) + except exceptions.HTTPNotFound as exc: + raise exceptions.CommandError(str(exc)) + # TODO(chenker) Add Storage MODEL_FIELDS when using Storage Datamodel. + if parsed_args.detail: + fields = res_fields.COMPUTE_MODEL_LIST_FIELDS + field_labels = res_fields.COMPUTE_MODEL_LIST_FIELD_LABELS + else: + fields = res_fields.COMPUTE_MODEL_SHORT_LIST_FIELDS + field_labels = res_fields.COMPUTE_MODEL_SHORT_LIST_FIELD_LABELS + return (field_labels, + (self.get_tuple(item, fields) for item in data_model.context)) diff --git a/watcherclient/v1/resource_fields.py b/watcherclient/v1/resource_fields.py index 317413d..2bfef2e 100755 --- a/watcherclient/v1/resource_fields.py +++ b/watcherclient/v1/resource_fields.py @@ -98,6 +98,28 @@ STRATEGY_FIELDS = ['uuid', 'name', 'display_name', 'goal_name', STRATEGY_FIELD_LABELS = ['UUID', 'Name', 'Display name', 'Goal', 'Parameters spec'] +# Data Model + +COMPUTE_MODEL_LIST_FIELDS = [ + 'server_uuid', 'server_name', 'server_vcpus', + 'server_memory', 'server_disk', 'server_state', 'node_uuid', + 'node_hostname', 'node_vcpus', 'node_vcpu_ratio', 'node_memory', + 'node_memory_ratio', 'node_disk', 'node_disk_ratio', 'node_state'] + +COMPUTE_MODEL_LIST_FIELD_LABELS = [ + 'Server_UUID', 'Server Name', 'Server Vcpus', + 'Server Memory', 'Server Disk', 'Server State', 'Node UUID', + 'Node Host Name', 'Node Vcpus', 'Node Vcpu Ratio', 'Node Memory', + 'Node Memory Ratio', 'Node Disk', 'Node Disk Ratio', 'Node State'] + +COMPUTE_MODEL_SHORT_LIST_FIELDS = [ + 'server_uuid', 'server_name', + 'server_state', 'node_uuid', 'node_hostname'] + +COMPUTE_MODEL_SHORT_LIST_FIELD_LABELS = [ + 'Server UUID', 'Server Name', + 'Server State', 'Node UUID', 'Node Host Name'] + STRATEGY_SHORT_LIST_FIELDS = ['uuid', 'name', 'display_name', 'goal_name'] STRATEGY_SHORT_LIST_FIELD_LABELS = ['UUID', 'Name', 'Display name', 'Goal']