blob: 2b1c4fb250d45e026d20955e4e29cecefd49be62 [file] [log] [blame]
lkuchlan3bd6e272018-01-25 11:16:10 +02001# Copyright 2018 Red Hat, Inc.
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
songwenping8c3dac12021-02-22 09:12:37 +080016import io
Ghanshyam Mann4346a822020-07-29 13:45:04 -050017
lkuchlan3bd6e272018-01-25 11:16:10 +020018from tempest.api.image import base
Ghanshyam Mann4346a822020-07-29 13:45:04 -050019from tempest.common import waiters
20from tempest import config
lkuchlan3bd6e272018-01-25 11:16:10 +020021from tempest.lib.common.utils import data_utils
22from tempest.lib import decorators
Ghanshyam Mannbd97ae92023-05-19 14:08:03 -050023from tempest.lib import exceptions as lib_exc
lkuchlan3bd6e272018-01-25 11:16:10 +020024
Ghanshyam Mann4346a822020-07-29 13:45:04 -050025CONF = config.CONF
26
lkuchlan3bd6e272018-01-25 11:16:10 +020027
28class BasicOperationsImagesAdminTest(base.BaseV2ImageAdminTest):
zhufle68f4352020-04-21 13:44:55 +080029 """"Test image operations about image owner"""
lkuchlan3bd6e272018-01-25 11:16:10 +020030
31 @decorators.related_bug('1420008')
32 @decorators.idempotent_id('646a6eaa-135f-4493-a0af-12583021224e')
33 def test_create_image_owner_param(self):
zhufle68f4352020-04-21 13:44:55 +080034 """Test creating image with specified owner"""
lkuchlan3bd6e272018-01-25 11:16:10 +020035 # NOTE: Create image with owner different from tenant owner by
36 # using "owner" parameter requires an admin privileges.
37 random_id = data_utils.rand_uuid_hex()
38 image = self.admin_client.create_image(
39 container_format='bare', disk_format='raw', owner=random_id)
40 self.addCleanup(self.admin_client.delete_image, image['id'])
41 image_info = self.admin_client.show_image(image['id'])
42 self.assertEqual(random_id, image_info['owner'])
lkuchlanf8ff1ff2018-02-07 11:29:54 +020043
44 @decorators.related_bug('1420008')
45 @decorators.idempotent_id('525ba546-10ef-4aad-bba1-1858095ce553')
46 def test_update_image_owner_param(self):
zhufle68f4352020-04-21 13:44:55 +080047 """Test updating image owner"""
lkuchlanf8ff1ff2018-02-07 11:29:54 +020048 random_id_1 = data_utils.rand_uuid_hex()
49 image = self.admin_client.create_image(
50 container_format='bare', disk_format='raw', owner=random_id_1)
51 self.addCleanup(self.admin_client.delete_image, image['id'])
52 created_image_info = self.admin_client.show_image(image['id'])
53
54 random_id_2 = data_utils.rand_uuid_hex()
55 self.admin_client.update_image(
56 image['id'], [dict(replace="/owner", value=random_id_2)])
57 updated_image_info = self.admin_client.show_image(image['id'])
58
59 self.assertEqual(random_id_2, updated_image_info['owner'])
60 self.assertNotEqual(created_image_info['owner'],
61 updated_image_info['owner'])
Ghanshyam Mann4346a822020-07-29 13:45:04 -050062
Maxim Sava9db106a2023-06-18 15:49:03 +030063 @decorators.idempotent_id('f6ab4aa0-035e-4664-9f2d-c57c6df50605')
64 def test_list_public_image(self):
65 """Test create image as admin and list public image as none admin"""
Martin Kopec213d0a42023-11-30 10:28:14 +010066 name = data_utils.rand_name(
67 prefix=CONF.resource_name_prefix,
68 name=self.__class__.__name__ + '-Image')
Maxim Sava9db106a2023-06-18 15:49:03 +030069 image = self.admin_client.create_image(
70 name=name,
71 container_format='bare',
72 visibility='public',
73 disk_format='raw')
74 waiters.wait_for_image_status(self.admin_client, image['id'], 'queued')
75 created_image = self.admin_client.show_image(image['id'])
76 self.assertEqual(image['id'], created_image['id'])
77 self.addCleanup(self.admin_client.delete_image, image['id'])
78
79 images_list = self.client.list_images()['images']
80 fetched_images_id = [img['id'] for img in images_list]
81 self.assertIn(image['id'], fetched_images_id)
82
Ghanshyam Mann4346a822020-07-29 13:45:04 -050083
84class ImportCopyImagesTest(base.BaseV2ImageAdminTest):
85 """Test the import copy-image operations"""
86
87 @classmethod
88 def skip_checks(cls):
89 super(ImportCopyImagesTest, cls).skip_checks()
90 if not CONF.image_feature_enabled.import_image:
91 skip_msg = (
92 "%s skipped as image import is not available" % cls.__name__)
93 raise cls.skipException(skip_msg)
94
95 @decorators.idempotent_id('9b3b644e-03d1-11eb-a036-fa163e2eaf49')
96 def test_image_copy_image_import(self):
97 """Test 'copy-image' import functionalities
98
99 Create image, import image with copy-image method and
100 verify that import succeeded.
101 """
102 available_stores = self.get_available_stores()
103 available_import_methods = self.client.info_import()[
104 'import-methods']['value']
105 # NOTE(gmann): Skip if copy-image import method and multistore
106 # are not available.
107 if ('copy-image' not in available_import_methods or
108 not available_stores):
109 raise self.skipException('Either copy-image import method or '
110 'multistore is not available')
111 uuid = data_utils.rand_uuid()
Martin Kopec213d0a42023-11-30 10:28:14 +0100112 image_name = data_utils.rand_name(
113 prefix=CONF.resource_name_prefix, name='copy-image')
Ghanshyam Mann4346a822020-07-29 13:45:04 -0500114 container_format = CONF.image.container_formats[0]
115 disk_format = CONF.image.disk_formats[0]
116 image = self.create_image(name=image_name,
117 container_format=container_format,
118 disk_format=disk_format,
119 visibility='private',
120 ramdisk_id=uuid)
121 self.assertEqual('queued', image['status'])
122
123 file_content = data_utils.random_bytes()
songwenping8c3dac12021-02-22 09:12:37 +0800124 image_file = io.BytesIO(file_content)
Ghanshyam Mann4346a822020-07-29 13:45:04 -0500125 self.client.store_image_file(image['id'], image_file)
126
127 body = self.client.show_image(image['id'])
128 self.assertEqual(image['id'], body['id'])
129 self.assertEqual(len(file_content), body.get('size'))
130 self.assertEqual('active', body['status'])
131
132 # Copy image to all the stores. In case of all_stores request
133 # glance will skip the stores where image is already available.
134 self.admin_client.image_import(image['id'], method='copy-image',
135 all_stores=True,
136 all_stores_must_succeed=False)
137
138 # Wait for copy to finished on all stores.
139 failed_stores = waiters.wait_for_image_copied_to_stores(
140 self.client, image['id'])
141 # Assert if copy is failed on any store.
142 self.assertEqual(0, len(failed_stores),
143 "Failed to copy the following stores: %s" %
144 str(failed_stores))
Ghanshyam Mannbd97ae92023-05-19 14:08:03 -0500145
146
147class ImageLocationsAdminTest(base.BaseV2ImageAdminTest):
148
149 @classmethod
150 def skip_checks(cls):
151 super(ImageLocationsAdminTest, cls).skip_checks()
152 if not CONF.image_feature_enabled.manage_locations:
153 skip_msg = (
154 "%s skipped as show_multiple_locations is not available" % (
155 cls.__name__))
156 raise cls.skipException(skip_msg)
157
158 @decorators.idempotent_id('8a648de4-b745-4c28-a7b5-20de1c3da4d2')
159 def test_delete_locations(self):
160 image = self.check_set_multiple_locations()
161 expected_remaining_loc = image['locations'][1]
162
163 self.admin_client.update_image(image['id'], [
164 dict(remove='/locations/0')])
165
166 # The image should now have only the one location we did not delete
167 image = self.client.show_image(image['id'])
168 self.assertEqual(1, len(image['locations']),
169 'Image should have one location but has %i' % (
170 len(image['locations'])))
171 self.assertEqual(expected_remaining_loc['url'],
172 image['locations'][0]['url'])
173
174 # The direct_url should now be the last remaining location
175 if 'direct_url' in image:
176 self.assertEqual(image['direct_url'], image['locations'][0]['url'])
177
178 # Removing the last location should be disallowed
179 self.assertRaises(lib_exc.Forbidden,
180 self.admin_client.update_image, image['id'], [
181 dict(remove='/locations/0')])
Maxim Savabd9cbd32023-10-17 13:13:33 +0300182
183
184class MultiStoresImagesTest(base.BaseV2ImageAdminTest, base.BaseV2ImageTest):
185 """Test importing and deleting image in multiple stores"""
186 @classmethod
187 def skip_checks(cls):
188 super(MultiStoresImagesTest, cls).skip_checks()
189 if not CONF.image_feature_enabled.import_image:
190 skip_msg = (
191 "%s skipped as image import is not available" % cls.__name__)
192 raise cls.skipException(skip_msg)
193
194 @classmethod
195 def resource_setup(cls):
196 super(MultiStoresImagesTest, cls).resource_setup()
197 cls.available_import_methods = \
198 cls.client.info_import()['import-methods']['value']
199 if not cls.available_import_methods:
200 raise cls.skipException('Server does not support '
201 'any import method')
202
203 # NOTE(pdeore): Skip if glance-direct import method and mutlistore
204 # are not enabled/configured, or only one store is configured in
205 # multiple stores setup.
206 cls.available_stores = cls.get_available_stores()
207 if ('glance-direct' not in cls.available_import_methods or
208 not len(cls.available_stores) > 1):
209 raise cls.skipException(
210 'Either glance-direct import method not present in %s or '
211 'None or only one store is '
212 'configured %s' % (cls.available_import_methods,
213 cls.available_stores))
214
215 @decorators.idempotent_id('1ecec683-41d4-4470-a0df-54969ec74514')
216 def test_delete_image_from_specific_store(self):
217 """Test delete image from specific store"""
218 # Import image to available stores
219 image, stores = self.create_and_stage_image(all_stores=True)
220 self.client.image_import(image['id'],
221 method='glance-direct',
222 all_stores=True)
223 self.addCleanup(self.admin_client.delete_image, image['id'])
224 waiters.wait_for_image_imported_to_stores(
225 self.client,
226 image['id'], stores)
227 observed_image = self.client.show_image(image['id'])
228
229 # Image will be deleted from first store
230 first_image_store_deleted = (observed_image['stores'].split(","))[0]
231 self.admin_client.delete_image_from_store(
232 observed_image['id'], first_image_store_deleted)
233 waiters.wait_for_image_deleted_from_store(
234 self.admin_client,
235 observed_image,
236 stores,
237 first_image_store_deleted)