Browse Source

Merge "vCPU model selection"

changes/93/671793/24
Zuul 1 week ago
parent
commit
22a440e0ed

+ 39
- 12
doc/source/admin/configuration/hypervisor-kvm.rst View File

@@ -283,7 +283,7 @@ determine which models are supported by your local installation.
283 283
 Two Compute configuration options in the :oslo.config:group:`libvirt` group
284 284
 of ``nova.conf`` define which type of CPU model is exposed to the hypervisor
285 285
 when using KVM: :oslo.config:option:`libvirt.cpu_mode` and
286
-:oslo.config:option:`libvirt.cpu_model`.
286
+:oslo.config:option:`libvirt.cpu_models`.
287 287
 
288 288
 The :oslo.config:option:`libvirt.cpu_mode` option can take one of the following
289 289
 values: ``none``, ``host-passthrough``, ``host-model``, and ``custom``.
@@ -337,27 +337,54 @@ may even include the running kernel. Use this mode only if
337 337
 Custom
338 338
 ------
339 339
 
340
-If your ``nova.conf`` file contains ``cpu_mode=custom``, you can explicitly
341
-specify one of the supported named models using the cpu_model configuration
342
-option. For example, to configure the KVM guests to expose Nehalem CPUs, your
343
-``nova.conf`` file should contain:
340
+If :file:`nova.conf` contains :oslo.config:option:`libvirt.cpu_mode`\ =custom,
341
+you can explicitly specify an ordered list of supported named models using
342
+the :oslo.config:option:`libvirt.cpu_models` configuration option. It is
343
+expected that the list is ordered so that the more common and less advanced cpu
344
+models are listed earlier.
345
+
346
+An end user can specify required CPU features through traits. When specified,
347
+the libvirt driver will select the first cpu model in the
348
+:oslo.config:option:`libvirt.cpu_models` list that can provide the requested
349
+feature traits. If no CPU feature traits are specified then the instance will
350
+be configured with the first cpu model in the list.
351
+
352
+For example, if specifying CPU features ``avx`` and ``avx2`` as follows:
353
+
354
+.. code-block:: console
355
+
356
+    $ openstack flavor set FLAVOR_ID --property trait:HW_CPU_X86_AVX=required \
357
+                                     --property trait:HW_CPU_X86_AVX2=required
358
+
359
+and :oslo.config:option:`libvirt.cpu_models` is configured like this:
344 360
 
345 361
 .. code-block:: ini
346 362
 
347
-   [libvirt]
348
-   cpu_mode = custom
349
-   cpu_model = Nehalem
363
+    [libvirt]
364
+    cpu_mode = custom
365
+    cpu_models = Penryn,IvyBridge,Haswell,Broadwell,Skylake-Client
366
+
367
+Then ``Haswell``, the first cpu model supporting both ``avx`` and ``avx2``,
368
+will be chosen by libvirt.
350 369
 
351 370
 In selecting the ``custom`` mode, along with a
352
-:oslo.config:option:`libvirt.cpu_model` that matches the oldest of your compute
371
+:oslo.config:option:`libvirt.cpu_models` that matches the oldest of your compute
353 372
 node CPUs, you can ensure that live migration between compute nodes will always
354 373
 be possible. However, you should ensure that the
355
-:oslo.config:option:`libvirt.cpu_model` you select passes the correct CPU
374
+:oslo.config:option:`libvirt.cpu_models` you select passes the correct CPU
356 375
 feature flags to the guest.
357 376
 
358 377
 If you need to further tweak your CPU feature flags in the ``custom``
359 378
 mode, see `Set CPU feature flags`_.
360 379
 
380
+.. note::
381
+
382
+  If :oslo.config:option:`libvirt.cpu_models` is configured,
383
+  the CPU models in the list needs to be compatible with the host CPU. Also, if
384
+  :oslo.config:option:`libvirt.cpu_model_extra_flags` is configured, all flags
385
+  needs to be compatible with the host CPU. If incompatible CPU models or flags
386
+  are specified, nova service will raise an error and fail to start.
387
+
361 388
 
362 389
 None (default for all libvirt-driven hypervisors other than KVM & QEMU)
363 390
 -----------------------------------------------------------------------
@@ -379,7 +406,7 @@ not enable the ``pcid`` feature flag --- but you do want to pass
379 406
 
380 407
    [libvirt]
381 408
    cpu_mode = custom
382
-   cpu_model = IvyBridge
409
+   cpu_models = IvyBridge
383 410
    cpu_model_extra_flags = pcid
384 411
 
385 412
 Nested guest support
@@ -451,7 +478,7 @@ Custom
451 478
 
452 479
      [libvirt]
453 480
      cpu_mode = custom
454
-     cpu_model = IvyBridge
481
+     cpu_models = IvyBridge
455 482
      cpu_model_extra_flags = vmx,pcid
456 483
 
457 484
 Nested guest support limitations

+ 12
- 11
doc/source/admin/mitigation-for-Intel-MDS-security-flaws.rst View File

@@ -34,23 +34,24 @@ as follows.
34 34
     three following ways, given that Nova supports three distinct CPU
35 35
     modes:
36 36
 
37
-    a. ``[libvirt]/cpu_mode = host-model``
37
+    a. :oslo.config:option:`libvirt.cpu_mode`\ =host-model
38 38
 
39 39
        When using ``host-model`` CPU mode, the ``md-clear`` CPU flag
40 40
        will be passed through to the Nova guests automatically.
41 41
 
42
-       This mode is the default, when ``virt_type=kvm|qemu`` is
43
-       set in ``/etc/nova/nova-cpu.conf`` on compute nodes.
42
+       This mode is the default, when
43
+       :oslo.config:option:`libvirt.virt_type`\ =kvm|qemu is set in
44
+       ``/etc/nova/nova-cpu.conf`` on compute nodes.
44 45
 
45
-    b. ``[libvirt]/cpu_mode = host-passthrough``
46
+    b. :oslo.config:option:`libvirt.cpu_mode`\ =host-passthrough
46 47
 
47 48
        When using ``host-passthrough`` CPU mode, the ``md-clear`` CPU
48 49
        flag will be passed through to the Nova guests automatically.
49 50
 
50
-    c. A specific custom CPU model — this can be enabled using the
51
-       Nova config attributes: ``[libvirt]/cpu_mode = custom`` plus a
52
-       particular named CPU model, e.g. ``[libvirt]/cpu_model =
53
-       IvyBridge``
51
+    c. Specific custom CPU models — this can be enabled using the
52
+       Nova config attributes :oslo.config:option:`libvirt.cpu_mode`\ =custom
53
+       plus particular named CPU models, e.g.
54
+       :oslo.config:option:`libvirt.cpu_models`\ =IvyBridge.
54 55
 
55 56
        (The list of all valid named CPU models that are supported by
56 57
        your host, QEMU, and libvirt can be found by running the
@@ -59,11 +60,11 @@ as follows.
59 60
        When using a custom CPU mode, you must *explicitly* enable the
60 61
        CPU flag ``md-clear`` to the Nova instances, in addition to the
61 62
        flags required for previous vulnerabilities, using the
62
-       ``cpu_model_extra_flags``.  E.g.::
63
+       :oslo.config:option:`libvirt.cpu_model_extra_flags`.  E.g.::
63 64
 
64 65
            [libvirt]
65 66
            cpu_mode = custom
66
-           cpu_model = IvyBridge
67
+           cpu_models = IvyBridge
67 68
            cpu_model_extra_flags = spec-ctrl,ssbd,md-clear
68 69
 
69 70
 (3) Reboot the compute node for the fixes to take effect.  (To minimize
@@ -73,7 +74,7 @@ as follows.
73 74
 Once the above steps have been taken on every vulnerable compute
74 75
 node in the deployment, each running guest in the cluster must be
75 76
 fully powered down, and cold-booted (i.e. an explicit stop followed
76
-by a start), in order to activate the new CPU model.  This can be done
77
+by a start), in order to activate the new CPU models.  This can be done
77 78
 by the guest administrators at a time of their choosing.
78 79
 
79 80
 

+ 22
- 11
nova/conf/libvirt.py View File

@@ -117,7 +117,7 @@ Related options:
117 117
 * ``connection_uri``: depends on this
118 118
 * ``disk_prefix``: depends on this
119 119
 * ``cpu_mode``: depends on this
120
-* ``cpu_model``: depends on this
120
+* ``cpu_models``: depends on this
121 121
 """),
122 122
     cfg.StrOpt('connection_uri',
123 123
                default='',
@@ -527,7 +527,7 @@ Related options:
527 527
         choices=[
528 528
             ('host-model', 'Clone the host CPU feature flags'),
529 529
             ('host-passthrough', 'Use the host CPU model exactly'),
530
-            ('custom', 'Use the CPU model in ``[libvirt]cpu_model``'),
530
+            ('custom', 'Use the CPU model in ``[libvirt]cpu_models``'),
531 531
             ('none', "Don't set a specific CPU model. For instances with "
532 532
              "``[libvirt] virt_type`` as KVM/QEMU, the default CPU model from "
533 533
              "QEMU will be used, which provides a basic set of CPU features "
@@ -541,13 +541,20 @@ will default to ``none``.
541 541
 
542 542
 Related options:
543 543
 
544
-* ``cpu_model``: This should be set ONLY when ``cpu_mode`` is set to
544
+* ``cpu_models``: This should be set ONLY when ``cpu_mode`` is set to
545 545
   ``custom``. Otherwise, it would result in an error and the instance launch
546 546
   will fail.
547 547
 """),
548
-    cfg.StrOpt('cpu_model',
549
-               help="""
550
-Set the name of the libvirt CPU model the instance should use.
548
+    cfg.ListOpt('cpu_models',
549
+        deprecated_name='cpu_model',
550
+        default=[],
551
+        help="""
552
+An ordered list of CPU models the host supports.
553
+
554
+It is expected that the list is ordered so that the more common and less
555
+advanced CPU models are listed earlier. Here is an example:
556
+``SandyBridge,IvyBridge,Haswell,Broadwell``, the latter CPU model's features is
557
+richer that the previous CPU model.
551 558
 
552 559
 Possible values:
553 560
 
@@ -558,9 +565,13 @@ Possible values:
558 565
 Related options:
559 566
 
560 567
 * ``cpu_mode``: This should be set to ``custom`` ONLY when you want to
561
-  configure (via ``cpu_model``) a specific named CPU model.  Otherwise, it
568
+  configure (via ``cpu_models``) a specific named CPU model.  Otherwise, it
562 569
   would result in an error and the instance launch will fail.
563 570
 * ``virt_type``: Only the virtualization types ``kvm`` and ``qemu`` use this.
571
+
572
+.. note::
573
+    Be careful to only specify models which can be fully supported in
574
+    hardware.
564 575
 """),
565 576
     cfg.ListOpt(
566 577
         'cpu_model_extra_flags',
@@ -578,7 +589,7 @@ to address the guest performance degradation as a result of applying the
578 589
 
579 590
     [libvirt]
580 591
     cpu_mode = custom
581
-    cpu_model = IvyBridge
592
+    cpu_models = IvyBridge
582 593
     cpu_model_extra_flags = pcid
583 594
 
584 595
 To specify multiple CPU flags (e.g. the Intel ``VMX`` to expose the
@@ -587,13 +598,13 @@ huge pages for CPU models that do not provide it)::
587 598
 
588 599
     [libvirt]
589 600
     cpu_mode = custom
590
-    cpu_model = Haswell-noTSX-IBRS
601
+    cpu_models = Haswell-noTSX-IBRS
591 602
     cpu_model_extra_flags = PCID, VMX, pdpe1gb
592 603
 
593 604
 As it can be noticed from above, the ``cpu_model_extra_flags`` config
594 605
 attribute is case insensitive.  And specifying extra flags is valid in
595 606
 combination with all the three possible values for ``cpu_mode``:
596
-``custom`` (this also requires an explicit ``cpu_model`` to be
607
+``custom`` (this also requires an explicit ``cpu_models`` to be
597 608
 specified), ``host-model``, or ``host-passthrough``.  A valid example
598 609
 for allowing extra CPU flags even for ``host-passthrough`` mode is that
599 610
 sometimes QEMU may disable certain CPU features -- e.g. Intel's
@@ -630,7 +641,7 @@ need to use the ``cpu_model_extra_flags``.
630 641
 Related options:
631 642
 
632 643
 * cpu_mode
633
-* cpu_model
644
+* cpu_models
634 645
 """),
635 646
     cfg.StrOpt('snapshots_directory',
636 647
                default='$instances_path/snapshots',

+ 61
- 0
nova/tests/unit/virt/libvirt/fakelibvirt.py View File

@@ -1344,6 +1344,67 @@ class Connection(object):
1344 1344
         raise Exception("fakelibvirt doesn't support getDomainCapabilities "
1345 1345
                         "for %s architecture" % arch)
1346 1346
 
1347
+    def getCPUModelNames(self, arch):
1348
+        mapping = {
1349
+            'x86_64': [
1350
+                '486',
1351
+                'pentium',
1352
+                'pentium2',
1353
+                'pentium3',
1354
+                'pentiumpro',
1355
+                'coreduo',
1356
+                'n270',
1357
+                'core2duo',
1358
+                'qemu32',
1359
+                'kvm32',
1360
+                'cpu64-rhel5',
1361
+                'cpu64-rhel6',
1362
+                'qemu64',
1363
+                'kvm64',
1364
+                'Conroe',
1365
+                'Penryn',
1366
+                'Nehalem',
1367
+                'Nehalem-IBRS',
1368
+                'Westmere',
1369
+                'Westmere-IBRS',
1370
+                'SandyBridge',
1371
+                'SandyBridge-IBRS',
1372
+                'IvyBridge',
1373
+                'IvyBridge-IBRS',
1374
+                'Haswell-noTSX',
1375
+                'Haswell-noTSX-IBRS',
1376
+                'Haswell',
1377
+                'Haswell-IBRS',
1378
+                'Broadwell-noTSX',
1379
+                'Broadwell-noTSX-IBRS',
1380
+                'Broadwell',
1381
+                'Broadwell-IBRS',
1382
+                'Skylake-Client',
1383
+                'Skylake-Client-IBRS',
1384
+                'Skylake-Server',
1385
+                'Skylake-Server-IBRS',
1386
+                'Cascadelake-Server',
1387
+                'Icelake-Client',
1388
+                'Icelake-Server',
1389
+                'athlon',
1390
+                'phenom',
1391
+                'Opteron_G1',
1392
+                'Opteron_G2',
1393
+                'Opteron_G3',
1394
+                'Opteron_G4',
1395
+                'Opteron_G5',
1396
+                'EPYC',
1397
+                'EPYC-IBPB'],
1398
+            'ppc64': [
1399
+                'POWER6',
1400
+                'POWER7',
1401
+                'POWER8',
1402
+                'POWER9',
1403
+                'POWERPC_e5500',
1404
+                'POWERPC_e6500']
1405
+        }
1406
+        return mapping.get(arch, [])
1407
+
1347 1408
     # Features are kept separately so that the tests can patch this
1348 1409
     # class variable with alternate values.
1349 1410
     _domain_capability_features = '''  <features>

+ 336
- 7
nova/tests/unit/virt/libvirt/test_driver.py View File

@@ -389,6 +389,49 @@ _fake_qemu64_cpu_feature = """
389 389
 </cpu>
390 390
 """
391 391
 
392
+_fake_sandy_bridge_cpu_feature = """<cpu mode='custom' match='exact'>
393
+  <model fallback='forbid'>SandyBridge</model>
394
+  <feature policy='require' name='aes'/>
395
+  <feature policy='require' name='apic'/>
396
+  <feature policy='require' name='avx'/>
397
+  <feature policy='require' name='clflush'/>
398
+  <feature policy='require' name='cmov'/>
399
+  <feature policy='require' name='cx16'/>
400
+  <feature policy='require' name='cx8'/>
401
+  <feature policy='require' name='de'/>
402
+  <feature policy='require' name='fpu'/>
403
+  <feature policy='require' name='fxsr'/>
404
+  <feature policy='require' name='lahf_lm'/>
405
+  <feature policy='require' name='lm'/>
406
+  <feature policy='require' name='mca'/>
407
+  <feature policy='require' name='mce'/>
408
+  <feature policy='require' name='mmx'/>
409
+  <feature policy='require' name='msr'/>
410
+  <feature policy='require' name='mtrr'/>
411
+  <feature policy='require' name='nx'/>
412
+  <feature policy='require' name='pae'/>
413
+  <feature policy='require' name='pat'/>
414
+  <feature policy='require' name='pclmuldq'/>
415
+  <feature policy='require' name='pge'/>
416
+  <feature policy='require' name='pni'/>
417
+  <feature policy='require' name='popcnt'/>
418
+  <feature policy='require' name='pse'/>
419
+  <feature policy='require' name='pse36'/>
420
+  <feature policy='require' name='rdtscp'/>
421
+  <feature policy='require' name='sep'/>
422
+  <feature policy='require' name='sse'/>
423
+  <feature policy='require' name='sse2'/>
424
+  <feature policy='require' name='sse4.1'/>
425
+  <feature policy='require' name='sse4.2'/>
426
+  <feature policy='require' name='ssse3'/>
427
+  <feature policy='require' name='syscall'/>
428
+  <feature policy='require' name='tsc'/>
429
+  <feature policy='require' name='tsc-deadline'/>
430
+  <feature policy='require' name='x2apic'/>
431
+  <feature policy='require' name='xsave'/>
432
+</cpu>
433
+"""
434
+
392 435
 _fake_broadwell_cpu_feature = """
393 436
 <cpu mode='custom' match='exact'>
394 437
   <model fallback='forbid'>Broadwell-noTSX</model>
@@ -6855,9 +6898,61 @@ class LibvirtConnTestCase(test.NoDBTestCase,
6855 6898
                       </cpu>
6856 6899
                    """
6857 6900
 
6901
+        def fake_getCPUModelNames(arch):
6902
+            return [
6903
+                '486',
6904
+                'pentium',
6905
+                'pentium2',
6906
+                'pentium3',
6907
+                'pentiumpro',
6908
+                'coreduo',
6909
+                'n270',
6910
+                'core2duo',
6911
+                'qemu32',
6912
+                'kvm32',
6913
+                'cpu64-rhel5',
6914
+                'cpu64-rhel6',
6915
+                'qemu64',
6916
+                'kvm64',
6917
+                'Conroe',
6918
+                'Penryn',
6919
+                'Nehalem',
6920
+                'Nehalem-IBRS',
6921
+                'Westmere',
6922
+                'Westmere-IBRS',
6923
+                'SandyBridge',
6924
+                'SandyBridge-IBRS',
6925
+                'IvyBridge',
6926
+                'IvyBridge-IBRS',
6927
+                'Haswell-noTSX',
6928
+                'Haswell-noTSX-IBRS',
6929
+                'Haswell',
6930
+                'Haswell-IBRS',
6931
+                'Broadwell-noTSX',
6932
+                'Broadwell-noTSX-IBRS',
6933
+                'Broadwell',
6934
+                'Broadwell-IBRS',
6935
+                'Skylake-Client',
6936
+                'Skylake-Client-IBRS',
6937
+                'Skylake-Server',
6938
+                'Skylake-Server-IBRS',
6939
+                'Cascadelake-Server',
6940
+                'Icelake-Client',
6941
+                'Icelake-Server',
6942
+                'athlon',
6943
+                'phenom',
6944
+                'Opteron_G1',
6945
+                'Opteron_G2',
6946
+                'Opteron_G3',
6947
+                'Opteron_G4',
6948
+                'Opteron_G5',
6949
+                'EPYC',
6950
+                'EPYC-IBPB']
6951
+
6858 6952
         # Make sure the host arch is mocked as x86_64
6859 6953
         self.create_fake_libvirt_mock(getCapabilities=fake_getCapabilities,
6860 6954
                                       baselineCPU=fake_baselineCPU,
6955
+                                      getCPUModelNames=fake_getCPUModelNames,
6861 6956
                                       getVersion=lambda: 1005001)
6862 6957
 
6863 6958
         drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
@@ -7084,7 +7179,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
7084 7179
         image_meta = objects.ImageMeta.from_dict(self.test_image_meta)
7085 7180
 
7086 7181
         self.flags(cpu_mode="custom",
7087
-                   cpu_model="Penryn",
7182
+                   cpu_models=["Penryn"],
7088 7183
                    group='libvirt')
7089 7184
         disk_info = blockinfo.get_disk_info(CONF.libvirt.virt_type,
7090 7185
                                             instance_ref,
@@ -7108,7 +7203,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
7108 7203
         image_meta = objects.ImageMeta.from_dict(self.test_image_meta)
7109 7204
 
7110 7205
         self.flags(cpu_mode="custom",
7111
-                   cpu_model="IvyBridge",
7206
+                   cpu_models=["IvyBridge"],
7112 7207
                    cpu_model_extra_flags="pcid",
7113 7208
                    group='libvirt')
7114 7209
         disk_info = blockinfo.get_disk_info(CONF.libvirt.virt_type,
@@ -7135,7 +7230,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
7135 7230
         image_meta = objects.ImageMeta.from_dict(self.test_image_meta)
7136 7231
 
7137 7232
         self.flags(cpu_mode="custom",
7138
-                   cpu_model="IvyBridge",
7233
+                   cpu_models=["IvyBridge"],
7139 7234
                    cpu_model_extra_flags="PCID",
7140 7235
                    group='libvirt')
7141 7236
         disk_info = blockinfo.get_disk_info(CONF.libvirt.virt_type,
@@ -7164,7 +7259,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
7164 7259
         image_meta = objects.ImageMeta.from_dict(self.test_image_meta)
7165 7260
 
7166 7261
         self.flags(cpu_mode="custom",
7167
-                   cpu_model="IvyBridge",
7262
+                   cpu_models=["IvyBridge"],
7168 7263
                    cpu_model_extra_flags=['pcid', 'vmx'],
7169 7264
                    group='libvirt')
7170 7265
         disk_info = blockinfo.get_disk_info(CONF.libvirt.virt_type,
@@ -7185,6 +7280,188 @@ class LibvirtConnTestCase(test.NoDBTestCase,
7185 7280
         self.assertEqual(conf.cpu.threads, 1)
7186 7281
         mock_warn.assert_not_called()
7187 7282
 
7283
+    def test_get_guest_cpu_config_custom_upper_cpu_model(self):
7284
+        drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
7285
+        instance_ref = objects.Instance(**self.test_instance)
7286
+        image_meta = objects.ImageMeta.from_dict(self.test_image_meta)
7287
+        disk_info = blockinfo.get_disk_info(CONF.libvirt.virt_type,
7288
+                                            instance_ref,
7289
+                                            image_meta)
7290
+
7291
+        self.flags(cpu_mode="custom",
7292
+                   cpu_models=["PENRYN", "IVYBRIDGE"],
7293
+                   group="libvirt")
7294
+        conf = drvr._get_guest_config(instance_ref,
7295
+                                      _fake_network_info(self, 1),
7296
+                                      image_meta, disk_info)
7297
+        self.assertEqual(conf.cpu.mode, "custom")
7298
+        self.assertEqual(conf.cpu.model, "Penryn")
7299
+        self.assertEqual(conf.cpu.sockets, instance_ref.flavor.vcpus)
7300
+        self.assertEqual(conf.cpu.cores, 1)
7301
+        self.assertEqual(conf.cpu.threads, 1)
7302
+
7303
+    def test_get_guest_cpu_config_custom_without_traits(self):
7304
+        drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
7305
+        instance_ref = objects.Instance(**self.test_instance)
7306
+        image_meta = objects.ImageMeta.from_dict(self.test_image_meta)
7307
+        disk_info = blockinfo.get_disk_info(CONF.libvirt.virt_type,
7308
+                                            instance_ref,
7309
+                                            image_meta)
7310
+
7311
+        self.flags(cpu_mode="custom",
7312
+                   cpu_models=["SandyBridge", "IvyBridge"],
7313
+                   group="libvirt")
7314
+        conf = drvr._get_guest_config(instance_ref,
7315
+                                      _fake_network_info(self, 1),
7316
+                                      image_meta, disk_info)
7317
+        self.assertEqual(conf.cpu.mode, "custom")
7318
+        self.assertEqual(conf.cpu.model, "SandyBridge")
7319
+        self.assertEqual(conf.cpu.sockets, instance_ref.flavor.vcpus)
7320
+        self.assertEqual(conf.cpu.cores, 1)
7321
+        self.assertEqual(conf.cpu.threads, 1)
7322
+
7323
+    @mock.patch('nova.virt.libvirt.host.libvirt.Connection.baselineCPU')
7324
+    def test_get_guest_cpu_config_custom_with_traits(self, mocked_baseline):
7325
+        mocked_baseline.side_effect = ('', _fake_qemu64_cpu_feature,
7326
+                                       _fake_broadwell_cpu_feature)
7327
+
7328
+        drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
7329
+
7330
+        extra_specs = {
7331
+            "trait:HW_CPU_X86_AVX": "required",
7332
+            "trait:HW_CPU_X86_AVX2": "required"
7333
+        }
7334
+        test_instance = _create_test_instance()
7335
+        test_instance["flavor"]["extra_specs"] = extra_specs
7336
+        instance_ref = objects.Instance(**test_instance)
7337
+        image_meta = objects.ImageMeta.from_dict(self.test_image_meta)
7338
+        disk_info = blockinfo.get_disk_info(CONF.libvirt.virt_type,
7339
+                                            instance_ref,
7340
+                                            image_meta)
7341
+
7342
+        self.flags(cpu_mode="custom",
7343
+                   cpu_models=["qemu64", "Broadwell-noTSX"],
7344
+                   group="libvirt")
7345
+        conf = drvr._get_guest_config(instance_ref,
7346
+                                      _fake_network_info(self, 1),
7347
+                                      image_meta, disk_info)
7348
+        self.assertEqual(conf.cpu.mode, "custom")
7349
+        self.assertEqual(conf.cpu.model, "Broadwell-noTSX")
7350
+        self.assertEqual(conf.cpu.sockets, instance_ref.flavor.vcpus)
7351
+        self.assertEqual(conf.cpu.cores, 1)
7352
+        self.assertEqual(conf.cpu.threads, 1)
7353
+
7354
+    @mock.patch('nova.virt.libvirt.host.libvirt.Connection.baselineCPU')
7355
+    def test_get_guest_cpu_config_custom_with_traits_multi_models(self,
7356
+            mocked_baseline):
7357
+        mocked_baseline.side_effect = ('', _fake_qemu64_cpu_feature,
7358
+                                       _fake_broadwell_cpu_feature)
7359
+
7360
+        drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
7361
+
7362
+        extra_specs = {
7363
+            "trait:HW_CPU_X86_SSE41": "required",
7364
+            "trait:HW_CPU_X86_SSE42": "required"
7365
+        }
7366
+        test_instance = _create_test_instance()
7367
+        test_instance["flavor"]["extra_specs"] = extra_specs
7368
+        instance_ref = objects.Instance(**test_instance)
7369
+        image_meta = objects.ImageMeta.from_dict(self.test_image_meta)
7370
+        disk_info = blockinfo.get_disk_info(CONF.libvirt.virt_type,
7371
+                                            instance_ref,
7372
+                                            image_meta)
7373
+
7374
+        self.flags(cpu_mode="custom",
7375
+                   cpu_models=["qemu64", "SandyBridge", "Broadwell-noTSX"],
7376
+                   group="libvirt")
7377
+        conf = drvr._get_guest_config(instance_ref,
7378
+                                      _fake_network_info(self, 1),
7379
+                                      image_meta, disk_info)
7380
+        self.assertEqual(conf.cpu.mode, "custom")
7381
+        self.assertEqual(conf.cpu.model, "SandyBridge")
7382
+        self.assertEqual(conf.cpu.sockets, instance_ref.flavor.vcpus)
7383
+        self.assertEqual(conf.cpu.cores, 1)
7384
+        self.assertEqual(conf.cpu.threads, 1)
7385
+
7386
+    @mock.patch('nova.virt.libvirt.host.libvirt.Connection.baselineCPU')
7387
+    def test_get_guest_cpu_config_custom_with_traits_none_model(self,
7388
+            mocked_baseline):
7389
+        mocked_baseline.side_effect = ('', _fake_qemu64_cpu_feature,
7390
+                                       _fake_sandy_bridge_cpu_feature)
7391
+
7392
+        drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
7393
+
7394
+        extra_specs = {
7395
+            "trait:HW_CPU_X86_AVX": "required",
7396
+            "trait:HW_CPU_X86_AVX2": "required"
7397
+        }
7398
+        test_instance = _create_test_instance()
7399
+        test_instance["flavor"]["extra_specs"] = extra_specs
7400
+        instance_ref = objects.Instance(**test_instance)
7401
+        image_meta = objects.ImageMeta.from_dict(self.test_image_meta)
7402
+        disk_info = blockinfo.get_disk_info(CONF.libvirt.virt_type,
7403
+                                            instance_ref,
7404
+                                            image_meta)
7405
+
7406
+        self.flags(cpu_mode="custom",
7407
+                   cpu_models=["qemu64", "SandyBridge"],
7408
+                   group="libvirt")
7409
+        self.assertRaises(exception.InvalidCPUInfo,
7410
+                          drvr._get_guest_config,
7411
+                          instance_ref,
7412
+                          _fake_network_info(self, 1),
7413
+                          image_meta,
7414
+                          disk_info)
7415
+
7416
+    @mock.patch('nova.virt.libvirt.host.libvirt.Connection.baselineCPU')
7417
+    def test_get_guest_cpu_config_custom_with_progressive_model(self,
7418
+            mocked_baseline):
7419
+        """Test progressive models
7420
+
7421
+        If require two flags: flag1 and flag2, and there are three sorted
7422
+        CPU models: [model1, model2, model3], model1 only has flag1, model2
7423
+        only has flag2, model3 both have flag1 and flag2.
7424
+
7425
+        Test that the driver will select model3 but not model2.
7426
+        """
7427
+        # Assume that qemu64 have flag avx2 for the test.
7428
+        fake_qemu64_cpu_feature_with_avx2 = _fake_qemu64_cpu_feature.split(
7429
+            '\n')
7430
+        fake_qemu64_cpu_feature_with_avx2.insert(
7431
+            -2, "  <feature policy='require' name='avx2'/>")
7432
+        fake_qemu64_cpu_feature_with_avx2 = "\n".join(
7433
+            fake_qemu64_cpu_feature_with_avx2)
7434
+
7435
+        mocked_baseline.side_effect = ('', fake_qemu64_cpu_feature_with_avx2,
7436
+                                       _fake_sandy_bridge_cpu_feature,
7437
+                                       _fake_broadwell_cpu_feature)
7438
+
7439
+        drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
7440
+
7441
+        extra_specs = {
7442
+            "trait:HW_CPU_X86_AVX": "required",
7443
+            "trait:HW_CPU_X86_AVX2": "required"
7444
+        }
7445
+        test_instance = _create_test_instance()
7446
+        test_instance["flavor"]["extra_specs"] = extra_specs
7447
+        instance_ref = objects.Instance(**test_instance)
7448
+        image_meta = objects.ImageMeta.from_dict(self.test_image_meta)
7449
+        disk_info = blockinfo.get_disk_info(CONF.libvirt.virt_type,
7450
+                                            instance_ref,
7451
+                                            image_meta)
7452
+
7453
+        self.flags(cpu_mode="custom",
7454
+                   cpu_models=["qemu64", "SandyBridge", "Broadwell-noTSX"],
7455
+                   group="libvirt")
7456
+        conf = drvr._get_guest_config(instance_ref,
7457
+                                      _fake_network_info(self, 1),
7458
+                                      image_meta, disk_info)
7459
+        self.assertEqual(conf.cpu.mode, "custom")
7460
+        self.assertEqual(conf.cpu.model, "Broadwell-noTSX")
7461
+        self.assertEqual(conf.cpu.sockets, instance_ref.flavor.vcpus)
7462
+        self.assertEqual(conf.cpu.cores, 1)
7463
+        self.assertEqual(conf.cpu.threads, 1)
7464
+
7188 7465
     @mock.patch.object(libvirt_driver.LOG, 'warning')
7189 7466
     def test_get_guest_cpu_config_host_model_with_extra_flags(self,
7190 7467
             mock_warn):
@@ -13099,12 +13376,64 @@ class LibvirtConnTestCase(test.NoDBTestCase,
13099 13376
                       </cpu>
13100 13377
                    """
13101 13378
 
13379
+        def fake_getCPUModelNames(arch):
13380
+            return [
13381
+                '486',
13382
+                'pentium',
13383
+                'pentium2',
13384
+                'pentium3',
13385
+                'pentiumpro',
13386
+                'coreduo',
13387
+                'n270',
13388
+                'core2duo',
13389
+                'qemu32',
13390
+                'kvm32',
13391
+                'cpu64-rhel5',
13392
+                'cpu64-rhel6',
13393
+                'qemu64',
13394
+                'kvm64',
13395
+                'Conroe',
13396
+                'Penryn',
13397
+                'Nehalem',
13398
+                'Nehalem-IBRS',
13399
+                'Westmere',
13400
+                'Westmere-IBRS',
13401
+                'SandyBridge',
13402
+                'SandyBridge-IBRS',
13403
+                'IvyBridge',
13404
+                'IvyBridge-IBRS',
13405
+                'Haswell-noTSX',
13406
+                'Haswell-noTSX-IBRS',
13407
+                'Haswell',
13408
+                'Haswell-IBRS',
13409
+                'Broadwell-noTSX',
13410
+                'Broadwell-noTSX-IBRS',
13411
+                'Broadwell',
13412
+                'Broadwell-IBRS',
13413
+                'Skylake-Client',
13414
+                'Skylake-Client-IBRS',
13415
+                'Skylake-Server',
13416
+                'Skylake-Server-IBRS',
13417
+                'Cascadelake-Server',
13418
+                'Icelake-Client',
13419
+                'Icelake-Server',
13420
+                'athlon',
13421
+                'phenom',
13422
+                'Opteron_G1',
13423
+                'Opteron_G2',
13424
+                'Opteron_G3',
13425
+                'Opteron_G4',
13426
+                'Opteron_G5',
13427
+                'EPYC',
13428
+                'EPYC-IBPB']
13429
+
13102 13430
         # _fake_network_info must be called before create_fake_libvirt_mock(),
13103 13431
         # as _fake_network_info calls importutils.import_class() and
13104 13432
         # create_fake_libvirt_mock() mocks importutils.import_class().
13105 13433
         network_info = _fake_network_info(self, 1)
13106 13434
         self.create_fake_libvirt_mock(getLibVersion=fake_getLibVersion,
13107 13435
                                       getCapabilities=fake_getCapabilities,
13436
+                                      getCPUModelNames=fake_getCPUModelNames,
13108 13437
                                       getVersion=lambda: 1005001,
13109 13438
                                       baselineCPU=fake_baselineCPU)
13110 13439
 
@@ -22024,7 +22353,7 @@ class LibvirtDriverTestCase(test.NoDBTestCase, TraitsComparisonMixin):
22024 22353
         _fake_broadwell_cpu_features.
22025 22354
         """
22026 22355
         self.flags(cpu_mode='custom',
22027
-                   cpu_model='Broadwell-noTSX',
22356
+                   cpu_models=['Broadwell-noTSX'],
22028 22357
                    group='libvirt')
22029 22358
         mock_baseline.return_value = _fake_broadwell_cpu_feature
22030 22359
 
@@ -22112,7 +22441,7 @@ class LibvirtDriverTestCase(test.NoDBTestCase, TraitsComparisonMixin):
22112 22441
         the feature, only kvm and qemu supports reporting CPU traits.
22113 22442
         """
22114 22443
         self.flags(cpu_mode='custom',
22115
-                   cpu_model='IvyBridge',
22444
+                   cpu_models=['IvyBridge'],
22116 22445
                    virt_type='lxc',
22117 22446
                    group='libvirt'
22118 22447
                    )
@@ -22152,7 +22481,7 @@ class LibvirtDriverTestCase(test.NoDBTestCase, TraitsComparisonMixin):
22152 22481
         """Test if extra flags are accounted when cpu_mode is set to custom.
22153 22482
         """
22154 22483
         self.flags(cpu_mode='custom',
22155
-                   cpu_model='IvyBridge',
22484
+                   cpu_models=['IvyBridge'],
22156 22485
                    cpu_model_extra_flags='PCID',
22157 22486
                    group='libvirt')
22158 22487
 

+ 55
- 15
nova/virt/libvirt/driver.py View File

@@ -424,6 +424,12 @@ class LibvirtDriver(driver.ComputeDriver):
424 424
         # intended to be updatable directly
425 425
         self.provider_tree = None
426 426
 
427
+        # The CPU models in the configuration are case-insensitive, but the CPU
428
+        # model in the libvirt is case-sensitive, therefore create a mapping to
429
+        # map the lower case CPU model name to normal CPU model name.
430
+        self.cpu_models_mapping = {}
431
+        self.cpu_model_flag_mapping = {}
432
+
427 433
     def _get_volume_drivers(self):
428 434
         driver_registry = dict()
429 435
 
@@ -3958,9 +3964,17 @@ class LibvirtDriver(driver.ComputeDriver):
3958 3964
         else:
3959 3965
             mount.get_manager().host_down()
3960 3966
 
3961
-    def _get_guest_cpu_model_config(self):
3967
+    def _get_cpu_model_mapping(self, model):
3968
+        if not self.cpu_models_mapping:
3969
+            cpu_models = self._host.get_cpu_model_names()
3970
+            for cpu_model in cpu_models:
3971
+                self.cpu_models_mapping[cpu_model.lower()] = cpu_model
3972
+        return self.cpu_models_mapping.get(model.lower())
3973
+
3974
+    def _get_guest_cpu_model_config(self, flavor=None):
3962 3975
         mode = CONF.libvirt.cpu_mode
3963
-        model = CONF.libvirt.cpu_model
3976
+        models = [self._get_cpu_model_mapping(model)
3977
+                  for model in CONF.libvirt.cpu_models]
3964 3978
         extra_flags = set([flag.lower() for flag in
3965 3979
             CONF.libvirt.cpu_model_extra_flags])
3966 3980
 
@@ -3997,25 +4011,32 @@ class LibvirtDriver(driver.ComputeDriver):
3997 4011
                     "support selecting CPU models") % CONF.libvirt.virt_type
3998 4012
             raise exception.Invalid(msg)
3999 4013
 
4000
-        if mode == "custom" and model is None:
4001
-            msg = _("Config requested a custom CPU model, but no "
4002
-                    "model name was provided")
4014
+        if mode == "custom" and not models:
4015
+            msg = _("Config requested custom CPU models, but no "
4016
+                    "model names was provided")
4003 4017
             raise exception.Invalid(msg)
4004
-        elif mode != "custom" and model is not None:
4005
-            msg = _("A CPU model name should not be set when a "
4018
+
4019
+        if mode != "custom" and models:
4020
+            msg = _("CPU model names should not be set when a "
4006 4021
                     "host CPU model is requested")
4007 4022
             raise exception.Invalid(msg)
4008 4023
 
4009
-        LOG.debug("CPU mode '%(mode)s' model '%(model)s' was chosen, "
4024
+        cpu = vconfig.LibvirtConfigGuestCPU()
4025
+        cpu.mode = mode
4026
+        cpu.model = models[0] if models else None
4027
+
4028
+        # compare flavor trait and cpu models, select the first mathched model
4029
+        if flavor and mode == "custom":
4030
+            flags = libvirt_utils.get_flags_by_flavor_specs(flavor)
4031
+            if flags:
4032
+                cpu.model = self._match_cpu_model_by_flags(models, flags)
4033
+
4034
+        LOG.debug("CPU mode '%(mode)s' models '%(models)s' was chosen, "
4010 4035
                   "with extra flags: '%(extra_flags)s'",
4011 4036
                   {'mode': mode,
4012
-                   'model': (model or ""),
4037
+                   'models': (cpu.model or ""),
4013 4038
                    'extra_flags': (extra_flags or "")})
4014 4039
 
4015
-        cpu = vconfig.LibvirtConfigGuestCPU()
4016
-        cpu.mode = mode
4017
-        cpu.model = model
4018
-
4019 4040
         # NOTE (kchamart): Currently there's no existing way to ask if a
4020 4041
         # given CPU model + CPU flags combination is supported by KVM &
4021 4042
         # a specific QEMU binary.  However, libvirt runs the 'CPUID'
@@ -4031,9 +4052,28 @@ class LibvirtDriver(driver.ComputeDriver):
4031 4052
 
4032 4053
         return cpu
4033 4054
 
4055
+    def _match_cpu_model_by_flags(self, models, flags):
4056
+        for model in models:
4057
+            if flags.issubset(self.cpu_model_flag_mapping.get(model, set([]))):
4058
+                return model
4059
+            cpu = vconfig.LibvirtConfigCPU()
4060
+            cpu.arch = self._host.get_capabilities().host.cpu.arch
4061
+            cpu.model = model
4062
+            features_xml = self._get_guest_baseline_cpu_features(cpu.to_xml())
4063
+            if features_xml:
4064
+                cpu.parse_str(features_xml)
4065
+                feature_names = [f.name for f in cpu.features]
4066
+                self.cpu_model_flag_mapping[model] = feature_names
4067
+                if flags.issubset(feature_names):
4068
+                    return model
4069
+
4070
+        msg = ('No CPU model match traits, models: {models}, required '
4071
+               'flags: {flags}'.format(models=models, flags=flags))
4072
+        raise exception.InvalidCPUInfo(msg)
4073
+
4034 4074
     def _get_guest_cpu_config(self, flavor, image_meta,
4035 4075
                               guest_cpu_numa_config, instance_numa_topology):
4036
-        cpu = self._get_guest_cpu_model_config()
4076
+        cpu = self._get_guest_cpu_model_config(flavor)
4037 4077
 
4038 4078
         if cpu is None:
4039 4079
             return None
@@ -9799,7 +9839,7 @@ class LibvirtDriver(driver.ComputeDriver):
9799 9839
         CPU features.
9800 9840
         2. if mode is None, choose a default CPU model based on CPU
9801 9841
         architecture.
9802
-        3. if mode is 'custom', use cpu_model to generate CPU features.
9842
+        3. if mode is 'custom', use cpu_models to generate CPU features.
9803 9843
         The code also accounts for cpu_model_extra_flags configuration when
9804 9844
         cpu_mode is 'host-model', 'host-passthrough' or 'custom', this
9805 9845
         ensures user specified CPU feature flags to be included.

+ 8
- 0
nova/virt/libvirt/host.py View File

@@ -637,6 +637,14 @@ class Host(object):
637 637
 
638 638
         return online_cpus
639 639
 
640
+    def get_cpu_model_names(self):
641
+        """Get the cpu models based on host CPU arch
642
+
643
+        :returns: a list of cpu models which supported by the given CPU arch
644
+        """
645
+        arch = self.get_capabilities().host.cpu.arch
646
+        return self.get_connection().getCPUModelNames(arch)
647
+
640 648
     def get_capabilities(self):
641 649
         """Returns the host capabilities information
642 650
 

+ 15
- 0
nova/virt/libvirt/utils.py View File

@@ -30,10 +30,12 @@ from oslo_utils import fileutils
30 30
 
31 31
 import nova.conf
32 32
 from nova.i18n import _
33
+from nova import objects
33 34
 from nova.objects import fields as obj_fields
34 35
 import nova.privsep.fs
35 36
 import nova.privsep.idmapshift
36 37
 import nova.privsep.libvirt
38
+from nova.scheduler import utils as scheduler_utils
37 39
 from nova import utils
38 40
 from nova.virt import images
39 41
 from nova.virt.libvirt import config as vconfig
@@ -82,6 +84,9 @@ CPU_TRAITS_MAPPING = {
82 84
     'xop': os_traits.HW_CPU_X86_XOP
83 85
 }
84 86
 
87
+# Reverse CPU_TRAITS_MAPPING
88
+TRAITS_CPU_MAPPING = {v: k for k, v in CPU_TRAITS_MAPPING.items()}
89
+
85 90
 
86 91
 def create_image(disk_format, path, size):
87 92
     """Create a disk image
@@ -585,3 +590,13 @@ def mdev_uuid2name(mdev_uuid):
585 590
     mdev_<uuid_with_underscores>).
586 591
     """
587 592
     return "mdev_" + mdev_uuid.replace('-', '_')
593
+
594
+
595
+def get_flags_by_flavor_specs(flavor):
596
+    req_spec = objects.RequestSpec(flavor=flavor)
597
+    resource_request = scheduler_utils.ResourceRequest(req_spec)
598
+    required_traits = resource_request.all_required_traits
599
+
600
+    flags = [TRAITS_CPU_MAPPING.get(trait) for trait in required_traits]
601
+
602
+    return set(flags)

+ 12
- 0
releasenotes/notes/libvirt-cpu-models-selection-153e734946a7f5cc.yaml View File

@@ -0,0 +1,12 @@
1
+---
2
+features:
3
+  - |
4
+    It is now possible to specify an ordered list of CPU models in the
5
+    ``[libvirt] cpu_models`` config option. If ``[libvirt] cpu_mode``
6
+    is set to ``custom``, the libvirt driver will select the first
7
+    CPU model in this list that can provide the required feature
8
+    traits.
9
+upgrades:
10
+  - |
11
+    The ``[libvirt] cpu_model`` config option has been renamed to
12
+    ``[libvirt] cpu_models`` and now accepts a list of CPU models.

Loading…
Cancel
Save