Coverage for nova/virt/hardware.py: 95%
974 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.
15import collections
16import itertools
17import re
18import typing as ty
20import os_resource_classes as orc
21import os_traits
22from oslo_log import log as logging
23from oslo_utils import strutils
24from oslo_utils import units
26from nova import compute
27import nova.conf
28from nova import exception
29from nova.i18n import _
30from nova import objects
31from nova.objects import compute_node
32from nova.objects import fields
33from nova.objects import service
34from nova.pci import stats
35from nova.scheduler.client import report
38CONF = nova.conf.CONF
39LOG = logging.getLogger(__name__)
41MEMPAGES_SMALL = -1
42MEMPAGES_LARGE = -2
43MEMPAGES_ANY = -3
46class VTPMConfig(ty.NamedTuple):
47 version: str
48 model: str
51def get_vcpu_pin_set():
52 """Parse ``vcpu_pin_set`` config.
54 :returns: A set of host CPU IDs that can be used for VCPU and PCPU
55 allocations.
56 """
57 if not CONF.vcpu_pin_set:
58 return None
60 cpuset_ids = parse_cpu_spec(CONF.vcpu_pin_set)
61 if not cpuset_ids:
62 msg = _("No CPUs available after parsing 'vcpu_pin_set' config, %r")
63 raise exception.Invalid(msg % CONF.vcpu_pin_set)
64 return cpuset_ids
67def get_cpu_dedicated_set():
68 """Parse ``[compute] cpu_dedicated_set`` config.
70 :returns: A set of host CPU IDs that can be used for PCPU allocations.
71 """
72 if not CONF.compute.cpu_dedicated_set:
73 return None
75 cpu_ids = parse_cpu_spec(CONF.compute.cpu_dedicated_set)
76 if not cpu_ids:
77 msg = _("No CPUs available after parsing '[compute] "
78 "cpu_dedicated_set' config, %r")
79 raise exception.Invalid(msg % CONF.compute.cpu_dedicated_set)
80 return cpu_ids
83def get_cpu_dedicated_set_nozero():
84 """Return cpu_dedicated_set without CPU0, if present"""
85 return (get_cpu_dedicated_set() or set()) - {0}
88def get_cpu_shared_set():
89 """Parse ``[compute] cpu_shared_set`` config.
91 :returns: A set of host CPU IDs that can be used for emulator threads and,
92 optionally, for VCPU allocations.
93 """
94 if not CONF.compute.cpu_shared_set:
95 return None
97 shared_ids = parse_cpu_spec(CONF.compute.cpu_shared_set)
98 if not shared_ids:
99 msg = _("No CPUs available after parsing '[compute] cpu_shared_set' "
100 "config, %r")
101 raise exception.Invalid(msg % CONF.compute.cpu_shared_set)
102 return shared_ids
105def parse_cpu_spec(spec: str) -> ty.Set[int]:
106 """Parse a CPU set specification.
108 Each element in the list is either a single CPU number, a range of
109 CPU numbers, or a caret followed by a CPU number to be excluded
110 from a previous range.
112 :param spec: cpu set string eg "1-4,^3,6"
114 :returns: a set of CPU indexes
115 """
116 cpuset_ids: ty.Set[int] = set()
117 cpuset_reject_ids: ty.Set[int] = set()
118 for rule in spec.split(','):
119 rule = rule.strip()
120 # Handle multi ','
121 if len(rule) < 1:
122 continue
123 # Note the count limit in the .split() call
124 range_parts = rule.split('-', 1)
125 if len(range_parts) > 1:
126 reject = False
127 if range_parts[0] and range_parts[0][0] == '^':
128 reject = True
129 range_parts[0] = str(range_parts[0][1:])
131 # So, this was a range; start by converting the parts to ints
132 try:
133 start, end = [int(p.strip()) for p in range_parts]
134 except ValueError:
135 raise exception.Invalid(_("Invalid range expression %r")
136 % rule)
137 # Make sure it's a valid range
138 if start > end:
139 raise exception.Invalid(_("Invalid range expression %r")
140 % rule)
141 # Add available CPU ids to set
142 if not reject:
143 cpuset_ids |= set(range(start, end + 1))
144 else:
145 cpuset_reject_ids |= set(range(start, end + 1))
146 elif rule[0] == '^':
147 # Not a range, the rule is an exclusion rule; convert to int
148 try:
149 cpuset_reject_ids.add(int(rule[1:].strip()))
150 except ValueError:
151 raise exception.Invalid(_("Invalid exclusion "
152 "expression %r") % rule)
153 else:
154 # OK, a single CPU to include; convert to int
155 try:
156 cpuset_ids.add(int(rule))
157 except ValueError:
158 raise exception.Invalid(_("Invalid inclusion "
159 "expression %r") % rule)
161 # Use sets to handle the exclusion rules for us
162 cpuset_ids -= cpuset_reject_ids
164 return cpuset_ids
167def format_cpu_spec(
168 cpuset: ty.Set[int],
169 allow_ranges: bool = True,
170) -> str:
171 """Format a libvirt CPU range specification.
173 Format a set/list of CPU indexes as a libvirt CPU range
174 specification. If allow_ranges is true, it will try to detect
175 continuous ranges of CPUs, otherwise it will just list each CPU
176 index explicitly.
178 :param cpuset: set (or list) of CPU indexes
179 :param allow_ranges: Whether we should attempt to detect continuous ranges
180 of CPUs.
182 :returns: a formatted CPU range string
183 """
184 # We attempt to detect ranges, but don't bother with
185 # trying to do range negations to minimize the overall
186 # spec string length
187 if allow_ranges:
188 ranges: ty.List[ty.List[int]] = []
189 previndex = None
190 for cpuindex in sorted(cpuset):
191 if previndex is None or previndex != (cpuindex - 1):
192 ranges.append([])
193 ranges[-1].append(cpuindex)
194 previndex = cpuindex
196 parts = []
197 for entry in ranges:
198 if len(entry) == 1:
199 parts.append(str(entry[0]))
200 else:
201 parts.append("%d-%d" % (entry[0], entry[len(entry) - 1]))
202 return ",".join(parts)
203 else:
204 return ",".join(str(id) for id in sorted(cpuset))
207def get_number_of_serial_ports(flavor, image_meta):
208 """Get the number of serial consoles from the flavor or image.
210 If flavor extra specs is not set, then any image meta value is
211 permitted. If flavor extra specs *is* set, then this provides the
212 default serial port count. The image meta is permitted to override
213 the extra specs, but *only* with a lower value, i.e.:
215 - flavor hw:serial_port_count=4
216 VM gets 4 serial ports
217 - flavor hw:serial_port_count=4 and image hw_serial_port_count=2
218 VM gets 2 serial ports
219 - image hw_serial_port_count=6
220 VM gets 6 serial ports
221 - flavor hw:serial_port_count=4 and image hw_serial_port_count=6
222 Abort guest boot - forbidden to exceed flavor value
224 :param flavor: Flavor object to read extra specs from
225 :param image_meta: nova.objects.ImageMeta object instance
227 :raises: exception.ImageSerialPortNumberInvalid if the serial port count
228 is not a valid integer
229 :raises: exception.ImageSerialPortNumberExceedFlavorValue if the serial
230 port count defined in image is greater than that of flavor
231 :returns: number of serial ports
232 """
233 flavor_num_ports, image_num_ports = _get_flavor_image_meta(
234 'serial_port_count', flavor, image_meta)
235 if flavor_num_ports:
236 try:
237 flavor_num_ports = int(flavor_num_ports)
238 except ValueError:
239 raise exception.ImageSerialPortNumberInvalid(
240 num_ports=flavor_num_ports)
242 if flavor_num_ports and image_num_ports:
243 if image_num_ports > flavor_num_ports:
244 raise exception.ImageSerialPortNumberExceedFlavorValue()
245 return image_num_ports
247 return flavor_num_ports or image_num_ports or 1
250class InstanceInfo(object):
252 def __init__(self, state, internal_id=None):
253 """Create a new Instance Info object
255 :param state: Required. The running state, one of the power_state codes
256 :param internal_id: Optional. A unique ID for the instance. Need not be
257 related to the Instance.uuid.
258 """
259 self.state = state
260 self.internal_id = internal_id
262 def __eq__(self, other):
263 return (self.__class__ == other.__class__ and
264 self.__dict__ == other.__dict__)
267def _score_cpu_topology(topology, wanttopology):
268 """Compare a topology against a desired configuration.
270 Calculate a score indicating how well a provided topology matches
271 against a preferred topology, where:
273 a score of 3 indicates an exact match for sockets, cores and
274 threads
275 a score of 2 indicates a match of sockets and cores, or sockets
276 and threads, or cores and threads
277 a score of 1 indicates a match of sockets or cores or threads
278 a score of 0 indicates no match
280 :param wanttopology: nova.objects.VirtCPUTopology instance for
281 preferred topology
283 :returns: score in range 0 (worst) to 3 (best)
284 """
285 score = 0
286 if wanttopology.sockets and topology.sockets == wanttopology.sockets:
287 score = score + 1
288 if wanttopology.cores and topology.cores == wanttopology.cores:
289 score = score + 1
290 if wanttopology.threads and topology.threads == wanttopology.threads:
291 score = score + 1
292 return score
295def get_cpu_topology_constraints(flavor, image_meta):
296 """Get the topology constraints declared in flavor or image
298 Extracts the topology constraints from the configuration defined in
299 the flavor extra specs or the image metadata. In the flavor this
300 will look for:
302 hw:cpu_sockets - preferred socket count
303 hw:cpu_cores - preferred core count
304 hw:cpu_threads - preferred thread count
305 hw:cpu_max_sockets - maximum socket count
306 hw:cpu_max_cores - maximum core count
307 hw:cpu_max_threads - maximum thread count
309 In the image metadata this will look at:
311 hw_cpu_sockets - preferred socket count
312 hw_cpu_cores - preferred core count
313 hw_cpu_threads - preferred thread count
314 hw_cpu_max_sockets - maximum socket count
315 hw_cpu_max_cores - maximum core count
316 hw_cpu_max_threads - maximum thread count
318 The image metadata must be strictly lower than any values set in
319 the flavor. All values are, however, optional.
321 :param flavor: Flavor object to read extra specs from
322 :param image_meta: nova.objects.ImageMeta object instance
324 :raises: exception.ImageVCPULimitsRangeExceeded if the maximum
325 counts set against the image exceed the maximum counts
326 set against the flavor
327 :raises: exception.ImageVCPUTopologyRangeExceeded if the preferred
328 counts set against the image exceed the maximum counts set
329 against the image or flavor
330 :raises: exception.InvalidRequest if one of the provided flavor properties
331 is a non-integer
332 :returns: A two-tuple of objects.VirtCPUTopology instances. The
333 first element corresponds to the preferred topology,
334 while the latter corresponds to the maximum topology,
335 based on upper limits.
336 """
337 flavor_max_sockets, image_max_sockets = _get_flavor_image_meta(
338 'cpu_max_sockets', flavor, image_meta, 0)
339 flavor_max_cores, image_max_cores = _get_flavor_image_meta(
340 'cpu_max_cores', flavor, image_meta, 0)
341 flavor_max_threads, image_max_threads = _get_flavor_image_meta(
342 'cpu_max_threads', flavor, image_meta, 0)
343 # image metadata is already of the correct type
344 try:
345 flavor_max_sockets = int(flavor_max_sockets)
346 flavor_max_cores = int(flavor_max_cores)
347 flavor_max_threads = int(flavor_max_threads)
348 except ValueError as e:
349 msg = _('Invalid flavor extra spec. Error: %s') % str(e)
350 raise exception.InvalidRequest(msg)
352 LOG.debug("Flavor limits %(sockets)d:%(cores)d:%(threads)d",
353 {"sockets": flavor_max_sockets,
354 "cores": flavor_max_cores,
355 "threads": flavor_max_threads})
356 LOG.debug("Image limits %(sockets)d:%(cores)d:%(threads)d",
357 {"sockets": image_max_sockets,
358 "cores": image_max_cores,
359 "threads": image_max_threads})
361 # Image limits are not permitted to exceed the flavor
362 # limits. ie they can only lower what the flavor defines
363 if ((flavor_max_sockets and image_max_sockets > flavor_max_sockets) or
364 (flavor_max_cores and image_max_cores > flavor_max_cores) or
365 (flavor_max_threads and image_max_threads > flavor_max_threads)):
366 raise exception.ImageVCPULimitsRangeExceeded(
367 image_sockets=image_max_sockets,
368 image_cores=image_max_cores,
369 image_threads=image_max_threads,
370 flavor_sockets=flavor_max_sockets,
371 flavor_cores=flavor_max_cores,
372 flavor_threads=flavor_max_threads)
374 max_sockets = image_max_sockets or flavor_max_sockets or 65536
375 max_cores = image_max_cores or flavor_max_cores or 65536
376 max_threads = image_max_threads or flavor_max_threads or 65536
378 flavor_sockets, image_sockets = _get_flavor_image_meta(
379 'cpu_sockets', flavor, image_meta, 0)
380 flavor_cores, image_cores = _get_flavor_image_meta(
381 'cpu_cores', flavor, image_meta, 0)
382 flavor_threads, image_threads = _get_flavor_image_meta(
383 'cpu_threads', flavor, image_meta, 0)
384 try:
385 flavor_sockets = int(flavor_sockets)
386 flavor_cores = int(flavor_cores)
387 flavor_threads = int(flavor_threads)
388 except ValueError as e:
389 msg = _('Invalid flavor extra spec. Error: %s') % str(e)
390 raise exception.InvalidRequest(msg)
392 LOG.debug("Flavor pref %(sockets)d:%(cores)d:%(threads)d",
393 {"sockets": flavor_sockets,
394 "cores": flavor_cores,
395 "threads": flavor_threads})
396 LOG.debug("Image pref %(sockets)d:%(cores)d:%(threads)d",
397 {"sockets": image_sockets,
398 "cores": image_cores,
399 "threads": image_threads})
401 # If the image limits have reduced the flavor limits we might need
402 # to discard the preferred topology from the flavor
403 if ((flavor_sockets > max_sockets) or
404 (flavor_cores > max_cores) or
405 (flavor_threads > max_threads)):
406 flavor_sockets = flavor_cores = flavor_threads = 0
408 # However, image topology is not permitted to exceed image/flavor
409 # limits
410 if ((image_sockets > max_sockets) or
411 (image_cores > max_cores) or
412 (image_threads > max_threads)):
413 raise exception.ImageVCPUTopologyRangeExceeded(
414 image_sockets=image_sockets,
415 image_cores=image_cores,
416 image_threads=image_threads,
417 max_sockets=max_sockets,
418 max_cores=max_cores,
419 max_threads=max_threads)
421 # If no preferred topology was set against the image then use the
422 # preferred topology from the flavor. We use 'not or' rather than
423 # 'not and', since if any value is set against the image this
424 # invalidates the entire set of values from the flavor
425 if not any((image_sockets, image_cores, image_threads)):
426 sockets = flavor_sockets
427 cores = flavor_cores
428 threads = flavor_threads
429 else:
430 sockets = image_sockets
431 cores = image_cores
432 threads = image_threads
434 LOG.debug('Chose sockets=%(sockets)d, cores=%(cores)d, '
435 'threads=%(threads)d; limits were sockets=%(maxsockets)d, '
436 'cores=%(maxcores)d, threads=%(maxthreads)d',
437 {"sockets": sockets, "cores": cores,
438 "threads": threads, "maxsockets": max_sockets,
439 "maxcores": max_cores, "maxthreads": max_threads})
441 return (objects.VirtCPUTopology(sockets=sockets, cores=cores,
442 threads=threads),
443 objects.VirtCPUTopology(sockets=max_sockets, cores=max_cores,
444 threads=max_threads))
447def _get_possible_cpu_topologies(vcpus, maxtopology,
448 allow_threads):
449 """Get a list of possible topologies for a vCPU count.
451 Given a total desired vCPU count and constraints on the maximum
452 number of sockets, cores and threads, return a list of
453 objects.VirtCPUTopology instances that represent every possible
454 topology that satisfies the constraints.
456 :param vcpus: total number of CPUs for guest instance
457 :param maxtopology: objects.VirtCPUTopology instance for upper
458 limits
459 :param allow_threads: True if the hypervisor supports CPU threads
461 :raises: exception.ImageVCPULimitsRangeImpossible if it is
462 impossible to achieve the total vcpu count given
463 the maximum limits on sockets, cores and threads
464 :returns: list of objects.VirtCPUTopology instances
465 """
466 # Clamp limits to number of vcpus to prevent
467 # iterating over insanely large list
468 maxsockets = min(vcpus, maxtopology.sockets)
469 maxcores = min(vcpus, maxtopology.cores)
470 maxthreads = min(vcpus, maxtopology.threads)
472 if not allow_threads:
473 maxthreads = 1
475 LOG.debug("Build topologies for %(vcpus)d vcpu(s) "
476 "%(maxsockets)d:%(maxcores)d:%(maxthreads)d",
477 {"vcpus": vcpus, "maxsockets": maxsockets,
478 "maxcores": maxcores, "maxthreads": maxthreads})
480 # Figure out all possible topologies that match
481 # the required vcpus count and satisfy the declared
482 # limits. If the total vCPU count were very high
483 # it might be more efficient to factorize the vcpu
484 # count and then only iterate over its factors, but
485 # that's overkill right now
486 possible = []
487 for s in range(1, maxsockets + 1):
488 for c in range(1, maxcores + 1):
489 for t in range(1, maxthreads + 1):
490 if (t * c * s) != vcpus:
491 continue
492 possible.append(
493 objects.VirtCPUTopology(sockets=s,
494 cores=c,
495 threads=t))
497 # We want to
498 # - Minimize threads (ie larger sockets * cores is best)
499 # - Prefer sockets over cores
500 possible = sorted(possible, reverse=True,
501 key=lambda x: (x.sockets * x.cores,
502 x.sockets,
503 x.threads))
505 LOG.debug("Got %d possible topologies", len(possible))
506 if len(possible) == 0:
507 raise exception.ImageVCPULimitsRangeImpossible(vcpus=vcpus,
508 sockets=maxsockets,
509 cores=maxcores,
510 threads=maxthreads)
512 return possible
515def _sort_possible_cpu_topologies(possible, wanttopology):
516 """Sort the topologies in order of preference.
518 Sort the provided list of possible topologies such that the
519 configurations which most closely match the preferred topology are
520 first.
522 :param possible: list of objects.VirtCPUTopology instances
523 :param wanttopology: objects.VirtCPUTopology instance for preferred
524 topology
526 :returns: sorted list of nova.objects.VirtCPUTopology instances
527 """
529 # Look at possible topologies and score them according
530 # to how well they match the preferred topologies
531 # We don't use python's sort(), since we want to
532 # preserve the sorting done when populating the
533 # 'possible' list originally
534 scores: ty.Dict[int, ty.List['objects.VirtCPUTopology']] = (
535 collections.defaultdict(list)
536 )
537 for topology in possible:
538 score = _score_cpu_topology(topology, wanttopology)
539 scores[score].append(topology)
541 # Build list of all possible topologies sorted
542 # by the match score, best match first
543 desired = []
544 desired.extend(scores[3])
545 desired.extend(scores[2])
546 desired.extend(scores[1])
547 desired.extend(scores[0])
549 return desired
552def _get_desirable_cpu_topologies(flavor, image_meta, allow_threads=True):
553 """Identify desirable CPU topologies based for given constraints.
555 Look at the properties set in the flavor extra specs and the image
556 metadata and build up a list of all possible valid CPU topologies
557 that can be used in the guest. Then return this list sorted in
558 order of preference.
560 :param flavor: objects.Flavor instance to query extra specs from
561 :param image_meta: nova.objects.ImageMeta object instance
562 :param allow_threads: if the hypervisor supports CPU threads
564 :returns: sorted list of objects.VirtCPUTopology instances
565 """
567 LOG.debug("Getting desirable topologies for flavor %(flavor)s "
568 "and image_meta %(image_meta)s, allow threads: %(threads)s",
569 {"flavor": flavor, "image_meta": image_meta,
570 "threads": allow_threads})
572 preferred, maximum = get_cpu_topology_constraints(flavor, image_meta)
573 LOG.debug("Topology preferred %(preferred)s, maximum %(maximum)s",
574 {"preferred": preferred, "maximum": maximum})
576 possible = _get_possible_cpu_topologies(flavor.vcpus,
577 maximum,
578 allow_threads)
579 LOG.debug("Possible topologies %s", possible)
580 desired = _sort_possible_cpu_topologies(possible, preferred)
581 LOG.debug("Sorted desired topologies %s", desired)
582 return desired
585def get_best_cpu_topology(flavor, image_meta, allow_threads=True):
586 """Identify best CPU topology for given constraints.
588 Look at the properties set in the flavor extra specs and the image
589 metadata and build up a list of all possible valid CPU topologies
590 that can be used in the guest. Then return the best topology to use
592 :param flavor: objects.Flavor instance to query extra specs from
593 :param image_meta: nova.objects.ImageMeta object instance
594 :param allow_threads: if the hypervisor supports CPU threads
596 :returns: an objects.VirtCPUTopology instance for best topology
597 """
598 return _get_desirable_cpu_topologies(
599 flavor, image_meta, allow_threads)[0]
602def _numa_cell_supports_pagesize_request(host_cell, inst_cell):
603 """Determine whether the cell can accept the request.
605 :param host_cell: host cell to fit the instance cell onto
606 :param inst_cell: instance cell we want to fit
608 :raises: exception.MemoryPageSizeNotSupported if custom page
609 size not supported in host cell
610 :returns: the page size able to be handled by host_cell
611 """
612 avail_pagesize = [page.size_kb for page in host_cell.mempages]
613 avail_pagesize.sort(reverse=True)
615 def verify_pagesizes(host_cell, inst_cell, avail_pagesize):
616 inst_cell_mem = inst_cell.memory * units.Ki
617 for pagesize in avail_pagesize:
618 if host_cell.can_fit_pagesize(pagesize, inst_cell_mem):
619 return pagesize
621 if inst_cell.pagesize == MEMPAGES_SMALL:
622 return verify_pagesizes(host_cell, inst_cell, avail_pagesize[-1:])
623 elif inst_cell.pagesize == MEMPAGES_LARGE:
624 return verify_pagesizes(host_cell, inst_cell, avail_pagesize[:-1])
625 elif inst_cell.pagesize == MEMPAGES_ANY:
626 return verify_pagesizes(host_cell, inst_cell, avail_pagesize)
627 else:
628 return verify_pagesizes(host_cell, inst_cell, [inst_cell.pagesize])
631def _pack_instance_onto_cores(host_cell, instance_cell,
632 num_cpu_reserved=0):
633 """Pack an instance onto a set of siblings.
635 Calculate the pinning for the given instance and its topology,
636 making sure that hyperthreads of the instance match up with those
637 of the host when the pinning takes effect. Also ensure that the
638 physical cores reserved for hypervisor on this host NUMA node do
639 not break any thread policies.
641 Currently the strategy for packing is to prefer siblings and try use
642 cores evenly by using emptier cores first. This is achieved by the
643 way we order cores in the sibling_sets structure, and the order in
644 which we iterate through it.
646 The main packing loop that iterates over the sibling_sets dictionary
647 will not currently try to look for a fit that maximizes number of
648 siblings, but will simply rely on the iteration ordering and picking
649 the first viable placement.
651 :param host_cell: objects.NUMACell instance - the host cell that
652 the instance should be pinned to
653 :param instance_cell: An instance of objects.InstanceNUMACell
654 describing the pinning requirements of the
655 instance
656 :param num_cpu_reserved: number of pCPUs reserved for hypervisor
658 :returns: An instance of objects.InstanceNUMACell containing the
659 pinning information, the physical cores reserved and
660 potentially a new topology to be exposed to the
661 instance. None if there is no valid way to satisfy the
662 sibling requirements for the instance.
663 """
664 # get number of threads per core in host's cell
665 threads_per_core = max(map(len, host_cell.siblings)) or 1
667 LOG.debug('Packing an instance onto a set of siblings: '
668 ' host_cell_free_siblings: %(siblings)s'
669 ' instance_cell: %(cells)s'
670 ' host_cell_id: %(host_cell_id)s'
671 ' threads_per_core: %(threads_per_core)s'
672 ' num_cpu_reserved: %(num_cpu_reserved)s',
673 {'siblings': host_cell.free_siblings,
674 'cells': instance_cell,
675 'host_cell_id': host_cell.id,
676 'threads_per_core': threads_per_core,
677 'num_cpu_reserved': num_cpu_reserved})
679 # We build up a data structure that answers the question: 'Given the
680 # number of threads I want to pack, give me a list of all the available
681 # sibling sets (or groups thereof) that can accommodate it'
682 sibling_sets: ty.Dict[int, ty.List[ty.Set[int]]] = (
683 collections.defaultdict(list)
684 )
685 for sib in host_cell.free_siblings:
686 for threads_no in range(1, len(sib) + 1):
687 sibling_sets[threads_no].append(sib)
688 LOG.debug('Built sibling_sets: %(siblings)s', {'siblings': sibling_sets})
690 pinning = None
691 threads_no = 1
693 def _get_pinning(threads_no, sibling_set, instance_cores):
694 """Determines pCPUs/vCPUs mapping
696 Determines the pCPUs/vCPUs mapping regarding the number of
697 threads which can be used per cores.
699 :param threads_no: Number of host threads per cores which can
700 be used to pin vCPUs according to the
701 policies.
702 :param sibling_set: List of available threads per host cores
703 on a specific host NUMA node.
704 :param instance_cores: Set of vCPUs requested.
706 NOTE: Depending on how host is configured (HT/non-HT) a thread can
707 be considered as an entire core.
708 """
709 if threads_no * len(sibling_set) < (len(instance_cores)):
710 return None
712 # Determines usable cores according the "threads number"
713 # constraint.
714 #
715 # For a sibling_set=[(0, 1, 2, 3), (4, 5, 6, 7)] and thread_no 1:
716 # usable_cores=[[0], [4]]
717 #
718 # For a sibling_set=[(0, 1, 2, 3), (4, 5, 6, 7)] and thread_no 2:
719 # usable_cores=[[0, 1], [4, 5]]
720 usable_cores = list(map(lambda s: list(s)[:threads_no], sibling_set))
722 # Determines the mapping vCPUs/pCPUs based on the sets of
723 # usable cores.
724 #
725 # For an instance_cores=[2, 3], usable_cores=[[0], [4]]
726 # vcpus_pinning=[(2, 0), (3, 4)]
727 vcpus_pinning = list(zip(sorted(instance_cores),
728 itertools.chain(*usable_cores)))
729 msg = ("Computed NUMA topology CPU pinning: usable pCPUs: "
730 "%(usable_cores)s, vCPUs mapping: %(vcpus_pinning)s")
731 msg_args = {
732 'usable_cores': usable_cores,
733 'vcpus_pinning': vcpus_pinning,
734 }
735 LOG.info(msg, msg_args)
737 return vcpus_pinning
739 def _get_reserved(sibling_set, vcpus_pinning, num_cpu_reserved=0,
740 cpu_thread_isolate=False):
741 """Given available sibling_set, returns the pCPUs reserved
742 for hypervisor.
744 :param sibling_set: List of available threads per host cores
745 on a specific host NUMA node.
746 :param vcpus_pinning: List of tuple of (pCPU, vCPU) mapping.
747 :param num_cpu_reserved: Number of additional host CPUs which
748 need to be reserved.
749 :param cpu_thread_isolate: True if CPUThreadAllocationPolicy
750 is ISOLATE.
751 """
752 if not vcpus_pinning:
753 return None
755 cpuset_reserved = None
756 usable_cores = list(map(lambda s: list(s), sibling_set))
758 if num_cpu_reserved:
759 # Updates the pCPUs used based on vCPUs pinned to.
760 # For the case vcpus_pinning=[(0, 0), (1, 2)] and
761 # usable_cores=[[0, 1], [2, 3], [4, 5]],
762 # if CPUThreadAllocationPolicy is isolated, we want
763 # to update usable_cores=[[4, 5]].
764 # If CPUThreadAllocationPolicy is *not* isolated,
765 # we want to update usable_cores=[[1],[3],[4, 5]].
766 for vcpu, pcpu in vcpus_pinning:
767 for sib in usable_cores:
768 if pcpu in sib:
769 if cpu_thread_isolate:
770 usable_cores.remove(sib)
771 else:
772 sib.remove(pcpu)
774 # Determines the pCPUs reserved for hypervisor
775 #
776 # For usable_cores=[[1],[3],[4, 5]], num_cpu_reserved=1
777 # cpuset_reserved=set([1])
778 cpuset_reserved = set(list(
779 itertools.chain(*usable_cores))[:num_cpu_reserved])
780 msg = ("Computed NUMA topology reserved pCPUs: usable pCPUs: "
781 "%(usable_cores)s, reserved pCPUs: %(cpuset_reserved)s")
782 msg_args = {
783 'usable_cores': usable_cores,
784 'cpuset_reserved': cpuset_reserved,
785 }
786 LOG.info(msg, msg_args)
788 return cpuset_reserved or None
790 if (instance_cell.cpu_thread_policy ==
791 fields.CPUThreadAllocationPolicy.REQUIRE):
792 LOG.debug("Requested 'require' thread policy for %d cores",
793 len(instance_cell))
794 elif (instance_cell.cpu_thread_policy ==
795 fields.CPUThreadAllocationPolicy.PREFER):
796 LOG.debug("Requested 'prefer' thread policy for %d cores",
797 len(instance_cell))
798 elif (instance_cell.cpu_thread_policy ==
799 fields.CPUThreadAllocationPolicy.ISOLATE):
800 LOG.debug("Requested 'isolate' thread policy for %d cores",
801 len(instance_cell))
802 else:
803 LOG.debug("User did not specify a thread policy. Using default "
804 "for %d cores", len(instance_cell))
806 if (instance_cell.cpu_thread_policy ==
807 fields.CPUThreadAllocationPolicy.ISOLATE):
808 # make sure we have at least one fully free core
809 if threads_per_core not in sibling_sets:
810 LOG.debug('Host does not have any fully free thread sibling sets.'
811 'It is not possible to emulate a non-SMT behavior '
812 'for the isolate policy without this.')
813 return
815 # TODO(stephenfin): Drop this when we drop support for 'vcpu_pin_set'
816 # NOTE(stephenfin): This is total hack. We're relying on the fact that
817 # the libvirt driver, which is the only one that currently supports
818 # pinned CPUs, will set cpuset and pcpuset to the same value if using
819 # legacy configuration, i.e. 'vcpu_pin_set', as part of
820 # '_get_host_numa_topology'. They can't be equal otherwise since
821 # 'cpu_dedicated_set' and 'cpu_shared_set' must be disjoint. Therefore,
822 # if these are equal, the host that this NUMA cell corresponds to is
823 # using legacy configuration and it's okay to use the old, "pin a core
824 # and reserve its siblings" implementation of the 'isolate' policy. If
825 # they're not, the host is using new-style configuration and we've just
826 # hit bug #1889633
827 if threads_per_core != 1 and host_cell.pcpuset != host_cell.cpuset:
828 LOG.warning(
829 "Host supports hyperthreads, but instance requested no "
830 "hyperthreads. This should have been rejected by the "
831 "scheduler but we likely got here due to the fallback VCPU "
832 "query. Consider setting '[workarounds] "
833 "disable_fallback_pcpu_query' to 'True' once hosts are no "
834 "longer using 'vcpu_pin_set'. Refer to bug #1889633 for more "
835 "information."
836 )
837 return
839 pinning = _get_pinning(
840 1, # we only want to "use" one thread per core
841 sibling_sets[threads_per_core],
842 instance_cell.pcpuset)
843 cpuset_reserved = _get_reserved(
844 sibling_sets[1], pinning, num_cpu_reserved=num_cpu_reserved,
845 cpu_thread_isolate=True)
846 if not pinning or (num_cpu_reserved and not cpuset_reserved):
847 pinning, cpuset_reserved = (None, None)
849 else: # REQUIRE, PREFER (explicit, implicit)
850 if (instance_cell.cpu_thread_policy ==
851 fields.CPUThreadAllocationPolicy.REQUIRE):
852 # make sure we actually have some siblings to play with
853 if threads_per_core <= 1:
854 LOG.info("Host does not support hyperthreading or "
855 "hyperthreading is disabled, but 'require' "
856 "threads policy was requested.")
857 return
859 # NOTE(ndipanov): We iterate over the sibling sets in descending order
860 # of cores that can be packed. This is an attempt to evenly distribute
861 # instances among physical cores
862 for threads_no, sibling_set in sorted(
863 (t for t in sibling_sets.items()), reverse=True):
865 # NOTE(sfinucan): The key difference between the require and
866 # prefer policies is that require will not settle for non-siblings
867 # if this is all that is available. Enforce this by ensuring we're
868 # using sibling sets that contain at least one sibling
869 if (instance_cell.cpu_thread_policy ==
870 fields.CPUThreadAllocationPolicy.REQUIRE):
871 if threads_no <= 1:
872 LOG.debug('Skipping threads_no: %s, as it does not satisfy'
873 ' the require policy', threads_no)
874 continue
876 pinning = _get_pinning(
877 threads_no, sibling_set,
878 instance_cell.pcpuset)
879 cpuset_reserved = _get_reserved(
880 sibling_sets[1], pinning, num_cpu_reserved=num_cpu_reserved)
881 if pinning is None or (num_cpu_reserved and not cpuset_reserved):
882 continue
883 break
885 # NOTE(sfinucan): If siblings weren't available and we're using PREFER
886 # (implicitly or explicitly), fall back to linear assignment across
887 # cores
888 if (instance_cell.cpu_thread_policy !=
889 fields.CPUThreadAllocationPolicy.REQUIRE and
890 not pinning):
891 # we create a fake sibling set by splitting all sibling sets and
892 # treating each core as if it has no siblings. This is necessary
893 # because '_get_pinning' will normally only take the same amount of
894 # cores ('threads_no' cores) from each sibling set. This is rather
895 # desirable when we're seeking to apply a thread policy but it is
896 # less desirable when we only care about resource usage as we do
897 # here. By treating each core as independent, as we do here, we
898 # maximize resource usage for almost-full nodes at the expense of a
899 # possible performance impact to the guest.
900 sibling_set = [set([x]) for x in itertools.chain(*sibling_sets[1])]
901 pinning = _get_pinning(
902 threads_no, sibling_set,
903 instance_cell.pcpuset)
904 cpuset_reserved = _get_reserved(
905 sibling_set, pinning, num_cpu_reserved=num_cpu_reserved)
907 if pinning is None or (num_cpu_reserved and not cpuset_reserved):
908 return
909 LOG.debug('Selected cores for pinning: %s, in cell %s', pinning,
910 host_cell.id)
912 instance_cell.pin_vcpus(*pinning)
913 instance_cell.id = host_cell.id
914 instance_cell.cpuset_reserved = cpuset_reserved
915 return instance_cell
918def _numa_fit_instance_cell(
919 host_cell: 'objects.NUMACell',
920 instance_cell: 'objects.InstanceNUMACell',
921 limits: ty.Optional['objects.NUMATopologyLimit'] = None,
922 cpuset_reserved: int = 0,
923) -> ty.Optional['objects.InstanceNUMACell']:
924 """Ensure an instance cell can fit onto a host cell
926 Ensure an instance cell can fit onto a host cell and, if so, return
927 a new objects.InstanceNUMACell with the id set to that of the host.
928 Returns None if the instance cell exceeds the limits of the host.
930 :param host_cell: host cell to fit the instance cell onto
931 :param instance_cell: instance cell we want to fit
932 :param limits: an objects.NUMATopologyLimit or None
933 :param cpuset_reserved: An int to indicate the number of CPUs overhead
935 :returns: objects.InstanceNUMACell with the id set to that of the
936 host, or None
937 """
938 LOG.debug('Attempting to fit instance cell %(cell)s on host_cell '
939 '%(host_cell)s', {'cell': instance_cell, 'host_cell': host_cell})
941 if 'pagesize' in instance_cell and instance_cell.pagesize:
942 # The instance has requested a page size. Verify that the requested
943 # size is valid and that there are available pages of that size on the
944 # host.
945 pagesize = _numa_cell_supports_pagesize_request(
946 host_cell, instance_cell)
947 if not pagesize:
948 LOG.debug('Host does not support requested memory pagesize, '
949 'or not enough free pages of the requested size. '
950 'Requested memory pagesize: %d '
951 '(small = -1, large = -2, any = -3)',
952 instance_cell.pagesize)
953 return None
954 LOG.debug('Selected memory pagesize: %(selected_mem_pagesize)d kB. '
955 'Requested memory pagesize: %(requested_mem_pagesize)d '
956 '(small = -1, large = -2, any = -3)',
957 {'selected_mem_pagesize': pagesize,
958 'requested_mem_pagesize': instance_cell.pagesize})
959 instance_cell.pagesize = pagesize
960 else:
961 # The instance provides a NUMA topology but does not define any
962 # particular page size for its memory.
963 if host_cell.mempages:
964 # The host supports explicit page sizes. Use a pagesize-aware
965 # memory check using the smallest available page size.
966 pagesize = _get_smallest_pagesize(host_cell)
967 LOG.debug('No specific pagesize requested for instance, '
968 'selected pagesize: %d', pagesize)
969 # we want to allow overcommit in this case as we're not using
970 # hugepages *except* if using CPU pinning, which for legacy reasons
971 # does not allow overcommit
972 use_free = instance_cell.cpu_policy in (
973 fields.CPUAllocationPolicy.DEDICATED,
974 fields.CPUAllocationPolicy.MIXED,
975 )
976 if not host_cell.can_fit_pagesize(
977 pagesize, instance_cell.memory * units.Ki, use_free=use_free
978 ):
979 LOG.debug('Not enough available memory to schedule instance '
980 'with pagesize %(pagesize)d. Required: '
981 '%(required)s, available: %(available)s, total: '
982 '%(total)s.',
983 {'required': instance_cell.memory,
984 'available': host_cell.avail_memory,
985 'total': host_cell.memory,
986 'pagesize': pagesize})
987 return None
988 else:
989 # The host does not support explicit page sizes. Ignore pagesizes
990 # completely.
992 # NOTE(stephenfin): Do not allow an instance to overcommit against
993 # itself on any NUMA cell, i.e. with 'ram_allocation_ratio = 2.0'
994 # on a host with 1GB RAM, we should allow two 1GB instances but not
995 # one 2GB instance. If CPU pinning is in use, don't allow
996 # overcommit at all.
997 if instance_cell.cpu_policy in (
998 fields.CPUAllocationPolicy.DEDICATED,
999 fields.CPUAllocationPolicy.MIXED,
1000 ):
1001 if host_cell.avail_memory < instance_cell.memory:
1002 LOG.debug(
1003 'Not enough host cell memory to fit instance cell. '
1004 'Oversubscription is not possible with pinned '
1005 'instances. '
1006 'Required: %(required)d, available: %(available)d, '
1007 'total: %(total)d. ',
1008 {'required': instance_cell.memory,
1009 'available': host_cell.avail_memory,
1010 'total': host_cell.memory})
1011 return None
1012 else:
1013 if host_cell.memory < instance_cell.memory:
1014 LOG.debug(
1015 'Not enough host cell memory to fit instance cell. '
1016 'Required: %(required)d, actual: %(total)d',
1017 {'required': instance_cell.memory,
1018 'total': host_cell.memory})
1019 return None
1021 # NOTE(stephenfin): As with memory, do not allow an instance to overcommit
1022 # against itself on any NUMA cell
1023 if instance_cell.cpu_policy in (
1024 fields.CPUAllocationPolicy.DEDICATED,
1025 fields.CPUAllocationPolicy.MIXED,
1026 ):
1027 required_cpus = len(instance_cell.pcpuset) + cpuset_reserved
1028 if required_cpus > len(host_cell.pcpuset):
1029 LOG.debug('Not enough host cell CPUs to fit instance cell; '
1030 'required: %(required)d + %(cpuset_reserved)d as '
1031 'overhead, actual: %(actual)d', {
1032 'required': len(instance_cell.pcpuset),
1033 'actual': len(host_cell.pcpuset),
1034 'cpuset_reserved': cpuset_reserved
1035 })
1036 return None
1038 if instance_cell.cpu_policy in (
1039 fields.CPUAllocationPolicy.SHARED,
1040 fields.CPUAllocationPolicy.MIXED,
1041 None,
1042 ):
1043 required_cpus = len(instance_cell.cpuset)
1044 if required_cpus > len(host_cell.cpuset):
1045 LOG.debug('Not enough host cell CPUs to fit instance cell; '
1046 'required: %(required)d, actual: %(actual)d', {
1047 'required': len(instance_cell.cpuset),
1048 'actual': len(host_cell.cpuset),
1049 })
1050 return None
1052 if instance_cell.cpu_policy in (
1053 fields.CPUAllocationPolicy.DEDICATED,
1054 fields.CPUAllocationPolicy.MIXED,
1055 ):
1056 LOG.debug('Instance has requested pinned CPUs')
1057 required_cpus = len(instance_cell.pcpuset) + cpuset_reserved
1058 if required_cpus > host_cell.avail_pcpus:
1059 LOG.debug('Not enough available CPUs to schedule instance. '
1060 'Oversubscription is not possible with pinned '
1061 'instances. Required: %(required)d (%(vcpus)d + '
1062 '%(num_cpu_reserved)d), actual: %(actual)d',
1063 {'required': required_cpus,
1064 'vcpus': len(instance_cell.pcpuset),
1065 'actual': host_cell.avail_pcpus,
1066 'num_cpu_reserved': cpuset_reserved})
1067 return None
1069 # Try to pack the instance cell onto cores
1070 instance_cell = _pack_instance_onto_cores(
1071 host_cell, instance_cell, num_cpu_reserved=cpuset_reserved,
1072 )
1073 if not instance_cell:
1074 LOG.debug('Failed to map instance cell CPUs to host cell CPUs')
1075 return None
1077 if instance_cell.cpu_policy in (
1078 fields.CPUAllocationPolicy.SHARED,
1079 fields.CPUAllocationPolicy.MIXED,
1080 None,
1081 ) and limits:
1082 LOG.debug(
1083 'Instance has requested shared CPUs; considering limitations '
1084 'on usable CPU and memory.')
1085 cpu_usage = host_cell.cpu_usage + len(instance_cell.cpuset)
1086 cpu_limit = len(host_cell.cpuset) * limits.cpu_allocation_ratio
1087 if cpu_usage > cpu_limit: 1087 ↛ 1088line 1087 didn't jump to line 1088 because the condition on line 1087 was never true
1088 LOG.debug('Host cell has limitations on usable CPUs. There are '
1089 'not enough free CPUs to schedule this instance. '
1090 'Usage: %(usage)d, limit: %(limit)d',
1091 {'usage': cpu_usage, 'limit': cpu_limit})
1092 return None
1094 ram_usage = host_cell.memory_usage + instance_cell.memory
1095 ram_limit = host_cell.memory * limits.ram_allocation_ratio
1096 if ram_usage > ram_limit: 1096 ↛ 1097line 1096 didn't jump to line 1097 because the condition on line 1096 was never true
1097 LOG.debug('Host cell has limitations on usable memory. There is '
1098 'not enough free memory to schedule this instance. '
1099 'Usage: %(usage)d, limit: %(limit)d',
1100 {'usage': ram_usage, 'limit': ram_limit})
1101 return None
1103 instance_cell.id = host_cell.id
1104 return instance_cell
1107def _get_flavor_image_meta(
1108 key: str,
1109 flavor: 'objects.Flavor',
1110 image_meta: 'objects.ImageMeta',
1111 default: ty.Any = None,
1112 prefix: str = 'hw',
1113) -> ty.Tuple[ty.Any, ty.Any]:
1114 """Extract both flavor- and image-based variants of metadata."""
1115 flavor_key = ':'.join([prefix, key])
1116 image_key = '_'.join([prefix, key])
1118 flavor_value = flavor.get('extra_specs', {}).get(flavor_key, default)
1119 image_value = image_meta.properties.get(image_key, default)
1121 return flavor_value, image_value
1124def _get_unique_flavor_image_meta(
1125 key: str,
1126 flavor: 'objects.Flavor',
1127 image_meta: 'objects.ImageMeta',
1128 default: ty.Any = None,
1129 prefix: str = 'hw',
1130) -> ty.Any:
1131 """A variant of '_get_flavor_image_meta' that errors out on conflicts."""
1132 flavor_value, image_value = _get_flavor_image_meta(
1133 key, flavor, image_meta, default, prefix,
1134 )
1135 if image_value and flavor_value and image_value != flavor_value:
1136 msg = _(
1137 "Flavor %(flavor_name)s has %(prefix)s:%(key)s extra spec "
1138 "explicitly set to %(flavor_val)s, conflicting with image "
1139 "%(image_name)s which has %(prefix)s_%(key)s explicitly set to "
1140 "%(image_val)s."
1141 )
1142 raise exception.FlavorImageConflict(
1143 msg % {
1144 'prefix': prefix,
1145 'key': key,
1146 'flavor_name': flavor.name,
1147 'flavor_val': flavor_value,
1148 'image_name': image_meta.name,
1149 'image_val': image_value,
1150 },
1151 )
1153 return flavor_value or image_value
1156def get_mem_encryption_constraint(
1157 flavor: 'objects.Flavor',
1158 image_meta: 'objects.ImageMeta',
1159 machine_type: ty.Optional[str] = None,
1160) -> bool:
1161 """Return a boolean indicating whether encryption of guest memory was
1162 requested, either via the hw:mem_encryption extra spec or the
1163 hw_mem_encryption image property (or both).
1165 Also watch out for contradictory requests between the flavor and
1166 image regarding memory encryption, and raise an exception where
1167 encountered. These conflicts can arise in two different ways:
1169 1) the flavor requests memory encryption but the image
1170 explicitly requests *not* to have memory encryption, or
1171 vice-versa
1173 2) the flavor and/or image request memory encryption, but the
1174 image is missing hw_firmware_type=uefi
1176 3) the flavor and/or image request memory encryption, but the
1177 machine type is set to a value which does not contain 'q35'
1179 This can be called from the libvirt driver on the compute node, in
1180 which case the driver should pass the result of
1181 nova.virt.libvirt.utils.get_machine_type() as the machine_type
1182 parameter, or from the API layer, in which case get_machine_type()
1183 cannot be called since it relies on being run from the compute
1184 node in order to retrieve CONF.libvirt.hw_machine_type.
1186 :param flavor: Flavor object
1187 :param image: an ImageMeta object
1188 :param machine_type: a string representing the machine type (optional)
1189 :raises: nova.exception.FlavorImageConflict
1190 :raises: nova.exception.InvalidMachineType
1191 :returns: boolean indicating whether encryption of guest memory
1192 was requested
1193 """
1195 flavor_mem_enc_str, image_mem_enc = _get_flavor_image_meta(
1196 'mem_encryption', flavor, image_meta)
1198 flavor_mem_enc = None
1199 if flavor_mem_enc_str is not None:
1200 flavor_mem_enc = strutils.bool_from_string(flavor_mem_enc_str)
1202 # Image property is a FlexibleBooleanField, so coercion to a
1203 # boolean is handled automatically
1205 if not flavor_mem_enc and not image_mem_enc:
1206 return False
1208 _check_for_mem_encryption_requirement_conflicts(
1209 flavor_mem_enc_str, flavor_mem_enc, image_mem_enc, flavor, image_meta)
1211 # If we get this far, either the extra spec or image property explicitly
1212 # specified a requirement regarding memory encryption, and if both did,
1213 # they are asking for the same thing.
1214 # NOTE(jie, sean): When creating a volume booted instance with memory
1215 # encryption enabled, image_meta has no id key. See bug #2041511.
1216 # So we check whether id exists. If there is no value, we set it
1217 # to a sentinel value we can detect later. i.e. '<no-id>'.
1218 requesters = []
1219 if flavor_mem_enc:
1220 requesters.append("hw:mem_encryption extra spec in %s flavor" %
1221 flavor.name)
1222 if image_mem_enc:
1223 image_id = (image_meta.id if 'id' in image_meta else '<no-id>')
1224 requesters.append("hw_mem_encryption property of image %s" %
1225 image_id)
1227 _check_mem_encryption_uses_uefi_image(requesters, image_meta)
1228 _check_mem_encryption_machine_type(image_meta, machine_type)
1230 LOG.debug("Memory encryption requested by %s", " and ".join(requesters))
1231 return True
1234def _check_for_mem_encryption_requirement_conflicts(
1235 flavor_mem_enc_str, flavor_mem_enc, image_mem_enc, flavor, image_meta):
1236 # Check for conflicts between explicit requirements regarding
1237 # memory encryption.
1238 if (flavor_mem_enc is not None and image_mem_enc is not None and
1239 flavor_mem_enc != image_mem_enc):
1240 emsg = _(
1241 "Flavor %(flavor_name)s has hw:mem_encryption extra spec "
1242 "explicitly set to %(flavor_val)s, conflicting with "
1243 "image %(image_name)s which has hw_mem_encryption property "
1244 "explicitly set to %(image_val)s"
1245 )
1246 # image_meta.name is not set if image object represents root
1247 # Cinder volume.
1248 image_name = (image_meta.name if 'name' in image_meta else None)
1249 data = {
1250 'flavor_name': flavor.name,
1251 'flavor_val': flavor_mem_enc_str,
1252 'image_name': image_name,
1253 'image_val': image_mem_enc,
1254 }
1255 raise exception.FlavorImageConflict(emsg % data)
1258def _check_mem_encryption_uses_uefi_image(requesters, image_meta):
1259 if image_meta.properties.get('hw_firmware_type') == 'uefi':
1260 return
1262 emsg = _(
1263 "Memory encryption requested by %(requesters)s but image "
1264 "%(image_name)s doesn't have 'hw_firmware_type' property set to "
1265 "'uefi' or volume-backed instance was requested"
1266 )
1267 # image_meta.name is not set if image object represents root Cinder
1268 # volume, for this case FlavorImageConflict should be raised, but
1269 # image_meta.name can't be extracted.
1270 image_name = (image_meta.name if 'name' in image_meta else None)
1271 data = {'requesters': " and ".join(requesters),
1272 'image_name': image_name}
1273 raise exception.FlavorImageConflict(emsg % data)
1276def _check_mem_encryption_machine_type(image_meta, machine_type=None):
1277 # NOTE(aspiers): As explained in the SEV spec, SEV needs a q35
1278 # machine type in order to bind all the virtio devices to the PCIe
1279 # bridge so that they use virtio 1.0 and not virtio 0.9, since
1280 # QEMU's iommu_platform feature was added in virtio 1.0 only:
1281 #
1282 # http://specs.openstack.org/openstack/nova-specs/specs/train/approved/amd-sev-libvirt-support.html
1283 #
1284 # So if the image explicitly requests a machine type which is not
1285 # in the q35 family, raise an exception.
1286 #
1287 # This check can be triggered both at API-level, at which point we
1288 # can't check here what value of CONF.libvirt.hw_machine_type may
1289 # have been configured on the compute node, and by the libvirt
1290 # driver, in which case the driver can check that config option
1291 # and will pass the machine_type parameter.
1292 mach_type = machine_type or image_meta.properties.get('hw_machine_type')
1294 # If hw_machine_type is not specified on the image and is not
1295 # configured correctly on SEV compute nodes, then a separate check
1296 # in the driver will catch that and potentially retry on other
1297 # compute nodes.
1298 if mach_type is None:
1299 return
1301 # image_meta.name is not set if image object represents root Cinder volume.
1302 image_name = (image_meta.name if 'name' in image_meta else None)
1303 # image_meta.id is not set when booting from volume.
1304 image_id = (image_meta.id if 'id' in image_meta else '<no-id>')
1305 # Could be something like pc-q35-2.11 if a specific version of the
1306 # machine type is required, so do substring matching.
1307 if 'q35' not in mach_type:
1308 raise exception.InvalidMachineType(
1309 mtype=mach_type,
1310 image_id=image_id, image_name=image_name,
1311 reason=_("q35 type is required for SEV to work"))
1314def _get_numa_pagesize_constraint(
1315 flavor: 'objects.Flavor',
1316 image_meta: 'objects.ImageMeta',
1317) -> ty.Optional[int]:
1318 """Return the requested memory page size
1320 :param flavor: a Flavor object to read extra specs from
1321 :param image_meta: nova.objects.ImageMeta object instance
1323 :raises: MemoryPageSizeInvalid if flavor extra spec or image
1324 metadata provides an invalid hugepage value
1325 :raises: MemoryPageSizeForbidden if flavor extra spec request
1326 conflicts with image metadata request
1327 :returns: a page size requested or MEMPAGES_*
1328 """
1330 def check_and_return_pages_size(request):
1331 if request == "any":
1332 return MEMPAGES_ANY
1333 elif request == "large":
1334 return MEMPAGES_LARGE
1335 elif request == "small":
1336 return MEMPAGES_SMALL
1337 elif request.isdigit():
1338 return int(request)
1340 try:
1341 return strutils.string_to_bytes(
1342 request, return_int=True) / units.Ki
1343 except ValueError:
1344 raise exception.MemoryPageSizeInvalid(pagesize=request) from None
1346 flavor_request, image_request = _get_flavor_image_meta(
1347 'mem_page_size', flavor, image_meta)
1349 if not flavor_request and image_request:
1350 raise exception.MemoryPageSizeForbidden(
1351 pagesize=image_request,
1352 against="<empty>")
1354 if not flavor_request:
1355 # Nothing was specified for hugepages,
1356 # let's the default process running.
1357 return None
1359 pagesize = check_and_return_pages_size(flavor_request)
1360 if image_request and (pagesize in (MEMPAGES_ANY, MEMPAGES_LARGE)):
1361 return check_and_return_pages_size(image_request)
1362 elif image_request:
1363 raise exception.MemoryPageSizeForbidden(
1364 pagesize=image_request,
1365 against=flavor_request)
1367 return pagesize
1370def _get_constraint_mappings_from_flavor(flavor, key, func):
1371 hw_numa_map = []
1372 extra_specs = flavor.get('extra_specs', {})
1373 for cellid in range(objects.ImageMetaProps.NUMA_NODES_MAX): 1373 ↛ 1379line 1373 didn't jump to line 1379 because the loop on line 1373 didn't complete
1374 prop = '%s.%d' % (key, cellid)
1375 if prop not in extra_specs:
1376 break
1377 hw_numa_map.append(func(extra_specs[prop]))
1379 return hw_numa_map or None
1382def get_locked_memory_constraint(
1383 flavor: 'objects.Flavor',
1384 image_meta: 'objects.ImageMeta',
1385) -> ty.Optional[bool]:
1386 """Validate and return the requested locked memory.
1388 :param flavor: ``nova.objects.Flavor`` instance
1389 :param image_meta: ``nova.objects.ImageMeta`` instance
1390 :raises: exception.LockMemoryForbidden if mem_page_size is not set
1391 while provide locked_memory value in image or flavor.
1392 :returns: The locked memory flag requested.
1393 """
1394 mem_page_size_flavor, mem_page_size_image = _get_flavor_image_meta(
1395 'mem_page_size', flavor, image_meta)
1397 locked_memory_flavor, locked_memory_image = _get_flavor_image_meta(
1398 'locked_memory', flavor, image_meta)
1400 if locked_memory_flavor is not None:
1401 # locked_memory_image is boolean type already
1402 locked_memory_flavor = strutils.bool_from_string(locked_memory_flavor)
1404 if locked_memory_image is not None and (
1405 locked_memory_flavor != locked_memory_image
1406 ):
1407 # We don't allow provide different value to flavor and image
1408 raise exception.FlavorImageLockedMemoryConflict(
1409 image=locked_memory_image, flavor=locked_memory_flavor)
1411 locked_memory = locked_memory_flavor
1413 else:
1414 locked_memory = locked_memory_image
1416 if locked_memory and not (
1417 mem_page_size_flavor or mem_page_size_image
1418 ):
1419 raise exception.LockMemoryForbidden()
1421 return locked_memory
1424def _get_numa_cpu_constraint(
1425 flavor: 'objects.Flavor',
1426 image_meta: 'objects.ImageMeta',
1427) -> ty.Optional[ty.List[ty.Set[int]]]:
1428 """Validate and return the requested guest NUMA-guest CPU mapping.
1430 Extract the user-provided mapping of guest CPUs to guest NUMA nodes. For
1431 example, the flavor extra spec ``hw:numa_cpus.0=0-1,4`` will map guest
1432 cores ``0``, ``1``, ``4`` to guest NUMA node ``0``.
1434 :param flavor: ``nova.objects.Flavor`` instance
1435 :param image_meta: ``nova.objects.ImageMeta`` instance
1436 :raises: exception.ImageNUMATopologyForbidden if both image metadata and
1437 flavor extra specs are defined.
1438 :return: An ordered list of sets of CPU indexes to assign to each guest
1439 NUMA node if matching extra specs or image metadata properties found,
1440 else None.
1441 """
1442 flavor_cpu_list = _get_constraint_mappings_from_flavor(
1443 flavor, 'hw:numa_cpus', parse_cpu_spec)
1444 image_cpu_list = image_meta.properties.get('hw_numa_cpus', None)
1446 if flavor_cpu_list is None:
1447 return image_cpu_list
1449 if image_cpu_list is not None: 1449 ↛ 1450line 1449 didn't jump to line 1450 because the condition on line 1449 was never true
1450 raise exception.ImageNUMATopologyForbidden(
1451 name='hw_numa_cpus')
1453 return flavor_cpu_list
1456def _get_numa_mem_constraint(
1457 flavor: 'objects.Flavor',
1458 image_meta: 'objects.ImageMeta',
1459) -> ty.Optional[ty.List[int]]:
1460 """Validate and return the requested guest NUMA-guest memory mapping.
1462 Extract the user-provided mapping of guest memory to guest NUMA nodes. For
1463 example, the flavor extra spec ``hw:numa_mem.0=1`` will map 1 GB of guest
1464 memory to guest NUMA node ``0``.
1466 :param flavor: ``nova.objects.Flavor`` instance
1467 :param image_meta: ``nova.objects.ImageMeta`` instance
1468 :raises: exception.ImageNUMATopologyForbidden if both image metadata and
1469 flavor extra specs are defined
1470 :return: An ordered list of memory (in GB) to assign to each guest NUMA
1471 node if matching extra specs or image metadata properties found, else
1472 None.
1473 """
1474 flavor_mem_list = _get_constraint_mappings_from_flavor(
1475 flavor, 'hw:numa_mem', int)
1476 image_mem_list = image_meta.properties.get('hw_numa_mem', None)
1478 if flavor_mem_list is None:
1479 return image_mem_list
1481 if image_mem_list is not None: 1481 ↛ 1482line 1481 didn't jump to line 1482 because the condition on line 1481 was never true
1482 raise exception.ImageNUMATopologyForbidden(
1483 name='hw_numa_mem')
1485 return flavor_mem_list
1488def _get_numa_node_count_constraint(
1489 flavor: 'objects.Flavor',
1490 image_meta: 'objects.ImageMeta',
1491) -> ty.Optional[int]:
1492 """Validate and return the requested NUMA nodes.
1494 :param flavor: ``nova.objects.Flavor`` instance
1495 :param image_meta: ``nova.objects.ImageMeta`` instance
1496 :raises: exception.ImageNUMATopologyForbidden if both image metadata and
1497 flavor extra specs are defined
1498 :raises: exception.InvalidNUMANodesNumber if the number of NUMA
1499 nodes is less than 1 or not an integer
1500 :returns: The number of NUMA nodes requested in either the flavor or image,
1501 else None.
1502 """
1503 flavor_nodes, image_nodes = _get_flavor_image_meta(
1504 'numa_nodes', flavor, image_meta)
1505 if flavor_nodes and image_nodes:
1506 raise exception.ImageNUMATopologyForbidden(name='hw_numa_nodes')
1508 nodes = flavor_nodes or image_nodes
1509 if nodes is not None and (not strutils.is_int_like(nodes) or
1510 int(nodes) < 1):
1511 raise exception.InvalidNUMANodesNumber(nodes=nodes)
1513 return int(nodes) if nodes else nodes
1516# NOTE(stephenfin): This must be public as it's used elsewhere
1517def get_cpu_policy_constraint(
1518 flavor: 'objects.Flavor',
1519 image_meta: 'objects.ImageMeta',
1520) -> ty.Optional[str]:
1521 """Validate and return the requested CPU policy.
1523 :param flavor: ``nova.objects.Flavor`` instance
1524 :param image_meta: ``nova.objects.ImageMeta`` instance
1525 :raises: exception.ImageCPUPinningForbidden if policy is defined on both
1526 image and flavor and these policies conflict.
1527 :raises: exception.InvalidCPUAllocationPolicy if policy is defined with
1528 invalid value in image or flavor.
1529 :returns: The CPU policy requested.
1530 """
1531 flavor_policy, image_policy = _get_flavor_image_meta(
1532 'cpu_policy', flavor, image_meta)
1534 if flavor_policy and (flavor_policy not in fields.CPUAllocationPolicy.ALL):
1535 raise exception.InvalidCPUAllocationPolicy(
1536 source='flavor extra specs',
1537 requested=flavor_policy,
1538 available=str(fields.CPUAllocationPolicy.ALL))
1540 if image_policy and (image_policy not in fields.CPUAllocationPolicy.ALL): 1540 ↛ 1541line 1540 didn't jump to line 1541 because the condition on line 1540 was never true
1541 raise exception.InvalidCPUAllocationPolicy(
1542 source='image properties',
1543 requested=image_policy,
1544 available=str(fields.CPUAllocationPolicy.ALL))
1546 if flavor_policy == fields.CPUAllocationPolicy.DEDICATED:
1547 cpu_policy = flavor_policy
1548 elif flavor_policy == fields.CPUAllocationPolicy.MIXED:
1549 if image_policy == fields.CPUAllocationPolicy.DEDICATED:
1550 raise exception.ImageCPUPinningForbidden()
1551 cpu_policy = flavor_policy
1552 elif flavor_policy == fields.CPUAllocationPolicy.SHARED:
1553 if image_policy in (
1554 fields.CPUAllocationPolicy.MIXED,
1555 fields.CPUAllocationPolicy.DEDICATED,
1556 ):
1557 raise exception.ImageCPUPinningForbidden()
1558 cpu_policy = flavor_policy
1559 elif image_policy in fields.CPUAllocationPolicy.ALL:
1560 cpu_policy = image_policy
1561 else:
1562 cpu_policy = None
1564 return cpu_policy
1567# NOTE(stephenfin): This must be public as it's used elsewhere
1568def get_cpu_thread_policy_constraint(
1569 flavor: 'objects.Flavor',
1570 image_meta: 'objects.ImageMeta',
1571) -> ty.Optional[str]:
1572 """Validate and return the requested CPU thread policy.
1574 :param flavor: ``nova.objects.Flavor`` instance
1575 :param image_meta: ``nova.objects.ImageMeta`` instance
1576 :raises: exception.ImageCPUThreadPolicyForbidden if policy is defined on
1577 both image and flavor and these policies conflict.
1578 :raises: exception.InvalidCPUThreadAllocationPolicy if policy is defined
1579 with invalid value in image or flavor.
1580 :returns: The CPU thread policy requested.
1581 """
1582 flavor_policy, image_policy = _get_flavor_image_meta(
1583 'cpu_thread_policy', flavor, image_meta)
1585 if flavor_policy and (
1586 flavor_policy not in fields.CPUThreadAllocationPolicy.ALL):
1587 raise exception.InvalidCPUThreadAllocationPolicy(
1588 source='flavor extra specs',
1589 requested=flavor_policy,
1590 available=str(fields.CPUThreadAllocationPolicy.ALL))
1592 if image_policy and ( 1592 ↛ 1594line 1592 didn't jump to line 1594 because the condition on line 1592 was never true
1593 image_policy not in fields.CPUThreadAllocationPolicy.ALL):
1594 raise exception.InvalidCPUThreadAllocationPolicy(
1595 source='image properties',
1596 requested=image_policy,
1597 available=str(fields.CPUThreadAllocationPolicy.ALL))
1599 if flavor_policy in [None, fields.CPUThreadAllocationPolicy.PREFER]:
1600 policy = flavor_policy or image_policy
1601 elif image_policy and image_policy != flavor_policy:
1602 raise exception.ImageCPUThreadPolicyForbidden()
1603 else:
1604 policy = flavor_policy
1606 return policy
1609def _get_numa_topology_auto(
1610 nodes: int,
1611 flavor: 'objects.Flavor',
1612 vcpus: ty.Set[int],
1613 pcpus: ty.Set[int],
1614) -> 'objects.InstanceNUMATopology':
1615 """Generate a NUMA topology automatically based on CPUs and memory.
1617 This is "automatic" because there's no user-provided per-node configuration
1618 here - it's all auto-generated based on the number of nodes.
1620 :param nodes: The number of nodes required in the generated topology.
1621 :param flavor: The flavor used for the instance, from which to extract the
1622 CPU and memory count.
1623 :param vcpus: A set of IDs for CPUs that should be shared.
1624 :param pcpus: A set of IDs for CPUs that should be dedicated.
1625 """
1626 if (flavor.vcpus % nodes) > 0 or (flavor.memory_mb % nodes) > 0:
1627 raise exception.ImageNUMATopologyAsymmetric()
1629 cells = []
1630 for node in range(nodes):
1631 ncpus = int(flavor.vcpus / nodes)
1632 mem = int(flavor.memory_mb / nodes)
1633 start = node * ncpus
1634 cpus = set(range(start, start + ncpus))
1636 cells.append(objects.InstanceNUMACell(
1637 id=node, cpuset=cpus & vcpus, pcpuset=cpus & pcpus, memory=mem))
1639 return objects.InstanceNUMATopology(cells=cells)
1642def _get_numa_topology_manual(
1643 nodes: int,
1644 flavor: 'objects.Flavor',
1645 vcpus: ty.Set[int],
1646 pcpus: ty.Set[int],
1647 cpu_list: ty.List[ty.Set[int]],
1648 mem_list: ty.List[int],
1649) -> 'objects.InstanceNUMATopology':
1650 """Generate a NUMA topology based on user-provided NUMA topology hints.
1652 :param nodes: The number of nodes required in the generated topology.
1653 :param flavor: The flavor used for the instance, from which to extract the
1654 CPU and memory count.
1655 :param vcpus: A set of IDs for CPUs that should be shared.
1656 :param pcpus: A set of IDs for CPUs that should be dedicated.
1657 :param cpu_list: A list of sets of ints; each set in the list corresponds
1658 to the set of guest cores to assign to NUMA node $index.
1659 :param mem_list: A list of ints; each int corresponds to the amount of
1660 memory to assign to NUMA node $index.
1661 :returns: The generated instance NUMA topology.
1662 """
1663 cells = []
1664 totalmem = 0
1666 availcpus = set(range(flavor.vcpus))
1668 for node in range(nodes):
1669 mem = mem_list[node]
1670 cpus = cpu_list[node]
1672 for cpu in cpus:
1673 if cpu > (flavor.vcpus - 1):
1674 raise exception.ImageNUMATopologyCPUOutOfRange(
1675 cpunum=cpu, cpumax=(flavor.vcpus - 1))
1677 if cpu not in availcpus:
1678 raise exception.ImageNUMATopologyCPUDuplicates(
1679 cpunum=cpu)
1681 availcpus.remove(cpu)
1683 cells.append(objects.InstanceNUMACell(
1684 id=node, cpuset=cpus & vcpus, pcpuset=cpus & pcpus, memory=mem))
1685 totalmem = totalmem + mem
1687 if availcpus:
1688 raise exception.ImageNUMATopologyCPUsUnassigned(
1689 cpuset=str(availcpus))
1691 if totalmem != flavor.memory_mb:
1692 raise exception.ImageNUMATopologyMemoryOutOfRange(
1693 memsize=totalmem,
1694 memtotal=flavor.memory_mb)
1696 return objects.InstanceNUMATopology(cells=cells)
1699def is_realtime_enabled(flavor):
1700 flavor_rt = flavor.get('extra_specs', {}).get("hw:cpu_realtime")
1701 return strutils.bool_from_string(flavor_rt)
1704def _get_vcpu_pcpu_resources(
1705 flavor: 'objects.Flavor',
1706) -> ty.Tuple[int, int]:
1707 requested_vcpu = 0
1708 requested_pcpu = 0
1710 for key, val in flavor.get('extra_specs', {}).items():
1711 if re.match('resources([1-9][0-9]*)?:%s' % orc.VCPU, key):
1712 try:
1713 requested_vcpu += int(val)
1714 except ValueError:
1715 # this is handled elsewhere
1716 pass
1717 if re.match('resources([1-9][0-9]*)?:%s' % orc.PCPU, key):
1718 try:
1719 requested_pcpu += int(val)
1720 except ValueError:
1721 # this is handled elsewhere
1722 pass
1724 return requested_vcpu, requested_pcpu
1727def _get_hyperthreading_trait(
1728 flavor: 'objects.Flavor',
1729 image_meta: 'objects.ImageMeta',
1730) -> ty.Optional[str]:
1731 for key, val in flavor.get('extra_specs', {}).items():
1732 if re.match('trait([1-9][0-9]*)?:%s' % os_traits.HW_CPU_HYPERTHREADING,
1733 key):
1734 return val
1736 if os_traits.HW_CPU_HYPERTHREADING in image_meta.properties.get(
1737 'traits_required', []):
1738 return 'required'
1740 return None
1743# NOTE(stephenfin): This must be public as it's used elsewhere
1744def get_dedicated_cpu_constraint(
1745 flavor: 'objects.Flavor',
1746) -> ty.Optional[ty.Set[int]]:
1747 """Validate and return the requested dedicated CPU mask.
1749 :param flavor: ``nova.objects.Flavor`` instance
1750 :returns: The dedicated CPUs requested, else None.
1751 """
1752 mask = flavor.get('extra_specs', {}).get('hw:cpu_dedicated_mask')
1753 if not mask:
1754 return None
1756 if mask.strip().startswith('^'):
1757 pcpus = parse_cpu_spec("0-%d,%s" % (flavor.vcpus - 1, mask))
1758 else:
1759 pcpus = parse_cpu_spec("%s" % (mask))
1761 cpus = set(range(flavor.vcpus))
1762 vcpus = cpus - pcpus
1763 if not pcpus or not vcpus:
1764 raise exception.InvalidMixedInstanceDedicatedMask()
1766 if not pcpus.issubset(cpus): 1766 ↛ 1767line 1766 didn't jump to line 1767 because the condition on line 1766 was never true
1767 msg = _('Mixed instance dedicated vCPU(s) mask is not a subset of '
1768 'vCPUs in the flavor. See "hw:cpu_dedicated_mask"')
1769 raise exception.InvalidMixedInstanceDedicatedMask(msg)
1771 return pcpus
1774# NOTE(stephenfin): This must be public as it's used elsewhere
1775def get_realtime_cpu_constraint(
1776 flavor: 'objects.Flavor',
1777 image_meta: 'objects.ImageMeta',
1778) -> ty.Optional[ty.Set[int]]:
1779 """Validate and return the requested realtime CPU mask.
1781 :param flavor: ``nova.objects.Flavor`` instance
1782 :param image_meta: ``nova.objects.ImageMeta`` instance
1783 :returns: The realtime CPU set requested, else None.
1784 """
1785 if not is_realtime_enabled(flavor):
1786 return None
1788 flavor_mask, image_mask = _get_flavor_image_meta(
1789 'cpu_realtime_mask', flavor, image_meta)
1791 # Image masks are used ahead of flavor masks as they will have more
1792 # specific requirements
1793 mask = image_mask or flavor_mask
1795 vcpus_set = set(range(flavor.vcpus))
1796 if mask:
1797 if mask.strip().startswith('^'):
1798 vcpus_rt = parse_cpu_spec("0-%d,%s" % (flavor.vcpus - 1, mask))
1799 else:
1800 vcpus_rt = parse_cpu_spec("%s" % (mask))
1801 else:
1802 vcpus_rt = set(range(flavor.vcpus))
1804 if not vcpus_rt:
1805 raise exception.RealtimeMaskNotFoundOrInvalid()
1807 # TODO(stephenfin): Do this check in numa_get_constraints instead
1808 emu_policy = get_emulator_thread_policy_constraint(flavor)
1809 if vcpus_set == vcpus_rt and not emu_policy:
1810 raise exception.RealtimeMaskNotFoundOrInvalid()
1812 if not vcpus_rt.issubset(vcpus_set):
1813 msg = _("Realtime policy vCPU(s) mask is configured with RT vCPUs "
1814 "that are not a subset of the vCPUs in the flavor. See "
1815 "hw:cpu_realtime_mask or hw_cpu_realtime_mask")
1816 raise exception.RealtimeMaskNotFoundOrInvalid(msg)
1818 return vcpus_rt
1821# NOTE(stephenfin): This must be public as it's used elsewhere
1822def get_emulator_thread_policy_constraint(
1823 flavor: 'objects.Flavor',
1824) -> ty.Optional[str]:
1825 """Validate and return the requested emulator threads policy.
1827 :param flavor: ``nova.objects.Flavor`` instance
1828 :raises: exception.InvalidEmulatorThreadsPolicy if mask was not found or
1829 is invalid.
1830 :returns: The emulator thread policy requested, else None.
1831 """
1832 emu_threads_policy = flavor.get('extra_specs', {}).get(
1833 'hw:emulator_threads_policy')
1835 if not emu_threads_policy:
1836 return None
1838 if emu_threads_policy not in fields.CPUEmulatorThreadsPolicy.ALL:
1839 raise exception.InvalidEmulatorThreadsPolicy(
1840 requested=emu_threads_policy,
1841 available=str(fields.CPUEmulatorThreadsPolicy.ALL))
1843 return emu_threads_policy
1846def get_pci_numa_policy_constraint(
1847 flavor: 'objects.Flavor',
1848 image_meta: 'objects.ImageMeta',
1849) -> str:
1850 """Validate and return the requested PCI NUMA affinity policy.
1852 :param flavor: a flavor object to read extra specs from
1853 :param image_meta: nova.objects.ImageMeta object instance
1854 :raises: nova.exception.ImagePCINUMAPolicyForbidden
1855 :raises: nova.exception.InvalidPCINUMAAffinity
1856 """
1857 flavor_policy, image_policy = _get_flavor_image_meta(
1858 'pci_numa_affinity_policy', flavor, image_meta)
1860 if flavor_policy and image_policy and flavor_policy != image_policy:
1861 raise exception.ImagePCINUMAPolicyForbidden()
1863 policy = flavor_policy or image_policy
1865 if policy and policy not in fields.PCINUMAAffinityPolicy.ALL:
1866 raise exception.InvalidPCINUMAAffinity(policy=policy)
1868 return policy
1871def get_pmu_constraint(
1872 flavor: 'objects.Flavor',
1873 image_meta: 'objects.ImageMeta',
1874) -> ty.Optional[bool]:
1875 """Validate and return the requested vPMU configuration.
1877 This one's a little different since we don't return False in the default
1878 case: the PMU should only be configured if explicit configuration is
1879 provided, otherwise we leave it to the hypervisor.
1881 :param flavor: ``nova.objects.Flavor`` instance
1882 :param image_meta: ``nova.objects.ImageMeta`` instance
1883 :raises: nova.exception.FlavorImageConflict if a value is specified in both
1884 the flavor and the image, but the values do not match
1885 :raises: nova.exception.Invalid if a value or combination of values is
1886 invalid
1887 :returns: True if the virtual Performance Monitoring Unit must be enabled,
1888 False if it should be disabled, or None if unconfigured.
1889 """
1890 flavor_value_str, image_value = _get_flavor_image_meta(
1891 'pmu', flavor, image_meta)
1893 flavor_value = None
1894 if flavor_value_str is not None:
1895 flavor_value = strutils.bool_from_string(flavor_value_str)
1897 if (
1898 image_value is not None and
1899 flavor_value is not None and
1900 image_value != flavor_value
1901 ):
1902 msg = _(
1903 "Flavor %(flavor_name)s has %(prefix)s:%(key)s extra spec "
1904 "explicitly set to %(flavor_val)s, conflicting with image "
1905 "%(image_name)s which has %(prefix)s_%(key)s explicitly set to "
1906 "%(image_val)s."
1907 )
1908 raise exception.FlavorImageConflict(
1909 msg % {
1910 'prefix': 'hw',
1911 'key': 'pmu',
1912 'flavor_name': flavor.name,
1913 'flavor_val': flavor_value,
1914 'image_name': image_meta.name,
1915 'image_val': image_value,
1916 },
1917 )
1919 return flavor_value if flavor_value is not None else image_value
1922def get_vif_multiqueue_constraint(
1923 flavor: 'objects.Flavor',
1924 image_meta: 'objects.ImageMeta',
1925) -> bool:
1926 """Validate and return the requested VIF multiqueue configuration.
1928 :param flavor: ``nova.objects.Flavor`` instance
1929 :param image_meta: ``nova.objects.ImageMeta`` instance
1930 :raises: nova.exception.FlavorImageConflict if a value is specified in both
1931 the flavor and the image, but the values do not match
1932 :raises: nova.exception.Invalid if a value or combination of values is
1933 invalid
1934 :returns: True if the multiqueue must be enabled, else False.
1935 """
1936 if flavor.vcpus < 2:
1937 return False
1939 flavor_value_str, image_value = _get_flavor_image_meta(
1940 'vif_multiqueue_enabled', flavor, image_meta)
1942 flavor_value = None
1943 if flavor_value_str is not None:
1944 flavor_value = strutils.bool_from_string(flavor_value_str)
1946 if (
1947 image_value is not None and
1948 flavor_value is not None and
1949 image_value != flavor_value
1950 ):
1951 msg = _(
1952 "Flavor %(flavor_name)s has %(prefix)s:%(key)s extra spec "
1953 "explicitly set to %(flavor_val)s, conflicting with image "
1954 "%(image_name)s which has %(prefix)s_%(key)s explicitly set to "
1955 "%(image_val)s."
1956 )
1957 raise exception.FlavorImageConflict(
1958 msg % {
1959 'prefix': 'hw',
1960 'key': 'vif_multiqueue_enabled',
1961 'flavor_name': flavor.name,
1962 'flavor_val': flavor_value,
1963 'image_name': image_meta.name,
1964 'image_val': image_value,
1965 }
1966 )
1968 return flavor_value or image_value or False
1971def get_packed_virtqueue_constraint(
1972 flavor,
1973 image_meta,
1974) -> bool:
1975 """Validate and return the requested Packed virtqueue configuration.
1977 :param flavor: ``nova.objects.Flavor`` or dict instance
1978 :param image_meta: ``nova.objects.ImageMeta`` or dict instance
1979 :raises: nova.exception.FlavorImageConflict if a value is specified in both
1980 the flavor and the image, but the values do not match
1981 :returns: True if the Packed virtqueue must be enabled, else False.
1982 """
1983 key_value = 'virtio_packed_ring'
1985 if type(image_meta) is dict:
1986 flavor_key = ':'.join(['hw', key_value])
1987 image_key = '_'.join(['hw', key_value])
1988 flavor_value_str = flavor.get('extra_specs', {}).get(flavor_key, None)
1989 image_value_str = image_meta.get('properties', {}).get(image_key, None)
1990 else:
1991 flavor_value_str, image_value_str = _get_flavor_image_meta(
1992 key_value, flavor, image_meta)
1994 flavor_value = None
1995 if flavor_value_str is not None:
1996 flavor_value = strutils.bool_from_string(flavor_value_str)
1998 image_value = None
1999 if image_value_str is not None:
2000 image_value = strutils.bool_from_string(image_value_str)
2002 if (
2003 image_value is not None and
2004 flavor_value is not None and
2005 image_value != flavor_value
2006 ):
2007 msg = _(
2008 "Flavor has %(prefix)s:%(key)s extra spec "
2009 "explicitly set to %(flavor_val)s, conflicting with image "
2010 "which has %(prefix)s_%(key)s explicitly set to "
2011 "%(image_val)s."
2012 )
2013 raise exception.FlavorImageConflict(
2014 msg % {
2015 'prefix': 'hw',
2016 'key': key_value,
2017 'flavor_val': flavor_value,
2018 'image_val': image_value,
2019 }
2020 )
2022 return flavor_value or image_value or False
2025def get_vtpm_constraint(
2026 flavor: 'objects.Flavor',
2027 image_meta: 'objects.ImageMeta',
2028) -> ty.Optional[VTPMConfig]:
2029 """Validate and return the requested vTPM configuration.
2031 :param flavor: ``nova.objects.Flavor`` instance
2032 :param image_meta: ``nova.objects.ImageMeta`` instance
2033 :raises: nova.exception.FlavorImageConflict if a value is specified in both
2034 the flavor and the image, but the values do not match
2035 :raises: nova.exception.Invalid if a value or combination of values is
2036 invalid
2037 :returns: A named tuple containing the vTPM version and model, else None.
2038 """
2039 version = _get_unique_flavor_image_meta('tpm_version', flavor, image_meta)
2040 if version is None:
2041 return None
2043 if version not in fields.TPMVersion.ALL:
2044 raise exception.Invalid(
2045 "Invalid TPM version %(version)r. Allowed values: %(valid)s." %
2046 {'version': version, 'valid': ', '.join(fields.TPMVersion.ALL)}
2047 )
2049 model = _get_unique_flavor_image_meta('tpm_model', flavor, image_meta)
2050 if model is None:
2051 # model defaults to TIS
2052 model = fields.TPMModel.TIS
2053 elif model not in fields.TPMModel.ALL:
2054 raise exception.Invalid(
2055 "Invalid TPM model %(model)r. Allowed values: %(valid)s." %
2056 {'model': model, 'valid': ', '.join(fields.TPMModel.ALL)}
2057 )
2058 elif model == fields.TPMModel.CRB and version != fields.TPMVersion.v2_0:
2059 raise exception.Invalid(
2060 "TPM model CRB is only valid with TPM version 2.0."
2061 )
2063 return VTPMConfig(version, model)
2066def get_secure_boot_constraint(
2067 flavor: 'objects.Flavor',
2068 image_meta: 'objects.ImageMeta',
2069) -> ty.Optional[str]:
2070 """Validate and return the requested secure boot policy.
2072 :param flavor: ``nova.objects.Flavor`` instance
2073 :param image_meta: ``nova.objects.ImageMeta`` instance
2074 :raises: nova.exception.Invalid if a value or combination of values is
2075 invalid
2076 :returns: The secure boot configuration requested, if any.
2077 """
2078 policy = _get_unique_flavor_image_meta(
2079 'secure_boot', flavor, image_meta, prefix='os',
2080 )
2081 if policy is None:
2082 return None
2084 if policy not in fields.SecureBoot.ALL:
2085 msg = _(
2086 'Invalid secure boot policy %(policy)r. Allowed values: %(valid)s.'
2087 )
2088 raise exception.Invalid(msg % {
2089 'policy': policy,
2090 'valid': ', '.join(fields.SecureBoot.ALL)
2091 })
2093 return policy
2096def get_stateless_firmware_constraint(
2097 image_meta: 'objects.ImageMeta',
2098) -> bool:
2099 """Validate and return the requested statless firmware policy.
2101 :param flavor: ``nova.objects.Flavor`` instance
2102 :param image_meta: ``nova.objects.ImageMeta`` instance
2103 :raises: nova.exception.Invalid if a value or combination of values is
2104 invalid
2105 """
2106 if not image_meta.properties.get('hw_firmware_stateless', False):
2107 return False
2109 if image_meta.properties.get('hw_firmware_type') != 'uefi':
2110 raise exception.Invalid(_(
2111 'Stateless firmware is supported only when UEFI firmware type is '
2112 'used.'
2113 ))
2114 return True
2117def numa_get_constraints(flavor, image_meta):
2118 """Return topology related to input request.
2120 :param flavor: a flavor object to read extra specs from
2121 :param image_meta: nova.objects.ImageMeta object instance
2123 :raises: exception.InvalidNUMANodesNumber if the number of NUMA
2124 nodes is less than 1 or not an integer
2125 :raises: exception.ImageNUMATopologyForbidden if an attempt is made
2126 to override flavor settings with image properties
2127 :raises: exception.MemoryPageSizeInvalid if flavor extra spec or
2128 image metadata provides an invalid hugepage value
2129 :raises: exception.MemoryPageSizeForbidden if flavor extra spec
2130 request conflicts with image metadata request
2131 :raises: exception.ImageNUMATopologyIncomplete if the image
2132 properties are not correctly specified
2133 :raises: exception.ImageNUMATopologyAsymmetric if the number of
2134 NUMA nodes is not a factor of the requested total CPUs or
2135 memory
2136 :raises: exception.ImageNUMATopologyCPUOutOfRange if an instance
2137 CPU given in a NUMA mapping is not valid
2138 :raises: exception.ImageNUMATopologyCPUDuplicates if an instance
2139 CPU is specified in CPU mappings for two NUMA nodes
2140 :raises: exception.ImageNUMATopologyCPUsUnassigned if an instance
2141 CPU given in a NUMA mapping is not assigned to any NUMA node
2142 :raises: exception.ImageNUMATopologyMemoryOutOfRange if sum of memory from
2143 each NUMA node is not equal with total requested memory
2144 :raises: exception.ImageCPUPinningForbidden if a CPU policy
2145 specified in a flavor conflicts with one defined in image
2146 metadata
2147 :raises: exception.RealtimeConfigurationInvalid if realtime is
2148 requested but dedicated CPU policy is not also requested
2149 :raises: exception.RealtimeMaskNotFoundOrInvalid if realtime is
2150 requested but no mask provided
2151 :raises: exception.CPUThreadPolicyConfigurationInvalid if a CPU thread
2152 policy conflicts with CPU allocation policy
2153 :raises: exception.ImageCPUThreadPolicyForbidden if a CPU thread policy
2154 specified in a flavor conflicts with one defined in image metadata
2155 :raises: exception.BadRequirementEmulatorThreadsPolicy if CPU emulator
2156 threads policy conflicts with CPU allocation policy
2157 :raises: exception.InvalidCPUAllocationPolicy if policy is defined with
2158 invalid value in image or flavor.
2159 :raises: exception.InvalidCPUThreadAllocationPolicy if policy is defined
2160 with invalid value in image or flavor.
2161 :raises: exception.InvalidRequest if there is a conflict between explicitly
2162 and implicitly requested resources of hyperthreading traits
2163 :raises: exception.RequiredMixedInstancePolicy if dedicated CPU mask is
2164 provided in flavor while CPU policy is not 'mixed'.
2165 :raises: exception.RequiredMixedOrRealtimeCPUMask the mixed policy instance
2166 dedicated CPU mask can only be specified through either
2167 'hw:cpu_realtime_mask' or 'hw:cpu_dedicated_mask', not both.
2168 :raises: exception.InvalidMixedInstanceDedicatedMask if specify an invalid
2169 CPU mask for 'hw:cpu_dedicated_mask'.
2170 :returns: objects.InstanceNUMATopology, or None
2171 """
2173 cpu_policy = get_cpu_policy_constraint(flavor, image_meta)
2174 cpu_thread_policy = get_cpu_thread_policy_constraint(flavor, image_meta)
2175 realtime_cpus = get_realtime_cpu_constraint(flavor, image_meta)
2176 dedicated_cpus = get_dedicated_cpu_constraint(flavor)
2177 emu_threads_policy = get_emulator_thread_policy_constraint(flavor)
2179 # handle explicit VCPU/PCPU resource requests and the HW_CPU_HYPERTHREADING
2180 # trait
2182 requested_vcpus, requested_pcpus = _get_vcpu_pcpu_resources(flavor)
2184 if cpu_policy and (requested_vcpus or requested_pcpus):
2185 raise exception.InvalidRequest(
2186 "It is not possible to use the 'resources:VCPU' or "
2187 "'resources:PCPU' extra specs in combination with the "
2188 "'hw:cpu_policy' extra spec or 'hw_cpu_policy' image metadata "
2189 "property; use one or the other")
2191 if requested_vcpus and requested_pcpus:
2192 raise exception.InvalidRequest(
2193 "It is not possible to specify both 'resources:VCPU' and "
2194 "'resources:PCPU' extra specs; use one or the other")
2196 if requested_pcpus:
2197 if (emu_threads_policy == fields.CPUEmulatorThreadsPolicy.ISOLATE and
2198 flavor.vcpus + 1 != requested_pcpus):
2199 raise exception.InvalidRequest(
2200 "You have requested 'hw:emulator_threads_policy=isolate' but "
2201 "have not requested sufficient PCPUs to handle this policy; "
2202 "you must allocate exactly flavor.vcpus + 1 PCPUs.")
2204 if (emu_threads_policy != fields.CPUEmulatorThreadsPolicy.ISOLATE and
2205 flavor.vcpus != requested_pcpus):
2206 raise exception.InvalidRequest(
2207 "There is a mismatch between the number of PCPUs requested "
2208 "via 'resourcesNN:PCPU' and the flavor); you must allocate "
2209 "exactly flavor.vcpus PCPUs")
2211 cpu_policy = fields.CPUAllocationPolicy.DEDICATED
2213 if requested_vcpus: 2213 ↛ 2217line 2213 didn't jump to line 2217 because the condition on line 2213 was never true
2214 # NOTE(stephenfin): It would be nice if we could error out if
2215 # flavor.vcpus != resources:PCPU, but that would be a breaking change.
2216 # Better to wait until we remove flavor.vcpus or something
2217 cpu_policy = fields.CPUAllocationPolicy.SHARED
2219 hyperthreading_trait = _get_hyperthreading_trait(flavor, image_meta)
2221 if cpu_thread_policy and hyperthreading_trait:
2222 raise exception.InvalidRequest(
2223 "It is not possible to use the 'trait:HW_CPU_HYPERTHREADING' "
2224 "extra spec in combination with the 'hw:cpu_thread_policy' "
2225 "extra spec or 'hw_cpu_thread_policy' image metadata property; "
2226 "use one or the other")
2228 if hyperthreading_trait == 'forbidden':
2229 cpu_thread_policy = fields.CPUThreadAllocationPolicy.ISOLATE
2230 elif hyperthreading_trait == 'required': 2230 ↛ 2231line 2230 didn't jump to line 2231 because the condition on line 2230 was never true
2231 cpu_thread_policy = fields.CPUThreadAllocationPolicy.REQUIRE
2233 # sanity checks
2235 if cpu_policy in (fields.CPUAllocationPolicy.SHARED, None):
2236 if cpu_thread_policy:
2237 raise exception.CPUThreadPolicyConfigurationInvalid()
2239 if emu_threads_policy == fields.CPUEmulatorThreadsPolicy.ISOLATE:
2240 raise exception.BadRequirementEmulatorThreadsPolicy()
2242 # 'hw:cpu_dedicated_mask' should not be defined in a flavor with
2243 # 'shared' policy.
2244 if dedicated_cpus:
2245 raise exception.RequiredMixedInstancePolicy()
2247 if realtime_cpus:
2248 raise exception.RealtimeConfigurationInvalid()
2249 elif cpu_policy == fields.CPUAllocationPolicy.DEDICATED:
2250 # 'hw:cpu_dedicated_mask' should not be defined in a flavor with
2251 # 'dedicated' policy.
2252 if dedicated_cpus:
2253 raise exception.RequiredMixedInstancePolicy()
2254 else: # MIXED
2255 if realtime_cpus and dedicated_cpus:
2256 raise exception.RequiredMixedOrRealtimeCPUMask()
2258 if not (realtime_cpus or dedicated_cpus):
2259 raise exception.RequiredMixedOrRealtimeCPUMask()
2261 # NOTE(huaquiang): If using mixed with realtime, then cores listed in
2262 # the realtime mask are dedicated and everything else is shared.
2263 dedicated_cpus = dedicated_cpus or realtime_cpus
2265 nodes = _get_numa_node_count_constraint(flavor, image_meta)
2266 pagesize = _get_numa_pagesize_constraint(flavor, image_meta)
2267 vpmems = get_vpmems(flavor)
2269 get_locked_memory_constraint(flavor, image_meta)
2271 # If 'hw:cpu_dedicated_mask' is not found in flavor extra specs, the
2272 # 'dedicated_cpus' variable is None, while we hope it being an empty set.
2273 dedicated_cpus = dedicated_cpus or set()
2274 if cpu_policy == fields.CPUAllocationPolicy.DEDICATED:
2275 # But for an instance with 'dedicated' CPU allocation policy, all
2276 # CPUs are 'dedicated' CPUs, which is 1:1 pinned to a host CPU.
2277 dedicated_cpus = set(range(flavor.vcpus))
2279 # NOTE(stephenfin): There are currently four things that will configure a
2280 # NUMA topology for an instance:
2281 #
2282 # - The user explicitly requesting one
2283 # - The use of CPU pinning
2284 # - The use of hugepages
2285 # - The use of vPMEM
2286 if nodes or pagesize or vpmems or cpu_policy in (
2287 fields.CPUAllocationPolicy.DEDICATED,
2288 fields.CPUAllocationPolicy.MIXED,
2289 ):
2290 # NOTE(huaqiang): Here we build the instance dedicated CPU set and the
2291 # shared CPU set, through 'pcpus' and 'vcpus' respectively,
2292 # which will be used later to calculate the per-NUMA-cell CPU set.
2293 cpus = set(range(flavor.vcpus))
2294 pcpus = dedicated_cpus
2295 vcpus = cpus - pcpus
2297 nodes = nodes or 1
2298 cpu_list = _get_numa_cpu_constraint(flavor, image_meta)
2299 mem_list = _get_numa_mem_constraint(flavor, image_meta)
2301 if cpu_list is None and mem_list is None:
2302 numa_topology = _get_numa_topology_auto(
2303 nodes, flavor, vcpus, pcpus,
2304 )
2305 elif cpu_list is not None and mem_list is not None:
2306 # If any node has data set, all nodes must have data set
2307 if len(cpu_list) != nodes or len(mem_list) != nodes:
2308 raise exception.ImageNUMATopologyIncomplete()
2310 numa_topology = _get_numa_topology_manual(
2311 nodes, flavor, vcpus, pcpus, cpu_list, mem_list
2312 )
2313 else:
2314 # If one property list is specified both must be
2315 raise exception.ImageNUMATopologyIncomplete()
2317 # We currently support the same pagesize, CPU policy and CPU thread
2318 # policy for all cells, but these are still stored on a per-cell
2319 # basis :(
2320 for c in numa_topology.cells:
2321 setattr(c, 'pagesize', pagesize)
2322 setattr(c, 'cpu_policy', cpu_policy)
2323 setattr(c, 'cpu_thread_policy', cpu_thread_policy)
2325 # ...but emulator threads policy is not \o/
2326 numa_topology.emulator_threads_policy = emu_threads_policy
2327 else:
2328 numa_topology = None
2330 return numa_topology
2333def _numa_cells_support_network_metadata(
2334 host_topology: 'objects.NUMATopology',
2335 chosen_host_cells: ty.List['objects.NUMACell'],
2336 network_metadata: 'objects.NetworkMetadata',
2337) -> bool:
2338 """Determine whether the cells can accept the network requests.
2340 :param host_topology: The entire host topology, used to find non-chosen
2341 host cells.
2342 :param chosen_host_cells: List of NUMACells to extract possible network
2343 NUMA affinity from.
2344 :param network_metadata: The combined summary of physnets and tunneled
2345 networks required by this topology or None.
2347 :return: True if any NUMA affinity constraints for requested networks can
2348 be satisfied, else False
2349 """
2350 if not network_metadata: 2350 ↛ 2351line 2350 didn't jump to line 2351 because the condition on line 2350 was never true
2351 return True
2353 required_physnets: ty.Set[str] = set()
2354 if 'physnets' in network_metadata: 2354 ↛ 2358line 2354 didn't jump to line 2358 because the condition on line 2354 was always true
2355 # use set() to avoid modifying the original data structure
2356 required_physnets = set(network_metadata.physnets)
2358 required_tunnel: bool = False
2359 if 'tunneled' in network_metadata: 2359 ↛ 2362line 2359 didn't jump to line 2362 because the condition on line 2359 was always true
2360 required_tunnel = network_metadata.tunneled
2362 if required_physnets:
2363 removed_physnets = False
2364 # identify requested physnets that have an affinity to any of our
2365 # chosen host NUMA cells
2366 for host_cell in chosen_host_cells:
2367 if 'network_metadata' not in host_cell: 2367 ↛ 2368line 2367 didn't jump to line 2368 because the condition on line 2367 was never true
2368 continue
2370 # if one of these cells provides affinity for one or more physnets,
2371 # drop said physnet(s) from the list we're searching for
2372 required_physnets -= required_physnets.intersection(
2373 host_cell.network_metadata.physnets)
2374 removed_physnets = True
2376 if required_physnets and removed_physnets:
2377 LOG.debug('Not all requested physnets have affinity to one '
2378 'of the chosen host NUMA cells. Remaining physnets '
2379 'are: %(physnets)s.', {'physnets': required_physnets})
2381 # however, if we still require some level of NUMA affinity, we need
2382 # to make sure one of the other NUMA cells isn't providing that; note
2383 # that NUMA affinity might not be provided for all physnets so we are
2384 # in effect skipping these
2385 for host_cell in host_topology.cells:
2386 if 'network_metadata' not in host_cell: 2386 ↛ 2387line 2386 didn't jump to line 2387 because the condition on line 2386 was never true
2387 continue
2389 # if one of these cells provides affinity for one or more physnets,
2390 # we need to fail because we should be using that node and are not
2391 required_physnets_outside = required_physnets.intersection(
2392 host_cell.network_metadata.physnets)
2394 if required_physnets_outside:
2395 LOG.debug('One or more requested physnets require affinity to '
2396 'a NUMA cell outside of the chosen host cells. This '
2397 'host cell cannot satisfy network requests for '
2398 'these physnets: %(physnets)s',
2399 {'physnets': required_physnets_outside})
2400 return False
2402 if required_tunnel:
2403 # identify if tunneled networks have an affinity to any of our chosen
2404 # host NUMA cells
2405 for host_cell in chosen_host_cells:
2406 if 'network_metadata' not in host_cell: 2406 ↛ 2407line 2406 didn't jump to line 2407 because the condition on line 2406 was never true
2407 continue
2409 if host_cell.network_metadata.tunneled:
2410 return True
2412 LOG.debug('Tunneled networks have no affinity to any of the chosen '
2413 'host NUMA cells.')
2415 # however, if we still require some level of NUMA affinity, we need to
2416 # make sure one of the other NUMA cells isn't providing that; note
2417 # that, as with physnets, NUMA affinity might not be defined for
2418 # tunneled networks and we'll simply continue if this is the case
2419 for host_cell in host_topology.cells: 2419 ↛ 2432line 2419 didn't jump to line 2432 because the loop on line 2419 didn't complete
2420 if 'network_metadata' not in host_cell: 2420 ↛ 2421line 2420 didn't jump to line 2421 because the condition on line 2420 was never true
2421 continue
2423 if host_cell.network_metadata.tunneled:
2424 LOG.debug('The host declares NUMA affinity for tunneled '
2425 'networks. The current instance requests a '
2426 'tunneled network but this host cell is out of '
2427 'the set declared to be local to the tunnel '
2428 'network endpoint. As such, this host cell cannot '
2429 'support the requested tunneled network.')
2430 return False
2432 return True
2435def numa_fit_instance_to_host(
2436 host_topology: 'objects.NUMATopology',
2437 instance_topology: 'objects.InstanceNUMATopology',
2438 provider_mapping: ty.Optional[ty.Dict[str, ty.List[str]]],
2439 limits: ty.Optional['objects.NUMATopologyLimit'] = None,
2440 pci_requests: ty.Optional['objects.InstancePCIRequests'] = None,
2441 pci_stats: ty.Optional[stats.PciDeviceStats] = None,
2442):
2443 """Fit the instance topology onto the host topology.
2445 Given a host, instance topology, and (optional) limits, attempt to
2446 fit instance cells onto all permutations of host cells by calling
2447 the _fit_instance_cell method, and return a new InstanceNUMATopology
2448 with its cell ids set to host cell ids of the first successful
2449 permutation, or None.
2451 :param host_topology: objects.NUMATopology object to fit an
2452 instance on
2453 :param instance_topology: objects.InstanceNUMATopology to be fitted
2454 :param provider_mapping: A dict keyed by RequestGroup requester_id,
2455 to a list of resource provider UUIDs which provide resource
2456 for that RequestGroup. If it is None then it signals that the
2457 InstancePCIRequest objects already stores a mapping per request.
2458 I.e.: we are called _after_ the scheduler made allocations for this
2459 request in placement.
2460 :param limits: objects.NUMATopologyLimits that defines limits
2461 :param pci_requests: instance pci_requests
2462 :param pci_stats: pci_stats for the host
2464 :returns: objects.InstanceNUMATopology with its cell IDs set to host
2465 cell ids of the first successful permutation, or None
2466 """
2467 if not (host_topology and instance_topology):
2468 LOG.debug("Require both a host and instance NUMA topology to "
2469 "fit instance on host.")
2470 return
2471 elif len(host_topology) < len(instance_topology):
2472 LOG.debug("There are not enough NUMA nodes on the system to schedule "
2473 "the instance correctly. Required: %(required)s, actual: "
2474 "%(actual)s",
2475 {'required': len(instance_topology),
2476 'actual': len(host_topology)})
2477 return
2479 emulator_threads_policy = None
2480 if 'emulator_threads_policy' in instance_topology:
2481 emulator_threads_policy = instance_topology.emulator_threads_policy
2483 network_metadata = None
2484 if limits and 'network_metadata' in limits:
2485 network_metadata = limits.network_metadata
2487 host_cells = host_topology.cells
2489 # We need to perform all optimizations only if number of instance's
2490 # cells less than host's cells number. If it's equal, we'll use
2491 # all cells and no sorting of the cells list is needed.
2492 if len(host_topology) > len(instance_topology):
2493 pack = CONF.compute.packing_host_numa_cells_allocation_strategy
2494 # To balance NUMA cells usage based on several parameters
2495 # some sorts performed on host_cells list to move less used cells
2496 # to the beginning of the host_cells list (when pack variable is set to
2497 # 'False'). When pack is set to 'True', most used cells will be put at
2498 # the beginning of the host_cells list.
2500 # Fist sort is based on memory usage. cell.avail_memory returns free
2501 # memory for cell. Revert sorting to get cells with more free memory
2502 # first when pack is 'False'
2503 host_cells = sorted(
2504 host_cells,
2505 reverse=not pack,
2506 key=lambda cell: cell.avail_memory)
2508 # Next sort based on available dedicated or shared CPUs.
2509 # cpu_policy is set to the same value in all cells so we use
2510 # first cell in list (it exists if instance_topology defined)
2511 # to get cpu_policy
2512 if instance_topology.cells[0].cpu_policy in (
2513 None, fields.CPUAllocationPolicy.SHARED):
2514 # sort based on used CPUs
2515 host_cells = sorted(
2516 host_cells,
2517 reverse=pack,
2518 key=lambda cell: cell.cpu_usage)
2520 else:
2521 # sort based on presence of pinned CPUs
2522 host_cells = sorted(
2523 host_cells,
2524 reverse=not pack,
2525 key=lambda cell: len(cell.free_pcpus))
2527 # Perform sort only if pci_stats exists
2528 if pci_stats:
2529 # Create dict with numa cell id as key
2530 # and total number of free pci devices as value.
2531 total_pci_in_cell: ty.Dict[int, int] = {}
2532 for pool in pci_stats.pools:
2533 if pool['numa_node'] in list(total_pci_in_cell): 2533 ↛ 2534line 2533 didn't jump to line 2534 because the condition on line 2533 was never true
2534 total_pci_in_cell[pool['numa_node']] += pool['count']
2535 else:
2536 total_pci_in_cell[pool['numa_node']] = pool['count']
2537 # For backward compatibility we will always 'spread':
2538 # we always move host cells with PCI at the beginning if PCI
2539 # requested by VM and move host cells with PCI at the end of the
2540 # list if PCI isn't requested by VM
2541 if pci_requests:
2542 host_cells = sorted(
2543 host_cells,
2544 reverse=True,
2545 key=lambda cell: total_pci_in_cell.get(cell.id, 0))
2546 else:
2547 host_cells = sorted(
2548 host_cells,
2549 key=lambda cell: total_pci_in_cell.get(cell.id, 0))
2551 # a set of host_cell.id, instance_cell.id pairs where we already checked
2552 # that the instance cell does not fit
2553 not_fit_cache = set()
2554 # a set of host_cell.id, instance_cell.id pairs where we already checked
2555 # that the instance cell does fit
2556 fit_cache = set()
2557 for host_cell_perm in itertools.permutations(
2558 host_cells, len(instance_topology)):
2559 chosen_instance_cells: ty.List['objects.InstanceNUMACell'] = []
2560 chosen_host_cells: ty.List['objects.NUMACell'] = []
2561 for host_cell, instance_cell in zip(
2562 host_cell_perm, instance_topology.cells):
2564 cell_pair = (host_cell.id, instance_cell.id)
2566 # if we already checked this pair, and they did not fit then no
2567 # need to check again just move to the next permutation
2568 if cell_pair in not_fit_cache: 2568 ↛ 2569line 2568 didn't jump to line 2569 because the condition on line 2568 was never true
2569 break
2571 # if we already checked this pair, and they fit before that they
2572 # will fit now too. So no need to check again. Just continue with
2573 # the next cell pair in the permutation
2574 if cell_pair in fit_cache: 2574 ↛ 2575line 2574 didn't jump to line 2575 because the condition on line 2574 was never true
2575 chosen_host_cells.append(host_cell)
2576 # Normally this would have done by _numa_fit_instance_cell
2577 # but we optimized that out here based on the cache
2578 instance_cell.id = host_cell.id
2579 chosen_instance_cells.append(instance_cell)
2580 continue
2582 try:
2583 cpuset_reserved = 0
2584 if (instance_topology.emulator_threads_isolated and
2585 len(chosen_instance_cells) == 0):
2586 # For the case of isolate emulator threads, to
2587 # make predictable where that CPU overhead is
2588 # located we always configure it to be on host
2589 # NUMA node associated to the guest NUMA node
2590 # 0.
2591 cpuset_reserved = 1
2592 got_cell = _numa_fit_instance_cell(
2593 host_cell, instance_cell, limits, cpuset_reserved)
2594 except exception.MemoryPageSizeNotSupported:
2595 # This exception will been raised if instance cell's
2596 # custom pagesize is not supported with host cell in
2597 # _numa_cell_supports_pagesize_request function.
2599 # cache the result
2600 not_fit_cache.add(cell_pair)
2601 break
2602 if got_cell is None:
2603 # cache the result
2604 not_fit_cache.add(cell_pair)
2605 break
2606 chosen_host_cells.append(host_cell)
2607 chosen_instance_cells.append(got_cell)
2608 # cache the result
2609 fit_cache.add(cell_pair)
2611 if len(chosen_instance_cells) != len(host_cell_perm):
2612 continue
2614 if pci_requests and pci_stats and not pci_stats.support_requests(
2615 pci_requests, provider_mapping, chosen_instance_cells):
2616 continue
2618 if network_metadata and not _numa_cells_support_network_metadata(
2619 host_topology, chosen_host_cells, network_metadata):
2620 continue
2622 return objects.InstanceNUMATopology(
2623 cells=chosen_instance_cells,
2624 emulator_threads_policy=emulator_threads_policy)
2627def numa_get_reserved_huge_pages():
2628 """Returns reserved memory pages from host option.
2630 Based from the compute node option reserved_huge_pages, generate
2631 a well formatted list of dict which can be used to build a valid
2632 NUMATopology.
2634 :raises: exception.InvalidReservedMemoryPagesOption when
2635 reserved_huge_pages option is not correctly set.
2636 :returns: A dict of dicts keyed by NUMA node IDs; keys of child dict
2637 are pages size and values of the number reserved.
2638 """
2639 if not CONF.reserved_huge_pages:
2640 return {}
2642 try:
2643 bucket: ty.Dict[int, ty.Dict[int, int]] = collections.defaultdict(dict)
2644 for cfg in CONF.reserved_huge_pages:
2645 try:
2646 pagesize = int(cfg['size'])
2647 except ValueError:
2648 pagesize = strutils.string_to_bytes(
2649 cfg['size'], return_int=True) / units.Ki
2650 bucket[int(cfg['node'])][pagesize] = int(cfg['count'])
2651 except (ValueError, TypeError, KeyError):
2652 raise exception.InvalidReservedMemoryPagesOption(
2653 conf=CONF.reserved_huge_pages)
2655 return bucket
2658def _get_smallest_pagesize(host_cell):
2659 """Returns the smallest available page size based on hostcell"""
2660 avail_pagesize = [page.size_kb for page in host_cell.mempages]
2661 avail_pagesize.sort()
2662 return avail_pagesize[0]
2665def _numa_pagesize_usage_from_cell(host_cell, instance_cell, sign):
2666 if 'pagesize' in instance_cell and instance_cell.pagesize:
2667 pagesize = instance_cell.pagesize
2668 else:
2669 pagesize = _get_smallest_pagesize(host_cell)
2671 topo = []
2672 for pages in host_cell.mempages:
2673 if pages.size_kb == pagesize:
2674 topo.append(objects.NUMAPagesTopology(
2675 size_kb=pages.size_kb,
2676 total=pages.total,
2677 used=max(0, pages.used +
2678 instance_cell.memory * units.Ki /
2679 pages.size_kb * sign),
2680 reserved=pages.reserved if 'reserved' in pages else 0))
2681 else:
2682 topo.append(pages)
2683 return topo
2686def numa_usage_from_instance_numa(host_topology, instance_topology,
2687 free=False):
2688 """Update the host topology usage.
2690 Update the host NUMA topology based on usage by the provided instance NUMA
2691 topology.
2693 :param host_topology: objects.NUMATopology to update usage information
2694 :param instance_topology: objects.InstanceNUMATopology from which to
2695 retrieve usage information.
2696 :param free: If true, decrease, rather than increase, host usage based on
2697 instance usage.
2699 :returns: Updated objects.NUMATopology for host
2700 """
2701 if not host_topology or not instance_topology:
2702 return host_topology
2704 cells = []
2705 sign = -1 if free else 1
2707 for host_cell in host_topology.cells:
2708 memory_usage = host_cell.memory_usage
2709 shared_cpus_usage = host_cell.cpu_usage
2711 new_cell = objects.NUMACell(
2712 id=host_cell.id,
2713 cpuset=host_cell.cpuset,
2714 pcpuset=host_cell.pcpuset,
2715 memory=host_cell.memory,
2716 socket=host_cell.socket,
2717 cpu_usage=0,
2718 memory_usage=0,
2719 mempages=host_cell.mempages,
2720 pinned_cpus=host_cell.pinned_cpus,
2721 siblings=host_cell.siblings)
2723 if 'network_metadata' in host_cell: 2723 ↛ 2724line 2723 didn't jump to line 2724 because the condition on line 2723 was never true
2724 new_cell.network_metadata = host_cell.network_metadata
2726 for cellid, instance_cell in enumerate(instance_topology.cells):
2727 if instance_cell.id != host_cell.id:
2728 continue
2730 new_cell.mempages = _numa_pagesize_usage_from_cell(
2731 new_cell, instance_cell, sign)
2733 memory_usage = memory_usage + sign * instance_cell.memory
2735 shared_cpus_usage += sign * len(instance_cell.cpuset)
2737 if instance_cell.cpu_policy in (
2738 None, fields.CPUAllocationPolicy.SHARED,
2739 ):
2740 continue
2741 if instance_cell.cpu_pinning: 2741 ↛ 2744line 2741 didn't jump to line 2744 because the condition on line 2741 was always true
2742 pinned_cpus = set(instance_cell.cpu_pinning.values())
2743 else:
2744 pinned_cpus = set()
2745 if instance_cell.cpuset_reserved:
2746 pinned_cpus |= instance_cell.cpuset_reserved
2748 # TODO(stephenfin): Remove the '_with_siblings' variants when we
2749 # drop support for 'vcpu_pin_set' since they will then be no-ops
2750 if free:
2751 if (instance_cell.cpu_thread_policy ==
2752 fields.CPUThreadAllocationPolicy.ISOLATE):
2753 new_cell.unpin_cpus_with_siblings(pinned_cpus)
2754 else:
2755 new_cell.unpin_cpus(pinned_cpus)
2756 else:
2757 if (instance_cell.cpu_thread_policy ==
2758 fields.CPUThreadAllocationPolicy.ISOLATE):
2759 new_cell.pin_cpus_with_siblings(pinned_cpus)
2760 else:
2761 new_cell.pin_cpus(pinned_cpus)
2763 # NOTE(stephenfin): We don't need to set 'pinned_cpus' here since that
2764 # was done in the above '(un)pin_cpus(_with_siblings)' functions
2765 new_cell.memory_usage = max(0, memory_usage)
2766 new_cell.cpu_usage = max(0, shared_cpus_usage)
2767 cells.append(new_cell)
2769 return objects.NUMATopology(cells=cells)
2772def get_vpmems(flavor):
2773 """Return vpmems related to input request.
2775 :param flavor: a flavor object to read extra specs from
2776 :returns: a vpmem label list
2777 """
2778 vpmems_info = flavor.get('extra_specs', {}).get('hw:pmem')
2779 if not vpmems_info:
2780 return []
2781 vpmem_labels = vpmems_info.split(',')
2782 formed_labels = []
2783 for label in vpmem_labels:
2784 formed_label = label.strip()
2785 if formed_label: 2785 ↛ 2783line 2785 didn't jump to line 2783 because the condition on line 2785 was always true
2786 formed_labels.append(formed_label)
2787 return formed_labels
2790def get_maxphysaddr_mode(
2791 flavor: 'objects.Flavor',
2792 image_meta: 'objects.ImageMeta',
2793) -> ty.Optional[str]:
2794 """Return maxphysaddr mode.
2796 :param flavor: a flavor object to read extra specs from
2797 :param image_meta: an objects.ImageMeta object
2798 :raises: nova.exception.Invalid if a value is invalid
2799 :returns: maxphysaddr mode if a value is valid, else None.
2800 """
2801 mode = _get_unique_flavor_image_meta(
2802 'maxphysaddr_mode', flavor, image_meta, prefix='hw',
2803 )
2805 if mode is None:
2806 return None
2808 if mode not in fields.MaxPhyAddrMode.ALL:
2809 raise exception.Invalid(
2810 "Invalid Maxphyaddr mode %(mode)r. Allowed values: %(valid)s." %
2811 {'mode': mode, 'valid': ', '.join(fields.MaxPhyAddrMode.ALL)}
2812 )
2814 return mode
2817def check_hw_rescue_props(image_meta):
2818 """Confirm that hw_rescue_* image properties are present.
2819 """
2820 hw_rescue_props = ['hw_rescue_device', 'hw_rescue_bus']
2821 return any(key in image_meta.properties for key in hw_rescue_props)
2824def get_ephemeral_encryption_constraint(
2825 flavor: 'objects.Flavor',
2826 image_meta: 'objects.ImageMeta',
2827) -> bool:
2828 """Get the ephemeral encryption constraints based on the flavor and image.
2830 :param flavor: an objects.Flavor object
2831 :param image_meta: an objects.ImageMeta object
2832 :raises: nova.exception.FlavorImageConflict
2833 :returns: boolean indicating whether encryption of guest ephemeral storage
2834 was requested
2835 """
2836 flavor_eph_encryption_str, image_eph_encryption = _get_flavor_image_meta(
2837 'ephemeral_encryption', flavor, image_meta)
2839 flavor_eph_encryption = None
2840 if flavor_eph_encryption_str is not None:
2841 flavor_eph_encryption = strutils.bool_from_string(
2842 flavor_eph_encryption_str)
2844 # Check for conflicts between explicit requirements regarding
2845 # ephemeral encryption.
2846 # TODO(layrwood): make _check_for_mem_encryption_requirement_conflicts
2847 # generic and reuse here
2848 if ( 2848 ↛ 2853line 2848 didn't jump to line 2853 because the condition on line 2848 was never true
2849 flavor_eph_encryption is not None and
2850 image_eph_encryption is not None and
2851 flavor_eph_encryption != image_eph_encryption
2852 ):
2853 emsg = _(
2854 "Flavor %(flavor_name)s has hw:ephemeral_encryption extra spec "
2855 "explicitly set to %(flavor_val)s, conflicting with "
2856 "image %(image_name)s which has hw_eph_encryption property "
2857 "explicitly set to %(image_val)s"
2858 )
2859 data = {
2860 'flavor_name': flavor.name,
2861 'flavor_val': flavor_eph_encryption_str,
2862 'image_name': image_meta.name,
2863 'image_val': image_eph_encryption,
2864 }
2865 raise exception.FlavorImageConflict(emsg % data)
2867 return flavor_eph_encryption or image_eph_encryption
2870def get_ephemeral_encryption_format(
2871 flavor: 'objects.Flavor',
2872 image_meta: 'objects.ImageMeta',
2873) -> ty.Optional[str]:
2874 """Get the ephemeral encryption format.
2876 :param flavor: an objects.Flavor object
2877 :param image_meta: an objects.ImageMeta object
2878 :raises: nova.exception.FlavorImageConflict or nova.exception.Invalid
2879 :returns: BlockDeviceEncryptionFormatType or None
2880 """
2881 eph_format = _get_unique_flavor_image_meta(
2882 'ephemeral_encryption_format', flavor, image_meta)
2883 if eph_format:
2884 if eph_format not in fields.BlockDeviceEncryptionFormatType.ALL: 2884 ↛ 2885line 2884 didn't jump to line 2885 because the condition on line 2884 was never true
2885 allowed = fields.BlockDeviceEncryptionFormatType.ALL
2886 raise exception.Invalid(
2887 f"Invalid ephemeral encryption format {eph_format}. "
2888 f"Allowed values: {', '.join(allowed)}"
2889 )
2890 return eph_format
2891 return None
2894def check_shares_supported(context, instance):
2895 """Check that the compute version support shares and required traits and
2896 instance extra specs are configured.
2897 """
2898 min_version = service.Service().get_minimum_version(
2899 context, 'nova-compute')
2900 if min_version < compute.api.SUPPORT_SHARES:
2901 raise exception.ForbiddenSharesNotSupported()
2903 host = compute_node.ComputeNode().get_first_node_by_host_for_old_compat(
2904 context, instance.host)
2905 client = report.report_client_singleton()
2906 trait_info = client.get_provider_traits(context, host.uuid)
2908 if not (
2909 (
2910 'COMPUTE_STORAGE_VIRTIO_FS' in trait_info.traits and
2911 'COMPUTE_MEM_BACKING_FILE' in trait_info.traits
2912 ) or
2913 (
2914 'COMPUTE_STORAGE_VIRTIO_FS' in trait_info.traits and
2915 'hw:mem_page_size' in instance.flavor.extra_specs
2916 )
2917 ):
2918 raise exception.ForbiddenSharesNotConfiguredCorrectly()