From d4bc9a8b64cb03508856f174f3e07d8bdc143260 Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Tue, 12 Jan 2021 17:25:07 -0800 Subject: [PATCH] Test glance distributed import Glance is developing functionality to solve the problem where an image is staged against one worker and the import call arrives at the other. This adds support to test that. The dependent devstack patch sets up another glance worker in the catalog as "image_remote", which we can use to ensure that stage and import calls arrive at different workers. Change-Id: I38bafb5316ce3b8aac9b8ac11ae27110a93d3ebd Depends-On: https://review.opendev.org/c/openstack/devstack/+/770487 --- tempest/api/image/v2/test_images.py | 76 +++++++++++++++++++++++------ tempest/clients.py | 7 +++ tempest/config.py | 9 ++++ 3 files changed, 78 insertions(+), 14 deletions(-) diff --git a/tempest/api/image/v2/test_images.py b/tempest/api/image/v2/test_images.py index 59067d12a9..8dba311bae 100644 --- a/tempest/api/image/v2/test_images.py +++ b/tempest/api/image/v2/test_images.py @@ -68,16 +68,12 @@ class ImportImagesTest(base.BaseV2ImageTest): self.assertEqual('queued', image['status']) return image - @decorators.idempotent_id('32ca0c20-e16f-44ac-8590-07869c9b4cc2') - def test_image_glance_direct_import(self): - """Test 'glance-direct' import functionalities - - Create image, stage image data, import image and verify - that import succeeded. - """ - if 'glance-direct' not in self.available_import_methods: + def _require_import_method(self, method): + if method not in self.available_import_methods: raise self.skipException('Server does not support ' - 'glance-direct import method') + '%s import method' % method) + + def _stage_and_check(self): image = self._create_image() # Stage image data file_content = data_utils.random_bytes() @@ -87,9 +83,21 @@ class ImportImagesTest(base.BaseV2ImageTest): body = self.client.show_image(image['id']) self.assertEqual(image['id'], body['id']) self.assertEqual('uploading', body['status']) + return image['id'] + + @decorators.idempotent_id('32ca0c20-e16f-44ac-8590-07869c9b4cc2') + def test_image_glance_direct_import(self): + """Test 'glance-direct' import functionalities + + Create image, stage image data, import image and verify + that import succeeded. + """ + self._require_import_method('glance-direct') + + image_id = self._stage_and_check() # import image from staging to backend - self.client.image_import(image['id'], method='glance-direct') - waiters.wait_for_image_imported_to_stores(self.client, image['id']) + self.client.image_import(image_id, method='glance-direct') + waiters.wait_for_image_imported_to_stores(self.client, image_id) @decorators.idempotent_id('f6feb7a4-b04f-4706-a011-206129f83e62') def test_image_web_download_import(self): @@ -98,9 +106,8 @@ class ImportImagesTest(base.BaseV2ImageTest): Create image, import image and verify that import succeeded. """ - if 'web-download' not in self.available_import_methods: - raise self.skipException('Server does not support ' - 'web-download import method') + self._require_import_method('web-download') + image = self._create_image() # Now try to get image details body = self.client.show_image(image['id']) @@ -112,6 +119,47 @@ class ImportImagesTest(base.BaseV2ImageTest): image_uri=image_uri) waiters.wait_for_image_imported_to_stores(self.client, image['id']) + @decorators.idempotent_id('e04761a1-22af-42c2-b8bc-a34a3f12b585') + def test_remote_import(self): + """Test image import against a different worker than stage. + + This creates and stages an image against the primary API worker, + but then calls import on a secondary worker (if available) to + test that distributed image import works (i.e. proxies the import + request to the proper worker). + """ + self._require_import_method('glance-direct') + + if not CONF.image.alternate_image_endpoint: + raise self.skipException('No image_remote service to test ' + 'against') + + image_id = self._stage_and_check() + # import image from staging to backend, but on the alternate worker + self.os_primary.image_client_remote.image_import( + image_id, method='glance-direct') + waiters.wait_for_image_imported_to_stores(self.client, image_id) + + @decorators.idempotent_id('44d60544-1524-42f7-8899-315301105dd8') + def test_remote_delete(self): + """Test image delete against a different worker than stage. + + This creates and stages an image against the primary API worker, + but then calls delete on a secondary worker (if available) to + test that distributed image import works (i.e. proxies the delete + request to the proper worker). + """ + self._require_import_method('glance-direct') + + if not CONF.image.alternate_image_endpoint: + raise self.skipException('No image_remote service to test ' + 'against') + + image_id = self._stage_and_check() + # delete image from staging to backend, but on the alternate worker + self.os_primary.image_client_remote.delete_image(image_id) + self.client.wait_for_resource_deletion(image_id) + class MultiStoresImportImagesTest(base.BaseV2ImageTest): """Test importing image in multiple stores""" diff --git a/tempest/clients.py b/tempest/clients.py index 9ff02ea7e0..d2fb3ebd1e 100644 --- a/tempest/clients.py +++ b/tempest/clients.py @@ -88,6 +88,13 @@ class Manager(clients.ServiceClients): self.image_v2.NamespacePropertiesClient() self.namespace_tags_client = self.image_v2.NamespaceTagsClient() self.image_versions_client = self.image_v2.VersionsClient() + # NOTE(danms): If no alternate endpoint is configured, + # this client will work the same as the base self.images_client. + # If your test needs to know if these are different, check the + # config option to see if the alternate_image_endpoint is set. + self.image_client_remote = self.image_v2.ImagesClient( + service=CONF.image.alternate_image_endpoint, + endpoint_type=CONF.image.alternate_image_endpoint_type) def _set_compute_clients(self): self.agents_client = self.compute.AgentsClient() diff --git a/tempest/config.py b/tempest/config.py index 0df50451cd..e7dca380df 100644 --- a/tempest/config.py +++ b/tempest/config.py @@ -657,6 +657,15 @@ ImageGroup = [ choices=['public', 'admin', 'internal', 'publicURL', 'adminURL', 'internalURL'], help="The endpoint type to use for the image service."), + cfg.StrOpt('alternate_image_endpoint', + default=None, + help="Alternate endpoint name for cross-worker testing"), + cfg.StrOpt('alternate_image_endpoint_type', + default='publicURL', + choices=['public', 'admin', 'internal', + 'publicURL', 'adminURL', 'internalURL'], + help=("The endpoint type to use for the alternate image " + "service.")), cfg.StrOpt('http_image', default='http://download.cirros-cloud.net/0.3.1/' 'cirros-0.3.1-x86_64-uec.tar.gz',