From d0a20fb072cca568410a9d5000358dbf3e1ed10d Mon Sep 17 00:00:00 2001 From: chenke Date: Fri, 16 Aug 2019 16:59:48 +0800 Subject: [PATCH] Implement watcher datamodel list in watcher-api 1. Add datamodel api and policy_enfoce file. 2. Add related unittest for data_model api and policy. Partially Implements:blueprint show-datamodel-api Change-Id: I1654685d8cf04db5dd132d43a8640ddf91893cad --- watcher/api/controllers/v1/__init__.py | 12 ++++ watcher/api/controllers/v1/data_model.py | 68 ++++++++++++++++++++++ watcher/common/exception.py | 4 ++ watcher/common/policies/__init__.py | 2 + watcher/common/policies/data_model.py | 37 ++++++++++++ watcher/tests/api/test_root.py | 2 +- watcher/tests/api/v1/test_data_model.py | 74 ++++++++++++++++++++++++ watcher/tests/fake_policy.py | 4 +- 8 files changed, 201 insertions(+), 2 deletions(-) create mode 100644 watcher/api/controllers/v1/data_model.py create mode 100644 watcher/common/policies/data_model.py create mode 100644 watcher/tests/api/v1/test_data_model.py diff --git a/watcher/api/controllers/v1/__init__.py b/watcher/api/controllers/v1/__init__.py index 8d5d711f7..6a5de97bd 100644 --- a/watcher/api/controllers/v1/__init__.py +++ b/watcher/api/controllers/v1/__init__.py @@ -35,6 +35,7 @@ from watcher.api.controllers.v1 import action from watcher.api.controllers.v1 import action_plan from watcher.api.controllers.v1 import audit from watcher.api.controllers.v1 import audit_template +from watcher.api.controllers.v1 import data_model from watcher.api.controllers.v1 import goal from watcher.api.controllers.v1 import scoring_engine from watcher.api.controllers.v1 import service @@ -114,6 +115,9 @@ class V1(APIBase): audits = [link.Link] """Links to the audits resource""" + data_model = [link.Link] + """Links to the data model resource""" + actions = [link.Link] """Links to the actions resource""" @@ -158,6 +162,13 @@ class V1(APIBase): 'audits', '', bookmark=True) ] + v1.data_model = [link.Link.make_link('self', pecan.request.host_url, + 'data_model', ''), + link.Link.make_link('bookmark', + pecan.request.host_url, + 'data_model', '', + bookmark=True) + ] v1.actions = [link.Link.make_link('self', pecan.request.host_url, 'actions', ''), link.Link.make_link('bookmark', @@ -202,6 +213,7 @@ class Controller(rest.RestController): scoring_engines = scoring_engine.ScoringEngineController() services = service.ServicesController() strategies = strategy.StrategiesController() + data_model = data_model.DataModelController() @wsme_pecan.wsexpose(V1) def get(self): diff --git a/watcher/api/controllers/v1/data_model.py b/watcher/api/controllers/v1/data_model.py new file mode 100644 index 000000000..f496a38ba --- /dev/null +++ b/watcher/api/controllers/v1/data_model.py @@ -0,0 +1,68 @@ +# -*- encoding: utf-8 -*- +# Copyright (c) 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. + +""" +An Interface for users and admin to List Data Model. +""" + +import pecan +from pecan import rest +from wsme import types as wtypes +import wsmeext.pecan as wsme_pecan + +from watcher.api.controllers.v1 import types +from watcher.common import exception +from watcher.common import policy +from watcher.decision_engine import rpcapi + + +class DataModelController(rest.RestController): + """REST controller for data model""" + def __init__(self): + super(DataModelController, self).__init__() + + from_data_model = False + """A flag to indicate if the requests to this controller are coming + from the top-level resource DataModel.""" + + @wsme_pecan.wsexpose(wtypes.text, wtypes.text, types.uuid) + def get_all(self, data_model_type='compute', audit_uuid=None): + """Retrieve information about the given 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_uuid: The UUID of the audit, used to filter data model + by the scope in audit. + """ + if self.from_data_model: + raise exception.OperationNotPermitted + allowed_data_model_type = [ + 'compute', + ] + if data_model_type not in allowed_data_model_type: + raise exception.DataModelTypeNotFound( + data_model_type=data_model_type) + context = pecan.request.context + de_client = rpcapi.DecisionEngineAPI() + policy.enforce(context, 'data_model:get_all', + action='data_model:get_all') + rpc_all_data_model = de_client.get_data_model_info( + context, + data_model_type, + audit_uuid) + return rpc_all_data_model diff --git a/watcher/common/exception.py b/watcher/common/exception.py index dcb489ec9..26dc5ebba 100644 --- a/watcher/common/exception.py +++ b/watcher/common/exception.py @@ -210,6 +210,10 @@ class InvalidIntervalOrCron(Invalid): msg_fmt = _("Expected an interval or cron syntax but received %(name)s") +class DataModelTypeNotFound(ResourceNotFound): + msg_fmt = _("The %(data_model_type)s data model could not be found") + + class GoalNotFound(ResourceNotFound): msg_fmt = _("Goal %(goal)s could not be found") diff --git a/watcher/common/policies/__init__.py b/watcher/common/policies/__init__.py index 3a96fdc44..b773f569a 100644 --- a/watcher/common/policies/__init__.py +++ b/watcher/common/policies/__init__.py @@ -17,6 +17,7 @@ from watcher.common.policies import action_plan from watcher.common.policies import audit from watcher.common.policies import audit_template from watcher.common.policies import base +from watcher.common.policies import data_model from watcher.common.policies import goal from watcher.common.policies import scoring_engine from watcher.common.policies import service @@ -30,6 +31,7 @@ def list_rules(): action_plan.list_rules(), audit.list_rules(), audit_template.list_rules(), + data_model.list_rules(), goal.list_rules(), scoring_engine.list_rules(), service.list_rules(), diff --git a/watcher/common/policies/data_model.py b/watcher/common/policies/data_model.py new file mode 100644 index 000000000..768240d23 --- /dev/null +++ b/watcher/common/policies/data_model.py @@ -0,0 +1,37 @@ +# 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 oslo_policy import policy + +from watcher.common.policies import base + +DATA_MODEL = 'data_model:%s' + +rules = [ + policy.DocumentedRuleDefault( + name=DATA_MODEL % 'get_all', + check_str=base.RULE_ADMIN_API, + description='List data model.', + operations=[ + { + 'path': '/v1/data_model', + 'method': 'GET' + } + ] + ), +] + + +def list_rules(): + return rules diff --git a/watcher/tests/api/test_root.py b/watcher/tests/api/test_root.py index 7d93c30b1..0e9676be6 100644 --- a/watcher/tests/api/test_root.py +++ b/watcher/tests/api/test_root.py @@ -37,7 +37,7 @@ class TestV1Root(base.FunctionalTest): not_resources = ('id', 'links', 'media_types') actual_resources = tuple(set(data.keys()) - set(not_resources)) expected_resources = ('audit_templates', 'audits', 'actions', - 'action_plans', 'scoring_engines', + 'action_plans', 'data_model', 'scoring_engines', 'services') self.assertEqual(sorted(expected_resources), sorted(actual_resources)) diff --git a/watcher/tests/api/v1/test_data_model.py b/watcher/tests/api/v1/test_data_model.py new file mode 100644 index 000000000..1ab999b37 --- /dev/null +++ b/watcher/tests/api/v1/test_data_model.py @@ -0,0 +1,74 @@ +# 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 mock + +from oslo_serialization import jsonutils + +from watcher.decision_engine import rpcapi as deapi +from watcher.tests.api import base as api_base + + +class TestListDataModel(api_base.FunctionalTest): + + def setUp(self): + super(TestListDataModel, self).setUp() + p_dcapi = mock.patch.object(deapi, 'DecisionEngineAPI') + self.mock_dcapi = p_dcapi.start() + self.mock_dcapi().get_data_model_info.return_value = \ + 'fake_response_value' + self.addCleanup(p_dcapi.stop) + + def test_get_all(self): + response = self.get_json('/data_model/?data_model_type=compute') + self.assertEqual('fake_response_value', response) + + +class TestDataModelPolicyEnforcement(api_base.FunctionalTest): + + def setUp(self): + super(TestDataModelPolicyEnforcement, self).setUp() + p_dcapi = mock.patch.object(deapi, 'DecisionEngineAPI') + self.mock_dcapi = p_dcapi.start() + self.addCleanup(p_dcapi.stop) + + def _common_policy_check(self, rule, func, *arg, **kwarg): + self.policy.set_rules({ + "admin_api": "(role:admin or role:administrator)", + "default": "rule:admin_api", + rule: "rule:defaut"}) + response = func(*arg, **kwarg) + self.assertEqual(403, response.status_int) + self.assertEqual('application/json', response.content_type) + self.assertTrue( + "Policy doesn't allow %s to be performed." % rule, + jsonutils.loads(response.json['error_message'])['faultstring']) + + def test_policy_disallow_get_all(self): + self._common_policy_check( + "data_model:get_all", self.get_json, + "/data_model/?data_model_type=compute", + expect_errors=True) + + +class TestDataModelEnforcementWithAdminContext( + TestListDataModel, api_base.AdminRoleTest): + + def setUp(self): + super(TestDataModelEnforcementWithAdminContext, self).setUp() + self.policy.set_rules({ + "admin_api": "(role:admin or role:administrator)", + "default": "rule:admin_api", + "data_model:get_all": "rule:default"}) diff --git a/watcher/tests/fake_policy.py b/watcher/tests/fake_policy.py index 74c82d25c..4d493e096 100644 --- a/watcher/tests/fake_policy.py +++ b/watcher/tests/fake_policy.py @@ -58,7 +58,9 @@ policy_data = """ "service:detail": "", "service:get": "", - "service:get_all": "" + "service:get_all": "", + + "data_model:get_all": "" } """