Coverage for nova/virt/hardware.py: 95%

974 statements  

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

1# Copyright 2014 Red Hat, Inc 

2# 

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

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

5# a copy of the License at 

6# 

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

8# 

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

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

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

12# License for the specific language governing permissions and limitations 

13# under the License. 

14 

15import collections 

16import itertools 

17import re 

18import typing as ty 

19 

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 

25 

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 

36 

37 

38CONF = nova.conf.CONF 

39LOG = logging.getLogger(__name__) 

40 

41MEMPAGES_SMALL = -1 

42MEMPAGES_LARGE = -2 

43MEMPAGES_ANY = -3 

44 

45 

46class VTPMConfig(ty.NamedTuple): 

47 version: str 

48 model: str 

49 

50 

51def get_vcpu_pin_set(): 

52 """Parse ``vcpu_pin_set`` config. 

53 

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 

59 

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 

65 

66 

67def get_cpu_dedicated_set(): 

68 """Parse ``[compute] cpu_dedicated_set`` config. 

69 

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 

74 

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 

81 

82 

83def get_cpu_dedicated_set_nozero(): 

84 """Return cpu_dedicated_set without CPU0, if present""" 

85 return (get_cpu_dedicated_set() or set()) - {0} 

86 

87 

88def get_cpu_shared_set(): 

89 """Parse ``[compute] cpu_shared_set`` config. 

90 

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 

96 

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 

103 

104 

105def parse_cpu_spec(spec: str) -> ty.Set[int]: 

106 """Parse a CPU set specification. 

107 

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. 

111 

112 :param spec: cpu set string eg "1-4,^3,6" 

113 

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

130 

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) 

160 

161 # Use sets to handle the exclusion rules for us 

162 cpuset_ids -= cpuset_reject_ids 

163 

164 return cpuset_ids 

165 

166 

167def format_cpu_spec( 

168 cpuset: ty.Set[int], 

169 allow_ranges: bool = True, 

170) -> str: 

171 """Format a libvirt CPU range specification. 

172 

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. 

177 

178 :param cpuset: set (or list) of CPU indexes 

179 :param allow_ranges: Whether we should attempt to detect continuous ranges 

180 of CPUs. 

181 

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 

195 

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

205 

206 

207def get_number_of_serial_ports(flavor, image_meta): 

208 """Get the number of serial consoles from the flavor or image. 

209 

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

214 

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 

223 

224 :param flavor: Flavor object to read extra specs from 

225 :param image_meta: nova.objects.ImageMeta object instance 

226 

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) 

241 

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 

246 

247 return flavor_num_ports or image_num_ports or 1 

248 

249 

250class InstanceInfo(object): 

251 

252 def __init__(self, state, internal_id=None): 

253 """Create a new Instance Info object 

254 

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 

261 

262 def __eq__(self, other): 

263 return (self.__class__ == other.__class__ and 

264 self.__dict__ == other.__dict__) 

265 

266 

267def _score_cpu_topology(topology, wanttopology): 

268 """Compare a topology against a desired configuration. 

269 

270 Calculate a score indicating how well a provided topology matches 

271 against a preferred topology, where: 

272 

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 

279 

280 :param wanttopology: nova.objects.VirtCPUTopology instance for 

281 preferred topology 

282 

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 

293 

294 

295def get_cpu_topology_constraints(flavor, image_meta): 

296 """Get the topology constraints declared in flavor or image 

297 

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: 

301 

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 

308 

309 In the image metadata this will look at: 

310 

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 

317 

318 The image metadata must be strictly lower than any values set in 

319 the flavor. All values are, however, optional. 

320 

321 :param flavor: Flavor object to read extra specs from 

322 :param image_meta: nova.objects.ImageMeta object instance 

323 

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) 

351 

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

360 

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) 

373 

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 

377 

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) 

391 

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

400 

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 

407 

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) 

420 

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 

433 

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

440 

441 return (objects.VirtCPUTopology(sockets=sockets, cores=cores, 

442 threads=threads), 

443 objects.VirtCPUTopology(sockets=max_sockets, cores=max_cores, 

444 threads=max_threads)) 

445 

446 

447def _get_possible_cpu_topologies(vcpus, maxtopology, 

448 allow_threads): 

449 """Get a list of possible topologies for a vCPU count. 

450 

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. 

455 

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 

460 

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) 

471 

472 if not allow_threads: 

473 maxthreads = 1 

474 

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

479 

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

496 

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

504 

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) 

511 

512 return possible 

513 

514 

515def _sort_possible_cpu_topologies(possible, wanttopology): 

516 """Sort the topologies in order of preference. 

517 

518 Sort the provided list of possible topologies such that the 

519 configurations which most closely match the preferred topology are 

520 first. 

521 

522 :param possible: list of objects.VirtCPUTopology instances 

523 :param wanttopology: objects.VirtCPUTopology instance for preferred 

524 topology 

525 

526 :returns: sorted list of nova.objects.VirtCPUTopology instances 

527 """ 

528 

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) 

540 

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

548 

549 return desired 

550 

551 

552def _get_desirable_cpu_topologies(flavor, image_meta, allow_threads=True): 

553 """Identify desirable CPU topologies based for given constraints. 

554 

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. 

559 

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 

563 

564 :returns: sorted list of objects.VirtCPUTopology instances 

565 """ 

566 

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

571 

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

575 

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 

583 

584 

585def get_best_cpu_topology(flavor, image_meta, allow_threads=True): 

586 """Identify best CPU topology for given constraints. 

587 

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 

591 

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 

595 

596 :returns: an objects.VirtCPUTopology instance for best topology 

597 """ 

598 return _get_desirable_cpu_topologies( 

599 flavor, image_meta, allow_threads)[0] 

600 

601 

602def _numa_cell_supports_pagesize_request(host_cell, inst_cell): 

603 """Determine whether the cell can accept the request. 

604 

605 :param host_cell: host cell to fit the instance cell onto 

606 :param inst_cell: instance cell we want to fit 

607 

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) 

614 

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 

620 

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

629 

630 

631def _pack_instance_onto_cores(host_cell, instance_cell, 

632 num_cpu_reserved=0): 

633 """Pack an instance onto a set of siblings. 

634 

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. 

640 

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. 

645 

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. 

650 

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 

657 

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 

666 

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

678 

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

689 

690 pinning = None 

691 threads_no = 1 

692 

693 def _get_pinning(threads_no, sibling_set, instance_cores): 

694 """Determines pCPUs/vCPUs mapping 

695 

696 Determines the pCPUs/vCPUs mapping regarding the number of 

697 threads which can be used per cores. 

698 

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. 

705 

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 

711 

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

721 

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) 

736 

737 return vcpus_pinning 

738 

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. 

743 

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 

754 

755 cpuset_reserved = None 

756 usable_cores = list(map(lambda s: list(s), sibling_set)) 

757 

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) 

773 

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) 

787 

788 return cpuset_reserved or None 

789 

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

805 

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 

814 

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 

838 

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) 

848 

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 

858 

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

864 

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 

875 

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 

884 

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) 

906 

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) 

911 

912 instance_cell.pin_vcpus(*pinning) 

913 instance_cell.id = host_cell.id 

914 instance_cell.cpuset_reserved = cpuset_reserved 

915 return instance_cell 

916 

917 

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 

925 

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. 

929 

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 

934 

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

940 

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. 

991 

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 

1020 

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 

1037 

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 

1051 

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 

1068 

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 

1076 

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 

1093 

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 

1102 

1103 instance_cell.id = host_cell.id 

1104 return instance_cell 

1105 

1106 

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

1117 

1118 flavor_value = flavor.get('extra_specs', {}).get(flavor_key, default) 

1119 image_value = image_meta.properties.get(image_key, default) 

1120 

1121 return flavor_value, image_value 

1122 

1123 

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 ) 

1152 

1153 return flavor_value or image_value 

1154 

1155 

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

1164 

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: 

1168 

1169 1) the flavor requests memory encryption but the image 

1170 explicitly requests *not* to have memory encryption, or 

1171 vice-versa 

1172 

1173 2) the flavor and/or image request memory encryption, but the 

1174 image is missing hw_firmware_type=uefi 

1175 

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' 

1178 

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. 

1185 

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

1194 

1195 flavor_mem_enc_str, image_mem_enc = _get_flavor_image_meta( 

1196 'mem_encryption', flavor, image_meta) 

1197 

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) 

1201 

1202 # Image property is a FlexibleBooleanField, so coercion to a 

1203 # boolean is handled automatically 

1204 

1205 if not flavor_mem_enc and not image_mem_enc: 

1206 return False 

1207 

1208 _check_for_mem_encryption_requirement_conflicts( 

1209 flavor_mem_enc_str, flavor_mem_enc, image_mem_enc, flavor, image_meta) 

1210 

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) 

1226 

1227 _check_mem_encryption_uses_uefi_image(requesters, image_meta) 

1228 _check_mem_encryption_machine_type(image_meta, machine_type) 

1229 

1230 LOG.debug("Memory encryption requested by %s", " and ".join(requesters)) 

1231 return True 

1232 

1233 

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) 

1256 

1257 

1258def _check_mem_encryption_uses_uefi_image(requesters, image_meta): 

1259 if image_meta.properties.get('hw_firmware_type') == 'uefi': 

1260 return 

1261 

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) 

1274 

1275 

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

1293 

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 

1300 

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

1312 

1313 

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 

1319 

1320 :param flavor: a Flavor object to read extra specs from 

1321 :param image_meta: nova.objects.ImageMeta object instance 

1322 

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

1329 

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) 

1339 

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 

1345 

1346 flavor_request, image_request = _get_flavor_image_meta( 

1347 'mem_page_size', flavor, image_meta) 

1348 

1349 if not flavor_request and image_request: 

1350 raise exception.MemoryPageSizeForbidden( 

1351 pagesize=image_request, 

1352 against="<empty>") 

1353 

1354 if not flavor_request: 

1355 # Nothing was specified for hugepages, 

1356 # let's the default process running. 

1357 return None 

1358 

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) 

1366 

1367 return pagesize 

1368 

1369 

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

1378 

1379 return hw_numa_map or None 

1380 

1381 

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. 

1387 

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) 

1396 

1397 locked_memory_flavor, locked_memory_image = _get_flavor_image_meta( 

1398 'locked_memory', flavor, image_meta) 

1399 

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) 

1403 

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) 

1410 

1411 locked_memory = locked_memory_flavor 

1412 

1413 else: 

1414 locked_memory = locked_memory_image 

1415 

1416 if locked_memory and not ( 

1417 mem_page_size_flavor or mem_page_size_image 

1418 ): 

1419 raise exception.LockMemoryForbidden() 

1420 

1421 return locked_memory 

1422 

1423 

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. 

1429 

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

1433 

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) 

1445 

1446 if flavor_cpu_list is None: 

1447 return image_cpu_list 

1448 

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

1452 

1453 return flavor_cpu_list 

1454 

1455 

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. 

1461 

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

1465 

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) 

1477 

1478 if flavor_mem_list is None: 

1479 return image_mem_list 

1480 

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

1484 

1485 return flavor_mem_list 

1486 

1487 

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. 

1493 

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

1507 

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) 

1512 

1513 return int(nodes) if nodes else nodes 

1514 

1515 

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. 

1522 

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) 

1533 

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

1539 

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

1545 

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 

1563 

1564 return cpu_policy 

1565 

1566 

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. 

1573 

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) 

1584 

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

1591 

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

1598 

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 

1605 

1606 return policy 

1607 

1608 

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. 

1616 

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. 

1619 

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

1628 

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

1635 

1636 cells.append(objects.InstanceNUMACell( 

1637 id=node, cpuset=cpus & vcpus, pcpuset=cpus & pcpus, memory=mem)) 

1638 

1639 return objects.InstanceNUMATopology(cells=cells) 

1640 

1641 

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. 

1651 

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 

1665 

1666 availcpus = set(range(flavor.vcpus)) 

1667 

1668 for node in range(nodes): 

1669 mem = mem_list[node] 

1670 cpus = cpu_list[node] 

1671 

1672 for cpu in cpus: 

1673 if cpu > (flavor.vcpus - 1): 

1674 raise exception.ImageNUMATopologyCPUOutOfRange( 

1675 cpunum=cpu, cpumax=(flavor.vcpus - 1)) 

1676 

1677 if cpu not in availcpus: 

1678 raise exception.ImageNUMATopologyCPUDuplicates( 

1679 cpunum=cpu) 

1680 

1681 availcpus.remove(cpu) 

1682 

1683 cells.append(objects.InstanceNUMACell( 

1684 id=node, cpuset=cpus & vcpus, pcpuset=cpus & pcpus, memory=mem)) 

1685 totalmem = totalmem + mem 

1686 

1687 if availcpus: 

1688 raise exception.ImageNUMATopologyCPUsUnassigned( 

1689 cpuset=str(availcpus)) 

1690 

1691 if totalmem != flavor.memory_mb: 

1692 raise exception.ImageNUMATopologyMemoryOutOfRange( 

1693 memsize=totalmem, 

1694 memtotal=flavor.memory_mb) 

1695 

1696 return objects.InstanceNUMATopology(cells=cells) 

1697 

1698 

1699def is_realtime_enabled(flavor): 

1700 flavor_rt = flavor.get('extra_specs', {}).get("hw:cpu_realtime") 

1701 return strutils.bool_from_string(flavor_rt) 

1702 

1703 

1704def _get_vcpu_pcpu_resources( 

1705 flavor: 'objects.Flavor', 

1706) -> ty.Tuple[int, int]: 

1707 requested_vcpu = 0 

1708 requested_pcpu = 0 

1709 

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 

1723 

1724 return requested_vcpu, requested_pcpu 

1725 

1726 

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 

1735 

1736 if os_traits.HW_CPU_HYPERTHREADING in image_meta.properties.get( 

1737 'traits_required', []): 

1738 return 'required' 

1739 

1740 return None 

1741 

1742 

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. 

1748 

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 

1755 

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

1760 

1761 cpus = set(range(flavor.vcpus)) 

1762 vcpus = cpus - pcpus 

1763 if not pcpus or not vcpus: 

1764 raise exception.InvalidMixedInstanceDedicatedMask() 

1765 

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) 

1770 

1771 return pcpus 

1772 

1773 

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. 

1780 

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 

1787 

1788 flavor_mask, image_mask = _get_flavor_image_meta( 

1789 'cpu_realtime_mask', flavor, image_meta) 

1790 

1791 # Image masks are used ahead of flavor masks as they will have more 

1792 # specific requirements 

1793 mask = image_mask or flavor_mask 

1794 

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

1803 

1804 if not vcpus_rt: 

1805 raise exception.RealtimeMaskNotFoundOrInvalid() 

1806 

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

1811 

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) 

1817 

1818 return vcpus_rt 

1819 

1820 

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. 

1826 

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

1834 

1835 if not emu_threads_policy: 

1836 return None 

1837 

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

1842 

1843 return emu_threads_policy 

1844 

1845 

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. 

1851 

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) 

1859 

1860 if flavor_policy and image_policy and flavor_policy != image_policy: 

1861 raise exception.ImagePCINUMAPolicyForbidden() 

1862 

1863 policy = flavor_policy or image_policy 

1864 

1865 if policy and policy not in fields.PCINUMAAffinityPolicy.ALL: 

1866 raise exception.InvalidPCINUMAAffinity(policy=policy) 

1867 

1868 return policy 

1869 

1870 

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. 

1876 

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. 

1880 

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) 

1892 

1893 flavor_value = None 

1894 if flavor_value_str is not None: 

1895 flavor_value = strutils.bool_from_string(flavor_value_str) 

1896 

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 ) 

1918 

1919 return flavor_value if flavor_value is not None else image_value 

1920 

1921 

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. 

1927 

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 

1938 

1939 flavor_value_str, image_value = _get_flavor_image_meta( 

1940 'vif_multiqueue_enabled', flavor, image_meta) 

1941 

1942 flavor_value = None 

1943 if flavor_value_str is not None: 

1944 flavor_value = strutils.bool_from_string(flavor_value_str) 

1945 

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 ) 

1967 

1968 return flavor_value or image_value or False 

1969 

1970 

1971def get_packed_virtqueue_constraint( 

1972 flavor, 

1973 image_meta, 

1974) -> bool: 

1975 """Validate and return the requested Packed virtqueue configuration. 

1976 

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' 

1984 

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) 

1993 

1994 flavor_value = None 

1995 if flavor_value_str is not None: 

1996 flavor_value = strutils.bool_from_string(flavor_value_str) 

1997 

1998 image_value = None 

1999 if image_value_str is not None: 

2000 image_value = strutils.bool_from_string(image_value_str) 

2001 

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 ) 

2021 

2022 return flavor_value or image_value or False 

2023 

2024 

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. 

2030 

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 

2042 

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 ) 

2048 

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 ) 

2062 

2063 return VTPMConfig(version, model) 

2064 

2065 

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. 

2071 

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 

2083 

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

2092 

2093 return policy 

2094 

2095 

2096def get_stateless_firmware_constraint( 

2097 image_meta: 'objects.ImageMeta', 

2098) -> bool: 

2099 """Validate and return the requested statless firmware policy. 

2100 

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 

2108 

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 

2115 

2116 

2117def numa_get_constraints(flavor, image_meta): 

2118 """Return topology related to input request. 

2119 

2120 :param flavor: a flavor object to read extra specs from 

2121 :param image_meta: nova.objects.ImageMeta object instance 

2122 

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

2172 

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) 

2178 

2179 # handle explicit VCPU/PCPU resource requests and the HW_CPU_HYPERTHREADING 

2180 # trait 

2181 

2182 requested_vcpus, requested_pcpus = _get_vcpu_pcpu_resources(flavor) 

2183 

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

2190 

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

2195 

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

2203 

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

2210 

2211 cpu_policy = fields.CPUAllocationPolicy.DEDICATED 

2212 

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 

2218 

2219 hyperthreading_trait = _get_hyperthreading_trait(flavor, image_meta) 

2220 

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

2227 

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 

2232 

2233 # sanity checks 

2234 

2235 if cpu_policy in (fields.CPUAllocationPolicy.SHARED, None): 

2236 if cpu_thread_policy: 

2237 raise exception.CPUThreadPolicyConfigurationInvalid() 

2238 

2239 if emu_threads_policy == fields.CPUEmulatorThreadsPolicy.ISOLATE: 

2240 raise exception.BadRequirementEmulatorThreadsPolicy() 

2241 

2242 # 'hw:cpu_dedicated_mask' should not be defined in a flavor with 

2243 # 'shared' policy. 

2244 if dedicated_cpus: 

2245 raise exception.RequiredMixedInstancePolicy() 

2246 

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

2257 

2258 if not (realtime_cpus or dedicated_cpus): 

2259 raise exception.RequiredMixedOrRealtimeCPUMask() 

2260 

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 

2264 

2265 nodes = _get_numa_node_count_constraint(flavor, image_meta) 

2266 pagesize = _get_numa_pagesize_constraint(flavor, image_meta) 

2267 vpmems = get_vpmems(flavor) 

2268 

2269 get_locked_memory_constraint(flavor, image_meta) 

2270 

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

2278 

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 

2296 

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) 

2300 

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

2309 

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

2316 

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) 

2324 

2325 # ...but emulator threads policy is not \o/ 

2326 numa_topology.emulator_threads_policy = emu_threads_policy 

2327 else: 

2328 numa_topology = None 

2329 

2330 return numa_topology 

2331 

2332 

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. 

2339 

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. 

2346 

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 

2352 

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) 

2357 

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 

2361 

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 

2369 

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 

2375 

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

2380 

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 

2388 

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) 

2393 

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 

2401 

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 

2408 

2409 if host_cell.network_metadata.tunneled: 

2410 return True 

2411 

2412 LOG.debug('Tunneled networks have no affinity to any of the chosen ' 

2413 'host NUMA cells.') 

2414 

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 

2422 

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 

2431 

2432 return True 

2433 

2434 

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. 

2444 

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. 

2450 

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 

2463 

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 

2478 

2479 emulator_threads_policy = None 

2480 if 'emulator_threads_policy' in instance_topology: 

2481 emulator_threads_policy = instance_topology.emulator_threads_policy 

2482 

2483 network_metadata = None 

2484 if limits and 'network_metadata' in limits: 

2485 network_metadata = limits.network_metadata 

2486 

2487 host_cells = host_topology.cells 

2488 

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. 

2499 

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) 

2507 

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) 

2519 

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

2526 

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

2550 

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

2563 

2564 cell_pair = (host_cell.id, instance_cell.id) 

2565 

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 

2570 

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 

2581 

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. 

2598 

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) 

2610 

2611 if len(chosen_instance_cells) != len(host_cell_perm): 

2612 continue 

2613 

2614 if pci_requests and pci_stats and not pci_stats.support_requests( 

2615 pci_requests, provider_mapping, chosen_instance_cells): 

2616 continue 

2617 

2618 if network_metadata and not _numa_cells_support_network_metadata( 

2619 host_topology, chosen_host_cells, network_metadata): 

2620 continue 

2621 

2622 return objects.InstanceNUMATopology( 

2623 cells=chosen_instance_cells, 

2624 emulator_threads_policy=emulator_threads_policy) 

2625 

2626 

2627def numa_get_reserved_huge_pages(): 

2628 """Returns reserved memory pages from host option. 

2629 

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. 

2633 

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 {} 

2641 

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) 

2654 

2655 return bucket 

2656 

2657 

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] 

2663 

2664 

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) 

2670 

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 

2684 

2685 

2686def numa_usage_from_instance_numa(host_topology, instance_topology, 

2687 free=False): 

2688 """Update the host topology usage. 

2689 

2690 Update the host NUMA topology based on usage by the provided instance NUMA 

2691 topology. 

2692 

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. 

2698 

2699 :returns: Updated objects.NUMATopology for host 

2700 """ 

2701 if not host_topology or not instance_topology: 

2702 return host_topology 

2703 

2704 cells = [] 

2705 sign = -1 if free else 1 

2706 

2707 for host_cell in host_topology.cells: 

2708 memory_usage = host_cell.memory_usage 

2709 shared_cpus_usage = host_cell.cpu_usage 

2710 

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) 

2722 

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 

2725 

2726 for cellid, instance_cell in enumerate(instance_topology.cells): 

2727 if instance_cell.id != host_cell.id: 

2728 continue 

2729 

2730 new_cell.mempages = _numa_pagesize_usage_from_cell( 

2731 new_cell, instance_cell, sign) 

2732 

2733 memory_usage = memory_usage + sign * instance_cell.memory 

2734 

2735 shared_cpus_usage += sign * len(instance_cell.cpuset) 

2736 

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 

2747 

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) 

2762 

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) 

2768 

2769 return objects.NUMATopology(cells=cells) 

2770 

2771 

2772def get_vpmems(flavor): 

2773 """Return vpmems related to input request. 

2774 

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 

2788 

2789 

2790def get_maxphysaddr_mode( 

2791 flavor: 'objects.Flavor', 

2792 image_meta: 'objects.ImageMeta', 

2793) -> ty.Optional[str]: 

2794 """Return maxphysaddr mode. 

2795 

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 ) 

2804 

2805 if mode is None: 

2806 return None 

2807 

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 ) 

2813 

2814 return mode 

2815 

2816 

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) 

2822 

2823 

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. 

2829 

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) 

2838 

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) 

2843 

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) 

2866 

2867 return flavor_eph_encryption or image_eph_encryption 

2868 

2869 

2870def get_ephemeral_encryption_format( 

2871 flavor: 'objects.Flavor', 

2872 image_meta: 'objects.ImageMeta', 

2873) -> ty.Optional[str]: 

2874 """Get the ephemeral encryption format. 

2875 

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 

2892 

2893 

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

2902 

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) 

2907 

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