Browse Source

Merge "Ensure non-q35 machine type is not used when booting with SEV"

changes/10/680810/1
Zuul 1 week ago
parent
commit
0a8444e903

+ 1
- 0
nova/api/openstack/compute/servers.py View File

@@ -75,6 +75,7 @@ INVALID_FLAVOR_IMAGE_EXCEPTIONS = (
75 75
     exception.InvalidCPUAllocationPolicy,
76 76
     exception.InvalidCPUThreadAllocationPolicy,
77 77
     exception.InvalidEmulatorThreadsPolicy,
78
+    exception.InvalidMachineType,
78 79
     exception.InvalidNUMANodesNumber,
79 80
     exception.InvalidRequest,
80 81
     exception.MemoryPageSizeForbidden,

+ 5
- 0
nova/exception.py View File

@@ -1977,6 +1977,11 @@ class InvalidHypervisorVirtType(Invalid):
1977 1977
                 "recognised")
1978 1978
 
1979 1979
 
1980
+class InvalidMachineType(Invalid):
1981
+    msg_fmt = _("Machine type '%(mtype)s' is not compatible with image "
1982
+                "%(image_name)s (%(image_id)s): %(reason)s")
1983
+
1984
+
1980 1985
 class InvalidVirtualMachineMode(Invalid):
1981 1986
     msg_fmt = _("Virtual machine mode '%(vmmode)s' is not recognised")
1982 1987
 

+ 9
- 0
nova/tests/unit/api/openstack/compute/test_serversV21.py View File

@@ -6140,6 +6140,15 @@ class ServersControllerCreateTest(test.TestCase):
6140 6140
                           self.controller.create,
6141 6141
                           self.req, body=self.body)
6142 6142
 
6143
+    @mock.patch('nova.virt.hardware.get_mem_encryption_constraint',
6144
+                side_effect=exception.InvalidMachineType(
6145
+                    message="fake conflict reason"))
6146
+    def test_create_instance_raise_invalid_machine_type(
6147
+            self, mock_conflict):
6148
+        self.assertRaises(webob.exc.HTTPBadRequest,
6149
+                          self.controller.create,
6150
+                          self.req, body=self.body)
6151
+
6143 6152
     @mock.patch('nova.virt.hardware.numa_get_constraints',
6144 6153
                 side_effect=exception.ImageCPUPinningForbidden())
6145 6154
     def test_create_instance_raise_image_cpu_pinning_forbidden(

+ 6
- 0
nova/tests/unit/scheduler/test_utils.py View File

@@ -1317,7 +1317,9 @@ class TestEncryptedMemoryTranslation(TestUtilsBase):
1317 1317
                 'hw:mem_encryption extra spec',
1318 1318
                 {'hw:mem_encryption': extra_spec},
1319 1319
                 image=objects.ImageMeta(
1320
+                    id='005249be-3c2f-4351-9df7-29bb13c21b14',
1320 1321
                     properties=objects.ImageMetaProps(
1322
+                        hw_machine_type='q35',
1321 1323
                         hw_firmware_type='uefi'))
1322 1324
             )
1323 1325
 
@@ -1327,8 +1329,10 @@ class TestEncryptedMemoryTranslation(TestUtilsBase):
1327 1329
                 'hw_mem_encryption image property',
1328 1330
                 {},
1329 1331
                 image=objects.ImageMeta(
1332
+                    id='005249be-3c2f-4351-9df7-29bb13c21b14',
1330 1333
                     name=self.image_name,
1331 1334
                     properties=objects.ImageMetaProps(
1335
+                        hw_machine_type='q35',
1332 1336
                         hw_firmware_type='uefi',
1333 1337
                         hw_mem_encryption=image_prop))
1334 1338
             )
@@ -1341,8 +1345,10 @@ class TestEncryptedMemoryTranslation(TestUtilsBase):
1341 1345
                     'hw_mem_encryption image property',
1342 1346
                     {'hw:mem_encryption': extra_spec},
1343 1347
                     image=objects.ImageMeta(
1348
+                        id='005249be-3c2f-4351-9df7-29bb13c21b14',
1344 1349
                         name=self.image_name,
1345 1350
                         properties=objects.ImageMetaProps(
1351
+                            hw_machine_type='q35',
1346 1352
                             hw_firmware_type='uefi',
1347 1353
                             hw_mem_encryption=image_prop))
1348 1354
                 )

+ 91
- 32
nova/tests/unit/virt/test_hardware.py View File

@@ -3798,32 +3798,50 @@ class MemEncryptionFlavorImageConflictTestCase(test.NoDBTestCase):
3798 3798
                 )
3799 3799
 
3800 3800
 
3801
-class MemEncryptionRequestedWithoutUEFITestCase(test.NoDBTestCase):
3801
+class MemEncryptionRequestedInvalidImagePropsTestCase(test.NoDBTestCase):
3802 3802
     flavor_name = 'm1.faketiny'
3803 3803
     image_name = 'fakecirros'
3804
+    image_id = '7ec4448e-f3fd-44b1-b172-9a7980f0f29f'
3804 3805
 
3805
-    def _test_encrypted_memory_support_no_uefi(self, extra_spec, image_prop,
3806
-                                               requesters):
3806
+    def _test_encrypted_memory_support_raises(self, enc_extra_spec,
3807
+                                              enc_image_prop, image_props,
3808
+                                              error_data):
3807 3809
         extra_specs = {}
3808
-        if extra_spec:
3809
-            extra_specs['hw:mem_encryption'] = extra_spec
3810
+        if enc_extra_spec:
3811
+            extra_specs['hw:mem_encryption'] = enc_extra_spec
3810 3812
         flavor = objects.Flavor(name=self.flavor_name, extra_specs=extra_specs)
3813
+        if enc_image_prop:
3814
+            image_props['hw_mem_encryption'] = enc_image_prop
3811 3815
         image_meta = fake_image_obj(
3812
-            {'name': self.image_name}, {'hw_firmware_type': 'bios'},
3813
-            {'hw_mem_encryption': True} if image_prop else {})
3814
-        error = (
3815
-            "Memory encryption requested by %(requesters)s but image "
3816
-            "%(image_name)s doesn't have 'hw_firmware_type' property "
3817
-            "set to 'uefi'"
3818
-        )
3819
-        exc = self.assertRaises(
3820
-            exception.FlavorImageConflict,
3821
-            hw.get_mem_encryption_constraint,
3822
-            flavor, image_meta
3823
-        )
3816
+            {'id': self.image_id, 'name': self.image_name},
3817
+            {}, image_props)
3818
+        exc = self.assertRaises(self.expected_exception,
3819
+                                hw.get_mem_encryption_constraint,
3820
+                                flavor, image_meta)
3821
+        self.assertEqual(self.expected_error % error_data, str(exc))
3822
+
3823
+
3824
+class MemEncryptionRequestedWithoutUEFITestCase(
3825
+        MemEncryptionRequestedInvalidImagePropsTestCase):
3826
+    expected_exception = exception.FlavorImageConflict
3827
+    expected_error = (
3828
+        "Memory encryption requested by %(requesters)s but image "
3829
+        "%(image_name)s doesn't have 'hw_firmware_type' property "
3830
+        "set to 'uefi'"
3831
+    )
3832
+
3833
+    def _test_encrypted_memory_support_no_uefi(self, enc_extra_spec,
3834
+                                               enc_image_prop, requesters):
3824 3835
         error_data = {'requesters': requesters,
3825 3836
                       'image_name': self.image_name}
3826
-        self.assertEqual(error % error_data, str(exc))
3837
+        for image_props in ({},
3838
+                            {'hw_machine_type': 'q35'},
3839
+                            {'hw_firmware_type': 'bios'},
3840
+                            {'hw_machine_type': 'q35',
3841
+                             'hw_firmware_type': 'bios'}):
3842
+            self._test_encrypted_memory_support_raises(enc_extra_spec,
3843
+                                                       enc_image_prop,
3844
+                                                       image_props, error_data)
3827 3845
 
3828 3846
     def test_flavor_requires_encrypted_memory_support_no_uefi(self):
3829 3847
         for extra_spec in ('1', 'true', 'True'):
@@ -3847,28 +3865,73 @@ class MemEncryptionRequestedWithoutUEFITestCase(test.NoDBTestCase):
3847 3865
                     % (self.flavor_name, self.image_name))
3848 3866
 
3849 3867
 
3868
+class MemEncryptionRequestedWithInvalidMachineTypeTestCase(
3869
+       MemEncryptionRequestedInvalidImagePropsTestCase):
3870
+    expected_exception = exception.InvalidMachineType
3871
+    expected_error = (
3872
+        "Machine type '%(mtype)s' is not compatible with image %(image_name)s "
3873
+        "(%(image_id)s): q35 type is required for SEV to work")
3874
+
3875
+    def _test_encrypted_memory_support_pc(self, enc_extra_spec,
3876
+                                              enc_image_prop):
3877
+        error_data = {'image_id': self.image_id,
3878
+                      'image_name': self.image_name,
3879
+                      'mtype': 'pc'}
3880
+        image_props = {'hw_firmware_type': 'uefi',
3881
+                       'hw_machine_type': 'pc'}
3882
+        self._test_encrypted_memory_support_raises(enc_extra_spec,
3883
+                                                   enc_image_prop,
3884
+                                                   image_props, error_data)
3885
+
3886
+    def test_flavor_requires_encrypted_memory_support_pc(self):
3887
+        for extra_spec in ('1', 'true', 'True'):
3888
+            self._test_encrypted_memory_support_pc(extra_spec, None)
3889
+
3890
+    def test_image_requires_encrypted_memory_support_pc(self):
3891
+        for image_prop in ('1', 'true', 'True'):
3892
+            self._test_encrypted_memory_support_pc(None, image_prop)
3893
+
3894
+    def test_flavor_image_require_encrypted_memory_support_pc(self):
3895
+        for extra_spec in ('1', 'true', 'True'):
3896
+            for image_prop in ('1', 'true', 'True'):
3897
+                self._test_encrypted_memory_support_pc(
3898
+                    extra_spec, image_prop)
3899
+
3900
+
3850 3901
 class MemEncryptionRequiredTestCase(test.NoDBTestCase):
3851 3902
     flavor_name = "m1.faketiny"
3852 3903
     image_name = 'fakecirros'
3904
+    image_id = '8a71a380-be23-47eb-9e72-9998586a0268'
3853 3905
 
3854 3906
     @mock.patch.object(hw, 'LOG')
3855 3907
     def _test_encrypted_memory_support_required(self, extra_specs,
3856 3908
                                                 image_props,
3857 3909
                                                 requesters, mock_log):
3858
-        flavor = objects.Flavor(name=self.flavor_name, extra_specs=extra_specs)
3859
-        image_meta = objects.ImageMeta(name=self.image_name,
3860
-                                       properties=image_props)
3910
+        image_props['hw_firmware_type'] = 'uefi'
3911
+
3912
+        def _test_get_mem_encryption_constraint():
3913
+            flavor = objects.Flavor(name=self.flavor_name,
3914
+                                    extra_specs=extra_specs)
3915
+            image_meta = objects.ImageMeta.from_dict({
3916
+                'id': self.image_id,
3917
+                'name': self.image_name,
3918
+                'properties': image_props})
3919
+            self.assertTrue(hw.get_mem_encryption_constraint(flavor,
3920
+                                                             image_meta))
3921
+            mock_log.debug.assert_has_calls([
3922
+                mock.call("Memory encryption requested by %s", requesters)
3923
+            ])
3861 3924
 
3862
-        self.assertTrue(hw.get_mem_encryption_constraint(flavor, image_meta))
3863
-        mock_log.debug.assert_has_calls([
3864
-            mock.call("Memory encryption requested by %s", requesters)
3865
-        ])
3925
+        _test_get_mem_encryption_constraint()
3926
+        for mtype in ('q35', 'pc-q35-2.1'):
3927
+            image_props['hw_machine_type'] = mtype
3928
+            _test_get_mem_encryption_constraint()
3866 3929
 
3867 3930
     def test_require_encrypted_memory_support_extra_spec(self):
3868 3931
         for extra_spec in ('1', 'true', 'True'):
3869 3932
             self._test_encrypted_memory_support_required(
3870 3933
                 {'hw:mem_encryption': extra_spec},
3871
-                objects.ImageMetaProps(hw_firmware_type='uefi'),
3934
+                {},
3872 3935
                 "hw:mem_encryption extra spec in %s flavor" % self.flavor_name
3873 3936
             )
3874 3937
 
@@ -3876,9 +3939,7 @@ class MemEncryptionRequiredTestCase(test.NoDBTestCase):
3876 3939
         for image_prop in ('1', 'true', 'True'):
3877 3940
             self._test_encrypted_memory_support_required(
3878 3941
                 {},
3879
-                objects.ImageMetaProps(
3880
-                    hw_mem_encryption=image_prop,
3881
-                    hw_firmware_type='uefi'),
3942
+                {'hw_mem_encryption': image_prop},
3882 3943
                 "hw_mem_encryption property of image %s" % self.image_name
3883 3944
             )
3884 3945
 
@@ -3887,9 +3948,7 @@ class MemEncryptionRequiredTestCase(test.NoDBTestCase):
3887 3948
             for image_prop in ('1', 'true', 'True'):
3888 3949
                 self._test_encrypted_memory_support_required(
3889 3950
                     {'hw:mem_encryption': extra_spec},
3890
-                    objects.ImageMetaProps(
3891
-                        hw_mem_encryption=image_prop,
3892
-                        hw_firmware_type='uefi'),
3951
+                    {'hw_mem_encryption': image_prop},
3893 3952
                     "hw:mem_encryption extra spec in %s flavor and "
3894 3953
                     "hw_mem_encryption property of image %s" %
3895 3954
                     (self.flavor_name, self.image_name)

+ 42
- 1
nova/virt/hardware.py View File

@@ -1153,9 +1153,17 @@ def get_mem_encryption_constraint(flavor, image_meta):
1153 1153
         2) the flavor and/or image request memory encryption, but the
1154 1154
            image is missing hw_firmware_type=uefi
1155 1155
 
1156
+        3) the flavor and/or image request memory encryption, but the
1157
+           machine type is set to a value which does not contain 'q35'
1158
+
1159
+    This is called from the API layer, so get_machine_type() cannot be
1160
+    called since it relies on being run from the compute node in order
1161
+    to retrieve CONF.libvirt.hw_machine_type.
1162
+
1156 1163
     :param instance_type: Flavor object
1157 1164
     :param image: an ImageMeta object
1158 1165
     :raises: nova.exception.FlavorImageConflict
1166
+    :raises: nova.exception.InvalidMachineType
1159 1167
     :returns: boolean indicating whether encryption of guest memory
1160 1168
     was requested
1161 1169
     """
@@ -1188,6 +1196,7 @@ def get_mem_encryption_constraint(flavor, image_meta):
1188 1196
                           image_meta.name)
1189 1197
 
1190 1198
     _check_mem_encryption_uses_uefi_image(requesters, image_meta)
1199
+    _check_mem_encryption_machine_type(image_meta)
1191 1200
 
1192 1201
     LOG.debug("Memory encryption requested by %s", " and ".join(requesters))
1193 1202
     return True
@@ -1215,7 +1224,7 @@ def _check_for_mem_encryption_requirement_conflicts(
1215 1224
 
1216 1225
 
1217 1226
 def _check_mem_encryption_uses_uefi_image(requesters, image_meta):
1218
-    if image_meta.properties.hw_firmware_type == 'uefi':
1227
+    if image_meta.properties.get('hw_firmware_type') == 'uefi':
1219 1228
         return
1220 1229
 
1221 1230
     emsg = _(
@@ -1227,6 +1236,38 @@ def _check_mem_encryption_uses_uefi_image(requesters, image_meta):
1227 1236
     raise exception.FlavorImageConflict(emsg % data)
1228 1237
 
1229 1238
 
1239
+def _check_mem_encryption_machine_type(image_meta):
1240
+    # NOTE(aspiers): As explained in the SEV spec, SEV needs a q35
1241
+    # machine type in order to bind all the virtio devices to the PCIe
1242
+    # bridge so that they use virtio 1.0 and not virtio 0.9, since
1243
+    # QEMU's iommu_platform feature was added in virtio 1.0 only:
1244
+    #
1245
+    # http://specs.openstack.org/openstack/nova-specs/specs/train/approved/amd-sev-libvirt-support.html
1246
+    #
1247
+    # So if the image explicitly requests a machine type which is not
1248
+    # in the q35 family, raise an exception.
1249
+    #
1250
+    # Note that this check occurs at API-level, therefore we can't
1251
+    # check here what value of CONF.libvirt.hw_machine_type may have
1252
+    # been configured on the compute node.
1253
+    mach_type = image_meta.properties.get('hw_machine_type')
1254
+
1255
+    # If hw_machine_type is not specified on the image and is not
1256
+    # configured correctly on SEV compute nodes, then a separate check
1257
+    # in the driver will catch that and potentially retry on other
1258
+    # compute nodes.
1259
+    if mach_type is None:
1260
+        return
1261
+
1262
+    # Could be something like pc-q35-2.11 if a specific version of the
1263
+    # machine type is required, so do substring matching.
1264
+    if 'q35' not in mach_type:
1265
+        raise exception.InvalidMachineType(
1266
+            mtype=mach_type,
1267
+            image_id=image_meta.id, image_name=image_meta.name,
1268
+            reason=_("q35 type is required for SEV to work"))
1269
+
1270
+
1230 1271
 def _get_numa_pagesize_constraint(flavor, image_meta):
1231 1272
     """Return the requested memory page size
1232 1273
 

Loading…
Cancel
Save