Coverage for nova/objects/numa.py: 97%

124 statements  

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

1# Copyright 2014 Red Hat Inc. 

2# 

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

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

5# a copy of the License at 

6# 

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

8# 

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

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

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

12# License for the specific language governing permissions and limitations 

13# under the License. 

14 

15from oslo_serialization import jsonutils 

16from oslo_utils import versionutils 

17 

18from nova import exception 

19from nova.objects import base 

20from nova.objects import fields as obj_fields 

21from nova.virt import hardware 

22 

23 

24@base.NovaObjectRegistry.register 

25class NUMACell(base.NovaObject): 

26 # Version 1.0: Initial version 

27 # Version 1.1: Added pinned_cpus and siblings fields 

28 # Version 1.2: Added mempages field 

29 # Version 1.3: Add network_metadata field 

30 # Version 1.4: Add pcpuset 

31 # Version 1.5: Add socket 

32 VERSION = '1.5' 

33 

34 fields = { 

35 'id': obj_fields.IntegerField(read_only=True), 

36 'cpuset': obj_fields.SetOfIntegersField(), 

37 'pcpuset': obj_fields.SetOfIntegersField(), 

38 'memory': obj_fields.IntegerField(), 

39 'cpu_usage': obj_fields.IntegerField(default=0), 

40 'memory_usage': obj_fields.IntegerField(default=0), 

41 'pinned_cpus': obj_fields.SetOfIntegersField(), 

42 'siblings': obj_fields.ListOfSetsOfIntegersField(), 

43 'mempages': obj_fields.ListOfObjectsField('NUMAPagesTopology'), 

44 'network_metadata': obj_fields.ObjectField('NetworkMetadata'), 

45 'socket': obj_fields.IntegerField(nullable=True), 

46 } 

47 

48 def obj_make_compatible(self, primitive, target_version): 

49 super(NUMACell, self).obj_make_compatible(primitive, target_version) 

50 target_version = versionutils.convert_version_to_tuple(target_version) 

51 if target_version < (1, 5): 

52 primitive.pop('socket', None) 

53 if target_version < (1, 4): 

54 primitive.pop('pcpuset', None) 

55 if target_version < (1, 3): 

56 primitive.pop('network_metadata', None) 

57 

58 def __eq__(self, other): 

59 return base.all_things_equal(self, other) 

60 

61 def __ne__(self, other): 

62 return not (self == other) 

63 

64 @property 

65 def free_pcpus(self): 

66 """Return available dedicated CPUs.""" 

67 return self.pcpuset - self.pinned_cpus or set() 

68 

69 @property 

70 def free_siblings(self): 

71 """Return available dedicated CPUs in their sibling set form.""" 

72 return [sibling_set & self.free_pcpus for sibling_set in self.siblings] 

73 

74 @property 

75 def avail_pcpus(self): 

76 """Return number of available dedicated CPUs.""" 

77 return len(self.free_pcpus) 

78 

79 @property 

80 def avail_memory(self): 

81 return self.memory - self.memory_usage 

82 

83 @property 

84 def has_threads(self): 

85 """Check if SMT threads, a.k.a. HyperThreads, are present.""" 

86 return any(len(sibling_set) > 1 for sibling_set in self.siblings) 

87 

88 def pin_cpus(self, cpus): 

89 if cpus - self.pcpuset: 

90 raise exception.CPUPinningUnknown(requested=list(cpus), 

91 available=list(self.pcpuset)) 

92 

93 if self.pinned_cpus & cpus: 

94 available = list(self.pcpuset - self.pinned_cpus) 

95 raise exception.CPUPinningInvalid(requested=list(cpus), 

96 available=available) 

97 

98 self.pinned_cpus |= cpus 

99 

100 def unpin_cpus(self, cpus): 

101 if cpus - self.pcpuset: 

102 raise exception.CPUUnpinningUnknown(requested=list(cpus), 

103 available=list(self.pcpuset)) 

104 

105 if (self.pinned_cpus & cpus) != cpus: 

106 raise exception.CPUUnpinningInvalid(requested=list(cpus), 

107 available=list( 

108 self.pinned_cpus)) 

109 

110 self.pinned_cpus -= cpus 

111 

112 def pin_cpus_with_siblings(self, cpus): 

113 """Pin (consume) both thread siblings if one of them is requested to 

114 be pinned. 

115 

116 :param cpus: set of CPUs to pin 

117 """ 

118 pin_siblings = set() 

119 for sib in self.siblings: 

120 if cpus & sib: 

121 # NOTE(artom) If the intersection between cpus and sib is not 

122 # empty - IOW, the CPU we want to pin has sibligns - pin the 

123 # sibling as well. This is because we normally got here because 

124 # the `isolate` CPU thread policy is set, so we don't want to 

125 # place guest CPUs on host thread siblings. 

126 pin_siblings.update(sib) 

127 self.pin_cpus(pin_siblings) 

128 

129 def unpin_cpus_with_siblings(self, cpus): 

130 """Unpin (free up) both thread siblings if one of them is requested to 

131 be freed. 

132 

133 :param cpus: set of CPUs to unpin. 

134 """ 

135 pin_siblings = set() 

136 for sib in self.siblings: 

137 if cpus & sib: 

138 # NOTE(artom) This is the inverse operation of 

139 # pin_cpus_with_siblings() - see the NOTE there. If the CPU 

140 # we're unpinning has siblings, unpin the sibling as well. 

141 pin_siblings.update(sib) 

142 self.unpin_cpus(pin_siblings) 

143 

144 def can_fit_pagesize(self, pagesize, memory, use_free=True): 

145 """Returns whether memory can fit into a given pagesize. 

146 

147 :param pagesize: a page size in KibB 

148 :param memory: a memory size asked to fit in KiB 

149 :param use_free: if true, assess based on free memory rather than total 

150 memory. This means overcommit is not allowed, which should be the 

151 case for hugepages since these are memlocked by the kernel and 

152 can't be swapped out. 

153 

154 :returns: whether memory can fit in hugepages 

155 :raises: MemoryPageSizeNotSupported if page size not supported 

156 """ 

157 for pages in self.mempages: 

158 avail_kb = pages.free_kb if use_free else pages.total_kb 

159 if pages.size_kb == pagesize: 

160 return memory <= avail_kb and (memory % pages.size_kb) == 0 

161 raise exception.MemoryPageSizeNotSupported(pagesize=pagesize) 

162 

163 

164@base.NovaObjectRegistry.register 

165class NUMAPagesTopology(base.NovaObject): 

166 # Version 1.0: Initial version 

167 # Version 1.1: Adds reserved field 

168 VERSION = '1.1' 

169 

170 fields = { 

171 'size_kb': obj_fields.IntegerField(), 

172 'total': obj_fields.IntegerField(), 

173 'used': obj_fields.IntegerField(default=0), 

174 'reserved': obj_fields.IntegerField(default=0), 

175 } 

176 

177 def obj_make_compatible(self, primitive, target_version): 

178 super(NUMAPagesTopology, self).obj_make_compatible(primitive, 

179 target_version) 

180 target_version = versionutils.convert_version_to_tuple(target_version) 

181 if target_version < (1, 1): 

182 primitive.pop('reserved', None) 

183 

184 def __eq__(self, other): 

185 return base.all_things_equal(self, other) 

186 

187 def __ne__(self, other): 

188 return not (self == other) 

189 

190 @property 

191 def free(self): 

192 """Returns the number of avail pages.""" 

193 if not self.obj_attr_is_set('reserved'): 

194 # In case where an old compute node is sharing resource to 

195 # an updated node we must ensure that this property is defined. 

196 self.reserved = 0 

197 return self.total - self.used - self.reserved 

198 

199 @property 

200 def free_kb(self): 

201 """Returns the avail memory size in KiB.""" 

202 return self.free * self.size_kb 

203 

204 @property 

205 def total_kb(self): 

206 """Returns the total memory size in KiB.""" 

207 return self.total * self.size_kb 

208 

209 

210@base.NovaObjectRegistry.register 

211class NUMATopology(base.NovaObject): 

212 # Version 1.0: Initial version 

213 # Version 1.1: Update NUMACell to 1.1 

214 # Version 1.2: Update NUMACell to 1.2 

215 VERSION = '1.2' 

216 

217 fields = { 

218 'cells': obj_fields.ListOfObjectsField('NUMACell'), 

219 } 

220 

221 def __eq__(self, other): 

222 return base.all_things_equal(self, other) 

223 

224 def __ne__(self, other): 

225 return not (self == other) 

226 

227 @property 

228 def has_threads(self): 

229 """Check if any cell use SMT threads (a.k.a. Hyperthreads)""" 

230 return any(cell.has_threads for cell in self.cells) 

231 

232 def _to_json(self): 

233 return jsonutils.dumps(self.obj_to_primitive()) 

234 

235 @classmethod 

236 def obj_from_db_obj(cls, db_obj): 

237 """Convert serialized representation to object. 

238 

239 Deserialize instances of this object that have been stored as JSON 

240 blobs in the database. 

241 """ 

242 return cls.obj_from_primitive(jsonutils.loads(db_obj)) 

243 

244 @classmethod 

245 def from_legacy_object(cls, primitive: str): 

246 """Convert a pre-Liberty object to a (serialized) real o.vo. 

247 

248 :param primitive: A serialized representation of the legacy object. 

249 :returns: A serialized representation of the updated object. 

250 """ 

251 topology = cls( 

252 cells=[ 

253 NUMACell( 

254 id=cell.get('id'), 

255 cpuset=hardware.parse_cpu_spec(cell.get('cpus', '')), 

256 cpu_usage=cell.get('cpu_usage', 0), 

257 memory=cell.get('mem', {}).get('total', 0), 

258 memory_usage=cell.get('mem', {}).get('used', 0), 

259 mempages=[], 

260 pinned_cpus=set(), 

261 siblings=[], 

262 ) for cell in jsonutils.loads(primitive).get('cells', []) 

263 ], 

264 ) 

265 return topology._to_json() 

266 

267 def __len__(self): 

268 """Defined so that boolean testing works the same as for lists.""" 

269 return len(self.cells) 

270 

271 

272@base.NovaObjectRegistry.register 

273class NUMATopologyLimits(base.NovaObject): 

274 # Version 1.0: Initial version 

275 # Version 1.1: Add network_metadata field 

276 VERSION = '1.1' 

277 

278 fields = { 

279 'cpu_allocation_ratio': obj_fields.FloatField(), 

280 'ram_allocation_ratio': obj_fields.FloatField(), 

281 'network_metadata': obj_fields.ObjectField('NetworkMetadata'), 

282 } 

283 

284 def obj_make_compatible(self, primitive, target_version): 

285 super(NUMATopologyLimits, self).obj_make_compatible(primitive, 

286 target_version) 

287 target_version = versionutils.convert_version_to_tuple(target_version) 

288 if target_version < (1, 1): 

289 primitive.pop('network_metadata', None)