Coverage for nova/virt/libvirt/cpu/api.py: 87%

121 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-04-17 15:08 +0000

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

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

3# a copy of the License at 

4# 

5# http://www.apache.org/licenses/LICENSE-2.0 

6# 

7# Unless required by applicable law or agreed to in writing, software 

8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 

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

10# License for the specific language governing permissions and limitations 

11# under the License. 

12 

13from dataclasses import dataclass 

14import typing as ty 

15 

16from oslo_log import log as logging 

17 

18import nova.conf 

19from nova import exception 

20from nova.i18n import _ 

21from nova import objects 

22from nova.virt import hardware 

23from nova.virt.libvirt.cpu import core 

24 

25LOG = logging.getLogger(__name__) 

26 

27CONF = nova.conf.CONF 

28 

29 

30@dataclass 

31class Core: 

32 """Class to model a CPU core as reported by sysfs. 

33 

34 It may be a physical CPU core or a hardware thread on a shared CPU core 

35 depending on if the system supports SMT. 

36 """ 

37 

38 # NOTE(sbauza): ident is a mandatory field. 

39 # The CPU core id/number 

40 ident: int 

41 

42 @property 

43 def online(self) -> bool: 

44 return core.get_online(self.ident) 

45 

46 @online.setter 

47 def online(self, state: bool) -> None: 

48 if state: 

49 core.set_online(self.ident) 

50 else: 

51 core.set_offline(self.ident) 

52 

53 def __hash__(self): 

54 return hash(self.ident) 

55 

56 def __eq__(self, other): 

57 return self.ident == other.ident 

58 

59 def __str__(self): 

60 return str(self.ident) 

61 

62 @property 

63 def governor(self) -> ty.Optional[str]: 

64 try: 

65 return core.get_governor(self.ident) 

66 # NOTE(sbauza): cpufreq/scaling_governor is not enabled for some OS 

67 # platforms. 

68 except exception.FileNotFound: 

69 return None 

70 

71 def set_high_governor(self) -> None: 

72 core.set_governor(self.ident, CONF.libvirt.cpu_power_governor_high) 

73 

74 def set_low_governor(self) -> None: 

75 core.set_governor(self.ident, CONF.libvirt.cpu_power_governor_low) 

76 

77 

78class API(object): 

79 

80 def core(self, i): 

81 """From a purely functional point of view, there is no need for this 

82 method. However, we want to test power management in multinode 

83 scenarios (ex: live migration) in our functional tests. If we 

84 instantiated the Core class directly in the methods below, the 

85 functional tests would not be able to distinguish between cores on the 

86 source and destination hosts. In functional tests we can replace this 

87 helper method by a stub that returns a fixture, allowing us to maintain 

88 distinct core power state for each host. 

89 

90 See also nova.virt.libvirt.driver.LibvirtDriver.cpu_api. 

91 """ 

92 return Core(i) 

93 

94 def power_up(self, cpus: ty.Set[int]) -> None: 

95 if not CONF.libvirt.cpu_power_management: 

96 return 

97 cpu_dedicated_set = hardware.get_cpu_dedicated_set_nozero() or set() 

98 powered_up = set() 

99 for cpu in cpus: 

100 if cpu in cpu_dedicated_set: 

101 pcpu = self.core(cpu) 

102 if CONF.libvirt.cpu_power_management_strategy == 'cpu_state': 

103 pcpu.online = True 

104 else: 

105 pcpu.set_high_governor() 

106 powered_up.add(str(pcpu)) 

107 LOG.debug("Cores powered up : %s", powered_up) 

108 

109 def power_up_for_instance(self, instance: objects.Instance) -> None: 

110 if instance.numa_topology is None: 

111 return 

112 pcpus = instance.numa_topology.cpu_pinning.union( 

113 instance.numa_topology.cpuset_reserved) 

114 self.power_up(pcpus) 

115 

116 def power_up_for_migration( 

117 self, dst_numa_info: objects.LibvirtLiveMigrateNUMAInfo 

118 ) -> None: 

119 pcpus = set() 

120 if 'emulator_pins' in dst_numa_info and dst_numa_info.emulator_pins: 

121 pcpus = dst_numa_info.emulator_pins 

122 for pins in dst_numa_info.cpu_pins.values(): 

123 pcpus = pcpus.union(pins) 

124 self.power_up(pcpus) 

125 

126 def _power_down(self, cpus: ty.Set[int]) -> None: 

127 if not CONF.libvirt.cpu_power_management: 

128 return 

129 cpu_dedicated_set = hardware.get_cpu_dedicated_set_nozero() or set() 

130 powered_down = set() 

131 for cpu in cpus: 

132 if cpu in cpu_dedicated_set: 

133 pcpu = self.core(cpu) 

134 if CONF.libvirt.cpu_power_management_strategy == 'cpu_state': 

135 pcpu.online = False 

136 else: 

137 pcpu.set_low_governor() 

138 powered_down.add(str(pcpu)) 

139 LOG.debug("Cores powered down : %s", powered_down) 

140 

141 def power_down_for_migration( 

142 self, dst_numa_info: objects.LibvirtLiveMigrateNUMAInfo 

143 ) -> None: 

144 pcpus = set() 

145 if 'emulator_pins' in dst_numa_info and dst_numa_info.emulator_pins: 

146 pcpus = dst_numa_info.emulator_pins 

147 for pins in dst_numa_info.cpu_pins.values(): 

148 pcpus = pcpus.union(pins) 

149 self._power_down(pcpus) 

150 

151 def power_down_for_instance(self, instance: objects.Instance) -> None: 

152 if instance.numa_topology is None: 

153 return 

154 pcpus = instance.numa_topology.cpu_pinning.union( 

155 instance.numa_topology.cpuset_reserved) 

156 self._power_down(pcpus) 

157 

158 def power_down_all_dedicated_cpus(self) -> None: 

159 if not CONF.libvirt.cpu_power_management: 

160 return 

161 

162 cpu_dedicated_set = hardware.get_cpu_dedicated_set_nozero() or set() 

163 for pcpu in cpu_dedicated_set: 

164 pcpu = self.core(pcpu) 

165 if CONF.libvirt.cpu_power_management_strategy == 'cpu_state': 

166 pcpu.online = False 

167 else: 

168 pcpu.set_low_governor() 

169 LOG.debug("Cores powered down : %s", cpu_dedicated_set) 

170 

171 def validate_all_dedicated_cpus(self) -> None: 

172 if not CONF.libvirt.cpu_power_management: 

173 return 

174 cpu_dedicated_set = hardware.get_cpu_dedicated_set() or set() 

175 pcpus = [] 

176 for pcpu in cpu_dedicated_set: 

177 if (pcpu == 0 and 

178 CONF.libvirt.cpu_power_management_strategy == 'cpu_state'): 

179 LOG.warning('CPU0 is in cpu_dedicated_set, ' 

180 'but it is not eligible for state management ' 

181 'and will be ignored') 

182 continue 

183 # we need to collect the governors strategy and the CPU states 

184 pcpus.append(self.core(pcpu)) 

185 if CONF.libvirt.cpu_power_management_strategy == 'cpu_state': 

186 # NOTE(sbauza): offline cores can't have a governor, it returns a 

187 # DeviceBusy exception. 

188 governors = set([pcpu.governor for pcpu in pcpus 

189 if pcpu.online]) 

190 # all the cores need to have the same governor strategy 

191 if len(governors) > 1: 

192 msg = _("All the cores need to have the same governor strategy" 

193 "before modifying the CPU states. You can reboot the " 

194 "compute node if you prefer.") 

195 raise exception.InvalidConfiguration(msg) 

196 elif CONF.libvirt.cpu_power_management_strategy == 'governor': 196 ↛ exitline 196 didn't return from function 'validate_all_dedicated_cpus' because the condition on line 196 was always true

197 cpu_states = set([pcpu.online for pcpu in pcpus]) 

198 # all the cores need to be online 

199 if False in cpu_states: 199 ↛ exitline 199 didn't return from function 'validate_all_dedicated_cpus' because the condition on line 199 was always true

200 msg = _("All the cores need to be online before modifying the " 

201 "governor strategy.") 

202 raise exception.InvalidConfiguration(msg)