blob: 40753e722bc4037aa23029143e7c77b6cbdefcab [file] [log] [blame]
Peter Pentchev6ef0f072022-09-26 16:20:30 +03001From 6809a2569d281eb6217edc3b7ce9663a996186fb Mon Sep 17 00:00:00 2001
2From: Peter Penchev <openstack-dev@storpool.com>
3Date: Wed, 22 Jun 2022 10:04:31 +0300
4Subject: [PATCH 2/7] Add the clone_across_pools driver capability
5
6Let drivers declare that they can clone a volume into a different pool and
7relax the checks when creating a volume from an image (both with and
8without the image cache).
9
10Change-Id: Idbac4dae00f9fa03fb14547a204666721bf67d96
11Implements: blueprint clone-across-pools
12---
13 cinder/image/cache.py | 11 ++-
14 cinder/tests/unit/image/test_cache.py | 14 ++--
15 .../volume/flows/test_create_volume_flow.py | 73 ++++++++++++++++++
16 cinder/volume/flows/manager/create_volume.py | 8 +-
17 cinder/volume/manager.py | 3 +-
18 doc/source/reference/support-matrix.ini | 76 +++++++++++++++++++
19 6 files changed, 172 insertions(+), 13 deletions(-)
20
21diff --git a/cinder/image/cache.py b/cinder/image/cache.py
22index 6d30210a4..a79451bf2 100644
23--- a/cinder/image/cache.py
24+++ b/cinder/image/cache.py
25@@ -34,11 +34,13 @@ class ImageVolumeCache(object):
26 db,
27 volume_api,
28 max_cache_size_gb: int = 0,
29- max_cache_size_count: int = 0):
30+ max_cache_size_count: int = 0,
31+ clone_across_pools: bool = False):
32 self.db = db
33 self.volume_api = volume_api
34 self.max_cache_size_gb = int(max_cache_size_gb)
35 self.max_cache_size_count = int(max_cache_size_count)
36+ self.clone_across_pools = bool(clone_across_pools)
37 self.notifier = rpc.get_notifier('volume', CONF.host)
38
39 def get_by_image_volume(self,
40@@ -55,11 +57,12 @@ class ImageVolumeCache(object):
41 self._notify_cache_eviction(context, cache_entry['image_id'],
42 cache_entry['host'])
43
44- @staticmethod
45- def _get_query_filters(volume_ref: objects.Volume) -> dict:
46+ def _get_query_filters(self, volume_ref: objects.Volume) -> dict:
47 if volume_ref.is_clustered:
48 return {'cluster_name': volume_ref.cluster_name}
49- return {'host': volume_ref.host}
50+ if not self.clone_across_pools:
51+ return {'host': volume_ref.host}
52+ return {}
53
54 def get_entry(self,
55 context: context.RequestContext,
56diff --git a/cinder/tests/unit/image/test_cache.py b/cinder/tests/unit/image/test_cache.py
57index c3aba9b28..b8b704e0d 100644
58--- a/cinder/tests/unit/image/test_cache.py
59+++ b/cinder/tests/unit/image/test_cache.py
60@@ -42,11 +42,12 @@ class ImageVolumeCacheTestCase(test.TestCase):
61 self.volume.update(vol_params)
62 self.volume_ovo = objects.Volume(self.context, **vol_params)
63
64- def _build_cache(self, max_gb=0, max_count=0):
65+ def _build_cache(self, max_gb=0, max_count=0, clone_across_pools=False):
66 cache = image_cache.ImageVolumeCache(self.mock_db,
67 self.mock_volume_api,
68 max_gb,
69- max_count)
70+ max_count,
71+ clone_across_pools)
72 cache.notifier = self.notifier
73 return cache
74
75@@ -91,9 +92,10 @@ class ImageVolumeCacheTestCase(test.TestCase):
76 self.assertEqual(entry['image_id'], msg['payload']['image_id'])
77 self.assertEqual(1, len(self.notifier.notifications))
78
79- @ddt.data(True, False)
80- def test_get_entry(self, clustered):
81- cache = self._build_cache()
82+ @ddt.data((True, True), (True, False), (False, True), (False, False))
83+ @ddt.unpack
84+ def test_get_entry(self, clustered, clone_across_pools):
85+ cache = self._build_cache(clone_across_pools=clone_across_pools)
86 entry = self._build_entry()
87 image_meta = {
88 'is_public': True,
89@@ -107,7 +109,7 @@ class ImageVolumeCacheTestCase(test.TestCase):
90 image_volume_cache_get_and_update_last_used.return_value) = entry
91 if not clustered:
92 self.volume_ovo.cluster_name = None
93- expect = {'host': self.volume.host}
94+ expect = {} if clone_across_pools else {'host': self.volume.host}
95 else:
96 expect = {'cluster_name': self.volume.cluster_name}
97 found_entry = cache.get_entry(self.context,
98diff --git a/cinder/tests/unit/volume/flows/test_create_volume_flow.py b/cinder/tests/unit/volume/flows/test_create_volume_flow.py
99index 5b4ddb35f..83880a9f9 100644
100--- a/cinder/tests/unit/volume/flows/test_create_volume_flow.py
101+++ b/cinder/tests/unit/volume/flows/test_create_volume_flow.py
102@@ -1060,6 +1060,7 @@ class CreateVolumeFlowManagerTestCase(test.TestCase):
103 self, volume_get_by_id, vol_update, rekey_vol, cleanup_cg):
104 fake_db = mock.MagicMock()
105 fake_driver = mock.MagicMock()
106+ fake_driver.capabilities = {}
107 fake_volume_manager = mock.MagicMock()
108 fake_manager = create_volume_manager.CreateVolumeFromSpecTask(
109 fake_volume_manager, fake_db, fake_driver)
110@@ -1085,6 +1086,7 @@ class CreateVolumeFlowManagerTestCase(test.TestCase):
111 handle_bootable, cleanup_cg):
112 fake_db = mock.MagicMock()
113 fake_driver = mock.MagicMock()
114+ fake_driver.capabilities = {}
115 fake_volume_manager = mock.MagicMock()
116 fake_manager = create_volume_manager.CreateVolumeFromSpecTask(
117 fake_volume_manager, fake_db, fake_driver)
118@@ -1110,6 +1112,7 @@ class CreateVolumeFlowManagerTestCase(test.TestCase):
119 mock_cleanup_cg):
120 fake_db = mock.MagicMock()
121 fake_driver = mock.MagicMock()
122+ fake_driver.capabilities = {}
123 fake_volume_manager = mock.MagicMock()
124 fake_manager = create_volume_manager.CreateVolumeFromSpecTask(
125 fake_volume_manager, fake_db, fake_driver)
126@@ -1146,6 +1149,7 @@ class CreateVolumeFlowManagerTestCase(test.TestCase):
127 mock_cleanup_cg):
128 fake_db = mock.MagicMock()
129 fake_driver = mock.MagicMock()
130+ fake_driver.capabilities = {}
131 fake_volume_manager = mock.MagicMock()
132 fake_cache = mock.MagicMock()
133 fake_manager = create_volume_manager.CreateVolumeFromSpecTask(
134@@ -1194,6 +1198,7 @@ class CreateVolumeFlowManagerTestCase(test.TestCase):
135 mock_cleanup_cg):
136 fake_db = mock.MagicMock()
137 fake_driver = mock.MagicMock()
138+ fake_driver.capabilities = {}
139 fake_volume_manager = mock.MagicMock()
140 fake_manager = create_volume_manager.CreateVolumeFromSpecTask(
141 fake_volume_manager, fake_db, fake_driver)
142@@ -1252,6 +1257,7 @@ class CreateVolumeFlowManagerTestCase(test.TestCase):
143 driver_error):
144 fake_db = mock.MagicMock()
145 fake_driver = mock.MagicMock()
146+ fake_driver.capabilities = {}
147 fake_volume_manager = mock.MagicMock()
148 backup_host = 'host@backend#pool'
149 fake_manager = create_volume_manager.CreateVolumeFromSpecTask(
150@@ -1291,6 +1297,7 @@ class CreateVolumeFlowManagerTestCase(test.TestCase):
151 def test_create_drive_error(self, mock_message_create):
152 fake_db = mock.MagicMock()
153 fake_driver = mock.MagicMock()
154+ fake_driver.capabilities = {}
155 fake_volume_manager = mock.MagicMock()
156 fake_manager = create_volume_manager.CreateVolumeFromSpecTask(
157 fake_volume_manager, fake_db, fake_driver)
158@@ -1492,6 +1499,7 @@ class CreateVolumeFlowManagerGlanceCinderBackendCase(test.TestCase):
159 spec=utils.get_file_spec())
160 fake_db = mock.MagicMock()
161 fake_driver = mock.MagicMock()
162+ fake_driver.capabilities = {}
163 fake_manager = create_volume_manager.CreateVolumeFromSpecTask(
164 mock.MagicMock(), fake_db, fake_driver)
165 fake_image_service = fake_image.FakeImageService()
166@@ -1518,6 +1526,7 @@ class CreateVolumeFlowManagerGlanceCinderBackendCase(test.TestCase):
167 'cinder_encryption_key_id': None}
168
169 fake_driver.clone_image.return_value = (None, False)
170+ fake_db.volume_get_all.return_value = []
171 fake_db.volume_get_all_by_host.return_value = [image_volume]
172
173 fake_manager._create_from_image(self.ctxt,
174@@ -1536,6 +1545,69 @@ class CreateVolumeFlowManagerGlanceCinderBackendCase(test.TestCase):
175 self.assertFalse(fake_driver.create_cloned_volume.called)
176 mock_cleanup_cg.assert_called_once_with(volume)
177
178+ @mock.patch('cinder.volume.flows.manager.create_volume.'
179+ 'CreateVolumeFromSpecTask.'
180+ '_cleanup_cg_in_volume')
181+ @mock.patch('cinder.image.image_utils.TemporaryImages.fetch')
182+ @mock.patch('cinder.volume.flows.manager.create_volume.'
183+ 'CreateVolumeFromSpecTask.'
184+ '_handle_bootable_volume_glance_meta')
185+ @mock.patch('cinder.image.image_utils.qemu_img_info')
186+ def test_create_from_image_across(self, mock_qemu_info, handle_bootable,
187+ mock_fetch_img, mock_cleanup_cg,
188+ format='raw', owner=None,
189+ location=True):
190+ self.flags(allowed_direct_url_schemes=['cinder'])
191+ mock_fetch_img.return_value = mock.MagicMock(
192+ spec=utils.get_file_spec())
193+ fake_db = mock.MagicMock()
194+ fake_driver = mock.MagicMock()
195+ fake_driver.capabilities = {'clone_across_pools': True}
196+ fake_manager = create_volume_manager.CreateVolumeFromSpecTask(
197+ mock.MagicMock(), fake_db, fake_driver)
198+ fake_image_service = fake_image.FakeImageService()
199+
200+ volume = fake_volume.fake_volume_obj(self.ctxt,
201+ host='host@backend#pool')
202+ image_volume = fake_volume.fake_volume_obj(self.ctxt,
203+ volume_metadata={})
204+ image_id = fakes.IMAGE_ID
205+ image_info = imageutils.QemuImgInfo()
206+ image_info.virtual_size = '1073741824'
207+ mock_qemu_info.return_value = image_info
208+
209+ url = 'cinder://%s' % image_volume['id']
210+ image_location = None
211+ if location:
212+ image_location = (url, [{'url': url, 'metadata': {}}])
213+ image_meta = {'id': image_id,
214+ 'container_format': 'bare',
215+ 'disk_format': format,
216+ 'size': 1024,
217+ 'owner': owner or self.ctxt.project_id,
218+ 'virtual_size': None,
219+ 'cinder_encryption_key_id': None}
220+
221+ fake_driver.clone_image.return_value = (None, False)
222+ fake_db.volume_get_all.return_value = [image_volume]
223+ fake_db.volume_get_all_by_host.return_value = []
224+
225+ fake_manager._create_from_image(self.ctxt,
226+ volume,
227+ image_location,
228+ image_id,
229+ image_meta,
230+ fake_image_service)
231+ if format == 'raw' and not owner and location:
232+ fake_driver.create_cloned_volume.assert_called_once_with(
233+ volume, image_volume)
234+ handle_bootable.assert_called_once_with(self.ctxt, volume,
235+ image_id=image_id,
236+ image_meta=image_meta)
237+ else:
238+ self.assertFalse(fake_driver.create_cloned_volume.called)
239+ mock_cleanup_cg.assert_called_once_with(volume)
240+
241 LEGACY_URI = 'cinder://%s' % fakes.VOLUME_ID
242 MULTISTORE_URI = 'cinder://fake-store/%s' % fakes.VOLUME_ID
243
244@@ -1562,6 +1634,7 @@ class CreateVolumeFlowManagerGlanceCinderBackendCase(test.TestCase):
245 spec=utils.get_file_spec())
246 fake_db = mock.MagicMock()
247 fake_driver = mock.MagicMock()
248+ fake_driver.capabilities = {}
249 fake_manager = create_volume_manager.CreateVolumeFromSpecTask(
250 mock.MagicMock(), fake_db, fake_driver)
251 fake_image_service = fake_image.FakeImageService()
252diff --git a/cinder/volume/flows/manager/create_volume.py b/cinder/volume/flows/manager/create_volume.py
253index 0ae3cb59d..45d06ebb9 100644
254--- a/cinder/volume/flows/manager/create_volume.py
255+++ b/cinder/volume/flows/manager/create_volume.py
256@@ -741,8 +741,12 @@ class CreateVolumeFromSpecTask(flow_utils.CinderTask):
257 urls = list(set([direct_url]
258 + [loc.get('url') for loc in locations or []]))
259 image_volume_ids = self._extract_cinder_ids(urls)
260- image_volumes = self.db.volume_get_all_by_host(
261- context, volume['host'], filters={'id': image_volume_ids})
262+ if self.driver.capabilities.get('clone_across_pools'):
263+ image_volumes = self.db.volume_get_all(
264+ context, filters={'id': image_volume_ids})
265+ else:
266+ image_volumes = self.db.volume_get_all_by_host(
267+ context, volume['host'], filters={'id': image_volume_ids})
268
269 for image_volume in image_volumes:
270 # For the case image volume is stored in the service tenant,
271diff --git a/cinder/volume/manager.py b/cinder/volume/manager.py
272index 2dcf7ae30..79a0ae9a2 100644
273--- a/cinder/volume/manager.py
274+++ b/cinder/volume/manager.py
275@@ -356,7 +356,8 @@ class VolumeManager(manager.CleanableManager,
276 self.db,
277 cinder_volume.API(),
278 max_cache_size,
279- max_cache_entries
280+ max_cache_entries,
281+ self.driver.capabilities.get('clone_across_pools', False)
282 )
283 LOG.info('Image-volume cache enabled for host %(host)s.',
284 {'host': self.host})
285diff --git a/doc/source/reference/support-matrix.ini b/doc/source/reference/support-matrix.ini
286index 384a633da..d01ecc15c 100644
287--- a/doc/source/reference/support-matrix.ini
288+++ b/doc/source/reference/support-matrix.ini
289@@ -996,3 +996,79 @@ driver.win_iscsi=missing
290 driver.win_smb=missing
291 driver.yadro=complete
292 driver.zadara=missing
293+
294+[operation.clone_across_pools]
295+title=Clone a volume into a different pool
296+status=optional
297+notes=Vendor drivers that support cloning a volume into a different
298+ storage pool, e.g. when creating a volume from a Cinder-backed
299+ Glance image.
300+driver.datacore=missing
301+driver.datera=missing
302+driver.dell_emc_powermax=missing
303+driver.dell_emc_powerstore=missing
304+driver.dell_emc_powerstore_nfs=missing
305+driver.dell_emc_powervault=missing
306+driver.dell_emc_sc=missing
307+driver.dell_emc_unity=missing
308+driver.dell_emc_vmax_af=missing
309+driver.dell_emc_vmax_3=missing
310+driver.dell_emc_vnx=missing
311+driver.dell_emc_powerflex=missing
312+driver.dell_emc_xtremio=missing
313+driver.fujitsu_eternus=missing
314+driver.hitachi_vsp=missing
315+driver.hpe_3par=missing
316+driver.hpe_msa=missing
317+driver.hpe_nimble=missing
318+driver.huawei_t_v1=missing
319+driver.huawei_t_v2=missing
320+driver.huawei_v3=missing
321+driver.huawei_f_v3=missing
322+driver.huawei_f_v5=missing
323+driver.huawei_v5=missing
324+driver.huawei_18000=missing
325+driver.huawei_dorado=missing
326+driver.huawei_fusionstorage=missing
327+driver.infinidat=missing
328+driver.ibm_ds8k=missing
329+driver.ibm_flashsystem=missing
330+driver.ibm_gpfs=missing
331+driver.ibm_storwize=missing
332+driver.ibm_xiv=missing
333+driver.infortrend=missing
334+driver.inspur=missing
335+driver.inspur_as13000=missing
336+driver.kaminario=missing
337+driver.kioxia_kumoscale=missing
338+driver.lenovo=missing
339+driver.lightbits_lightos=missing
340+driver.linbit_linstor=missing
341+driver.lvm=missing
342+driver.macrosan=missing
343+driver.nec=missing
344+driver.nec_v=missing
345+driver.netapp_ontap=missing
346+driver.netapp_solidfire=missing
347+driver.nexenta=missing
348+driver.nfs=missing
349+driver.opene_joviandss=missing
350+driver.prophetstor=missing
351+driver.pure=missing
352+driver.qnap=missing
353+driver.quobyte=missing
354+driver.rbd=missing
355+driver.rbd_iscsi=missing
356+driver.sandstone=missing
357+driver.seagate=missing
358+driver.storpool=missing
359+driver.synology=missing
360+driver.toyou_netstor=missing
361+driver.vrtsaccess=missing
362+driver.vrtscnfs=missing
363+driver.vzstorage=missing
364+driver.vmware=missing
365+driver.win_iscsi=missing
366+driver.win_smb=missing
367+driver.yadro=missing
368+driver.zadara=missing
369--
3702.35.1
371