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