Coverage for nova/virt/libvirt/utils.py: 92%

258 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-04-24 11:16 +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. 

20 

21import grp 

22import os 

23import pwd 

24import re 

25import tempfile 

26import typing as ty 

27import uuid 

28 

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 

34 

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 

51 

52CONF = nova.conf.CONF 

53LOG = logging.getLogger(__name__) 

54 

55RESIZE_SNAPSHOT_NAME = 'nova-resize' 

56 

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} 

106 

107 

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 

117 

118 

119# Reverse CPU_TRAITS_MAPPING 

120TRAITS_CPU_MAPPING = make_reverse_cpu_traits_mapping() 

121 

122# global directory for emulated TPM 

123VTPM_DIR = '/var/lib/libvirt/swtpm/' 

124 

125 

126class EncryptionOptions(ty.TypedDict): 

127 secret: str 

128 format: str 

129 

130 

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 ] 

156 

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')) 

177 

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')) 

193 

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}'] 

205 

206 # Format as a comma separated list 

207 csv_opts = ",".join(cow_opts) 

208 cmd += ['-o', csv_opts] 

209 

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 [] 

212 

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']) 

217 

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() 

221 

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 } 

248 

249 for option, value in encryption_options.items(): 

250 encryption_opts += [ 

251 '-o', 

252 f'encrypt.{option}={value}', 

253 ] 

254 

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) 

262 

263 

264def create_ploop_image( 

265 disk_format: str, path: str, size: ty.Union[int, str], fs_type: str, 

266) -> None: 

267 """Create ploop image 

268 

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) 

285 

286 

287def get_disk_size(path: str, format: ty.Optional[str] = None) -> int: 

288 """Get the (virtual) size of a disk image 

289 

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) 

297 

298 

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 

303 

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) 

310 

311 return backing_file 

312 

313 

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 

324 

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 """ 

334 

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) 

347 

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) 

352 

353 

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 

359 

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) 

368 

369 

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. 

375 

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' 

384 

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) 

388 

389 

390# TODO(stephenfin): This is dumb; remove it. 

391def load_file(path: str) -> str: 

392 """Read contents of file 

393 

394 :param path: File to read 

395 """ 

396 with open(path, 'r') as fp: 

397 return fp.read() 

398 

399 

400# TODO(stephenfin): Remove this; we have suitably powerful mocking abilities 

401# nowadays 

402def file_open(*args, **kwargs): 

403 """Open file 

404 

405 see built-in open() documentation for more details 

406 

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) 

412 

413 

414def find_disk(guest: libvirt_guest.Guest) -> ty.Tuple[str, ty.Optional[str]]: 

415 """Find root device path for instance 

416 

417 May be file or device 

418 """ 

419 guest_config = guest.get_config() 

420 

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 

443 

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")) 

447 

448 return disk_path, disk_format 

449 

450 

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' 

460 

461 # We can't reliably determine the type from this path 

462 return None 

463 

464 

465def get_fs_info(path: str) -> ty.Dict[str, int]: 

466 """Get free/used/total space info for a filesystem 

467 

468 :param path: Any dirent on the filesystem 

469 :returns: A dict containing: 

470 

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} 

480 

481 

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. 

489 

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) 

496 

497 

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. 

505 

506 This function does not attempt raw conversion, as these images will 

507 already be in raw format. 

508 

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) 

515 

516 

517def get_instance_path( 

518 instance: 'objects.Instance', relative: bool = False, 

519) -> str: 

520 """Determine the correct path for instance storage. 

521 

522 This method determines the directory name for instance storage. 

523 

524 :param instance: the instance we want a path for 

525 :param relative: if True, just the relative path is returned 

526 

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) 

532 

533 

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. 

539 

540 This method determines the directory name for instance storage on 

541 destination node, while live migration. 

542 

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. 

547 

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 

562 

563 

564def get_arch(image_meta: 'objects.ImageMeta') -> str: 

565 """Determine the architecture of the guest (or host). 

566 

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. 

571 

572 :param image_meta: the metadata associated with the instance image 

573 

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 

580 

581 return obj_fields.Architecture.from_host() 

582 

583 

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 

588 

589 if source is None: 

590 return True 

591 

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) 

595 

596 

597def is_valid_hostname(hostname: str) -> bool: 

598 return bool(re.match(r"^[\w\-\.:]+$", hostname)) 

599 

600 

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]) 

604 

605 

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. 

610 

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 

619 

620 return traits 

621 

622 

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 

637 

638 

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 

647 

648 # If set in the config, use that as the default. 

649 return get_default_machine_type(get_arch(image_meta)) 

650 

651 

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) 

681 

682 

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). 

687 

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)) 

695 

696 

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>]). 

700 

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 

709 

710 

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 

716 

717 flags = [TRAITS_CPU_MAPPING[trait] for trait in required_traits 

718 if trait in TRAITS_CPU_MAPPING] 

719 

720 return set(flags) 

721 

722 

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. 

732 

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. 

738 

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 

750 

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. 

755 

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) 

769 

770 

771def restore_vtpm_dir(swtpm_dir: str) -> None: 

772 """Given a saved TPM directory, restore it where libvirt can find it. 

773 

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) 

786 

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 

790 

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)