diff --git a/releasenotes/notes/bp-audit-scope-exclude-project-511a7720aac00dff.yaml b/releasenotes/notes/bp-audit-scope-exclude-project-511a7720aac00dff.yaml
new file mode 100644
index 000000000..ccca33368
--- /dev/null
+++ b/releasenotes/notes/bp-audit-scope-exclude-project-511a7720aac00dff.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - |
+ Feature to exclude instances from audit scope based on project_id is added.
+ Now instances from particular project in OpenStack can be excluded from audit
+ defining scope in audit templates.
diff --git a/watcher/decision_engine/model/collector/nova.py b/watcher/decision_engine/model/collector/nova.py
index 55d06c00f..d4fccdb5c 100644
--- a/watcher/decision_engine/model/collector/nova.py
+++ b/watcher/decision_engine/model/collector/nova.py
@@ -104,6 +104,18 @@ class NovaClusterDataModelCollector(base.BaseClusterDataModelCollector):
"items": {
"type": "object"
}
+ },
+ "projects": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "uuid": {
+ "type": "string"
+ }
+ },
+ "additionalProperties": False
+ }
}
},
"additionalProperties": False
@@ -348,7 +360,8 @@ class ModelBuilder(object):
"disk_capacity": flavor["disk"],
"vcpus": flavor["vcpus"],
"state": getattr(instance, "OS-EXT-STS:vm_state"),
- "metadata": instance.metadata}
+ "metadata": instance.metadata,
+ "project_id": instance.tenant_id}
# node_attributes = dict()
# node_attributes["layer"] = "virtual"
diff --git a/watcher/decision_engine/model/element/instance.py b/watcher/decision_engine/model/element/instance.py
index 5b4dca935..c0ef7dcaa 100644
--- a/watcher/decision_engine/model/element/instance.py
+++ b/watcher/decision_engine/model/element/instance.py
@@ -52,6 +52,7 @@ class Instance(compute_resource.ComputeResource):
"disk_capacity": wfields.NonNegativeIntegerField(),
"vcpus": wfields.NonNegativeIntegerField(),
"metadata": wfields.JsonField(),
+ "project_id": wfields.UUIDField(),
}
def accept(self, visitor):
diff --git a/watcher/decision_engine/model/notification/nova.py b/watcher/decision_engine/model/notification/nova.py
index b788113a2..f8640badf 100644
--- a/watcher/decision_engine/model/notification/nova.py
+++ b/watcher/decision_engine/model/notification/nova.py
@@ -76,6 +76,7 @@ class NovaNotification(base.NotificationEndpoint):
'disk': disk_gb,
'disk_capacity': disk_gb,
'metadata': instance_metadata,
+ 'tenant_id': instance_data['tenant_id']
})
try:
diff --git a/watcher/decision_engine/scope/compute.py b/watcher/decision_engine/scope/compute.py
index 75764fe2c..903642833 100644
--- a/watcher/decision_engine/scope/compute.py
+++ b/watcher/decision_engine/scope/compute.py
@@ -87,6 +87,7 @@ class ComputeScope(base.BaseScope):
instances_to_exclude = kwargs.get('instances')
nodes_to_exclude = kwargs.get('nodes')
instance_metadata = kwargs.get('instance_metadata')
+ projects_to_exclude = kwargs.get('projects')
for resource in resources:
if 'instances' in resource:
@@ -105,6 +106,9 @@ class ComputeScope(base.BaseScope):
elif 'instance_metadata' in resource:
instance_metadata.extend(
[metadata for metadata in resource['instance_metadata']])
+ elif 'projects' in resource:
+ projects_to_exclude.extend(
+ [project['uuid'] for project in resource['projects']])
def remove_nodes_from_model(self, nodes_to_remove, cluster_model):
for node_uuid in nodes_to_remove:
@@ -144,6 +148,13 @@ class ComputeScope(base.BaseScope):
if str(value).lower() == str(metadata.get(key)).lower():
instances_to_remove.add(uuid)
+ def exclude_instances_with_given_project(
+ self, projects_to_exclude, cluster_model, instances_to_exclude):
+ all_instances = cluster_model.get_all_instances()
+ for uuid, instance in all_instances.items():
+ if instance.project_id in projects_to_exclude:
+ instances_to_exclude.add(uuid)
+
def get_scoped_model(self, cluster_model):
"""Leave only nodes and instances proposed in the audit scope"""
if not cluster_model:
@@ -154,6 +165,7 @@ class ComputeScope(base.BaseScope):
nodes_to_remove = set()
instances_to_exclude = []
instance_metadata = []
+ projects_to_exclude = []
compute_scope = []
model_hosts = list(cluster_model.get_all_compute_nodes().keys())
@@ -177,7 +189,8 @@ class ComputeScope(base.BaseScope):
self.exclude_resources(
rule['exclude'], instances=instances_to_exclude,
nodes=nodes_to_exclude,
- instance_metadata=instance_metadata)
+ instance_metadata=instance_metadata,
+ projects=projects_to_exclude)
instances_to_exclude = set(instances_to_exclude)
if allowed_nodes:
@@ -190,6 +203,10 @@ class ComputeScope(base.BaseScope):
self.exclude_instances_with_given_metadata(
instance_metadata, cluster_model, instances_to_exclude)
+ if projects_to_exclude:
+ self.exclude_instances_with_given_project(
+ projects_to_exclude, cluster_model, instances_to_exclude)
+
self.update_exclude_instance_in_model(instances_to_exclude,
cluster_model)
diff --git a/watcher/tests/decision_engine/cluster/test_nova_cdmc.py b/watcher/tests/decision_engine/cluster/test_nova_cdmc.py
index 715ed01d6..424b4d93b 100644
--- a/watcher/tests/decision_engine/cluster/test_nova_cdmc.py
+++ b/watcher/tests/decision_engine/cluster/test_nova_cdmc.py
@@ -60,6 +60,7 @@ class TestNovaClusterDataModelCollector(base.TestCase):
human_id='fake_instance',
flavor={'ram': 333, 'disk': 222, 'vcpus': 4, 'id': 1},
metadata={'hi': 'hello'},
+ tenant_id='ff560f7e-dbc8-771f-960c-164482fce21b',
)
setattr(fake_instance, 'OS-EXT-STS:vm_state', 'VM_STATE')
setattr(fake_instance, 'OS-EXT-SRV-ATTR:host', 'test_hostname')
diff --git a/watcher/tests/decision_engine/model/data/scenario_1.xml b/watcher/tests/decision_engine/model/data/scenario_1.xml
index 95cba68a9..da8f7b2a0 100644
--- a/watcher/tests/decision_engine/model/data/scenario_1.xml
+++ b/watcher/tests/decision_engine/model/data/scenario_1.xml
@@ -1,47 +1,47 @@
-
-
+
+
-
+
-
-
-
+
+
+
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/watcher/tests/decision_engine/model/data/scenario_1_with_1_node_unavailable.xml b/watcher/tests/decision_engine/model/data/scenario_1_with_1_node_unavailable.xml
index fb19025f1..aad624bb9 100644
--- a/watcher/tests/decision_engine/model/data/scenario_1_with_1_node_unavailable.xml
+++ b/watcher/tests/decision_engine/model/data/scenario_1_with_1_node_unavailable.xml
@@ -1,50 +1,50 @@
-
-
+
+
-
+
-
-
-
+
+
+
-
+
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/watcher/tests/decision_engine/model/data/scenario_1_with_metrics.xml b/watcher/tests/decision_engine/model/data/scenario_1_with_metrics.xml
index fffa2fcc2..cc624882e 100644
--- a/watcher/tests/decision_engine/model/data/scenario_1_with_metrics.xml
+++ b/watcher/tests/decision_engine/model/data/scenario_1_with_metrics.xml
@@ -1,8 +1,8 @@
-
+
-
+
diff --git a/watcher/tests/decision_engine/model/data/scenario_2_with_metrics.xml b/watcher/tests/decision_engine/model/data/scenario_2_with_metrics.xml
index 6e94543f4..177d3f249 100644
--- a/watcher/tests/decision_engine/model/data/scenario_2_with_metrics.xml
+++ b/watcher/tests/decision_engine/model/data/scenario_2_with_metrics.xml
@@ -1,11 +1,11 @@
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/watcher/tests/decision_engine/model/data/scenario_3_with_2_nodes.xml b/watcher/tests/decision_engine/model/data/scenario_3_with_2_nodes.xml
index 209a83684..aa96f89ce 100644
--- a/watcher/tests/decision_engine/model/data/scenario_3_with_2_nodes.xml
+++ b/watcher/tests/decision_engine/model/data/scenario_3_with_2_nodes.xml
@@ -1,8 +1,8 @@
-
+
-
+
diff --git a/watcher/tests/decision_engine/model/data/scenario_3_with_metrics.xml b/watcher/tests/decision_engine/model/data/scenario_3_with_metrics.xml
index e734d3595..996499d70 100644
--- a/watcher/tests/decision_engine/model/data/scenario_3_with_metrics.xml
+++ b/watcher/tests/decision_engine/model/data/scenario_3_with_metrics.xml
@@ -1,9 +1,9 @@
-
-
-
-
+
+
+
+
diff --git a/watcher/tests/decision_engine/model/data/scenario_5_with_instance_disk_0.xml b/watcher/tests/decision_engine/model/data/scenario_5_with_instance_disk_0.xml
index 0d71f90f8..5555d9bd0 100644
--- a/watcher/tests/decision_engine/model/data/scenario_5_with_instance_disk_0.xml
+++ b/watcher/tests/decision_engine/model/data/scenario_5_with_instance_disk_0.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/watcher/tests/decision_engine/model/data/scenario_6_with_2_nodes.xml b/watcher/tests/decision_engine/model/data/scenario_6_with_2_nodes.xml
index a75be0abe..bccc33e85 100644
--- a/watcher/tests/decision_engine/model/data/scenario_6_with_2_nodes.xml
+++ b/watcher/tests/decision_engine/model/data/scenario_6_with_2_nodes.xml
@@ -1,10 +1,10 @@
-
-
+
+
-
-
+
+
diff --git a/watcher/tests/decision_engine/model/data/scenario_7_with_2_nodes.xml b/watcher/tests/decision_engine/model/data/scenario_7_with_2_nodes.xml
index 2e9e2a134..e66cb4cd2 100644
--- a/watcher/tests/decision_engine/model/data/scenario_7_with_2_nodes.xml
+++ b/watcher/tests/decision_engine/model/data/scenario_7_with_2_nodes.xml
@@ -1,10 +1,10 @@
-
-
+
+
-
-
+
+
diff --git a/watcher/tests/decision_engine/model/data/scenario_8_with_4_nodes.xml b/watcher/tests/decision_engine/model/data/scenario_8_with_4_nodes.xml
index dac56c1bf..aaa318980 100644
--- a/watcher/tests/decision_engine/model/data/scenario_8_with_4_nodes.xml
+++ b/watcher/tests/decision_engine/model/data/scenario_8_with_4_nodes.xml
@@ -1,16 +1,16 @@
-
-
-
+
+
+
-
+
-
+
-
+
diff --git a/watcher/tests/decision_engine/model/data/scenario_9_with_3_active_plus_1_disabled_nodes.xml b/watcher/tests/decision_engine/model/data/scenario_9_with_3_active_plus_1_disabled_nodes.xml
index c66239edb..a87b9a37b 100644
--- a/watcher/tests/decision_engine/model/data/scenario_9_with_3_active_plus_1_disabled_nodes.xml
+++ b/watcher/tests/decision_engine/model/data/scenario_9_with_3_active_plus_1_disabled_nodes.xml
@@ -1,16 +1,16 @@
-
-
-
+
+
+
-
+
-
+
-
+
diff --git a/watcher/tests/decision_engine/model/faker_cluster_state.py b/watcher/tests/decision_engine/model/faker_cluster_state.py
index 0e05714dc..e5fc503c4 100644
--- a/watcher/tests/decision_engine/model/faker_cluster_state.py
+++ b/watcher/tests/decision_engine/model/faker_cluster_state.py
@@ -83,6 +83,7 @@ class FakerModelCollector(base.BaseClusterDataModelCollector):
for i in range(0, instance_count):
instance_uuid = "INSTANCE_{0}".format(i)
+ project_id = "project_{0}".format(i)
instance_attributes = {
"uuid": instance_uuid,
"memory": 2,
@@ -90,7 +91,8 @@ class FakerModelCollector(base.BaseClusterDataModelCollector):
"disk_capacity": 20,
"vcpus": 10,
"metadata":
- '{"optimize": true,"top": "floor","nested": {"x": "y"}}'
+ '{"optimize": true,"top": "floor","nested": {"x": "y"}}',
+ "project_id": project_id
}
instance = element.Instance(**instance_attributes)
diff --git a/watcher/tests/decision_engine/scope/test_compute.py b/watcher/tests/decision_engine/scope/test_compute.py
index e714b8bac..03979e6ba 100644
--- a/watcher/tests/decision_engine/scope/test_compute.py
+++ b/watcher/tests/decision_engine/scope/test_compute.py
@@ -198,14 +198,18 @@ class TestComputeScope(base.TestCase):
{'compute_nodes': [{'name': 'Node_2'},
{'name': 'Node_3'}]},
{'instance_metadata': [{'optimize': True},
- {'optimize1': False}]}]
+ {'optimize1': False}]},
+ {'projects': [{'uuid': 'PROJECT_1'},
+ {'uuid': 'PROJECT_2'}]}]
instances_to_exclude = []
nodes_to_exclude = []
instance_metadata = []
+ projects_to_exclude = []
compute.ComputeScope([], mock.Mock(),
osc=mock.Mock()).exclude_resources(
resources_to_exclude, instances=instances_to_exclude,
- nodes=nodes_to_exclude, instance_metadata=instance_metadata)
+ nodes=nodes_to_exclude, instance_metadata=instance_metadata,
+ projects=projects_to_exclude)
self.assertEqual(['Node_0', 'Node_1', 'Node_2', 'Node_3'],
sorted(nodes_to_exclude))
@@ -213,6 +217,8 @@ class TestComputeScope(base.TestCase):
sorted(instances_to_exclude))
self.assertEqual([{'optimize': True}, {'optimize1': False}],
instance_metadata)
+ self.assertEqual(['PROJECT_1', 'PROJECT_2'],
+ sorted(projects_to_exclude))
def test_exclude_instances_with_given_metadata(self):
cluster = self.fake_cluster.generate_scenario_1()
@@ -233,6 +239,17 @@ class TestComputeScope(base.TestCase):
instance_metadata, cluster, instances_to_remove)
self.assertEqual(set(), instances_to_remove)
+ def test_exclude_instances_with_given_project(self):
+ cluster = self.fake_cluster.generate_scenario_1()
+ instances_to_exclude = set()
+ projects_to_exclude = ['project_1', 'project_2']
+ compute.ComputeScope(
+ [], mock.Mock(),
+ osc=mock.Mock()).exclude_instances_with_given_project(
+ projects_to_exclude, cluster, instances_to_exclude)
+ self.assertEqual(['INSTANCE_1', 'INSTANCE_2'],
+ sorted(instances_to_exclude))
+
def test_remove_nodes_from_model(self):
model = self.fake_cluster.generate_scenario_1()
compute.ComputeScope([], mock.Mock(),