From af0c90db4d5d941b8d48cc3d269105baa6fd7b8f Mon Sep 17 00:00:00 2001 From: Yumeng_Bao Date: Mon, 3 Sep 2018 04:59:29 -0700 Subject: [PATCH] Add audit scoper for baremetal data model Bare metal cluster data model was introduced in Queens cycle. Since the model is different from compute data model, we need add CDM scoper for bare metal cluster data model Change-Id: Idd041cefb692085d4545252d229ebe8602926b58 Implements: blueprint audit-scoper-for-baremetal-data-model --- ...add-baremetal-scoper-9ef23f5fb8f0be6a.yaml | 3 + .../decision_engine/model/collector/ironic.py | 5 +- .../decision_engine/model/collector/nova.py | 1 + watcher/decision_engine/scope/baremetal.py | 29 ++++++++- .../decision_engine/scope/fake_scopes.py | 14 +++++ .../decision_engine/scope/test_baremetal.py | 62 +++++++++++++++++++ 6 files changed, 110 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/add-baremetal-scoper-9ef23f5fb8f0be6a.yaml create mode 100644 watcher/tests/decision_engine/scope/test_baremetal.py diff --git a/releasenotes/notes/add-baremetal-scoper-9ef23f5fb8f0be6a.yaml b/releasenotes/notes/add-baremetal-scoper-9ef23f5fb8f0be6a.yaml new file mode 100644 index 000000000..fdf312ace --- /dev/null +++ b/releasenotes/notes/add-baremetal-scoper-9ef23f5fb8f0be6a.yaml @@ -0,0 +1,3 @@ +--- +features: + - Baremetal Model gets Audit scoper with an ability to exclude Ironic nodes. diff --git a/watcher/decision_engine/model/collector/ironic.py b/watcher/decision_engine/model/collector/ironic.py index e8af73a4c..dccad8287 100644 --- a/watcher/decision_engine/model/collector/ironic.py +++ b/watcher/decision_engine/model/collector/ironic.py @@ -21,6 +21,7 @@ from watcher.common import ironic_helper from watcher.decision_engine.model.collector import base from watcher.decision_engine.model import element from watcher.decision_engine.model import model_root +from watcher.decision_engine.scope import baremetal as baremetal_scope LOG = log.getLogger(__name__) @@ -45,7 +46,9 @@ class BaremetalClusterDataModelCollector(base.BaseClusterDataModelCollector): return None def get_audit_scope_handler(self, audit_scope): - return None + self._audit_scope_handler = baremetal_scope.BaremetalScope( + audit_scope, self.config) + return self._audit_scope_handler def execute(self): """Build the baremetal cluster data model""" diff --git a/watcher/decision_engine/model/collector/nova.py b/watcher/decision_engine/model/collector/nova.py index ae15a49c0..62abe4716 100644 --- a/watcher/decision_engine/model/collector/nova.py +++ b/watcher/decision_engine/model/collector/nova.py @@ -193,6 +193,7 @@ class ModelBuilder(object): re-scheduled for Pike. In the meantime, all the associated code has been commented out. """ + def __init__(self, osc): self.osc = osc self.model = model_root.ModelRoot() diff --git a/watcher/decision_engine/scope/baremetal.py b/watcher/decision_engine/scope/baremetal.py index d355dc60a..5d8af161f 100644 --- a/watcher/decision_engine/scope/baremetal.py +++ b/watcher/decision_engine/scope/baremetal.py @@ -24,11 +24,30 @@ class BaremetalScope(base.BaseScope): super(BaremetalScope, self).__init__(scope, config) self._osc = osc + def exclude_resources(self, resources, **kwargs): + nodes_to_exclude = kwargs.get('nodes') + for resource in resources: + if 'ironic_nodes' in resource: + nodes_to_exclude.extend( + [node['uuid'] for node + in resource['ironic_nodes']]) + + def remove_nodes_from_model(self, nodes_to_exclude, cluster_model): + for node_uuid in nodes_to_exclude: + node = cluster_model.get_node_by_uuid(node_uuid) + cluster_model.remove_node(node) + def get_scoped_model(self, cluster_model): """Leave only nodes and instances proposed in the audit scope""" if not cluster_model: return None + nodes_to_exclude = [] + baremetal_scope = [] + + if not self.scope: + return cluster_model + for scope in self.scope: baremetal_scope = scope.get('baremetal') if baremetal_scope: @@ -37,7 +56,11 @@ class BaremetalScope(base.BaseScope): if not baremetal_scope: return cluster_model - # TODO(yumeng-bao): currently self.scope is always [] - # Audit scoper for baremetal data model will be implemented: - # https://blueprints.launchpad.net/watcher/+spec/audit-scoper-for-baremetal-data-model + for rule in baremetal_scope: + if 'exclude' in rule: + self.exclude_resources( + rule['exclude'], nodes=nodes_to_exclude) + + self.remove_nodes_from_model(nodes_to_exclude, cluster_model) + return cluster_model diff --git a/watcher/tests/decision_engine/scope/fake_scopes.py b/watcher/tests/decision_engine/scope/fake_scopes.py index 607034f51..638d17c1d 100644 --- a/watcher/tests/decision_engine/scope/fake_scopes.py +++ b/watcher/tests/decision_engine/scope/fake_scopes.py @@ -57,3 +57,17 @@ fake_scope_3 = [{'compute': [{'host_aggregates': [{'id': '1'}]}, }] } ] + +baremetal_scope = [ + {'baremetal': [ + {'exclude': [ + {'ironic_nodes': [ + {'uuid': 'c5941348-5a87-4016-94d4-4f9e0ce2b87a'}, + {'uuid': 'c5941348-5a87-4016-94d4-4f9e0ce2b87c'} + ] + } + ] + } + ] + } +] diff --git a/watcher/tests/decision_engine/scope/test_baremetal.py b/watcher/tests/decision_engine/scope/test_baremetal.py new file mode 100644 index 000000000..9522cbc9f --- /dev/null +++ b/watcher/tests/decision_engine/scope/test_baremetal.py @@ -0,0 +1,62 @@ +# -*- encoding: utf-8 -*- +# Copyright (c) 2018 SBCloud +# +# 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 watcher.decision_engine.scope import baremetal +from watcher.tests import base +from watcher.tests.decision_engine.model import faker_cluster_state +from watcher.tests.decision_engine.scope import fake_scopes + + +class TestBaremetalScope(base.TestCase): + + def setUp(self): + super(TestBaremetalScope, self).setUp() + self.fake_cluster = faker_cluster_state.FakerBaremetalModelCollector() + self.audit_scope = fake_scopes.baremetal_scope + + def test_exclude_all_ironic_nodes(self): + cluster = self.fake_cluster.generate_scenario_1() + baremetal.BaremetalScope( + self.audit_scope, + mock.Mock(), + osc=mock.Mock()).get_scoped_model(cluster) + + self.assertEqual({}, cluster.get_all_ironic_nodes()) + + def test_exclude_resources(self): + nodes_to_exclude = [] + resources = fake_scopes.baremetal_scope[0]['baremetal'][0]['exclude'] + baremetal.BaremetalScope( + self.audit_scope, mock.Mock(), osc=mock.Mock()).exclude_resources( + resources, + nodes=nodes_to_exclude) + + self.assertEqual(sorted(nodes_to_exclude), + sorted(['c5941348-5a87-4016-94d4-4f9e0ce2b87a', + 'c5941348-5a87-4016-94d4-4f9e0ce2b87c'])) + + def test_remove_nodes_from_model(self): + cluster = self.fake_cluster.generate_scenario_1() + baremetal.BaremetalScope( + self.audit_scope, + mock.Mock(), + osc=mock.Mock()).remove_nodes_from_model( + ['c5941348-5a87-4016-94d4-4f9e0ce2b87a'], + cluster) + self.assertEqual(len(cluster.get_all_ironic_nodes()), 1)