Coverage for watcher/common/metal_helper/maas.py: 65%

64 statements  

« prev     ^ index     » next       coverage.py v7.8.2, created at 2025-06-17 12:22 +0000

1# Copyright 2023 Cloudbase Solutions 

2# All Rights Reserved. 

3# 

4# Licensed under the Apache License, Version 2.0 (the "License"); you may 

5# not use this file except in compliance with the License. You may obtain 

6# 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, WITHOUT 

12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 

13# License for the specific language governing permissions and limitations 

14# under the License. 

15 

16from oslo_config import cfg 

17from oslo_log import log 

18 

19from watcher.common import exception 

20from watcher.common.metal_helper import base 

21from watcher.common.metal_helper import constants as metal_constants 

22from watcher.common import utils 

23 

24CONF = cfg.CONF 

25LOG = log.getLogger(__name__) 

26 

27try: 

28 from maas.client import enum as maas_enum 

29except ImportError: 

30 maas_enum = None 

31 

32 

33class MaasNode(base.BaseMetalNode): 

34 hv_up_when_powered_off = False 

35 

36 def __init__(self, maas_node, nova_node, maas_client): 

37 super().__init__(nova_node) 

38 

39 self._maas_client = maas_client 

40 self._maas_node = maas_node 

41 

42 def get_power_state(self): 

43 maas_state = utils.async_compat_call( 

44 self._maas_node.query_power_state, 

45 timeout=CONF.maas_client.timeout) 

46 

47 # python-libmaas may not be available, so we'll avoid a global 

48 # variable. 

49 power_states_map = { 

50 maas_enum.PowerState.ON: metal_constants.PowerState.ON, 

51 maas_enum.PowerState.OFF: metal_constants.PowerState.OFF, 

52 maas_enum.PowerState.ERROR: metal_constants.PowerState.ERROR, 

53 maas_enum.PowerState.UNKNOWN: metal_constants.PowerState.UNKNOWN, 

54 } 

55 return power_states_map.get(maas_state, 

56 metal_constants.PowerState.UNKNOWN) 

57 

58 def get_id(self): 

59 return self._maas_node.system_id 

60 

61 def power_on(self): 

62 LOG.info("Powering on MAAS node: %s %s", 

63 self._maas_node.fqdn, 

64 self._maas_node.system_id) 

65 utils.async_compat_call( 

66 self._maas_node.power_on, 

67 timeout=CONF.maas_client.timeout) 

68 

69 def power_off(self): 

70 LOG.info("Powering off MAAS node: %s %s", 

71 self._maas_node.fqdn, 

72 self._maas_node.system_id) 

73 utils.async_compat_call( 

74 self._maas_node.power_off, 

75 timeout=CONF.maas_client.timeout) 

76 

77 

78class MaasHelper(base.BaseMetalHelper): 

79 def __init__(self, *args, **kwargs): 

80 super().__init__(*args, **kwargs) 

81 if not maas_enum: 81 ↛ 82line 81 didn't jump to line 82 because the condition on line 81 was never true

82 raise exception.UnsupportedError( 

83 "MAAS client unavailable. Please install python-libmaas.") 

84 

85 @property 

86 def _client(self): 

87 if not getattr(self, "_cached_client", None): 

88 self._cached_client = self._osc.maas() 

89 return self._cached_client 

90 

91 def list_compute_nodes(self): 

92 out_list = [] 

93 node_list = utils.async_compat_call( 

94 self._client.machines.list, 

95 timeout=CONF.maas_client.timeout) 

96 

97 compute_nodes = self.nova_client.hypervisors.list() 

98 compute_node_map = dict() 

99 for compute_node in compute_nodes: 

100 compute_node_map[compute_node.hypervisor_hostname] = compute_node 

101 

102 for node in node_list: 

103 hypervisor_node = compute_node_map.get(node.fqdn) 

104 if not hypervisor_node: 

105 LOG.info('Cannot find hypervisor %s', node.fqdn) 

106 continue 

107 

108 out_node = MaasNode(node, hypervisor_node, self._client) 

109 out_list.append(out_node) 

110 

111 return out_list 

112 

113 def _get_compute_node_by_hostname(self, hostname): 

114 compute_nodes = self.nova_client.hypervisors.search( 

115 hostname, detailed=True) 

116 for compute_node in compute_nodes: 116 ↛ exitline 116 didn't return from function '_get_compute_node_by_hostname' because the loop on line 116 didn't complete

117 if compute_node.hypervisor_hostname == hostname: 

118 return compute_node 

119 

120 def get_node(self, node_id): 

121 maas_node = utils.async_compat_call( 

122 self._client.machines.get, node_id, 

123 timeout=CONF.maas_client.timeout) 

124 compute_node = self._get_compute_node_by_hostname(maas_node.fqdn) 

125 return MaasNode(maas_node, compute_node, self._client)