blob: 2363ced5af581af686538d8c41043133fcafe26e [file] [log] [blame]
Peter Pentchev6ef0f072022-09-26 16:20:30 +03001From 46facc035f5fa0cfba08a6493f08e05be2023d40 Mon Sep 17 00:00:00 2001
2From: Peter Penchev <openstack-dev@storpool.com>
3Date: Thu, 9 Jun 2022 01:43:28 +0300
4Subject: [PATCH 1/7] Send the correct location URI to the Glance v2 API
5
6When uploading a volume to an image, send the new-style
7cinder://<store-id>/<volume-id> URL to the Glance API if
8image_service:store_id is present in the volume type extra specs.
9
10Closes-Bug: #1978020
11Co-Authored-By: Rajat Dhasmana <rajatdhasmana@gmail.com>
12Change-Id: I815706f691a7d1e5a0c54eb15222417008ef1f34
13---
14 cinder/tests/unit/volume/test_image.py | 2 +-
15 .../tests/unit/volume/test_volume_manager.py | 160 ++++++++++++++++++
16 cinder/volume/manager.py | 4 +-
17 ...20-glance-upload-uri-8fbc70c442ac620c.yaml | 11 ++
18 4 files changed, 175 insertions(+), 2 deletions(-)
19 create mode 100644 releasenotes/notes/bug-1978020-glance-upload-uri-8fbc70c442ac620c.yaml
20
21diff --git a/cinder/tests/unit/volume/test_image.py b/cinder/tests/unit/volume/test_image.py
22index ebc7904c4..2b1b9c392 100644
23--- a/cinder/tests/unit/volume/test_image.py
24+++ b/cinder/tests/unit/volume/test_image.py
25@@ -366,7 +366,7 @@ class CopyVolumeToImageTestCase(base.BaseVolumeTestCase):
26 def test_copy_volume_to_image_with_image_volume(self):
27 image = self._test_copy_volume_to_image_with_image_volume()
28 self.assertTrue(image['locations'][0]['url'].startswith('cinder://'))
29- image_volume_id = image['locations'][0]['url'][9:]
30+ image_volume_id = image['locations'][0]['url'].split('/')[-1]
31 # The image volume does NOT include the snapshot_id, and include the
32 # source_volid which is the uploaded-volume id.
33 vol_ref = db.volume_get(self.context, image_volume_id)
34diff --git a/cinder/tests/unit/volume/test_volume_manager.py b/cinder/tests/unit/volume/test_volume_manager.py
35index 341e7326f..9a7a50ff6 100644
36--- a/cinder/tests/unit/volume/test_volume_manager.py
37+++ b/cinder/tests/unit/volume/test_volume_manager.py
38@@ -342,3 +342,163 @@ class VolumeManagerTestCase(base.BaseVolumeTestCase):
39
40 res = manager._driver_shares_targets()
41 self.assertFalse(res)
42+
43+ @mock.patch('cinder.message.api.API.create')
44+ @mock.patch('cinder.volume.volume_utils.require_driver_initialized')
45+ @mock.patch('cinder.volume.manager.VolumeManager._clone_image_volume')
46+ @mock.patch('cinder.db.volume_metadata_update')
47+ def test_clone_image_no_volume(self,
48+ fake_update,
49+ fake_clone,
50+ fake_msg_create,
51+ fake_init):
52+ """Make sure nothing happens if no volume was created."""
53+ manager = vol_manager.VolumeManager()
54+
55+ ctx = mock.sentinel.context
56+ volume = fake_volume.fake_volume_obj(ctx)
57+ image_service = mock.MagicMock(spec=[])
58+
59+ fake_clone.return_value = None
60+
61+ image_meta = {'disk_format': 'raw', 'container_format': 'ova'}
62+ manager._clone_image_volume_and_add_location(ctx, volume,
63+ image_service, image_meta)
64+ fake_clone.assert_not_called()
65+ fake_update.assert_not_called()
66+
67+ image_meta = {'disk_format': 'qcow2', 'container_format': 'bare'}
68+ manager._clone_image_volume_and_add_location(ctx, volume,
69+ image_service, image_meta)
70+ fake_clone.assert_not_called()
71+ fake_update.assert_not_called()
72+
73+ image_meta = {'disk_format': 'raw', 'container_format': 'bare'}
74+ manager._clone_image_volume_and_add_location(ctx, volume,
75+ image_service, image_meta)
76+ fake_clone.assert_called_once_with(ctx, volume, image_meta)
77+ fake_update.assert_not_called()
78+
79+ @mock.patch('cinder.message.api.API.create')
80+ @mock.patch('cinder.objects.VolumeType.get_by_id')
81+ @mock.patch('cinder.volume.volume_utils.require_driver_initialized')
82+ @mock.patch('cinder.volume.manager.VolumeManager._clone_image_volume')
83+ @mock.patch('cinder.db.volume_metadata_update')
84+ def test_clone_image_no_store_id(self,
85+ fake_update,
86+ fake_clone,
87+ fake_msg_create,
88+ fake_volume_type_get,
89+ fake_init):
90+ """Send a cinder://<volume-id> URL if no store ID in extra specs."""
91+ manager = vol_manager.VolumeManager()
92+
93+ project_id = fake.PROJECT_ID
94+
95+ ctx = mock.MagicMock()
96+ ctx.elevated.return_value = ctx
97+ ctx.project_id = project_id
98+
99+ vol_type = fake_volume.fake_volume_type_obj(
100+ ctx,
101+ id=fake.VOLUME_TYPE_ID,
102+ name=fake.VOLUME_TYPE_NAME,
103+ extra_specs={'volume_type_backend': 'unknown'})
104+ fake_volume_type_get.return_value = vol_type
105+
106+ volume = fake_volume.fake_volume_obj(ctx,
107+ id=fake.VOLUME_ID,
108+ volume_type_id=vol_type.id)
109+
110+ image_volume_id = fake.VOLUME2_ID
111+ image_volume = fake_volume.fake_volume_obj(ctx, id=image_volume_id)
112+ url = 'cinder://%(vol)s' % {'vol': image_volume_id}
113+
114+ image_service = mock.MagicMock(spec=['add_location'])
115+ image_meta_id = fake.IMAGE_ID
116+ image_meta = {
117+ 'id': image_meta_id,
118+ 'disk_format': 'raw',
119+ 'container_format': 'bare',
120+ }
121+ image_volume_meta = {
122+ 'image_owner': project_id,
123+ 'glance_image_id': image_meta_id,
124+ }
125+
126+ fake_clone.return_value = image_volume
127+
128+ manager._clone_image_volume_and_add_location(ctx, volume,
129+ image_service, image_meta)
130+ fake_clone.assert_called_once_with(ctx, volume, image_meta)
131+ fake_update.assert_called_with(ctx, image_volume_id,
132+ image_volume_meta, False)
133+ image_service.add_location.assert_called_once_with(ctx, image_meta_id,
134+ url, {})
135+
136+ @mock.patch('cinder.message.api.API.create')
137+ @mock.patch('cinder.objects.VolumeType.get_by_id')
138+ @mock.patch('cinder.volume.volume_utils.require_driver_initialized')
139+ @mock.patch('cinder.volume.manager.VolumeManager._clone_image_volume')
140+ @mock.patch('cinder.db.volume_metadata_update')
141+ def test_clone_image_with_store_id(self,
142+ fake_update,
143+ fake_clone,
144+ fake_msg_create,
145+ fake_volume_type_get,
146+ fake_init):
147+ """Send a cinder://<store-id>/<volume-id> URL."""
148+ manager = vol_manager.VolumeManager()
149+
150+ project_id = fake.PROJECT_ID
151+
152+ ctx = mock.MagicMock()
153+ ctx.elevated.return_value = ctx
154+ ctx.project_id = project_id
155+
156+ store_id = 'muninn'
157+ vol_type = fake_volume.fake_volume_type_obj(
158+ ctx,
159+ id=fake.VOLUME_TYPE_ID,
160+ name=fake.VOLUME_TYPE_NAME,
161+ extra_specs={
162+ 'volume_type_backend': 'unknown',
163+ 'image_service:store_id': store_id,
164+ })
165+ fake_volume_type_get.return_value = vol_type
166+
167+ volume = fake_volume.fake_volume_obj(ctx,
168+ id=fake.VOLUME_ID,
169+ volume_type_id=vol_type.id)
170+
171+ image_volume_id = '42'
172+ image_volume = mock.MagicMock(spec=['id'])
173+ image_volume.id = image_volume_id
174+ url = 'cinder://%(store)s/%(vol)s' % {
175+ 'store': store_id,
176+ 'vol': image_volume_id,
177+ }
178+
179+ image_service = mock.MagicMock(spec=['add_location'])
180+ image_meta_id = fake.IMAGE_ID
181+ image_meta = {
182+ 'id': image_meta_id,
183+ 'disk_format': 'raw',
184+ 'container_format': 'bare',
185+ }
186+ image_volume_meta = {
187+ 'image_owner': project_id,
188+ 'glance_image_id': image_meta_id,
189+ }
190+
191+ fake_clone.return_value = image_volume
192+
193+ manager._clone_image_volume_and_add_location(ctx, volume,
194+ image_service, image_meta)
195+ fake_clone.assert_called_once_with(ctx, volume, image_meta)
196+ fake_update.assert_called_with(ctx, image_volume_id,
197+ image_volume_meta, False)
198+ image_service.add_location.assert_called_once_with(ctx,
199+ image_meta_id,
200+ url,
201+ {'store': store_id})
202diff --git a/cinder/volume/manager.py b/cinder/volume/manager.py
203index 57ac77931..2dcf7ae30 100644
204--- a/cinder/volume/manager.py
205+++ b/cinder/volume/manager.py
206@@ -1723,7 +1723,6 @@ class VolumeManager(manager.CleanableManager,
207 image_volume_meta,
208 False)
209
210- uri = 'cinder://%s' % image_volume.id
211 image_registered = None
212
213 # retrieve store information from extra-specs
214@@ -1732,6 +1731,9 @@ class VolumeManager(manager.CleanableManager,
215
216 if store_id:
217 location_metadata['store'] = store_id
218+ uri = 'cinder://%s/%s' % (store_id, image_volume.id)
219+ else:
220+ uri = 'cinder://%s' % image_volume.id
221
222 try:
223 image_registered = image_service.add_location(
224diff --git a/releasenotes/notes/bug-1978020-glance-upload-uri-8fbc70c442ac620c.yaml b/releasenotes/notes/bug-1978020-glance-upload-uri-8fbc70c442ac620c.yaml
225new file mode 100644
226index 000000000..04a909d3d
227--- /dev/null
228+++ b/releasenotes/notes/bug-1978020-glance-upload-uri-8fbc70c442ac620c.yaml
229@@ -0,0 +1,11 @@
230+---
231+fixes:
232+ - |
233+ `Bug #1978020 <https://bugs.launchpad.net/cinder/+bug/1978020>`_: Fixed
234+ uploading a volume to a Cinder-backed Glance image; if a store name is set
235+ in the volume type's extra specs, it must also be sent to Glance as part of
236+ the new image location URI.
237+ Please note that while the `image_service:store_id` extra spec is
238+ validated when it is set for the volume type, it is not validated later;
239+ it is the operator's responsibility to make sure that the Glance store is
240+ not renamed or removed or that the volume types are updated accordingly.
241--
2422.35.1
243