diff --git a/watcher/decision_engine/manager.py b/watcher/decision_engine/manager.py index b3abd8fa5..907aca81e 100644 --- a/watcher/decision_engine/manager.py +++ b/watcher/decision_engine/manager.py @@ -38,13 +38,13 @@ See :doc:`../architecture` for more details on this component. """ from watcher.common import service_manager +from watcher import conf from watcher.decision_engine.messaging import audit_endpoint +from watcher.decision_engine.messaging import data_model_endpoint from watcher.decision_engine.model.collector import manager from watcher.decision_engine.strategy.strategies import base \ as strategy_endpoint -from watcher import conf - CONF = conf.CONF @@ -73,7 +73,8 @@ class DecisionEngineManager(service_manager.ServiceManager): @property def conductor_endpoints(self): return [audit_endpoint.AuditEndpoint, - strategy_endpoint.StrategyEndpoint] + strategy_endpoint.StrategyEndpoint, + data_model_endpoint.DataModelEndpoint] @property def notification_endpoints(self): diff --git a/watcher/decision_engine/messaging/data_model_endpoint.py b/watcher/decision_engine/messaging/data_model_endpoint.py new file mode 100644 index 000000000..ae02abe56 --- /dev/null +++ b/watcher/decision_engine/messaging/data_model_endpoint.py @@ -0,0 +1,60 @@ +# -*- encoding: utf-8 -*- +# 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. +# +from watcher.common import exception +from watcher.common import utils +from watcher.decision_engine.model.collector import manager +from watcher import objects + + +class DataModelEndpoint(object): + def __init__(self, messaging): + self._messaging = messaging + + def get_audit_scope(self, context, audit=None): + scope = None + try: + if utils.is_uuid_like(audit) or utils.is_int_like(audit): + audit = objects.Audit.get( + context, audit) + else: + audit = objects.Audit.get_by_name( + context, audit) + except exception.AuditNotFound: + raise exception.InvalidIdentity(identity=audit) + if audit: + scope = audit.scope + else: + scope = [] + return scope + + def get_data_model_info(self, context, data_model_type='compute', + audit=None): + if audit is not None: + scope = self.get_audit_scope(context, audit) + else: + scope = [] + collector_manager = manager.CollectorManager() + collector = collector_manager.get_cluster_model_collector( + data_model_type) + audit_scope_handler = collector.get_audit_scope_handler( + audit_scope=scope) + available_data_model = audit_scope_handler.get_scoped_model( + collector.get_latest_cluster_data_model()) + if not available_data_model: + return {"context": []} + return {"context": available_data_model.to_list()} diff --git a/watcher/decision_engine/model/model_root.py b/watcher/decision_engine/model/model_root.py index 37b9e2296..020199f89 100644 --- a/watcher/decision_engine/model/model_root.py +++ b/watcher/decision_engine/model/model_root.py @@ -229,6 +229,24 @@ class ModelRoot(nx.DiGraph, base.Model): return etree.tostring(root, pretty_print=True).decode('utf-8') + def to_list(self): + ret_list = [] + for cn in sorted(self.get_all_compute_nodes().values(), + key=lambda cn: cn.uuid): + in_dict = {} + for field in cn.fields: + new_name = "node_"+str(field) + in_dict[new_name] = cn[field] + node_instances = self.get_node_instances(cn) + for instance in sorted(node_instances, key=lambda x: x.uuid): + for field in instance.fields: + new_name = "server_"+str(field) + in_dict[new_name] = instance[field] + if in_dict != {}: + deep_in_dict = in_dict.copy() + ret_list.append(deep_in_dict) + return ret_list + @classmethod def from_xml(cls, data): model = cls() diff --git a/watcher/decision_engine/rpcapi.py b/watcher/decision_engine/rpcapi.py index 852b5e7a3..bbeef938f 100644 --- a/watcher/decision_engine/rpcapi.py +++ b/watcher/decision_engine/rpcapi.py @@ -44,6 +44,11 @@ class DecisionEngineAPI(service.Service): return self.conductor_client.call( context, 'get_strategy_info', strategy_name=strategy_name) + def get_data_model_info(self, context, data_model_type, audit): + return self.conductor_client.call( + context, 'get_data_model_info', + data_model_type=data_model_type, audit=audit) + class DecisionEngineAPIManager(service_manager.ServiceManager): diff --git a/watcher/tests/decision_engine/messaging/test_data_model_endpoint.py b/watcher/tests/decision_engine/messaging/test_data_model_endpoint.py new file mode 100644 index 000000000..fda6b259a --- /dev/null +++ b/watcher/tests/decision_engine/messaging/test_data_model_endpoint.py @@ -0,0 +1,54 @@ +# -*- encoding: utf-8 -*- +# 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 unittest + +from watcher.common import exception +from watcher.common import utils +from watcher.decision_engine.messaging import data_model_endpoint +from watcher.decision_engine.model.collector import manager +from watcher.objects import audit + + +class TestDataModelEndpoint(unittest.TestCase): + def setUp(self): + self.endpoint_instance = data_model_endpoint.DataModelEndpoint('fake') + + @mock.patch.object(audit.Audit, 'get') + def test_get_audit_scope(self, mock_get): + mock_get.return_value = mock.Mock(scope='fake_scope') + audit_uuid = utils.generate_uuid() + + result = self.endpoint_instance.get_audit_scope( + context=None, + audit=audit_uuid) + self.assertEqual('fake_scope', result) + + @mock.patch.object(audit.Audit, 'get_by_name') + def test_get_audit_scope_with_error_name(self, mock_get_by_name): + mock_get_by_name.side_effect = exception.AuditNotFound() + audit_name = 'error_audit_name' + + self.assertRaises( + exception.InvalidIdentity, + self.endpoint_instance.get_audit_scope, + context=None, + audit=audit_name) + + @mock.patch.object(manager, 'CollectorManager', mock.Mock()) + def test_get_data_model_info(self): + result = self.endpoint_instance.get_data_model_info(context='fake') + self.assertIn('context', result) diff --git a/watcher/tests/decision_engine/model/test_model.py b/watcher/tests/decision_engine/model/test_model.py index 27016ee14..387d905cf 100644 --- a/watcher/tests/decision_engine/model/test_model.py +++ b/watcher/tests/decision_engine/model/test_model.py @@ -16,6 +16,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import mock import os from oslo_utils import uuidutils @@ -63,6 +64,27 @@ class TestModel(base.TestCase): model = model_root.ModelRoot.from_xml(struct_str) self.assertEqual(expected_model.to_string(), model.to_string()) + @mock.patch.object(model_root.ModelRoot, 'get_all_compute_nodes') + @mock.patch.object(model_root.ModelRoot, 'get_node_instances') + def test_get_model_to_list(self, mock_instances, mock_nodes): + fake_compute_node = mock.MagicMock( + uuid='fake_node_uuid', + fields=['uuid']) + fake_instance = mock.MagicMock( + uuid='fake_instance_uuid', + fields=['uuid']) + + mock_nodes.return_value = {'fake_node_uuid': fake_compute_node} + mock_instances.return_value = [fake_instance] + + expected_keys = ['server_uuid', 'node_uuid'] + + result = model_root.ModelRoot().to_list() + self.assertEqual(1, len(result)) + + result_keys = result[0].keys() + self.assertEqual(sorted(expected_keys), sorted(result_keys)) + def test_get_node_by_instance_uuid(self): model = model_root.ModelRoot() uuid_ = "{0}".format(uuidutils.generate_uuid()) diff --git a/watcher/tests/decision_engine/test_rpcapi.py b/watcher/tests/decision_engine/test_rpcapi.py index 01b5eac93..163035764 100644 --- a/watcher/tests/decision_engine/test_rpcapi.py +++ b/watcher/tests/decision_engine/test_rpcapi.py @@ -52,3 +52,14 @@ class TestDecisionEngineAPI(base.TestCase): self.api.get_strategy_info(self.context, "dummy") mock_call.assert_called_once_with( self.context, 'get_strategy_info', strategy_name="dummy") + + def test_get_data_model_info(self): + with mock.patch.object(om.RPCClient, 'call') as mock_call: + self.api.get_data_model_info( + self.context, + data_model_type='compute', + audit=None) + mock_call.assert_called_once_with( + self.context, 'get_data_model_info', + data_model_type='compute', + audit=None)