Coverage for nova/virt/libvirt/utils.py: 92%
258 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-04-17 15:08 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-04-17 15:08 +0000
1# Copyright 2010 United States Government as represented by the
2# Administrator of the National Aeronautics and Space Administration.
3# All Rights Reserved.
4# Copyright (c) 2010 Citrix Systems, Inc.
5# Copyright (c) 2011 Piston Cloud Computing, Inc
6# Copyright (c) 2011 OpenStack Foundation
7# (c) Copyright 2013 Hewlett-Packard Development Company, L.P.
8#
9# Licensed under the Apache License, Version 2.0 (the "License"); you may
10# not use this file except in compliance with the License. You may obtain
11# a copy of the License at
12#
13# http://www.apache.org/licenses/LICENSE-2.0
14#
15# Unless required by applicable law or agreed to in writing, software
16# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
17# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
18# License for the specific language governing permissions and limitations
19# under the License.
21import grp
22import os
23import pwd
24import re
25import tempfile
26import typing as ty
27import uuid
29import os_traits
30from oslo_concurrency import processutils
31from oslo_log import log as logging
32from oslo_utils import fileutils
33from oslo_utils.imageutils import format_inspector
35import nova.conf
36from nova import context as nova_context
37from nova import exception
38from nova.i18n import _
39from nova import objects
40from nova.objects import fields as obj_fields
41import nova.privsep.fs
42import nova.privsep.idmapshift
43import nova.privsep.libvirt
44import nova.privsep.path
45from nova.scheduler import utils as scheduler_utils
46from nova import utils
47from nova.virt import images
48from nova.virt.libvirt import config as vconfig
49from nova.virt.libvirt import guest as libvirt_guest
50from nova.virt.libvirt.volume import remotefs
52CONF = nova.conf.CONF
53LOG = logging.getLogger(__name__)
55RESIZE_SNAPSHOT_NAME = 'nova-resize'
57# Mapping used to convert libvirt cpu features to traits, for more details, see
58# https://github.com/libvirt/libvirt/blob/master/src/cpu_map/
59CPU_TRAITS_MAPPING = {
60 '3dnow': os_traits.HW_CPU_X86_3DNOW,
61 'abm': os_traits.HW_CPU_X86_ABM,
62 'aes': os_traits.HW_CPU_X86_AESNI,
63 'avx': os_traits.HW_CPU_X86_AVX,
64 'avx2': os_traits.HW_CPU_X86_AVX2,
65 'avx512bw': os_traits.HW_CPU_X86_AVX512BW,
66 'avx512cd': os_traits.HW_CPU_X86_AVX512CD,
67 'avx512dq': os_traits.HW_CPU_X86_AVX512DQ,
68 'avx512er': os_traits.HW_CPU_X86_AVX512ER,
69 'avx512f': os_traits.HW_CPU_X86_AVX512F,
70 'avx512pf': os_traits.HW_CPU_X86_AVX512PF,
71 'avx512vl': os_traits.HW_CPU_X86_AVX512VL,
72 'avx512vnni': os_traits.HW_CPU_X86_AVX512VNNI,
73 'avx512vbmi': os_traits.HW_CPU_X86_AVX512VBMI,
74 'avx512ifma': os_traits.HW_CPU_X86_AVX512IFMA,
75 'avx512vbmi2': os_traits.HW_CPU_X86_AVX512VBMI2,
76 'avx512bitalg': os_traits.HW_CPU_X86_AVX512BITALG,
77 'vaes': os_traits.HW_CPU_X86_AVX512VAES,
78 'gfni': os_traits.HW_CPU_X86_AVX512GFNI,
79 'vpclmulqdq': os_traits.HW_CPU_X86_AVX512VPCLMULQDQ,
80 'avx512-vpopcntdq': os_traits.HW_CPU_X86_AVX512VPOPCNTDQ,
81 'bmi1': os_traits.HW_CPU_X86_BMI,
82 'bmi2': os_traits.HW_CPU_X86_BMI2,
83 'pclmuldq': os_traits.HW_CPU_X86_CLMUL,
84 'f16c': os_traits.HW_CPU_X86_F16C,
85 'fma': os_traits.HW_CPU_X86_FMA3,
86 'fma4': os_traits.HW_CPU_X86_FMA4,
87 'mmx': os_traits.HW_CPU_X86_MMX,
88 'mpx': os_traits.HW_CPU_X86_MPX,
89 'sha-ni': os_traits.HW_CPU_X86_SHA,
90 'sse': os_traits.HW_CPU_X86_SSE,
91 'sse2': os_traits.HW_CPU_X86_SSE2,
92 'sse3': os_traits.HW_CPU_X86_SSE3,
93 'sse4.1': os_traits.HW_CPU_X86_SSE41,
94 'sse4.2': os_traits.HW_CPU_X86_SSE42,
95 'sse4a': os_traits.HW_CPU_X86_SSE4A,
96 'ssse3': os_traits.HW_CPU_X86_SSSE3,
97 # We have to continue to support the old (generic) trait for the
98 # AMD-specific svm feature.
99 'svm': (os_traits.HW_CPU_X86_SVM, os_traits.HW_CPU_X86_AMD_SVM),
100 'tbm': os_traits.HW_CPU_X86_TBM,
101 # We have to continue to support the old (generic) trait for the
102 # Intel-specific vmx feature.
103 'vmx': (os_traits.HW_CPU_X86_VMX, os_traits.HW_CPU_X86_INTEL_VMX),
104 'xop': os_traits.HW_CPU_X86_XOP
105}
108def make_reverse_cpu_traits_mapping() -> ty.Dict[str, str]:
109 traits_cpu_mapping = dict()
110 for k, v in CPU_TRAITS_MAPPING.items():
111 if isinstance(v, tuple):
112 for trait in v:
113 traits_cpu_mapping[trait] = k
114 else:
115 traits_cpu_mapping[v] = k
116 return traits_cpu_mapping
119# Reverse CPU_TRAITS_MAPPING
120TRAITS_CPU_MAPPING = make_reverse_cpu_traits_mapping()
122# global directory for emulated TPM
123VTPM_DIR = '/var/lib/libvirt/swtpm/'
126class EncryptionOptions(ty.TypedDict):
127 secret: str
128 format: str
131def create_image(
132 path: str,
133 disk_format: str,
134 disk_size: ty.Optional[ty.Union[str, int]],
135 backing_file: ty.Optional[str] = None,
136 encryption: ty.Optional[EncryptionOptions] = None,
137 safe: bool = False,
138) -> None:
139 """Disk image creation with qemu-img
140 :param path: Desired location of the disk image
141 :param disk_format: Disk image format (as known by qemu-img)
142 :param disk_size: Desired size of disk image. May be given as an int or
143 a string. If given as an int, it will be interpreted as bytes. If it's
144 a string, it should consist of a number with an optional suffix ('K'
145 for Kibibytes, M for Mebibytes, 'G' for Gibibytes, 'T' for Tebibytes).
146 If no suffix is given, it will be interpreted as bytes.
147 Can be None in the case of a COW image.
148 :param backing_file: (Optional) Backing file to use.
149 :param encryption: (Optional) Dict detailing various encryption attributes
150 such as the format and passphrase.
151 :param safe: If True, the image is know to be safe.
152 """
153 cmd = [
154 'env', 'LC_ALL=C', 'LANG=C', 'qemu-img', 'create', '-f', disk_format
155 ]
157 if backing_file:
158 # NOTE(danms): We need to perform safety checks on the base image
159 # before we inspect it for other attributes. We do this each time
160 # because additional safety checks could have been added since we
161 # downloaded the image.
162 # Note(sean-k-mooney): We only need to perform the safety check for
163 # the backing file if the image is not created by nova for swap or
164 # ephemeral disks.
165 if not CONF.workarounds.disable_deep_image_inspection and not safe: 165 ↛ 178line 165 didn't jump to line 178 because the condition on line 165 was always true
166 inspector = images.get_image_format(backing_file)
167 try:
168 inspector.safety_check()
169 except format_inspector.SafetyCheckFailed as e:
170 LOG.warning('Base image %s failed safety check: %s',
171 backing_file, e)
172 # NOTE(danms): This is the same exception as would be raised
173 # by qemu_img_info() if the disk format was unreadable or
174 # otherwise unsuitable.
175 raise exception.InvalidDiskInfo(
176 reason=_('Base image failed safety check'))
178 base_details = images.qemu_img_info(backing_file)
179 if base_details.file_format == 'vmdk':
180 images.check_vmdk_image('base', base_details)
181 if base_details.backing_file is not None:
182 LOG.warning('Base image %s failed safety check', backing_file)
183 raise exception.InvalidDiskInfo(
184 reason=_('Base image failed safety check'))
185 try:
186 data_file = base_details.format_specific['data']['data-file']
187 except (KeyError, TypeError, AttributeError):
188 data_file = None
189 if data_file is not None: 189 ↛ 190line 189 didn't jump to line 190 because the condition on line 189 was never true
190 LOG.warning('Base image %s failed safety check', backing_file)
191 raise exception.InvalidDiskInfo(
192 reason=_('Base image failed safety check'))
194 cow_opts = [
195 f'backing_file={backing_file}',
196 f'backing_fmt={base_details.file_format}'
197 ]
198 # Explicitly inherit the value of 'cluster_size' property of a qcow2
199 # overlay image from its backing file. This can be useful in cases when
200 # people create a base image with a non-default 'cluster_size' value or
201 # cases when images were created with very old QEMU versions which had
202 # a different default 'cluster_size'.
203 if base_details.cluster_size is not None: 203 ↛ 207line 203 didn't jump to line 207 because the condition on line 203 was always true
204 cow_opts += [f'cluster_size={base_details.cluster_size}']
206 # Format as a comma separated list
207 csv_opts = ",".join(cow_opts)
208 cmd += ['-o', csv_opts]
210 # Disk size can be None in the case of a COW image
211 disk_size_arg = [str(disk_size)] if disk_size is not None else []
213 if encryption:
214 with tempfile.NamedTemporaryFile(mode='tr+', encoding='utf-8') as f:
215 # Write out the passphrase secret to a temp file
216 f.write(encryption['secret'])
218 # Ensure the secret is written to disk, we can't .close() here as
219 # that removes the file when using NamedTemporaryFile
220 f.flush()
222 # The basic options include the secret and encryption format
223 encryption_opts = [
224 '--object', f"secret,id=sec,file={f.name}",
225 '-o', 'encrypt.key-secret=sec',
226 '-o', f"encrypt.format={encryption['format']}",
227 ]
228 # Supported luks options:
229 # cipher-alg=<str> - Name of cipher algorithm and key length
230 # cipher-mode=<str> - Name of encryption cipher mode
231 # hash-alg=<str> - Name of hash algorithm to use for PBKDF
232 # iter-time=<num> - Time to spend in PBKDF in milliseconds
233 # ivgen-alg=<str> - Name of IV generator algorithm
234 # ivgen-hash-alg=<str> - Name of IV generator hash algorithm
235 #
236 # NOTE(melwitt): Sensible defaults (that match the qemu defaults)
237 # are hardcoded at this time for simplicity and consistency when
238 # instances are migrated. Configuration of luks options could be
239 # added in a future release.
240 encryption_options = {
241 'cipher-alg': 'aes-256',
242 'cipher-mode': 'xts',
243 'hash-alg': 'sha256',
244 'iter-time': 2000,
245 'ivgen-alg': 'plain64',
246 'ivgen-hash-alg': 'sha256',
247 }
249 for option, value in encryption_options.items():
250 encryption_opts += [
251 '-o',
252 f'encrypt.{option}={value}',
253 ]
255 # We need to execute the command while the NamedTemporaryFile still
256 # exists
257 cmd += encryption_opts + [path] + disk_size_arg
258 processutils.execute(*cmd)
259 else:
260 cmd += [path] + disk_size_arg
261 processutils.execute(*cmd)
264def create_ploop_image(
265 disk_format: str, path: str, size: ty.Union[int, str], fs_type: str,
266) -> None:
267 """Create ploop image
269 :param disk_format: Disk image format (as known by ploop)
270 :param path: Desired location of the ploop image
271 :param size: Desired size of ploop image. May be given as an int or
272 a string. If given as an int, it will be interpreted
273 as bytes. If it's a string, it should consist of a number
274 with an optional suffix ('K' for Kibibytes,
275 M for Mebibytes, 'G' for Gibibytes, 'T' for Tebibytes).
276 If no suffix is given, it will be interpreted as bytes.
277 :param fs_type: Filesystem type
278 """
279 if not fs_type:
280 fs_type = CONF.default_ephemeral_format or \
281 nova.privsep.fs.FS_FORMAT_EXT4
282 fileutils.ensure_tree(path)
283 disk_path = os.path.join(path, 'root.hds')
284 nova.privsep.libvirt.ploop_init(size, disk_format, fs_type, disk_path)
287def get_disk_size(path: str, format: ty.Optional[str] = None) -> int:
288 """Get the (virtual) size of a disk image
290 :param path: Path to the disk image
291 :param format: the on-disk format of path
292 :returns: Size (in bytes) of the given disk image as it would be seen
293 by a virtual machine.
294 """
295 size = images.qemu_img_info(path, format).virtual_size
296 return int(size)
299def get_disk_backing_file(
300 path: str, basename: bool = True, format: ty.Optional[str] = None,
301) -> ty.Optional[str]:
302 """Get the backing file of a disk image
304 :param path: Path to the disk image
305 :returns: a path to the image's backing store
306 """
307 backing_file = images.qemu_img_info(path, format).backing_file
308 if backing_file and basename: 308 ↛ 309line 308 didn't jump to line 309 because the condition on line 308 was never true
309 backing_file = os.path.basename(backing_file)
311 return backing_file
314def copy_image(
315 src: str,
316 dest: str,
317 host: ty.Optional[str] = None,
318 receive: bool = False,
319 on_execute: ty.Optional[ty.Callable] = None,
320 on_completion: ty.Optional[ty.Callable] = None,
321 compression: bool = True,
322) -> None:
323 """Copy a disk image to an existing directory
325 :param src: Source image
326 :param dest: Destination path
327 :param host: Remote host
328 :param receive: Reverse the rsync direction
329 :param on_execute: Callback method to store pid of process in cache
330 :param on_completion: Callback method to remove pid of process from cache
331 :param compression: Allows to use rsync operation with or without
332 compression
333 """
335 if not host:
336 # We shell out to cp because that will intelligently copy
337 # sparse files. I.E. holes will not be written to DEST,
338 # rather recreated efficiently. In addition, since
339 # coreutils 8.11, holes can be read efficiently too.
340 # we add '-r' argument because ploop disks are directories
341 processutils.execute('cp', '-r', src, dest)
342 else:
343 if receive: 343 ↛ 344line 343 didn't jump to line 344 because the condition on line 343 was never true
344 src = "%s:%s" % (utils.safe_ip_format(host), src)
345 else:
346 dest = "%s:%s" % (utils.safe_ip_format(host), dest)
348 remote_filesystem_driver = remotefs.RemoteFilesystem()
349 remote_filesystem_driver.copy_file(src, dest,
350 on_execute=on_execute, on_completion=on_completion,
351 compression=compression)
354def chown_for_id_maps(
355 path: str, id_maps: ty.List[vconfig.LibvirtConfigGuestIDMap],
356) -> None:
357 """Change ownership of file or directory for an id mapped
358 environment
360 :param path: File or directory whose ownership to change
361 :param id_maps: List of type LibvirtConfigGuestIDMap
362 """
363 uid_maps = [id_map for id_map in id_maps if
364 isinstance(id_map, vconfig.LibvirtConfigGuestUIDMap)]
365 gid_maps = [id_map for id_map in id_maps if
366 isinstance(id_map, vconfig.LibvirtConfigGuestGIDMap)]
367 nova.privsep.idmapshift.shift(path, uid_maps, gid_maps)
370def extract_snapshot(
371 disk_path: str, source_fmt: str, out_path: str, dest_fmt: str,
372) -> None:
373 """Extract a snapshot from a disk image.
374 Note that nobody should write to the disk image during this operation.
376 :param disk_path: Path to disk image
377 :param out_path: Desired path of extracted snapshot
378 """
379 # NOTE(markmc): ISO is just raw to qemu-img
380 if dest_fmt == 'iso':
381 dest_fmt = 'raw'
382 if dest_fmt == 'ploop':
383 dest_fmt = 'parallels'
385 compress = CONF.libvirt.snapshot_compression and dest_fmt == "qcow2"
386 images.convert_image(disk_path, out_path, source_fmt, dest_fmt,
387 compress=compress)
390# TODO(stephenfin): This is dumb; remove it.
391def load_file(path: str) -> str:
392 """Read contents of file
394 :param path: File to read
395 """
396 with open(path, 'r') as fp:
397 return fp.read()
400# TODO(stephenfin): Remove this; we have suitably powerful mocking abilities
401# nowadays
402def file_open(*args, **kwargs):
403 """Open file
405 see built-in open() documentation for more details
407 Note: The reason this is kept in a separate module is to easily
408 be able to provide a stub module that doesn't alter system
409 state at all (for unit tests)
410 """
411 return open(*args, **kwargs)
414def find_disk(guest: libvirt_guest.Guest) -> ty.Tuple[str, ty.Optional[str]]:
415 """Find root device path for instance
417 May be file or device
418 """
419 guest_config = guest.get_config()
421 disk_format = None
422 if guest_config.virt_type == 'lxc':
423 filesystem = next(d for d in guest_config.devices
424 if isinstance(d, vconfig.LibvirtConfigGuestFilesys))
425 disk_path = filesystem.source_dir
426 disk_path = disk_path[0:disk_path.rfind('rootfs')]
427 disk_path = os.path.join(disk_path, 'disk')
428 elif (guest_config.virt_type == 'parallels' and
429 guest_config.os_type == obj_fields.VMMode.EXE):
430 filesystem = next(d for d in guest_config.devices
431 if isinstance(d, vconfig.LibvirtConfigGuestFilesys))
432 disk_format = filesystem.driver_type
433 disk_path = filesystem.source_file
434 else:
435 disk = next(d for d in guest_config.devices
436 if isinstance(d, vconfig.LibvirtConfigGuestDisk))
437 disk_format = disk.driver_format
438 disk_path = disk.source_path if disk.source_type != 'mount' else None
439 if not disk_path and disk.source_protocol == 'rbd':
440 disk_path = disk.source_name
441 if disk_path: 441 ↛ 444line 441 didn't jump to line 444 because the condition on line 441 was always true
442 disk_path = 'rbd:' + disk_path
444 if not disk_path: 444 ↛ 445line 444 didn't jump to line 445 because the condition on line 444 was never true
445 raise RuntimeError(_("Can't retrieve root device path "
446 "from instance libvirt configuration"))
448 return disk_path, disk_format
451def get_disk_type_from_path(path: str) -> ty.Optional[str]:
452 """Retrieve disk type (raw, qcow2, lvm, ploop) for given file."""
453 if path.startswith('/dev'):
454 return 'lvm'
455 elif path.startswith('rbd:'):
456 return 'rbd'
457 elif (os.path.isdir(path) and
458 os.path.exists(os.path.join(path, "DiskDescriptor.xml"))):
459 return 'ploop'
461 # We can't reliably determine the type from this path
462 return None
465def get_fs_info(path: str) -> ty.Dict[str, int]:
466 """Get free/used/total space info for a filesystem
468 :param path: Any dirent on the filesystem
469 :returns: A dict containing:
471 :free: How much space is free (in bytes)
472 :used: How much space is used (in bytes)
473 :total: How big the filesystem is (in bytes)
474 """
475 hddinfo = os.statvfs(path)
476 total = hddinfo.f_frsize * hddinfo.f_blocks
477 free = hddinfo.f_frsize * hddinfo.f_bavail
478 used = hddinfo.f_frsize * (hddinfo.f_blocks - hddinfo.f_bfree)
479 return {'total': total, 'free': free, 'used': used}
482def fetch_image(
483 context: nova_context.RequestContext,
484 target: str,
485 image_id: str,
486 trusted_certs: ty.Optional['objects.TrustedCerts'] = None,
487) -> None:
488 """Grab image.
490 :param context: nova.context.RequestContext auth request context
491 :param target: target path to put the image
492 :param image_id: id of the image to fetch
493 :param trusted_certs: optional objects.TrustedCerts for image validation
494 """
495 images.fetch_to_raw(context, image_id, target, trusted_certs)
498def fetch_raw_image(
499 context: nova_context.RequestContext,
500 target: str,
501 image_id: str,
502 trusted_certs: ty.Optional['objects.TrustedCerts'] = None,
503) -> None:
504 """Grab initrd or kernel image.
506 This function does not attempt raw conversion, as these images will
507 already be in raw format.
509 :param context: nova.context.RequestContext auth request context
510 :param target: target path to put the image
511 :param image_id: id of the image to fetch
512 :param trusted_certs: optional objects.TrustedCerts for image validation
513 """
514 images.fetch(context, image_id, target, trusted_certs)
517def get_instance_path(
518 instance: 'objects.Instance', relative: bool = False,
519) -> str:
520 """Determine the correct path for instance storage.
522 This method determines the directory name for instance storage.
524 :param instance: the instance we want a path for
525 :param relative: if True, just the relative path is returned
527 :returns: a path to store information about that instance
528 """
529 if relative:
530 return instance.uuid
531 return os.path.join(CONF.instances_path, instance.uuid)
534def get_instance_path_at_destination(
535 instance: 'objects.Instance',
536 migrate_data: ty.Optional['objects.LibvirtLiveMigrateData'] = None,
537) -> str:
538 """Get the instance path on destination node while live migration.
540 This method determines the directory name for instance storage on
541 destination node, while live migration.
543 :param instance: the instance we want a path for
544 :param migrate_data: if not None, it is a dict which holds data
545 required for live migration without shared
546 storage.
548 :returns: a path to store information about that instance
549 """
550 instance_relative_path = None
551 if migrate_data:
552 instance_relative_path = migrate_data.instance_relative_path
553 # NOTE(mikal): this doesn't use libvirt_utils.get_instance_path
554 # because we are ensuring that the same instance directory name
555 # is used as was at the source
556 if instance_relative_path:
557 instance_dir = os.path.join(CONF.instances_path,
558 instance_relative_path)
559 else:
560 instance_dir = get_instance_path(instance)
561 return instance_dir
564def get_arch(image_meta: 'objects.ImageMeta') -> str:
565 """Determine the architecture of the guest (or host).
567 This method determines the CPU architecture that must be supported by
568 the hypervisor. It gets the (guest) arch info from image_meta properties,
569 and it will fallback to the nova-compute (host) arch if no architecture
570 info is provided in image_meta.
572 :param image_meta: the metadata associated with the instance image
574 :returns: guest (or host) architecture
575 """
576 if image_meta: 576 ↛ 581line 576 didn't jump to line 581 because the condition on line 576 was always true
577 image_arch = image_meta.properties.get('hw_architecture')
578 if image_arch is not None:
579 return image_arch
581 return obj_fields.Architecture.from_host()
584def is_mounted(mount_path: str, source: ty.Optional[str] = None) -> bool:
585 """Check if the given source is mounted at given destination point."""
586 if not os.path.ismount(mount_path):
587 return False
589 if source is None:
590 return True
592 with open('/proc/mounts', 'r') as proc_mounts:
593 mounts = [mount.split() for mount in proc_mounts.readlines()]
594 return any(mnt[0] == source and mnt[1] == mount_path for mnt in mounts)
597def is_valid_hostname(hostname: str) -> bool:
598 return bool(re.match(r"^[\w\-\.:]+$", hostname))
601def version_to_string(version: ty.Tuple[int, int, int]) -> str:
602 """Returns string version based on tuple"""
603 return '.'.join([str(x) for x in version])
606def cpu_features_to_traits(features: ty.Set[str]) -> ty.Dict[str, bool]:
607 """Returns this driver's CPU traits dict where keys are trait names from
608 CPU_TRAITS_MAPPING, values are boolean indicates whether the trait should
609 be set in the provider tree.
611 :param features: A set of feature names (short, lowercase,
612 CPU_TRAITS_MAPPING's keys).
613 """
614 traits = {}
615 for feature_name, val in CPU_TRAITS_MAPPING.items():
616 trait_tuple = val if isinstance(val, tuple) else (val,)
617 for trait_name in trait_tuple:
618 traits[trait_name] = feature_name in features
620 return traits
623def get_cpu_model_from_arch(arch: str) -> str:
624 mode = 'qemu64'
625 if arch == obj_fields.Architecture.I686: 625 ↛ 626line 625 didn't jump to line 626 because the condition on line 625 was never true
626 mode = 'qemu32'
627 elif arch == obj_fields.Architecture.PPC64LE:
628 mode = 'POWER8'
629 # TODO(chateaulav): Testing of emulated archs ongoing
630 # elif arch == obj_fields.Architecture.MIPSEL:
631 # mode = '24Kf-mips-cpu'
632 # NOTE(kevinz): In aarch64, cpu model 'max' will offer the capabilities
633 # that all the stuff it can currently emulate, both for "TCG" and "KVM"
634 elif arch == obj_fields.Architecture.AARCH64: 634 ↛ 635line 634 didn't jump to line 635 because the condition on line 634 was never true
635 mode = 'max'
636 return mode
639def get_machine_type(image_meta: 'objects.ImageMeta') -> ty.Optional[str]:
640 """The guest machine type can be set as an image metadata property, or
641 otherwise based on architecture-specific defaults. If no defaults are
642 found then None will be returned. This will ultimately lead to QEMU using
643 its own default which is currently the 'pc' machine type.
644 """
645 if image_meta.properties.get('hw_machine_type') is not None:
646 return image_meta.properties.hw_machine_type
648 # If set in the config, use that as the default.
649 return get_default_machine_type(get_arch(image_meta))
652def get_default_machine_type(arch: str) -> ty.Optional[str]:
653 # NOTE(lyarwood): Values defined in [libvirt]/hw_machine_type take
654 # precedence here if available for the provided arch.
655 for mapping in CONF.libvirt.hw_machine_type or {}:
656 host_arch, _, machine_type = mapping.partition('=')
657 if machine_type == '':
658 LOG.warning("Invalid hw_machine_type config value %s", mapping)
659 elif host_arch == arch:
660 return machine_type
661 # NOTE(kchamart): For ARMv7 and AArch64, use the 'virt' board as the
662 # default machine type. It is the recommended board, which is designed
663 # to be used with virtual machines. The 'virt' board is more flexible,
664 # supports PCI, 'virtio', has decent RAM limits, etc.
665 #
666 # NOTE(sean-k-mooney): Nova's default for x86 is still 'pc', so
667 # use that, not 'q35', for x86_64 and i686.
668 #
669 # NOTE(aspiers): If you change this, don't forget to update the
670 # docs and metadata for hw_machine_type in glance.
671 default_mtypes = {
672 obj_fields.Architecture.ARMV7: "virt",
673 obj_fields.Architecture.AARCH64: "virt",
674 obj_fields.Architecture.PPC64LE: "pseries",
675 obj_fields.Architecture.S390: "s390-ccw-virtio",
676 obj_fields.Architecture.S390X: "s390-ccw-virtio",
677 obj_fields.Architecture.I686: "pc",
678 obj_fields.Architecture.X86_64: "pc",
679 }
680 return default_mtypes.get(arch)
683def mdev_name2uuid(mdev_name: str) -> str:
684 """Convert an mdev name (of the form mdev_<uuid_with_underscores> or
685 mdev_<uuid_with_underscores>_<pciaddress>) to a uuid
686 (of the form 8-4-4-4-12).
688 :param mdev_name: the name of the mdev to parse the UUID from
689 :returns: string containing the uuid
690 """
691 mdev_uuid = mdev_name[5:].replace('_', '-')
692 # Unconditionally remove the PCI address from the name
693 mdev_uuid = mdev_uuid[:36]
694 return str(uuid.UUID(mdev_uuid))
697def mdev_uuid2name(mdev_uuid: str, parent: ty.Optional[str] = None) -> str:
698 """Convert an mdev uuid (of the form 8-4-4-4-12) and optionally its parent
699 device to a name (of the form mdev_<uuid_with_underscores>[_<pciid>]).
701 :param mdev_uuid: the uuid of the mediated device
702 :param parent: the parent device id for the mediated device
703 :returns: name of the mdev to reference in libvirt
704 """
705 name = "mdev_" + mdev_uuid.replace('-', '_')
706 if parent and parent.startswith('pci_'): 706 ↛ 707line 706 didn't jump to line 707 because the condition on line 706 was never true
707 name = name + parent[4:]
708 return name
711def get_flags_by_flavor_specs(flavor: 'objects.Flavor') -> ty.Set[str]:
712 req_spec = objects.RequestSpec(flavor=flavor)
713 resource_request = scheduler_utils.ResourceRequest.from_request_spec(
714 req_spec)
715 required_traits = resource_request.all_required_traits
717 flags = [TRAITS_CPU_MAPPING[trait] for trait in required_traits
718 if trait in TRAITS_CPU_MAPPING]
720 return set(flags)
723def save_and_migrate_vtpm_dir(
724 instance_uuid: str,
725 inst_base_resize: str,
726 inst_base: str,
727 dest: str,
728 on_execute: ty.Callable,
729 on_completion: ty.Callable,
730) -> None:
731 """Save vTPM data to instance directory and migrate to the destination.
733 If the instance has vTPM enabled, then we need to save its vTPM data
734 locally (to allow for revert) and then migrate the data to the dest node.
735 Do so by copying vTPM data from the swtpm data directory to a resize
736 working directory, $inst_base_resize, and then copying this to the remote
737 directory at $dest:$inst_base.
739 :param instance_uuid: The instance's UUID.
740 :param inst_base_resize: The instance's base resize working directory.
741 :param inst_base: The instances's base directory on the destination host.
742 :param dest: Destination host.
743 :param on_execute: Callback method to store PID of process in cache.
744 :param on_completion: Callback method to remove PID of process from cache.
745 :returns: None.
746 """
747 vtpm_dir = os.path.join(VTPM_DIR, instance_uuid)
748 if not os.path.exists(vtpm_dir):
749 return
751 # We likely need to create the instance swtpm directory on the dest node
752 # with ownership that is not the user running nova. We only have
753 # permissions to copy files to <instance_path> on the dest node so we need
754 # to get creative.
756 # First, make a new directory in the local instance directory
757 swtpm_dir = os.path.join(inst_base_resize, 'swtpm')
758 fileutils.ensure_tree(swtpm_dir)
759 # Now move the per-instance swtpm persistent files into the
760 # local instance directory.
761 nova.privsep.path.move_tree(vtpm_dir, swtpm_dir)
762 # Now adjust ownership.
763 nova.privsep.path.chown(
764 swtpm_dir, os.geteuid(), os.getegid(), recursive=True)
765 # Copy the swtpm subtree to the remote instance directory
766 copy_image(
767 swtpm_dir, inst_base, host=dest, on_execute=on_execute,
768 on_completion=on_completion)
771def restore_vtpm_dir(swtpm_dir: str) -> None:
772 """Given a saved TPM directory, restore it where libvirt can find it.
774 :path swtpm_dir: Path to swtpm directory.
775 :returns: None
776 """
777 # Ensure global swtpm dir exists with suitable
778 # permissions/ownership
779 if not os.path.exists(VTPM_DIR):
780 nova.privsep.path.makedirs(VTPM_DIR)
781 nova.privsep.path.chmod(VTPM_DIR, 0o711)
782 elif not os.path.isdir(VTPM_DIR):
783 msg = _(
784 'Guest wants emulated TPM but host path %s is not a directory.')
785 raise exception.Invalid(msg % VTPM_DIR)
787 # These can raise KeyError but they're validated by the driver on startup.
788 uid = pwd.getpwnam(CONF.libvirt.swtpm_user).pw_uid
789 gid = grp.getgrnam(CONF.libvirt.swtpm_group).gr_gid
791 # Set ownership of instance-specific files
792 nova.privsep.path.chown(swtpm_dir, uid, gid, recursive=True)
793 # Move instance-specific directory to global dir
794 nova.privsep.path.move_tree(swtpm_dir, VTPM_DIR)