Coverage for watcher/decision_engine/scope/compute.py: 91%
135 statements
« prev ^ index » next coverage.py v7.8.2, created at 2025-06-17 12:22 +0000
« prev ^ index » next coverage.py v7.8.2, created at 2025-06-17 12:22 +0000
1# -*- encoding: utf-8 -*-
2# Copyright (c) 2016 Servionica
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13# implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
17from oslo_log import log
19from watcher.common import exception
20from watcher.common import nova_helper
21from watcher.decision_engine.scope import base
24LOG = log.getLogger(__name__)
27class ComputeScope(base.BaseScope):
28 """Compute Audit Scope Handler"""
30 def __init__(self, scope, config, osc=None):
31 super(ComputeScope, self).__init__(scope, config)
32 self._osc = osc
33 self.wrapper = nova_helper.NovaHelper(osc=self._osc)
35 def remove_instance(self, cluster_model, instance, node_uuid):
36 node = cluster_model.get_node_by_uuid(node_uuid)
37 cluster_model.delete_instance(instance, node)
39 def update_exclude_instance(self, cluster_model, instance, node_uuid):
40 node = cluster_model.get_node_by_uuid(node_uuid)
41 cluster_model.unmap_instance(instance, node)
42 instance.update({"watcher_exclude": True})
43 cluster_model.map_instance(instance, node)
45 def _check_wildcard(self, aggregate_list):
46 if '*' in aggregate_list:
47 if len(aggregate_list) == 1:
48 return True
49 else:
50 raise exception.WildcardCharacterIsUsed(
51 resource="host aggregates")
52 return False
54 def _collect_aggregates(self, host_aggregates, compute_nodes):
55 aggregate_list = self.wrapper.get_aggregate_list()
56 aggregate_ids = [aggregate['id'] for aggregate
57 in host_aggregates if 'id' in aggregate]
58 aggregate_names = [aggregate['name'] for aggregate
59 in host_aggregates if 'name' in aggregate]
60 include_all_nodes = any(self._check_wildcard(field)
61 for field in (aggregate_ids, aggregate_names))
63 for aggregate in aggregate_list:
64 if (aggregate.id in aggregate_ids or
65 aggregate.name in aggregate_names or
66 include_all_nodes):
67 compute_nodes.extend(aggregate.hosts)
69 def _collect_zones(self, availability_zones, allowed_nodes):
70 service_list = self.wrapper.get_service_list()
71 zone_names = [zone['name'] for zone
72 in availability_zones]
73 include_all_nodes = False
74 if '*' in zone_names:
75 if len(zone_names) == 1:
76 include_all_nodes = True
77 else:
78 raise exception.WildcardCharacterIsUsed(
79 resource="availability zones")
80 for service in service_list:
81 if service.zone in zone_names or include_all_nodes:
82 allowed_nodes.extend(service.host)
84 def exclude_resources(self, resources, **kwargs):
85 instances_to_exclude = kwargs.get('instances')
86 nodes_to_exclude = kwargs.get('nodes')
87 instance_metadata = kwargs.get('instance_metadata')
88 projects_to_exclude = kwargs.get('projects')
90 for resource in resources:
91 if 'instances' in resource:
92 instances_to_exclude.extend(
93 [instance['uuid'] for instance
94 in resource['instances']])
95 elif 'compute_nodes' in resource:
96 nodes_to_exclude.extend(
97 [host['name'] for host
98 in resource['compute_nodes']])
99 elif 'host_aggregates' in resource:
100 prohibited_nodes = []
101 self._collect_aggregates(resource['host_aggregates'],
102 prohibited_nodes)
103 nodes_to_exclude.extend(prohibited_nodes)
104 elif 'instance_metadata' in resource:
105 instance_metadata.extend(
106 [metadata for metadata in resource['instance_metadata']])
107 elif 'projects' in resource: 107 ↛ 90line 107 didn't jump to line 90 because the condition on line 107 was always true
108 projects_to_exclude.extend(
109 [project['uuid'] for project in resource['projects']])
111 def remove_nodes_from_model(self, nodes_to_remove, cluster_model):
112 for node_name in nodes_to_remove:
113 node = cluster_model.get_node_by_name(node_name)
114 instances = cluster_model.get_node_instances(node)
115 for instance in instances:
116 self.remove_instance(cluster_model, instance, node.uuid)
117 cluster_model.remove_node(node)
119 def update_exclude_instance_in_model(
120 self, instances_to_exclude, cluster_model):
121 for instance_uuid in instances_to_exclude:
122 try:
123 node_uuid = cluster_model.get_node_by_instance_uuid(
124 instance_uuid).uuid
125 except exception.ComputeResourceNotFound:
126 LOG.warning("The following instance %s cannot be found. "
127 "It might be deleted from CDM along with node"
128 " instance was hosted on.",
129 instance_uuid)
130 continue
131 self.update_exclude_instance(
132 cluster_model,
133 cluster_model.get_instance_by_uuid(instance_uuid),
134 node_uuid)
136 def exclude_instances_with_given_metadata(
137 self, instance_metadata, cluster_model, instances_to_remove):
138 metadata_dict = {
139 key: val for d in instance_metadata for key, val in d.items()}
140 instances = cluster_model.get_all_instances()
141 for uuid, instance in instances.items():
142 metadata = instance.metadata
143 common_metadata = set(metadata_dict) & set(metadata)
144 if common_metadata and len(common_metadata) == len(metadata_dict): 144 ↛ 141line 144 didn't jump to line 141 because the condition on line 144 was always true
145 for key, value in metadata_dict.items():
146 if str(value).lower() == str(metadata.get(key)).lower():
147 instances_to_remove.add(uuid)
149 def exclude_instances_with_given_project(
150 self, projects_to_exclude, cluster_model, instances_to_exclude):
151 all_instances = cluster_model.get_all_instances()
152 for uuid, instance in all_instances.items():
153 if instance.project_id in projects_to_exclude:
154 instances_to_exclude.add(uuid)
156 def get_scoped_model(self, cluster_model):
157 """Leave only nodes and instances proposed in the audit scope"""
158 if not cluster_model: 158 ↛ 159line 158 didn't jump to line 159 because the condition on line 158 was never true
159 return None
161 allowed_nodes = []
162 nodes_to_exclude = []
163 nodes_to_remove = set()
164 instances_to_exclude = []
165 instance_metadata = []
166 projects_to_exclude = []
167 compute_scope = []
168 found_nothing_flag = False
169 model_hosts = [n.hostname for n in
170 cluster_model.get_all_compute_nodes().values()]
172 if not self.scope:
173 return cluster_model
175 for scope in self.scope: 175 ↛ 180line 175 didn't jump to line 180 because the loop on line 175 didn't complete
176 compute_scope = scope.get('compute')
177 if compute_scope: 177 ↛ 175line 177 didn't jump to line 175 because the condition on line 177 was always true
178 break
180 if not compute_scope: 180 ↛ 181line 180 didn't jump to line 181 because the condition on line 180 was never true
181 return cluster_model
183 for rule in compute_scope:
184 if 'host_aggregates' in rule:
185 self._collect_aggregates(rule['host_aggregates'],
186 allowed_nodes)
187 if not allowed_nodes: 187 ↛ 183line 187 didn't jump to line 183 because the condition on line 187 was always true
188 found_nothing_flag = True
189 elif 'availability_zones' in rule:
190 self._collect_zones(rule['availability_zones'],
191 allowed_nodes)
192 if not allowed_nodes: 192 ↛ 193line 192 didn't jump to line 193 because the condition on line 192 was never true
193 found_nothing_flag = True
194 elif 'exclude' in rule: 194 ↛ 183line 194 didn't jump to line 183 because the condition on line 194 was always true
195 self.exclude_resources(
196 rule['exclude'], instances=instances_to_exclude,
197 nodes=nodes_to_exclude,
198 instance_metadata=instance_metadata,
199 projects=projects_to_exclude)
201 instances_to_exclude = set(instances_to_exclude)
202 if allowed_nodes:
203 nodes_to_remove = set(model_hosts) - set(allowed_nodes)
204 # This branch means user set host_aggregates and/or availability_zones
205 # but can't find any nodes, so we should remove all nodes.
206 elif found_nothing_flag: 206 ↛ 208line 206 didn't jump to line 208 because the condition on line 206 was always true
207 nodes_to_remove = set(model_hosts)
208 nodes_to_remove.update(nodes_to_exclude)
210 self.remove_nodes_from_model(nodes_to_remove, cluster_model)
212 if instance_metadata and self.config.check_optimize_metadata: 212 ↛ 213line 212 didn't jump to line 213 because the condition on line 212 was never true
213 self.exclude_instances_with_given_metadata(
214 instance_metadata, cluster_model, instances_to_exclude)
216 if projects_to_exclude: 216 ↛ 217line 216 didn't jump to line 217 because the condition on line 216 was never true
217 self.exclude_instances_with_given_project(
218 projects_to_exclude, cluster_model, instances_to_exclude)
220 self.update_exclude_instance_in_model(instances_to_exclude,
221 cluster_model)
223 return cluster_model