Coverage for nova/objects/numa.py: 97%
124 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-04-24 11:16 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-04-24 11:16 +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.
15from oslo_serialization import jsonutils
16from oslo_utils import versionutils
18from nova import exception
19from nova.objects import base
20from nova.objects import fields as obj_fields
21from nova.virt import hardware
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'
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 }
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)
58 def __eq__(self, other):
59 return base.all_things_equal(self, other)
61 def __ne__(self, other):
62 return not (self == other)
64 @property
65 def free_pcpus(self):
66 """Return available dedicated CPUs."""
67 return self.pcpuset - self.pinned_cpus or set()
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]
74 @property
75 def avail_pcpus(self):
76 """Return number of available dedicated CPUs."""
77 return len(self.free_pcpus)
79 @property
80 def avail_memory(self):
81 return self.memory - self.memory_usage
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)
88 def pin_cpus(self, cpus):
89 if cpus - self.pcpuset:
90 raise exception.CPUPinningUnknown(requested=list(cpus),
91 available=list(self.pcpuset))
93 if self.pinned_cpus & cpus:
94 available = list(self.pcpuset - self.pinned_cpus)
95 raise exception.CPUPinningInvalid(requested=list(cpus),
96 available=available)
98 self.pinned_cpus |= cpus
100 def unpin_cpus(self, cpus):
101 if cpus - self.pcpuset:
102 raise exception.CPUUnpinningUnknown(requested=list(cpus),
103 available=list(self.pcpuset))
105 if (self.pinned_cpus & cpus) != cpus:
106 raise exception.CPUUnpinningInvalid(requested=list(cpus),
107 available=list(
108 self.pinned_cpus))
110 self.pinned_cpus -= cpus
112 def pin_cpus_with_siblings(self, cpus):
113 """Pin (consume) both thread siblings if one of them is requested to
114 be pinned.
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)
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.
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)
144 def can_fit_pagesize(self, pagesize, memory, use_free=True):
145 """Returns whether memory can fit into a given pagesize.
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.
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)
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'
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 }
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)
184 def __eq__(self, other):
185 return base.all_things_equal(self, other)
187 def __ne__(self, other):
188 return not (self == other)
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
199 @property
200 def free_kb(self):
201 """Returns the avail memory size in KiB."""
202 return self.free * self.size_kb
204 @property
205 def total_kb(self):
206 """Returns the total memory size in KiB."""
207 return self.total * self.size_kb
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'
217 fields = {
218 'cells': obj_fields.ListOfObjectsField('NUMACell'),
219 }
221 def __eq__(self, other):
222 return base.all_things_equal(self, other)
224 def __ne__(self, other):
225 return not (self == other)
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)
232 def _to_json(self):
233 return jsonutils.dumps(self.obj_to_primitive())
235 @classmethod
236 def obj_from_db_obj(cls, db_obj):
237 """Convert serialized representation to object.
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))
244 @classmethod
245 def from_legacy_object(cls, primitive: str):
246 """Convert a pre-Liberty object to a (serialized) real o.vo.
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()
267 def __len__(self):
268 """Defined so that boolean testing works the same as for lists."""
269 return len(self.cells)
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'
278 fields = {
279 'cpu_allocation_ratio': obj_fields.FloatField(),
280 'ram_allocation_ratio': obj_fields.FloatField(),
281 'network_metadata': obj_fields.ObjectField('NetworkMetadata'),
282 }
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)