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)