blob: 1f8dbbc271678d2bbf1ba345d403cdb46fe5cf14 [file] [log] [blame]
Peter Pentchev4ef68d02022-06-23 14:21:37 +03001From d3730dc49d52228fc771d514b853dc536ef1b534 Mon Sep 17 00:00:00 2001
Peter Pentchevd0130fb2021-11-30 10:50:05 +02002From: Peter Penchev <openstack-dev@storpool.com>
3Date: Mon, 12 Mar 2018 12:00:10 +0200
Peter Pentchev4ef68d02022-06-23 14:21:37 +03004Subject: [PATCH 8/8] Add iSCSI export support to the StorPool driver
Peter Pentchevd0130fb2021-11-30 10:50:05 +02005
6Add four new driver options:
7- iscsi_cinder_volume: use StorPool iSCSI attachments whenever
8 the cinder-volume service needs to attach a volume to the controller,
9 e.g. for copying an image to a volume or vice versa
Peter Pentchev4ef68d02022-06-23 14:21:37 +030010- iscsi_export_to:
11 - an empty string to use the StorPool native protocol for exporting volumes
12 protocol for exporting volumes)
13 - the string "*" to always use iSCSI for exporting volumes
14 - an experimental, not fully supported list of IQN patterns to export
15 volumes to using iSCSI; this results in a Cinder driver that exports
16 different volumes using different storage protocols
Peter Pentchevd0130fb2021-11-30 10:50:05 +020017- iscsi_portal_group: the name of the iSCSI portal group defined in
18 the StorPool configuration to use for these export
19- iscsi_learn_initiator_iqns: automatically create StorPool configuration
20 records for an initiator when a volume is first exported to it
21
22Change-Id: I9de64306e0e6976268df782053b0651dd1cca96f
23---
Peter Pentchev4ef68d02022-06-23 14:21:37 +030024 .../unit/volume/drivers/test_storpool.py | 64 ++-
25 cinder/volume/drivers/storpool.py | 369 +++++++++++++++++-
26 2 files changed, 429 insertions(+), 4 deletions(-)
Peter Pentchevd0130fb2021-11-30 10:50:05 +020027
28diff --git a/cinder/tests/unit/volume/drivers/test_storpool.py b/cinder/tests/unit/volume/drivers/test_storpool.py
Peter Pentchev4ef68d02022-06-23 14:21:37 +030029index 51db7f292..aafaf7108 100644
Peter Pentchevd0130fb2021-11-30 10:50:05 +020030--- a/cinder/tests/unit/volume/drivers/test_storpool.py
31+++ b/cinder/tests/unit/volume/drivers/test_storpool.py
Peter Pentchev4ef68d02022-06-23 14:21:37 +030032@@ -32,6 +32,7 @@ fakeStorPool.sptypes = mock.Mock()
33 sys.modules['storpool'] = fakeStorPool
34
35
36+from cinder.common import constants
37 from cinder import exception
38 from cinder.tests.unit import test
39 from cinder.volume import configuration as conf
40@@ -219,7 +220,14 @@ class StorPoolTestCase(test.TestCase):
Peter Pentchevd0130fb2021-11-30 10:50:05 +020041 self.cfg.volume_backend_name = 'storpool_test'
42 self.cfg.storpool_template = None
43 self.cfg.storpool_replication = 3
44+ self.cfg.iscsi_cinder_volume = False
45+ self.cfg.iscsi_export_to = ''
46+ self.cfg.iscsi_portal_group = 'test-group'
47
Peter Pentchev4ef68d02022-06-23 14:21:37 +030048+ self._setup_test_driver()
49+
50+ def _setup_test_driver(self):
51+ """Initialize a StorPool driver as per the current configuration."""
Peter Pentchevd0130fb2021-11-30 10:50:05 +020052 mock_exec = mock.Mock()
53 mock_exec.return_value = ('', '')
Peter Pentchev4ef68d02022-06-23 14:21:37 +030054
55@@ -228,7 +236,7 @@ class StorPoolTestCase(test.TestCase):
Peter Pentchevd0130fb2021-11-30 10:50:05 +020056 self.driver.check_for_setup_error()
57
58 @ddt.data(
59- (5, TypeError),
60+ (5, (TypeError, AttributeError)),
61 ({'no-host': None}, KeyError),
62 ({'host': 'sbad'}, driver.StorPoolConfigurationInvalid),
63 ({'host': 's01'}, None),
Peter Pentchev4ef68d02022-06-23 14:21:37 +030064@@ -244,7 +252,7 @@ class StorPoolTestCase(test.TestCase):
Peter Pentchevd0130fb2021-11-30 10:50:05 +020065 conn)
66
67 @ddt.data(
68- (5, TypeError),
69+ (5, (TypeError, AttributeError)),
70 ({'no-host': None}, KeyError),
71 ({'host': 'sbad'}, driver.StorPoolConfigurationInvalid),
72 )
Peter Pentchev4ef68d02022-06-23 14:21:37 +030073@@ -635,3 +643,55 @@ class StorPoolTestCase(test.TestCase):
74 self.driver.get_pool({
75 'volume_type': volume_type
76 }))
77+
78+ @ddt.data(
79+ # The default values
80+ ('', False, constants.STORPOOL, 'beleriand', False),
81+
82+ # Export to all
83+ ('*', True, constants.ISCSI, 'beleriand', True),
84+ ('*', True, constants.ISCSI, 'beleriand', True),
85+
86+ # Only export to the controller
87+ ('', False, constants.STORPOOL, 'beleriand', False),
88+
89+ # Some of the not-fully-supported pattern lists
90+ ('roh*', False, constants.STORPOOL, 'beleriand', False),
91+ ('roh*', False, constants.STORPOOL, 'rohan', True),
92+ ('*riand roh*', False, constants.STORPOOL, 'beleriand', True),
93+ ('*riand roh*', False, constants.STORPOOL, 'rohan', True),
94+ )
95+ @ddt.unpack
96+ def test_wants_iscsi(self, iscsi_export_to, use_iscsi, storage_protocol,
97+ hostname, expected):
98+ """Check the "should this export use iSCSI?" detection."""
99+ self.cfg.iscsi_export_to = iscsi_export_to
100+ self._setup_test_driver()
101+ self.assertEqual(self.driver._use_iscsi, use_iscsi)
102+
103+ # Make sure the driver reports the correct protocol in the stats
104+ self.driver._update_volume_stats()
105+ self.assertEqual(self.driver._stats["vendor_name"], "StorPool")
106+ self.assertEqual(self.driver._stats["storage_protocol"],
107+ storage_protocol)
108+
109+ def check(conn, forced, expected):
110+ """Pass partially or completely valid connector info."""
111+ for initiator in (None, hostname):
112+ for host in (None, 'gondor'):
113+ self.assertEqual(
114+ self.driver._connector_wants_iscsi({
115+ "host": host,
116+ "initiator": initiator,
117+ **conn,
118+ }),
119+ expected if initiator is not None and host is not None
120+ else forced)
121+
122+ # If iscsi_cinder_volume is set and this is the controller, then yes.
123+ check({"storpool_wants_iscsi": True}, True, True)
124+
125+ # If iscsi_cinder_volume is not set or this is not the controller, then
126+ # look at the specified expected value.
127+ check({"storpool_wants_iscsi": False}, use_iscsi, expected)
128+ check({}, use_iscsi, expected)
Peter Pentchevd0130fb2021-11-30 10:50:05 +0200129diff --git a/cinder/volume/drivers/storpool.py b/cinder/volume/drivers/storpool.py
Peter Pentchev4ef68d02022-06-23 14:21:37 +0300130index 401e3709a..aeed8dc62 100644
Peter Pentchevd0130fb2021-11-30 10:50:05 +0200131--- a/cinder/volume/drivers/storpool.py
132+++ b/cinder/volume/drivers/storpool.py
133@@ -15,6 +15,7 @@
134
135 """StorPool block device driver"""
136
137+import fnmatch
138 import platform
139
140 from oslo_config import cfg
Peter Pentchev4ef68d02022-06-23 14:21:37 +0300141@@ -44,6 +45,31 @@ if storpool:
Peter Pentchevd0130fb2021-11-30 10:50:05 +0200142
143
144 storpool_opts = [
145+ cfg.BoolOpt('iscsi_cinder_volume',
146+ default=False,
147+ help='Let the cinder-volume service use iSCSI instead of '
148+ 'the StorPool block device driver for accessing '
149+ 'StorPool volumes, e.g. when creating a volume from '
150+ 'an image or vice versa.'),
151+ cfg.StrOpt('iscsi_export_to',
152+ default='',
Peter Pentchev4ef68d02022-06-23 14:21:37 +0300153+ help='Whether to export volumes using iSCSI. '
154+ 'An empty string (the default) makes the driver export '
155+ 'all volumes using the StorPool native network protocol. '
156+ 'The value "*" makes the driver export all volumes using '
157+ 'iSCSI. '
158+ 'Any other value leads to an experimental not fully '
159+ 'supported configuration and is interpreted as '
160+ 'a whitespace-separated list of patterns for IQNs for '
161+ 'hosts that need volumes to be exported via iSCSI, e.g. '
162+ '"iqn.1991-05.com.microsoft:*" for Windows hosts.'),
Peter Pentchevd0130fb2021-11-30 10:50:05 +0200163+ cfg.BoolOpt('iscsi_learn_initiator_iqns',
164+ default=True,
165+ help='Create a StorPool record for a new initiator as soon as '
166+ 'Cinder asks for a volume to be exported to it.'),
167+ cfg.StrOpt('iscsi_portal_group',
168+ default=None,
169+ help='The portal group to export volumes via iSCSI in.'),
170 cfg.StrOpt('storpool_template',
171 default=None,
172 help='The StorPool template for volumes with no type.'),
Peter Pentchev4ef68d02022-06-23 14:21:37 +0300173@@ -102,6 +128,7 @@ class StorPoolDriver(driver.VolumeDriver):
174 - Declare the capability to clone a volume into a different
175 pool, thus enabling the use of create_cloned_volume() for
176 Cinder-backed Glance images on StorPool volumes
177+ - Add support for exporting volumes via iSCSI
Peter Pentchevd0130fb2021-11-30 10:50:05 +0200178 """
179
Peter Pentchev4ef68d02022-06-23 14:21:37 +0300180 VERSION = '2.0.0'
181@@ -114,6 +141,7 @@ class StorPoolDriver(driver.VolumeDriver):
182 self._ourId = None
183 self._ourIdInt = None
184 self._attach = None
185+ self._use_iscsi = None
Peter Pentchevd0130fb2021-11-30 10:50:05 +0200186
Peter Pentchev4ef68d02022-06-23 14:21:37 +0300187 @staticmethod
188 def get_driver_options():
189@@ -171,10 +199,322 @@ class StorPoolDriver(driver.VolumeDriver):
Peter Pentchevd0130fb2021-11-30 10:50:05 +0200190 raise StorPoolConfigurationInvalid(
191 section=hostname, param='SP_OURID', error=e)
192
193+ def _connector_wants_iscsi(self, connector):
194+ """Should we do this export via iSCSI?
195+
196+ Check the configuration to determine whether this connector is
197+ expected to provide iSCSI exports as opposed to native StorPool
198+ protocol ones. Match the initiator's IQN against the list of
199+ patterns supplied in the "iscsi_export_to" configuration setting.
200+ """
201+ if connector is None:
202+ return False
Peter Pentchev4ef68d02022-06-23 14:21:37 +0300203+ if self._use_iscsi:
204+ LOG.debug(' - forcing iSCSI for all exported volumes')
205+ return True
Peter Pentchevd0130fb2021-11-30 10:50:05 +0200206+ if connector.get('storpool_wants_iscsi'):
207+ LOG.debug(' - forcing iSCSI for the controller')
208+ return True
209+
210+ try:
211+ iqn = connector.get('initiator')
212+ except Exception:
213+ iqn = None
214+ try:
215+ host = connector.get('host')
216+ except Exception:
217+ host = None
218+ if iqn is None or host is None:
219+ LOG.debug(' - this connector certainly does not want iSCSI')
220+ return False
221+
222+ LOG.debug(' - check whether %(host)s (%(iqn)s) wants iSCSI',
223+ {
224+ 'host': host,
225+ 'iqn': iqn,
226+ })
227+
228+ export_to = self.configuration.iscsi_export_to
229+ if export_to is None:
230+ return False
231+
232+ for pat in export_to.split():
233+ LOG.debug(' - matching against %(pat)s', {'pat': pat})
234+ if fnmatch.fnmatch(iqn, pat):
235+ LOG.debug(' - got it!')
236+ return True
237+ LOG.debug(' - nope')
238+ return False
239+
240 def validate_connector(self, connector):
241+ if self._connector_wants_iscsi(connector):
242+ return True
243 return self._storpool_client_id(connector) >= 0
244
245+ def _get_iscsi_config(self, iqn, volume_id):
246+ """Get the StorPool iSCSI config items pertaining to this volume.
247+
248+ Find the elements of the StorPool iSCSI configuration tree that
249+ will be needed to create, ensure, or remove the iSCSI export of
250+ the specified volume to the specified initiator.
251+ """
252+ cfg = self._attach.api().iSCSIConfig()
253+
254+ pg_name = self.configuration.iscsi_portal_group
255+ pg_found = [
256+ pg for pg in cfg.iscsi.portalGroups.values() if pg.name == pg_name
257+ ]
258+ if not pg_found:
259+ raise Exception('StorPool Cinder iSCSI configuration error: '
260+ 'no portal group "{pg}"'.format(pg=pg_name))
261+ pg = pg_found[0]
262+
263+ # Do we know about this initiator?
264+ i_found = [
265+ init for init in cfg.iscsi.initiators.values() if init.name == iqn
266+ ]
267+ if i_found:
268+ initiator = i_found[0]
269+ else:
270+ initiator = None
271+
272+ # Is this volume already being exported?
273+ volname = self._attach.volumeName(volume_id)
274+ t_found = [
275+ tgt for tgt in cfg.iscsi.targets.values() if tgt.volume == volname
276+ ]
277+ if t_found:
278+ target = t_found[0]
279+ else:
280+ target = None
281+
282+ # OK, so is this volume being exported to this initiator?
283+ export = None
284+ if initiator is not None and target is not None:
285+ e_found = [
286+ exp for exp in initiator.exports
287+ if exp.portalGroup == pg.name and exp.target == target.name
288+ ]
289+ if e_found:
290+ export = e_found[0]
291+
292+ return {
293+ 'cfg': cfg,
294+ 'pg': pg,
295+ 'initiator': initiator,
296+ 'target': target,
297+ 'export': export,
298+ 'volume_name': volname,
299+ 'volume_id': volume_id,
300+ }
301+
302+ def _create_iscsi_export(self, volume, connector):
303+ """Create (if needed) an iSCSI export for the StorPool volume."""
304+ LOG.debug(
305+ '_create_iscsi_export() invoked for volume '
306+ '"%(vol_name)s" (%(vol_id)s) connector %(connector)s',
307+ {
308+ 'vol_name': volume['display_name'],
309+ 'vol_id': volume['id'],
310+ 'connector': connector,
311+ }
312+ )
313+ iqn = connector['initiator']
314+ try:
315+ cfg = self._get_iscsi_config(iqn, volume['id'])
316+ except Exception as exc:
317+ LOG.error(
318+ 'Could not fetch the iSCSI config: %(exc)s', {'exc': exc}
319+ )
320+ raise
321+
322+ if cfg['initiator'] is None:
323+ LOG.info(
324+ 'RDBG initiator? learn %(learn)s vol %(vol)s want %(want)s',
325+ {
326+ 'learn': repr(
327+ self.configuration.iscsi_learn_initiator_iqns
328+ ),
329+ 'vol': repr(self.configuration.iscsi_cinder_volume),
330+ 'want': repr(connector.get('storpool_wants_iscsi')),
331+ }
332+ )
333+ if not (self.configuration.iscsi_learn_initiator_iqns or
334+ self.configuration.iscsi_cinder_volume and
335+ connector.get('storpool_wants_iscsi')):
336+ raise Exception('The "{iqn}" initiator IQN for the "{host}" '
337+ 'host is not defined in the StorPool '
338+ 'configuration.'
339+ .format(iqn=iqn, host=connector['host']))
340+ else:
341+ LOG.info('Creating a StorPool iSCSI initiator '
342+ 'for "{host}s" ({iqn}s)',
343+ {'host': connector['host'], 'iqn': iqn})
344+ self._attach.api().iSCSIConfigChange({
345+ 'commands': [
346+ {
347+ 'createInitiator': {
348+ 'name': iqn,
349+ 'username': '',
350+ 'secret': '',
351+ },
352+ },
353+ {
354+ 'initiatorAddNetwork': {
355+ 'initiator': iqn,
356+ 'net': '0.0.0.0/0',
357+ },
358+ },
359+ ]
360+ })
361+
362+ if cfg['target'] is None:
363+ LOG.info(
364+ 'Creating a StorPool iSCSI target '
365+ 'for the "%(vol_name)s" volume (%(vol_id)s)',
366+ {
367+ 'vol_name': volume['display_name'],
368+ 'vol_id': volume['id'],
369+ }
370+ )
371+ self._attach.api().iSCSIConfigChange({
372+ 'commands': [
373+ {
374+ 'createTarget': {
375+ 'volumeName': cfg['volume_name'],
376+ },
377+ },
378+ ]
379+ })
380+ cfg = self._get_iscsi_config(iqn, volume['id'])
381+
382+ if cfg['export'] is None:
383+ LOG.info('Creating a StorPool iSCSI export '
384+ 'for the "{vol_name}s" volume ({vol_id}s) '
385+ 'to the "{host}s" initiator ({iqn}s) '
386+ 'in the "{pg}s" portal group',
387+ {
388+ 'vol_name': volume['display_name'],
389+ 'vol_id': volume['id'],
390+ 'host': connector['host'],
391+ 'iqn': iqn,
392+ 'pg': cfg['pg'].name
393+ })
394+ self._attach.api().iSCSIConfigChange({
395+ 'commands': [
396+ {
397+ 'export': {
398+ 'initiator': iqn,
399+ 'portalGroup': cfg['pg'].name,
400+ 'volumeName': cfg['volume_name'],
401+ },
402+ },
403+ ]
404+ })
405+
406+ res = {
407+ 'driver_volume_type': 'iscsi',
408+ 'data': {
409+ 'target_discovered': False,
410+ 'target_iqn': cfg['target'].name,
411+ 'target_portal': '{}:3260'.format(
412+ cfg['pg'].networks[0].address
413+ ),
414+ 'target_lun': 0,
415+ 'volume_id': volume['id'],
416+ 'discard': True,
417+ },
418+ }
419+ LOG.debug('returning %(res)s', {'res': res})
420+ return res
421+
422+ def _remove_iscsi_export(self, volume, connector):
423+ """Remove an iSCSI export for the specified StorPool volume."""
424+ LOG.debug(
425+ '_remove_iscsi_export() invoked for volume '
426+ '"%(vol_name)s" (%(vol_id)s) connector %(conn)s',
427+ {
428+ 'vol_name': volume['display_name'],
429+ 'vol_id': volume['id'],
430+ 'conn': connector,
431+ }
432+ )
433+ try:
434+ cfg = self._get_iscsi_config(connector['initiator'], volume['id'])
435+ except Exception as exc:
436+ LOG.error(
437+ 'Could not fetch the iSCSI config: %(exc)s', {'exc': exc}
438+ )
439+ raise
440+
441+ if cfg['export'] is not None:
442+ LOG.info('Removing the StorPool iSCSI export '
443+ 'for the "%(vol_name)s" volume (%(vol_id)s) '
444+ 'to the "%(host)s" initiator (%(iqn)s) '
445+ 'in the "%(pg)s" portal group',
446+ {
447+ 'vol_name': volume['display_name'],
448+ 'vol_id': volume['id'],
449+ 'host': connector['host'],
450+ 'iqn': connector['initiator'],
451+ 'pg': cfg['pg'].name,
452+ })
453+ try:
454+ self._attach.api().iSCSIConfigChange({
455+ 'commands': [
456+ {
457+ 'exportDelete': {
458+ 'initiator': cfg['initiator'].name,
459+ 'portalGroup': cfg['pg'].name,
460+ 'volumeName': cfg['volume_name'],
461+ },
462+ },
463+ ]
464+ })
465+ except spapi.ApiError as e:
466+ if e.name not in ('objectExists', 'objectDoesNotExist'):
467+ raise
468+ LOG.info('Looks like somebody beat us to it')
469+
470+ if cfg['target'] is not None:
471+ last = True
472+ for initiator in cfg['cfg'].iscsi.initiators.values():
473+ if initiator.name == cfg['initiator'].name:
474+ continue
475+ for exp in initiator.exports:
476+ if exp.target == cfg['target'].name:
477+ last = False
478+ break
479+ if not last:
480+ break
481+
482+ if last:
483+ LOG.info(
484+ 'Removing the StorPool iSCSI target '
485+ 'for the "{vol_name}s" volume ({vol_id}s)',
486+ {
487+ 'vol_name': volume['display_name'],
488+ 'vol_id': volume['id'],
489+ }
490+ )
491+ try:
492+ self._attach.api().iSCSIConfigChange({
493+ 'commands': [
494+ {
495+ 'deleteTarget': {
496+ 'volumeName': cfg['volume_name'],
497+ },
498+ },
499+ ]
500+ })
501+ except spapi.ApiError as e:
502+ if e.name not in ('objectDoesNotExist', 'invalidParam'):
503+ raise
504+ LOG.info('Looks like somebody beat us to it')
505+
506 def initialize_connection(self, volume, connector):
507+ if self._connector_wants_iscsi(connector):
508+ return self._create_iscsi_export(volume, connector)
509 return {'driver_volume_type': 'storpool',
510 'data': {
511 'client_id': self._storpool_client_id(connector),
Peter Pentchev4ef68d02022-06-23 14:21:37 +0300512@@ -183,6 +523,9 @@ class StorPoolDriver(driver.VolumeDriver):
Peter Pentchevd0130fb2021-11-30 10:50:05 +0200513 }}
514
515 def terminate_connection(self, volume, connector, **kwargs):
516+ if self._connector_wants_iscsi(connector):
517+ LOG.debug('- removing an iSCSI export')
518+ self._remove_iscsi_export(volume, connector)
519 pass
520
521 def create_snapshot(self, snapshot):
Peter Pentchev4ef68d02022-06-23 14:21:37 +0300522@@ -284,11 +627,20 @@ class StorPoolDriver(driver.VolumeDriver):
523 )
Peter Pentchevd0130fb2021-11-30 10:50:05 +0200524
525 def create_export(self, context, volume, connector):
526- pass
527+ if self._connector_wants_iscsi(connector):
528+ LOG.debug('- creating an iSCSI export')
529+ self._create_iscsi_export(volume, connector)
530
531 def remove_export(self, context, volume):
532 pass
533
534+ def _attach_volume(self, context, volume, properties, remote=False):
Peter Pentcheva102cc52021-11-30 16:10:27 +0200535+ if self.configuration.iscsi_cinder_volume and not remote:
Peter Pentchevd0130fb2021-11-30 10:50:05 +0200536+ LOG.debug('- adding the "storpool_wants_iscsi" flag')
537+ properties['storpool_wants_iscsi'] = True
538+
539+ return super()._attach_volume(context, volume, properties, remote)
540+
541 def delete_volume(self, volume):
542 name = self._attach.volumeName(volume['id'])
543 try:
Peter Pentchev4ef68d02022-06-23 14:21:37 +0300544@@ -325,6 +677,17 @@ class StorPoolDriver(driver.VolumeDriver):
Peter Pentchevd0130fb2021-11-30 10:50:05 +0200545 LOG.error("StorPoolDriver API initialization failed: %s", e)
546 raise
547
548+ export_to = self.configuration.iscsi_export_to
549+ export_to_set = export_to is not None and export_to.split()
550+ vol_iscsi = self.configuration.iscsi_cinder_volume
551+ pg_name = self.configuration.iscsi_portal_group
552+ if (export_to_set or vol_iscsi) and pg_name is None:
553+ msg = _('The "iscsi_portal_group" option is required if '
554+ 'any patterns are listed in "iscsi_export_to"')
555+ raise exception.VolumeDriverException(message=msg)
556+
Peter Pentchev4ef68d02022-06-23 14:21:37 +0300557+ self._use_iscsi = export_to == "*"
558+
Peter Pentchevd0130fb2021-11-30 10:50:05 +0200559 def _update_volume_stats(self):
560 try:
561 dl = self._attach.api().disksList()
Peter Pentchev4ef68d02022-06-23 14:21:37 +0300562@@ -368,7 +731,9 @@ class StorPoolDriver(driver.VolumeDriver):
563 'volume_backend_name') or 'storpool',
564 'vendor_name': 'StorPool',
565 'driver_version': self.VERSION,
566- 'storage_protocol': constants.STORPOOL,
567+ 'storage_protocol': (
568+ constants.ISCSI if self._use_iscsi else constants.STORPOOL
569+ ),
570
571 'clone_across_pools': True,
572 'sparse_copy_volume': True,
Peter Pentchevd0130fb2021-11-30 10:50:05 +0200573--
Peter Pentchev4ef68d02022-06-23 14:21:37 +03005742.35.1
Peter Pentchevd0130fb2021-11-30 10:50:05 +0200575