blob: 3279d6d57f49c2399c111096674ceefb9e0b3a61 [file] [log] [blame]
Peter Pentchev7d659682022-06-23 14:20:39 +03001From 8fafd470b34b4b94d0e6e65f1a6049232f06b9f8 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/8] 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
10TODO: document this capability a bit more.
11
12Change-Id: Idbac4dae00f9fa03fb14547a204666721bf67d96
13Implements: blueprint clone-across-pools
14---
15 cinder/image/cache.py | 11 ++-
16 cinder/tests/unit/image/test_cache.py | 14 ++--
17 .../volume/flows/test_create_volume_flow.py | 73 +++++++++++++++++++
18 cinder/volume/flows/manager/create_volume.py | 8 +-
19 cinder/volume/manager.py | 3 +-
20 doc/source/reference/support-matrix.ini | 73 +++++++++++++++++++
21 6 files changed, 169 insertions(+), 13 deletions(-)
22
23diff --git a/cinder/image/cache.py b/cinder/image/cache.py
24index 6d30210a4..a79451bf2 100644
25--- a/cinder/image/cache.py
26+++ b/cinder/image/cache.py
27@@ -34,11 +34,13 @@ class ImageVolumeCache(object):
28 db,
29 volume_api,
30 max_cache_size_gb: int = 0,
31- max_cache_size_count: int = 0):
32+ max_cache_size_count: int = 0,
33+ clone_across_pools: bool = False):
34 self.db = db
35 self.volume_api = volume_api
36 self.max_cache_size_gb = int(max_cache_size_gb)
37 self.max_cache_size_count = int(max_cache_size_count)
38+ self.clone_across_pools = bool(clone_across_pools)
39 self.notifier = rpc.get_notifier('volume', CONF.host)
40
41 def get_by_image_volume(self,
42@@ -55,11 +57,12 @@ class ImageVolumeCache(object):
43 self._notify_cache_eviction(context, cache_entry['image_id'],
44 cache_entry['host'])
45
46- @staticmethod
47- def _get_query_filters(volume_ref: objects.Volume) -> dict:
48+ def _get_query_filters(self, volume_ref: objects.Volume) -> dict:
49 if volume_ref.is_clustered:
50 return {'cluster_name': volume_ref.cluster_name}
51- return {'host': volume_ref.host}
52+ if not self.clone_across_pools:
53+ return {'host': volume_ref.host}
54+ return {}
55
56 def get_entry(self,
57 context: context.RequestContext,
58diff --git a/cinder/tests/unit/image/test_cache.py b/cinder/tests/unit/image/test_cache.py
59index c3aba9b28..b8b704e0d 100644
60--- a/cinder/tests/unit/image/test_cache.py
61+++ b/cinder/tests/unit/image/test_cache.py
62@@ -42,11 +42,12 @@ class ImageVolumeCacheTestCase(test.TestCase):
63 self.volume.update(vol_params)
64 self.volume_ovo = objects.Volume(self.context, **vol_params)
65
66- def _build_cache(self, max_gb=0, max_count=0):
67+ def _build_cache(self, max_gb=0, max_count=0, clone_across_pools=False):
68 cache = image_cache.ImageVolumeCache(self.mock_db,
69 self.mock_volume_api,
70 max_gb,
71- max_count)
72+ max_count,
73+ clone_across_pools)
74 cache.notifier = self.notifier
75 return cache
76
77@@ -91,9 +92,10 @@ class ImageVolumeCacheTestCase(test.TestCase):
78 self.assertEqual(entry['image_id'], msg['payload']['image_id'])
79 self.assertEqual(1, len(self.notifier.notifications))
80
81- @ddt.data(True, False)
82- def test_get_entry(self, clustered):
83- cache = self._build_cache()
84+ @ddt.data((True, True), (True, False), (False, True), (False, False))
85+ @ddt.unpack
86+ def test_get_entry(self, clustered, clone_across_pools):
87+ cache = self._build_cache(clone_across_pools=clone_across_pools)
88 entry = self._build_entry()
89 image_meta = {
90 'is_public': True,
91@@ -107,7 +109,7 @@ class ImageVolumeCacheTestCase(test.TestCase):
92 image_volume_cache_get_and_update_last_used.return_value) = entry
93 if not clustered:
94 self.volume_ovo.cluster_name = None
95- expect = {'host': self.volume.host}
96+ expect = {} if clone_across_pools else {'host': self.volume.host}
97 else:
98 expect = {'cluster_name': self.volume.cluster_name}
99 found_entry = cache.get_entry(self.context,
100diff --git a/cinder/tests/unit/volume/flows/test_create_volume_flow.py b/cinder/tests/unit/volume/flows/test_create_volume_flow.py
101index 2a7b625bc..b7d43008f 100644
102--- a/cinder/tests/unit/volume/flows/test_create_volume_flow.py
103+++ b/cinder/tests/unit/volume/flows/test_create_volume_flow.py
104@@ -1058,6 +1058,7 @@ class CreateVolumeFlowManagerTestCase(test.TestCase):
105 self, volume_get_by_id, vol_update, rekey_vol, cleanup_cg):
106 fake_db = mock.MagicMock()
107 fake_driver = mock.MagicMock()
108+ fake_driver.capabilities = {}
109 fake_volume_manager = mock.MagicMock()
110 fake_manager = create_volume_manager.CreateVolumeFromSpecTask(
111 fake_volume_manager, fake_db, fake_driver)
112@@ -1083,6 +1084,7 @@ class CreateVolumeFlowManagerTestCase(test.TestCase):
113 handle_bootable, cleanup_cg):
114 fake_db = mock.MagicMock()
115 fake_driver = mock.MagicMock()
116+ fake_driver.capabilities = {}
117 fake_volume_manager = mock.MagicMock()
118 fake_manager = create_volume_manager.CreateVolumeFromSpecTask(
119 fake_volume_manager, fake_db, fake_driver)
120@@ -1108,6 +1110,7 @@ class CreateVolumeFlowManagerTestCase(test.TestCase):
121 mock_cleanup_cg):
122 fake_db = mock.MagicMock()
123 fake_driver = mock.MagicMock()
124+ fake_driver.capabilities = {}
125 fake_volume_manager = mock.MagicMock()
126 fake_manager = create_volume_manager.CreateVolumeFromSpecTask(
127 fake_volume_manager, fake_db, fake_driver)
128@@ -1144,6 +1147,7 @@ class CreateVolumeFlowManagerTestCase(test.TestCase):
129 mock_cleanup_cg):
130 fake_db = mock.MagicMock()
131 fake_driver = mock.MagicMock()
132+ fake_driver.capabilities = {}
133 fake_volume_manager = mock.MagicMock()
134 fake_cache = mock.MagicMock()
135 fake_manager = create_volume_manager.CreateVolumeFromSpecTask(
136@@ -1192,6 +1196,7 @@ class CreateVolumeFlowManagerTestCase(test.TestCase):
137 mock_cleanup_cg):
138 fake_db = mock.MagicMock()
139 fake_driver = mock.MagicMock()
140+ fake_driver.capabilities = {}
141 fake_volume_manager = mock.MagicMock()
142 fake_manager = create_volume_manager.CreateVolumeFromSpecTask(
143 fake_volume_manager, fake_db, fake_driver)
144@@ -1250,6 +1255,7 @@ class CreateVolumeFlowManagerTestCase(test.TestCase):
145 driver_error):
146 fake_db = mock.MagicMock()
147 fake_driver = mock.MagicMock()
148+ fake_driver.capabilities = {}
149 fake_volume_manager = mock.MagicMock()
150 backup_host = 'host@backend#pool'
151 fake_manager = create_volume_manager.CreateVolumeFromSpecTask(
152@@ -1289,6 +1295,7 @@ class CreateVolumeFlowManagerTestCase(test.TestCase):
153 def test_create_drive_error(self, mock_message_create):
154 fake_db = mock.MagicMock()
155 fake_driver = mock.MagicMock()
156+ fake_driver.capabilities = {}
157 fake_volume_manager = mock.MagicMock()
158 fake_manager = create_volume_manager.CreateVolumeFromSpecTask(
159 fake_volume_manager, fake_db, fake_driver)
160@@ -1490,6 +1497,7 @@ class CreateVolumeFlowManagerGlanceCinderBackendCase(test.TestCase):
161 spec=utils.get_file_spec())
162 fake_db = mock.MagicMock()
163 fake_driver = mock.MagicMock()
164+ fake_driver.capabilities = {}
165 fake_manager = create_volume_manager.CreateVolumeFromSpecTask(
166 mock.MagicMock(), fake_db, fake_driver)
167 fake_image_service = fake_image.FakeImageService()
168@@ -1516,6 +1524,7 @@ class CreateVolumeFlowManagerGlanceCinderBackendCase(test.TestCase):
169 'cinder_encryption_key_id': None}
170
171 fake_driver.clone_image.return_value = (None, False)
172+ fake_db.volume_get_all.return_value = []
173 fake_db.volume_get_all_by_host.return_value = [image_volume]
174
175 fake_manager._create_from_image(self.ctxt,
176@@ -1534,6 +1543,69 @@ class CreateVolumeFlowManagerGlanceCinderBackendCase(test.TestCase):
177 self.assertFalse(fake_driver.create_cloned_volume.called)
178 mock_cleanup_cg.assert_called_once_with(volume)
179
180+ @mock.patch('cinder.volume.flows.manager.create_volume.'
181+ 'CreateVolumeFromSpecTask.'
182+ '_cleanup_cg_in_volume')
183+ @mock.patch('cinder.image.image_utils.TemporaryImages.fetch')
184+ @mock.patch('cinder.volume.flows.manager.create_volume.'
185+ 'CreateVolumeFromSpecTask.'
186+ '_handle_bootable_volume_glance_meta')
187+ @mock.patch('cinder.image.image_utils.qemu_img_info')
188+ def test_create_from_image_across(self, mock_qemu_info, handle_bootable,
189+ mock_fetch_img, mock_cleanup_cg,
190+ format='raw', owner=None,
191+ location=True):
192+ self.flags(allowed_direct_url_schemes=['cinder'])
193+ mock_fetch_img.return_value = mock.MagicMock(
194+ spec=utils.get_file_spec())
195+ fake_db = mock.MagicMock()
196+ fake_driver = mock.MagicMock()
197+ fake_driver.capabilities = {'clone_across_pools': True}
198+ fake_manager = create_volume_manager.CreateVolumeFromSpecTask(
199+ mock.MagicMock(), fake_db, fake_driver)
200+ fake_image_service = fake_image.FakeImageService()
201+
202+ volume = fake_volume.fake_volume_obj(self.ctxt,
203+ host='host@backend#pool')
204+ image_volume = fake_volume.fake_volume_obj(self.ctxt,
205+ volume_metadata={})
206+ image_id = fakes.IMAGE_ID
207+ image_info = imageutils.QemuImgInfo()
208+ image_info.virtual_size = '1073741824'
209+ mock_qemu_info.return_value = image_info
210+
211+ url = 'cinder://%s' % image_volume['id']
212+ image_location = None
213+ if location:
214+ image_location = (url, [{'url': url, 'metadata': {}}])
215+ image_meta = {'id': image_id,
216+ 'container_format': 'bare',
217+ 'disk_format': format,
218+ 'size': 1024,
219+ 'owner': owner or self.ctxt.project_id,
220+ 'virtual_size': None,
221+ 'cinder_encryption_key_id': None}
222+
223+ fake_driver.clone_image.return_value = (None, False)
224+ fake_db.volume_get_all.return_value = [image_volume]
225+ fake_db.volume_get_all_by_host.return_value = []
226+
227+ fake_manager._create_from_image(self.ctxt,
228+ volume,
229+ image_location,
230+ image_id,
231+ image_meta,
232+ fake_image_service)
233+ if format == 'raw' and not owner and location:
234+ fake_driver.create_cloned_volume.assert_called_once_with(
235+ volume, image_volume)
236+ handle_bootable.assert_called_once_with(self.ctxt, volume,
237+ image_id=image_id,
238+ image_meta=image_meta)
239+ else:
240+ self.assertFalse(fake_driver.create_cloned_volume.called)
241+ mock_cleanup_cg.assert_called_once_with(volume)
242+
243 LEGACY_URI = 'cinder://%s' % fakes.VOLUME_ID
244 MULTISTORE_URI = 'cinder://fake-store/%s' % fakes.VOLUME_ID
245
246@@ -1560,6 +1632,7 @@ class CreateVolumeFlowManagerGlanceCinderBackendCase(test.TestCase):
247 spec=utils.get_file_spec())
248 fake_db = mock.MagicMock()
249 fake_driver = mock.MagicMock()
250+ fake_driver.capabilities = {}
251 fake_manager = create_volume_manager.CreateVolumeFromSpecTask(
252 mock.MagicMock(), fake_db, fake_driver)
253 fake_image_service = fake_image.FakeImageService()
254diff --git a/cinder/volume/flows/manager/create_volume.py b/cinder/volume/flows/manager/create_volume.py
255index 4c18d7d84..187f3b3ed 100644
256--- a/cinder/volume/flows/manager/create_volume.py
257+++ b/cinder/volume/flows/manager/create_volume.py
258@@ -732,8 +732,12 @@ class CreateVolumeFromSpecTask(flow_utils.CinderTask):
259 urls = list(set([direct_url]
260 + [loc.get('url') for loc in locations or []]))
261 image_volume_ids = self._extract_cinder_ids(urls)
262- image_volumes = self.db.volume_get_all_by_host(
263- context, volume['host'], filters={'id': image_volume_ids})
264+ if self.driver.capabilities.get('clone_across_pools'):
265+ image_volumes = self.db.volume_get_all(
266+ context, filters={'id': image_volume_ids})
267+ else:
268+ image_volumes = self.db.volume_get_all_by_host(
269+ context, volume['host'], filters={'id': image_volume_ids})
270
271 for image_volume in image_volumes:
272 # For the case image volume is stored in the service tenant,
273diff --git a/cinder/volume/manager.py b/cinder/volume/manager.py
274index 804a1c6cb..ecf264d10 100644
275--- a/cinder/volume/manager.py
276+++ b/cinder/volume/manager.py
277@@ -356,7 +356,8 @@ class VolumeManager(manager.CleanableManager,
278 self.db,
279 cinder_volume.API(),
280 max_cache_size,
281- max_cache_entries
282+ max_cache_entries,
283+ self.driver.capabilities.get('clone_across_pools', False)
284 )
285 LOG.info('Image-volume cache enabled for host %(host)s.',
286 {'host': self.host})
287diff --git a/doc/source/reference/support-matrix.ini b/doc/source/reference/support-matrix.ini
288index a379139b9..38a279266 100644
289--- a/doc/source/reference/support-matrix.ini
290+++ b/doc/source/reference/support-matrix.ini
291@@ -957,3 +957,76 @@ driver.vmware=missing
292 driver.win_iscsi=missing
293 driver.win_smb=missing
294 driver.zadara=missing
295+
296+[operation.clone_across_pools]
297+title=Clone a volume into a different pool
298+status=optional
299+notes=Vendor drivers that support cloning a volume into a different
300+ storage pool, e.g. when creating a volume from a Cinder-backed
301+ Glance image.
302+driver.datera=missing
303+driver.dell_emc_powermax=missing
304+driver.dell_emc_powerstore=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.zadara=missing
368--
3692.35.1
370