Coverage for watcher/decision_engine/model/model_root.py: 91%

474 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 Intel Innovation and Research Ireland Ltd. 

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 implied. 

13# See the License for the specific language governing permissions and 

14# limitations under the License. 

15""" 

16Openstack implementation of the cluster graph. 

17""" 

18 

19import ast 

20from lxml import etree # nosec: B410 

21import networkx as nx 

22from oslo_concurrency import lockutils 

23from oslo_log import log 

24 

25from watcher._i18n import _ 

26from watcher.common import exception 

27from watcher.decision_engine.model import base 

28from watcher.decision_engine.model import element 

29 

30LOG = log.getLogger(__name__) 

31 

32 

33class ModelRoot(nx.DiGraph, base.Model): 

34 """Cluster graph for an Openstack cluster.""" 

35 

36 def __init__(self, stale=False): 

37 super(ModelRoot, self).__init__() 

38 self.stale = stale 

39 

40 def __nonzero__(self): 

41 return not self.stale 

42 

43 __bool__ = __nonzero__ 

44 

45 @staticmethod 

46 def assert_node(obj): 

47 if not isinstance(obj, element.ComputeNode): 

48 raise exception.IllegalArgumentException( 

49 message=_("'obj' argument type is not valid: %s") % type(obj)) 

50 

51 @staticmethod 

52 def assert_instance(obj): 

53 if not isinstance(obj, element.Instance): 

54 raise exception.IllegalArgumentException( 

55 message=_("'obj' argument type is not valid")) 

56 

57 @lockutils.synchronized("model_root") 

58 def add_node(self, node): 

59 self.assert_node(node) 

60 super(ModelRoot, self).add_node(node.uuid, attr=node) 

61 

62 @lockutils.synchronized("model_root") 

63 def remove_node(self, node): 

64 self.assert_node(node) 

65 try: 

66 super(ModelRoot, self).remove_node(node.uuid) 

67 except nx.NetworkXError as exc: 

68 LOG.exception(exc) 

69 raise exception.ComputeNodeNotFound(name=node.uuid) 

70 

71 @lockutils.synchronized("model_root") 

72 def add_instance(self, instance): 

73 self.assert_instance(instance) 

74 try: 

75 super(ModelRoot, self).add_node(instance.uuid, attr=instance) 

76 except nx.NetworkXError as exc: 

77 LOG.exception(exc) 

78 raise exception.InstanceNotFound(name=instance.uuid) 

79 

80 @lockutils.synchronized("model_root") 

81 def remove_instance(self, instance): 

82 self.assert_instance(instance) 

83 super(ModelRoot, self).remove_node(instance.uuid) 

84 

85 @lockutils.synchronized("model_root") 

86 def map_instance(self, instance, node): 

87 """Map a newly created instance to a node 

88 

89 :param instance: :py:class:`~.instance.Instance` object or instance 

90 UUID 

91 :type instance: str or :py:class:`~.instance.Instance` 

92 :param node: :py:class:`~.node.ComputeNode` object or node UUID 

93 :type node: str or :py:class:`~.instance.Instance` 

94 """ 

95 if isinstance(instance, str): 95 ↛ 96line 95 didn't jump to line 96 because the condition on line 95 was never true

96 instance = self.get_instance_by_uuid(instance) 

97 if isinstance(node, str): 97 ↛ 98line 97 didn't jump to line 98 because the condition on line 97 was never true

98 node = self.get_node_by_uuid(node) 

99 self.assert_node(node) 

100 self.assert_instance(instance) 

101 

102 self.add_edge(instance.uuid, node.uuid) 

103 

104 @lockutils.synchronized("model_root") 

105 def unmap_instance(self, instance, node): 

106 if isinstance(instance, str): 106 ↛ 107line 106 didn't jump to line 107 because the condition on line 106 was never true

107 instance = self.get_instance_by_uuid(instance) 

108 if isinstance(node, str): 108 ↛ 109line 108 didn't jump to line 109 because the condition on line 108 was never true

109 node = self.get_node_by_uuid(node) 

110 

111 self.remove_edge(instance.uuid, node.uuid) 

112 

113 def delete_instance(self, instance, node=None): 

114 self.assert_instance(instance) 

115 self.remove_instance(instance) 

116 

117 @lockutils.synchronized("model_root") 

118 def migrate_instance(self, instance, source_node, destination_node): 

119 """Migrate single instance from source_node to destination_node 

120 

121 :param instance: 

122 :param source_node: 

123 :param destination_node: 

124 :return: 

125 """ 

126 self.assert_instance(instance) 

127 self.assert_node(source_node) 

128 self.assert_node(destination_node) 

129 

130 if source_node == destination_node: 

131 return False 

132 

133 # unmap 

134 self.remove_edge(instance.uuid, source_node.uuid) 

135 # map 

136 self.add_edge(instance.uuid, destination_node.uuid) 

137 return True 

138 

139 @lockutils.synchronized("model_root") 

140 def get_all_compute_nodes(self): 

141 return {uuid: cn['attr'] for uuid, cn in self.nodes(data=True) 

142 if isinstance(cn['attr'], element.ComputeNode)} 

143 

144 @lockutils.synchronized("model_root") 

145 def get_node_by_uuid(self, uuid): 

146 try: 

147 return self._get_by_uuid(uuid) 

148 except exception.ComputeResourceNotFound: 

149 raise exception.ComputeNodeNotFound(name=uuid) 

150 

151 @lockutils.synchronized("model_root") 

152 def get_node_by_name(self, name): 

153 try: 

154 node_list = [cn['attr'] for uuid, cn in self.nodes(data=True) 

155 if (isinstance(cn['attr'], element.ComputeNode) and 

156 cn['attr']['hostname'] == name)] 

157 if node_list: 

158 return node_list[0] 

159 else: 

160 raise exception.ComputeNodeNotFound(name=name) 

161 except exception.ComputeResourceNotFound: 

162 raise exception.ComputeNodeNotFound(name=name) 

163 

164 @lockutils.synchronized("model_root") 

165 def get_instance_by_uuid(self, uuid): 

166 try: 

167 return self._get_by_uuid(uuid) 

168 except exception.ComputeResourceNotFound: 

169 raise exception.InstanceNotFound(name=uuid) 

170 

171 def _get_by_uuid(self, uuid): 

172 try: 

173 return self.nodes[uuid]['attr'] 

174 except Exception as exc: 

175 LOG.exception(exc) 

176 raise exception.ComputeResourceNotFound(name=uuid) 

177 

178 @lockutils.synchronized("model_root") 

179 def get_node_by_instance_uuid(self, instance_uuid): 

180 instance = self._get_by_uuid(instance_uuid) 

181 for node_uuid in self.neighbors(instance.uuid): 

182 node = self._get_by_uuid(node_uuid) 

183 if isinstance(node, element.ComputeNode): 183 ↛ 181line 183 didn't jump to line 181 because the condition on line 183 was always true

184 return node 

185 raise exception.InstanceNotMapped(uuid=instance_uuid) 

186 

187 @lockutils.synchronized("model_root") 

188 def get_all_instances(self): 

189 return {uuid: inst['attr'] for uuid, inst in self.nodes(data=True) 

190 if isinstance(inst['attr'], element.Instance)} 

191 

192 @lockutils.synchronized("model_root") 

193 def get_node_instances(self, node): 

194 self.assert_node(node) 

195 node_instances = [] 

196 for instance_uuid in self.predecessors(node.uuid): 

197 instance = self._get_by_uuid(instance_uuid) 

198 if isinstance(instance, element.Instance): 198 ↛ 196line 198 didn't jump to line 196 because the condition on line 198 was always true

199 node_instances.append(instance) 

200 

201 return node_instances 

202 

203 def get_node_used_resources(self, node): 

204 vcpu_used = 0 

205 memory_used = 0 

206 disk_used = 0 

207 for instance in self.get_node_instances(node): 

208 vcpu_used += instance.vcpus 

209 memory_used += instance.memory 

210 disk_used += instance.disk 

211 

212 return dict(vcpu=vcpu_used, memory=memory_used, disk=disk_used) 

213 

214 def get_node_free_resources(self, node): 

215 resources_used = self.get_node_used_resources(node) 

216 vcpu_free = node.vcpu_capacity-resources_used.get('vcpu') 

217 memory_free = node.memory_mb_capacity-resources_used.get('memory') 

218 disk_free = node.disk_gb_capacity-resources_used.get('disk') 

219 

220 return dict(vcpu=vcpu_free, memory=memory_free, disk=disk_free) 

221 

222 def to_string(self): 

223 return self.to_xml() 

224 

225 def to_xml(self): 

226 root = etree.Element("ModelRoot") 

227 # Build compute node tree 

228 for cn in sorted(self.get_all_compute_nodes().values(), 

229 key=lambda cn: cn.uuid): 

230 compute_node_el = cn.as_xml_element() 

231 

232 # Build mapped instance tree 

233 node_instances = self.get_node_instances(cn) 

234 for instance in sorted(node_instances, key=lambda x: x.uuid): 

235 instance_el = instance.as_xml_element() 

236 compute_node_el.append(instance_el) 

237 

238 root.append(compute_node_el) 

239 

240 # Build unmapped instance tree (i.e. not assigned to any compute node) 

241 for instance in sorted(self.get_all_instances().values(), 

242 key=lambda inst: inst.uuid): 

243 try: 

244 self.get_node_by_instance_uuid(instance.uuid) 

245 except exception.ComputeResourceNotFound: 

246 root.append(instance.as_xml_element()) 

247 

248 return etree.tostring(root, pretty_print=True).decode('utf-8') 

249 

250 def to_list(self): 

251 ret_list = [] 

252 for cn in sorted(self.get_all_compute_nodes().values(), 

253 key=lambda cn: cn.uuid): 

254 in_dict = {} 

255 for field in cn.fields: 

256 new_name = "node_"+str(field) 

257 in_dict[new_name] = cn[field] 

258 node_instances = self.get_node_instances(cn) 

259 if not node_instances: 

260 deep_in_dict = in_dict.copy() 

261 ret_list.append(deep_in_dict) 

262 continue 

263 for instance in sorted(node_instances, key=lambda x: x.uuid): 

264 for field in instance.fields: 

265 new_name = "server_"+str(field) 

266 in_dict[new_name] = instance[field] 

267 if in_dict != {}: 267 ↛ 263line 267 didn't jump to line 263 because the condition on line 267 was always true

268 deep_in_dict = in_dict.copy() 

269 ret_list.append(deep_in_dict) 

270 return ret_list 

271 

272 @classmethod 

273 def from_xml(cls, data): 

274 model = cls() 

275 

276 root = etree.fromstring(data) 

277 for cn in root.findall('.//ComputeNode'): 

278 node = element.ComputeNode(**cn.attrib) 

279 model.add_node(node) 

280 

281 for inst in root.findall('.//Instance'): 

282 instance = element.Instance(**inst.attrib) 

283 instance.watcher_exclude = ast.literal_eval( 

284 inst.attrib["watcher_exclude"]) 

285 model.add_instance(instance) 

286 

287 parent = inst.getparent() 

288 if parent.tag == 'ComputeNode': 

289 node = model.get_node_by_uuid(parent.get('uuid')) 

290 model.map_instance(instance, node) 

291 else: 

292 model.add_instance(instance) 

293 

294 return model 

295 

296 @classmethod 

297 def is_isomorphic(cls, G1, G2): 

298 def node_match(node1, node2): 

299 return node1['attr'].as_dict() == node2['attr'].as_dict() 

300 return nx.algorithms.isomorphism.isomorph.is_isomorphic( 

301 G1, G2, node_match=node_match) 

302 

303 

304class StorageModelRoot(nx.DiGraph, base.Model): 

305 """Cluster graph for an Openstack cluster.""" 

306 

307 def __init__(self, stale=False): 

308 super(StorageModelRoot, self).__init__() 

309 self.stale = stale 

310 

311 def __nonzero__(self): 

312 return not self.stale 

313 

314 __bool__ = __nonzero__ 

315 

316 @staticmethod 

317 def assert_node(obj): 

318 if not isinstance(obj, element.StorageNode): 

319 raise exception.IllegalArgumentException( 

320 message=_("'obj' argument type is not valid: %s") % type(obj)) 

321 

322 @staticmethod 

323 def assert_pool(obj): 

324 if not isinstance(obj, element.Pool): 

325 raise exception.IllegalArgumentException( 

326 message=_("'obj' argument type is not valid: %s") % type(obj)) 

327 

328 @staticmethod 

329 def assert_volume(obj): 

330 if not isinstance(obj, element.Volume): 

331 raise exception.IllegalArgumentException( 

332 message=_("'obj' argument type is not valid: %s") % type(obj)) 

333 

334 @lockutils.synchronized("storage_model") 

335 def add_node(self, node): 

336 self.assert_node(node) 

337 super(StorageModelRoot, self).add_node(node.host, attr=node) 

338 

339 @lockutils.synchronized("storage_model") 

340 def add_pool(self, pool): 

341 self.assert_pool(pool) 

342 super(StorageModelRoot, self).add_node(pool.name, attr=pool) 

343 

344 @lockutils.synchronized("storage_model") 

345 def remove_node(self, node): 

346 self.assert_node(node) 

347 try: 

348 super(StorageModelRoot, self).remove_node(node.host) 

349 except nx.NetworkXError as exc: 

350 LOG.exception(exc) 

351 raise exception.StorageNodeNotFound(name=node.host) 

352 

353 @lockutils.synchronized("storage_model") 

354 def remove_pool(self, pool): 

355 self.assert_pool(pool) 

356 try: 

357 super(StorageModelRoot, self).remove_node(pool.name) 

358 except nx.NetworkXError as exc: 

359 LOG.exception(exc) 

360 raise exception.PoolNotFound(name=pool.name) 

361 

362 @lockutils.synchronized("storage_model") 

363 def map_pool(self, pool, node): 

364 """Map a newly created pool to a node 

365 

366 :param pool: :py:class:`~.node.Pool` object or pool name 

367 :param node: :py:class:`~.node.StorageNode` object or node host 

368 """ 

369 if isinstance(pool, str): 369 ↛ 370line 369 didn't jump to line 370 because the condition on line 369 was never true

370 pool = self.get_pool_by_pool_name(pool) 

371 if isinstance(node, str): 371 ↛ 372line 371 didn't jump to line 372 because the condition on line 371 was never true

372 node = self.get_node_by_name(node) 

373 self.assert_node(node) 

374 self.assert_pool(pool) 

375 

376 self.add_edge(pool.name, node.host) 

377 

378 @lockutils.synchronized("storage_model") 

379 def unmap_pool(self, pool, node): 

380 """Unmap a pool from a node 

381 

382 :param pool: :py:class:`~.node.Pool` object or pool name 

383 :param node: :py:class:`~.node.StorageNode` object or node name 

384 """ 

385 if isinstance(pool, str): 385 ↛ 386line 385 didn't jump to line 386 because the condition on line 385 was never true

386 pool = self.get_pool_by_pool_name(pool) 

387 if isinstance(node, str): 387 ↛ 388line 387 didn't jump to line 388 because the condition on line 387 was never true

388 node = self.get_node_by_name(node) 

389 

390 self.remove_edge(pool.name, node.host) 

391 

392 @lockutils.synchronized("storage_model") 

393 def add_volume(self, volume): 

394 self.assert_volume(volume) 

395 super(StorageModelRoot, self).add_node(volume.uuid, attr=volume) 

396 

397 @lockutils.synchronized("storage_model") 

398 def remove_volume(self, volume): 

399 self.assert_volume(volume) 

400 try: 

401 super(StorageModelRoot, self).remove_node(volume.uuid) 

402 except nx.NetworkXError as exc: 

403 LOG.exception(exc) 

404 raise exception.VolumeNotFound(name=volume.uuid) 

405 

406 @lockutils.synchronized("storage_model") 

407 def map_volume(self, volume, pool): 

408 """Map a newly created volume to a pool 

409 

410 :param volume: :py:class:`~.volume.Volume` object or volume UUID 

411 :param pool: :py:class:`~.node.Pool` object or pool name 

412 """ 

413 if isinstance(volume, str): 413 ↛ 414line 413 didn't jump to line 414 because the condition on line 413 was never true

414 volume = self.get_volume_by_uuid(volume) 

415 if isinstance(pool, str): 415 ↛ 416line 415 didn't jump to line 416 because the condition on line 415 was never true

416 pool = self.get_pool_by_pool_name(pool) 

417 self.assert_pool(pool) 

418 self.assert_volume(volume) 

419 

420 self.add_edge(volume.uuid, pool.name) 

421 

422 @lockutils.synchronized("storage_model") 

423 def unmap_volume(self, volume, pool): 

424 """Unmap a volume from a pool 

425 

426 :param volume: :py:class:`~.volume.Volume` object or volume UUID 

427 :param pool: :py:class:`~.node.Pool` object or pool name 

428 """ 

429 if isinstance(volume, str): 429 ↛ 430line 429 didn't jump to line 430 because the condition on line 429 was never true

430 volume = self.get_volume_by_uuid(volume) 

431 if isinstance(pool, str): 431 ↛ 432line 431 didn't jump to line 432 because the condition on line 431 was never true

432 pool = self.get_pool_by_pool_name(pool) 

433 

434 self.remove_edge(volume.uuid, pool.name) 

435 

436 def delete_volume(self, volume): 

437 self.assert_volume(volume) 

438 self.remove_volume(volume) 

439 

440 @lockutils.synchronized("storage_model") 

441 def get_all_storage_nodes(self): 

442 return {host: cn['attr'] for host, cn in self.nodes(data=True) 

443 if isinstance(cn['attr'], element.StorageNode)} 

444 

445 @lockutils.synchronized("storage_model") 

446 def get_node_by_name(self, name): 

447 try: 

448 return self._get_by_name(name.split("#")[0]) 

449 except exception.StorageResourceNotFound: 

450 raise exception.StorageNodeNotFound(name=name) 

451 

452 @lockutils.synchronized("storage_model") 

453 def get_pool_by_pool_name(self, name): 

454 try: 

455 return self._get_by_name(name) 

456 except exception.StorageResourceNotFound: 

457 raise exception.PoolNotFound(name=name) 

458 

459 @lockutils.synchronized("storage_model") 

460 def get_volume_by_uuid(self, uuid): 

461 try: 

462 return self._get_by_uuid(uuid) 

463 except exception.StorageResourceNotFound: 

464 raise exception.VolumeNotFound(name=uuid) 

465 

466 def _get_by_uuid(self, uuid): 

467 try: 

468 return self.nodes[uuid]['attr'] 

469 except Exception as exc: 

470 LOG.exception(exc) 

471 raise exception.StorageResourceNotFound(name=uuid) 

472 

473 def _get_by_name(self, name): 

474 try: 

475 return self.nodes[name]['attr'] 

476 except Exception as exc: 

477 LOG.exception(exc) 

478 raise exception.StorageResourceNotFound(name=name) 

479 

480 @lockutils.synchronized("storage_model") 

481 def get_node_by_pool_name(self, pool_name): 

482 pool = self._get_by_name(pool_name) 

483 for node_name in self.neighbors(pool.name): 483 ↛ 487line 483 didn't jump to line 487 because the loop on line 483 didn't complete

484 node = self._get_by_name(node_name) 

485 if isinstance(node, element.StorageNode): 485 ↛ 483line 485 didn't jump to line 483 because the condition on line 485 was always true

486 return node 

487 raise exception.StorageNodeNotFound(name=pool_name) 

488 

489 @lockutils.synchronized("storage_model") 

490 def get_node_pools(self, node): 

491 self.assert_node(node) 

492 node_pools = [] 

493 for pool_name in self.predecessors(node.host): 

494 pool = self._get_by_name(pool_name) 

495 if isinstance(pool, element.Pool): 495 ↛ 493line 495 didn't jump to line 493 because the condition on line 495 was always true

496 node_pools.append(pool) 

497 

498 return node_pools 

499 

500 @lockutils.synchronized("storage_model") 

501 def get_pool_by_volume(self, volume): 

502 self.assert_volume(volume) 

503 volume = self._get_by_uuid(volume.uuid) 

504 for p in self.neighbors(volume.uuid): 

505 pool = self._get_by_name(p) 

506 if isinstance(pool, element.Pool): 506 ↛ 504line 506 didn't jump to line 504 because the condition on line 506 was always true

507 return pool 

508 raise exception.PoolNotFound(name=volume.uuid) 

509 

510 @lockutils.synchronized("storage_model") 

511 def get_all_volumes(self): 

512 return {name: vol['attr'] for name, vol in self.nodes(data=True) 

513 if isinstance(vol['attr'], element.Volume)} 

514 

515 @lockutils.synchronized("storage_model") 

516 def get_pool_volumes(self, pool): 

517 self.assert_pool(pool) 

518 volumes = [] 

519 for vol in self.predecessors(pool.name): 

520 volume = self._get_by_uuid(vol) 

521 if isinstance(volume, element.Volume): 521 ↛ 519line 521 didn't jump to line 519 because the condition on line 521 was always true

522 volumes.append(volume) 

523 

524 return volumes 

525 

526 def to_string(self): 

527 return self.to_xml() 

528 

529 def to_xml(self): 

530 root = etree.Element("ModelRoot") 

531 # Build storage node tree 

532 for cn in sorted(self.get_all_storage_nodes().values(), 

533 key=lambda cn: cn.host): 

534 storage_node_el = cn.as_xml_element() 

535 # Build mapped pool tree 

536 node_pools = self.get_node_pools(cn) 

537 for pool in sorted(node_pools, key=lambda x: x.name): 

538 pool_el = pool.as_xml_element() 

539 storage_node_el.append(pool_el) 

540 # Build mapped volume tree 

541 pool_volumes = self.get_pool_volumes(pool) 

542 for volume in sorted(pool_volumes, key=lambda x: x.uuid): 

543 volume_el = volume.as_xml_element() 

544 pool_el.append(volume_el) 

545 

546 root.append(storage_node_el) 

547 

548 # Build unmapped volume tree (i.e. not assigned to any pool) 

549 for volume in sorted(self.get_all_volumes().values(), 

550 key=lambda vol: vol.uuid): 

551 try: 

552 self.get_pool_by_volume(volume) 

553 except (exception.VolumeNotFound, exception.PoolNotFound): 

554 root.append(volume.as_xml_element()) 

555 

556 return etree.tostring(root, pretty_print=True).decode('utf-8') 

557 

558 @classmethod 

559 def from_xml(cls, data): 

560 model = cls() 

561 

562 root = etree.fromstring(data) 

563 for cn in root.findall('.//StorageNode'): 

564 ndata = {} 

565 for attr, val in cn.items(): 

566 ndata[attr] = val 

567 volume_type = ndata.get('volume_type') 

568 if volume_type: 568 ↛ 570line 568 didn't jump to line 570 because the condition on line 568 was always true

569 ndata['volume_type'] = [volume_type] 

570 node = element.StorageNode(**ndata) 

571 model.add_node(node) 

572 

573 for p in root.findall('.//Pool'): 

574 pool = element.Pool(**p.attrib) 

575 model.add_pool(pool) 

576 

577 parent = p.getparent() 

578 if parent.tag == 'StorageNode': 578 ↛ 582line 578 didn't jump to line 582 because the condition on line 578 was always true

579 node = model.get_node_by_name(parent.get('host')) 

580 model.map_pool(pool, node) 

581 else: 

582 model.add_pool(pool) 

583 

584 for vol in root.findall('.//Volume'): 

585 volume = element.Volume(**vol.attrib) 

586 model.add_volume(volume) 

587 

588 parent = vol.getparent() 

589 if parent.tag == 'Pool': 

590 pool = model.get_pool_by_pool_name(parent.get('name')) 

591 model.map_volume(volume, pool) 

592 else: 

593 model.add_volume(volume) 

594 

595 return model 

596 

597 @classmethod 

598 def is_isomorphic(cls, G1, G2): 

599 return nx.algorithms.isomorphism.isomorph.is_isomorphic( 

600 G1, G2) 

601 

602 

603class BaremetalModelRoot(nx.DiGraph, base.Model): 

604 

605 """Cluster graph for an Openstack cluster: Baremetal Cluster.""" 

606 

607 def __init__(self, stale=False): 

608 super(BaremetalModelRoot, self).__init__() 

609 self.stale = stale 

610 

611 def __nonzero__(self): 

612 return not self.stale 

613 

614 __bool__ = __nonzero__ 

615 

616 @staticmethod 

617 def assert_node(obj): 

618 if not isinstance(obj, element.IronicNode): 

619 raise exception.IllegalArgumentException( 

620 message=_("'obj' argument type is not valid: %s") % type(obj)) 

621 

622 @lockutils.synchronized("baremetal_model") 

623 def add_node(self, node): 

624 self.assert_node(node) 

625 super(BaremetalModelRoot, self).add_node(node.uuid, attr=node) 

626 

627 @lockutils.synchronized("baremetal_model") 

628 def remove_node(self, node): 

629 self.assert_node(node) 

630 try: 

631 super(BaremetalModelRoot, self).remove_node(node.uuid) 

632 except nx.NetworkXError as exc: 

633 LOG.exception(exc) 

634 raise exception.IronicNodeNotFound(uuid=node.uuid) 

635 

636 @lockutils.synchronized("baremetal_model") 

637 def get_all_ironic_nodes(self): 

638 return {uuid: cn['attr'] for uuid, cn in self.nodes(data=True) 

639 if isinstance(cn['attr'], element.IronicNode)} 

640 

641 @lockutils.synchronized("baremetal_model") 

642 def get_node_by_uuid(self, uuid): 

643 try: 

644 return self._get_by_uuid(uuid) 

645 except exception.BaremetalResourceNotFound: 

646 raise exception.IronicNodeNotFound(uuid=uuid) 

647 

648 def _get_by_uuid(self, uuid): 

649 try: 

650 return self.nodes[uuid]['attr'] 

651 except Exception as exc: 

652 LOG.exception(exc) 

653 raise exception.BaremetalResourceNotFound(name=uuid) 

654 

655 def to_string(self): 

656 return self.to_xml() 

657 

658 def to_xml(self): 

659 root = etree.Element("ModelRoot") 

660 # Build Ironic node tree 

661 for cn in sorted(self.get_all_ironic_nodes().values(), 

662 key=lambda cn: cn.uuid): 

663 ironic_node_el = cn.as_xml_element() 

664 root.append(ironic_node_el) 

665 

666 return etree.tostring(root, pretty_print=True).decode('utf-8') 

667 

668 @classmethod 

669 def from_xml(cls, data): 

670 model = cls() 

671 

672 root = etree.fromstring(data) 

673 for cn in root.findall('.//IronicNode'): 

674 node = element.IronicNode(**cn.attrib) 

675 model.add_node(node) 

676 

677 return model 

678 

679 @classmethod 

680 def is_isomorphic(cls, G1, G2): 

681 return nx.algorithms.isomorphism.isomorph.is_isomorphic( 

682 G1, G2)