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

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. 

16 

17from oslo_log import log 

18 

19from watcher.common import exception 

20from watcher.common import nova_helper 

21from watcher.decision_engine.scope import base 

22 

23 

24LOG = log.getLogger(__name__) 

25 

26 

27class ComputeScope(base.BaseScope): 

28 """Compute Audit Scope Handler""" 

29 

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) 

34 

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) 

38 

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) 

44 

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 

53 

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)) 

62 

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) 

68 

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) 

83 

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') 

89 

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']]) 

110 

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) 

118 

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) 

135 

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) 

148 

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) 

155 

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 

160 

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()] 

171 

172 if not self.scope: 

173 return cluster_model 

174 

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 

179 

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 

182 

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) 

200 

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) 

209 

210 self.remove_nodes_from_model(nodes_to_remove, cluster_model) 

211 

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) 

215 

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) 

219 

220 self.update_exclude_instance_in_model(instances_to_exclude, 

221 cluster_model) 

222 

223 return cluster_model