diff --git a/nova/tests/functional/regressions/test_bug_1895696.py b/nova/tests/functional/regressions/test_bug_1895696.py new file mode 100644 index 000000000000..96ff1c28346c --- /dev/null +++ b/nova/tests/functional/regressions/test_bug_1895696.py @@ -0,0 +1,160 @@ +# Copyright 2020, Red Hat, Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import datetime + +from oslo_utils.fixture import uuidsentinel as uuids + +from nova.tests import fixtures as nova_fixtures +from nova.tests.functional.api import client +from nova.tests.functional import integrated_helpers + + +class TestNonBootableImageMeta(integrated_helpers._IntegratedTestBase): + """Regression test for bug 1895696 + + This regression test asserts the behaviour of server creation requests when + using an image with nonbootable properties either directly in the request + or to create a volume that is then booted from. + """ + + microversion = 'latest' + + def setUp(self): + super().setUp() + + # Add an image to the Glance fixture with cinder_encryption_key set + timestamp = datetime.datetime(2011, 1, 1, 1, 2, 3) + cinder_encrypted_image = { + 'id': uuids.cinder_encrypted_image_uuid, + 'name': 'cinder_encryption_key_image', + 'created_at': timestamp, + 'updated_at': timestamp, + 'deleted_at': None, + 'deleted': False, + 'status': 'active', + 'is_public': False, + 'container_format': 'ova', + 'disk_format': 'vhd', + 'size': '74185822', + 'min_ram': 0, + 'min_disk': 0, + 'protected': False, + 'visibility': 'public', + 'tags': [], + 'properties': { + 'cinder_encryption_key_id': uuids.cinder_encryption_key_id, + } + } + + self.fake_image_service.create(None, cinder_encrypted_image) + self.cinder = self.useFixture(nova_fixtures.CinderFixture(self)) + # Mock out nova.volume.cinder.API.{create,get} so that when n-api + # requests that c-api create a volume from the above image that the + # response includes cinder_encryption_key_id in the + # volume_image_metadata + cinder_encrypted_volume = { + 'status': 'available', + 'display_name': 'cinder_encrypted_volume', + 'attach_status': 'detached', + 'id': uuids.cinder_encrypted_volume_uuid, + 'multiattach': False, + 'size': 1, + 'encrypted': True, + 'volume_image_metadata': { + 'cinder_encryption_key_id': uuids.cinder_encryption_key_id + } + } + + def fake_cinder_create(self_api, context, size, name, description, + snapshot=None, image_id=None, volume_type=None, metadata=None, + availability_zone=None): + if image_id == uuids.cinder_encrypted_image_uuid: + return cinder_encrypted_volume + self.stub_out( + 'nova.volume.cinder.API.create', fake_cinder_create) + + def fake_cinder_get(self_api, context, volume_id, microversion=None): + return cinder_encrypted_volume + self.stub_out( + 'nova.volume.cinder.API.get', fake_cinder_get) + + def test_nonbootable_metadata_image_metadata(self): + """Assert behaviour when booting from an encrypted image + """ + server = self._build_server( + name='test_nonbootable_metadata_bfv_image_metadata', + image_uuid=uuids.cinder_encrypted_image_uuid, + networks='none' + ) + # NOTE(lyarwood): This should always fail as Nova will attempt to boot + # directly from this encrypted image. + ex = self.assertRaises( + client.OpenStackApiException, self.api.post_server, + {'server': server}) + self.assertEqual(400, ex.response.status_code) + self.assertIn( + "Direct booting of an image uploaded from an encrypted volume is " + "unsupported", str(ex)) + + def test_nonbootable_metadata_bfv_image_metadata(self): + """Assert behaviour when n-api creates volume using an encrypted image + """ + server = self._build_server( + name='test_nonbootable_metadata_bfv_image_metadata', + image_uuid='', networks='none' + ) + # TODO(lyarwood): Merge this into _build_server + server['block_device_mapping_v2'] = [{ + 'source_type': 'image', + 'destination_type': 'volume', + 'boot_index': 0, + 'uuid': uuids.cinder_encrypted_image_uuid, + 'volume_size': 1, + }] + + # FIXME(lyarwood) n-api should ignore cinder_encryption_key_id in the + # original image in this case and accept the request. + ex = self.assertRaises( + client.OpenStackApiException, self.api.post_server, + {'server': server}) + self.assertEqual(400, ex.response.status_code) + self.assertIn( + "Direct booting of an image uploaded from an encrypted volume is " + "unsupported", str(ex)) + + def test_nonbootable_metadata_bfv_volume_image_metadata(self): + """Assert behaviour when c-api has created volume using encrypted image + """ + server = self._build_server( + name='test_nonbootable_metadata_bfv_volume_image_metadata', + image_uuid='', networks='none' + ) + # TODO(lyarwood): Merge this into _build_server + server['block_device_mapping_v2'] = [{ + 'source_type': 'volume', + 'destination_type': 'volume', + 'boot_index': 0, + 'uuid': uuids.cinder_encrypted_volume_uuid, + }] + + # FIXME(lyarwood) n-api should ignore cinder_encryption_key_id in the + # volume volume_image_metadata in this case and accept the request. + ex = self.assertRaises( + client.OpenStackApiException, self.api.post_server, + {'server': server}) + self.assertEqual(400, ex.response.status_code) + self.assertIn( + "Direct booting of an image uploaded from an encrypted volume is " + "unsupported", str(ex))