NetApp: Support new parameter to cDOT clone API
Clustered Data ONTAP can improve its clone scalability if it can distinguish between a Cinder create_snapshot operation and a Cinder create_volume_from_snapshot operation. This commit provides that contextual hint to cDOT using a new parameter to the file/LUN clone API. Closes-Bug: #1596569 Change-Id: I4faad276dd4a778e172b5c136f78d9e3be2404c6
This commit is contained in:
parent
6156a45591
commit
3af88ab4e6
|
@ -170,7 +170,8 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
|||
mox.StubOutWithMock(drv, '_clone_backing_file_for_volume')
|
||||
drv._clone_backing_file_for_volume(mox_lib.IgnoreArg(),
|
||||
mox_lib.IgnoreArg(),
|
||||
mox_lib.IgnoreArg())
|
||||
mox_lib.IgnoreArg(),
|
||||
is_snapshot=True)
|
||||
mox.ReplayAll()
|
||||
|
||||
drv.create_snapshot(FakeSnapshot())
|
||||
|
@ -249,7 +250,7 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
|||
drv.zapi_client.get_vol_by_junc_vserver('openstack', '/nfs').AndReturn(
|
||||
'nfsvol')
|
||||
drv.zapi_client.clone_file('nfsvol', 'volume_name', 'clone_name',
|
||||
'openstack')
|
||||
'openstack', is_snapshot=False)
|
||||
drv._get_host_ip(mox_lib.IgnoreArg()).AndReturn('127.0.0.1')
|
||||
drv._get_export_path(mox_lib.IgnoreArg()).AndReturn('/nfs')
|
||||
return mox
|
||||
|
@ -296,7 +297,7 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
|||
share = 'ip:/share'
|
||||
|
||||
drv._clone_backing_file_for_volume(volume_name, clone_name, volume_id,
|
||||
share)
|
||||
share, is_snapshot=False)
|
||||
|
||||
mox.VerifyAll()
|
||||
|
||||
|
|
|
@ -576,6 +576,31 @@ class NetAppCmodeClientTestCase(test.TestCase):
|
|||
|
||||
self.assertEqual(1, self.connection.invoke_successfully.call_count)
|
||||
|
||||
@ddt.data({'supports_is_backup': True, 'is_snapshot': True},
|
||||
{'supports_is_backup': True, 'is_snapshot': False},
|
||||
{'supports_is_backup': False, 'is_snapshot': True},
|
||||
{'supports_is_backup': False, 'is_snapshot': False})
|
||||
@ddt.unpack
|
||||
def test_clone_lun_is_snapshot(self, supports_is_backup, is_snapshot):
|
||||
|
||||
self.client.features.add_feature('BACKUP_CLONE_PARAM',
|
||||
supported=supports_is_backup)
|
||||
|
||||
self.client.clone_lun(
|
||||
'volume', 'fakeLUN', 'newFakeLUN', is_snapshot=is_snapshot)
|
||||
|
||||
clone_create_args = {
|
||||
'volume': 'volume',
|
||||
'source-path': 'fakeLUN',
|
||||
'destination-path': 'newFakeLUN',
|
||||
'space-reserve': 'true',
|
||||
}
|
||||
if is_snapshot and supports_is_backup:
|
||||
clone_create_args['is-backup'] = 'true'
|
||||
self.connection.invoke_successfully.assert_called_once_with(
|
||||
netapp_api.NaElement.create_node_with_children(
|
||||
'clone-create', **clone_create_args), True)
|
||||
|
||||
def test_clone_lun_multiple_zapi_calls(self):
|
||||
"""Test for when lun clone requires more than one zapi call."""
|
||||
|
||||
|
@ -1043,6 +1068,32 @@ class NetAppCmodeClientTestCase(test.TestCase):
|
|||
self.assertIsNone(actual_request.get_child_by_name(
|
||||
'destination-exists'))
|
||||
|
||||
@ddt.data({'supports_is_backup': True, 'is_snapshot': True},
|
||||
{'supports_is_backup': True, 'is_snapshot': False},
|
||||
{'supports_is_backup': False, 'is_snapshot': True},
|
||||
{'supports_is_backup': False, 'is_snapshot': False})
|
||||
@ddt.unpack
|
||||
def test_clone_file_is_snapshot(self, supports_is_backup, is_snapshot):
|
||||
|
||||
self.connection.get_api_version.return_value = (1, 20)
|
||||
self.client.features.add_feature('BACKUP_CLONE_PARAM',
|
||||
supported=supports_is_backup)
|
||||
|
||||
self.client.clone_file(
|
||||
'volume', 'fake_source', 'fake_destination', 'fake_vserver',
|
||||
is_snapshot=is_snapshot)
|
||||
|
||||
clone_create_args = {
|
||||
'volume': 'volume',
|
||||
'source-path': 'fake_source',
|
||||
'destination-path': 'fake_destination',
|
||||
}
|
||||
if is_snapshot and supports_is_backup:
|
||||
clone_create_args['is-backup'] = 'true'
|
||||
self.connection.invoke_successfully.assert_called_once_with(
|
||||
netapp_api.NaElement.create_node_with_children(
|
||||
'clone-create', **clone_create_args), True)
|
||||
|
||||
def test_get_file_usage(self):
|
||||
expected_bytes = "2048"
|
||||
fake_vserver = 'fake_vserver'
|
||||
|
|
|
@ -216,6 +216,7 @@ SNAPSHOT_LUN_HANDLE = 'fake_snapshot_lun_handle'
|
|||
|
||||
SNAPSHOT = {
|
||||
'name': SNAPSHOT_NAME,
|
||||
'volume_name': 'volume-fake_volume_id',
|
||||
'volume_size': SIZE,
|
||||
'volume_id': 'fake_volume_id',
|
||||
'busy': False,
|
||||
|
|
|
@ -291,7 +291,8 @@ class NetAppBlockStorage7modeLibraryTestCase(test.TestCase):
|
|||
self.assertEqual(0, self.library.partner_zapi_client.
|
||||
has_luns_mapped_to_initiators.call_count)
|
||||
|
||||
def test_clone_lun_zero_block_count(self):
|
||||
@ddt.data(True, False)
|
||||
def test_clone_lun_zero_block_count(self, is_snapshot):
|
||||
"""Test for when clone lun is not passed a block count."""
|
||||
self.library._get_lun_attr = mock.Mock(return_value={
|
||||
'Volume': 'fakeLUN', 'Path': '/vol/fake/fakeLUN'})
|
||||
|
@ -299,7 +300,8 @@ class NetAppBlockStorage7modeLibraryTestCase(test.TestCase):
|
|||
self.library.zapi_client.get_lun_by_args.return_value = [fake.FAKE_LUN]
|
||||
self.library._add_lun_to_table = mock.Mock()
|
||||
|
||||
self.library._clone_lun('fakeLUN', 'newFakeLUN', 'false')
|
||||
self.library._clone_lun('fakeLUN', 'newFakeLUN', 'false',
|
||||
is_snapshot=is_snapshot)
|
||||
|
||||
self.library.zapi_client.clone_lun.assert_called_once_with(
|
||||
'/vol/fake/fakeLUN', '/vol/fake/newFakeLUN', 'fakeLUN',
|
||||
|
|
|
@ -892,6 +892,21 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
|
|||
self.assertRaises(NotImplementedError, self.library._clone_lun,
|
||||
fake.VOLUME_ID, 'new-' + fake.VOLUME_ID)
|
||||
|
||||
def test_create_snapshot(self):
|
||||
|
||||
fake_lun = block_base.NetAppLun(fake.LUN_HANDLE, fake.LUN_ID,
|
||||
fake.LUN_SIZE, fake.LUN_METADATA)
|
||||
mock_clone_lun = self.mock_object(self.library, '_clone_lun')
|
||||
self.mock_object(
|
||||
self.library, '_get_lun_from_table',
|
||||
mock.Mock(return_value=fake_lun))
|
||||
|
||||
self.library.create_snapshot(fake.SNAPSHOT)
|
||||
|
||||
mock_clone_lun.assert_called_once_with(
|
||||
fake_lun.name, fake.SNAPSHOT_NAME, space_reserved='false',
|
||||
is_snapshot=True)
|
||||
|
||||
def test_create_volume_from_snapshot(self):
|
||||
mock_do_clone = self.mock_object(self.library,
|
||||
'_clone_source_to_destination')
|
||||
|
|
|
@ -203,7 +203,7 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
|
|||
self.library.zapi_client.clone_lun.assert_called_once_with(
|
||||
'fakeLUN', 'fakeLUN', 'newFakeLUN', 'false', block_count=0,
|
||||
dest_block=0, src_block=0, qos_policy_group_name=None,
|
||||
source_snapshot=None)
|
||||
source_snapshot=None, is_snapshot=False)
|
||||
|
||||
def test_clone_lun_blocks(self):
|
||||
"""Test for when clone lun is passed block information."""
|
||||
|
@ -229,7 +229,7 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
|
|||
'fakeLUN', 'fakeLUN', 'newFakeLUN', 'false',
|
||||
block_count=block_count, dest_block=dest_block,
|
||||
src_block=src_block, qos_policy_group_name=None,
|
||||
source_snapshot=None)
|
||||
source_snapshot=None, is_snapshot=False)
|
||||
|
||||
def test_clone_lun_no_space_reservation(self):
|
||||
"""Test for when space_reservation is not passed."""
|
||||
|
@ -245,12 +245,12 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
|
|||
self.library._add_lun_to_table = mock.Mock()
|
||||
self.library._update_stale_vols = mock.Mock()
|
||||
|
||||
self.library._clone_lun('fakeLUN', 'newFakeLUN')
|
||||
self.library._clone_lun('fakeLUN', 'newFakeLUN', is_snapshot=True)
|
||||
|
||||
self.library.zapi_client.clone_lun.assert_called_once_with(
|
||||
'fakeLUN', 'fakeLUN', 'newFakeLUN', 'false', block_count=0,
|
||||
dest_block=0, src_block=0, qos_policy_group_name=None,
|
||||
source_snapshot=None)
|
||||
source_snapshot=None, is_snapshot=True)
|
||||
|
||||
def test_get_fc_target_wwpns(self):
|
||||
ports = [fake.FC_FORMATTED_TARGET_WWPNS[0],
|
||||
|
|
|
@ -55,6 +55,31 @@ class NetApp7modeNfsDriverTestCase(test.TestCase):
|
|||
config.netapp_server_port = '80'
|
||||
return config
|
||||
|
||||
@ddt.data({'share': None, 'is_snapshot': False},
|
||||
{'share': None, 'is_snapshot': True},
|
||||
{'share': 'fake_share', 'is_snapshot': False},
|
||||
{'share': 'fake_share', 'is_snapshot': True})
|
||||
@ddt.unpack
|
||||
def test_clone_backing_file_for_volume(self, share, is_snapshot):
|
||||
|
||||
mock_get_export_ip_path = self.mock_object(
|
||||
self.driver, '_get_export_ip_path',
|
||||
mock.Mock(return_value=(fake.SHARE_IP, fake.EXPORT_PATH)))
|
||||
mock_get_actual_path_for_export = self.mock_object(
|
||||
self.driver.zapi_client, 'get_actual_path_for_export',
|
||||
mock.Mock(return_value='fake_path'))
|
||||
|
||||
self.driver._clone_backing_file_for_volume(
|
||||
fake.FLEXVOL, 'fake_clone', fake.VOLUME_ID, share=share,
|
||||
is_snapshot=is_snapshot)
|
||||
|
||||
mock_get_export_ip_path.assert_called_once_with(
|
||||
fake.VOLUME_ID, share)
|
||||
mock_get_actual_path_for_export.assert_called_once_with(
|
||||
fake.EXPORT_PATH)
|
||||
self.driver.zapi_client.clone_file.assert_called_once_with(
|
||||
'fake_path/' + fake.FLEXVOL, 'fake_path/fake_clone')
|
||||
|
||||
@ddt.data({'nfs_sparsed_volumes': True},
|
||||
{'nfs_sparsed_volumes': False})
|
||||
@ddt.unpack
|
||||
|
|
|
@ -259,6 +259,17 @@ class NetAppNfsDriverTestCase(test.TestCase):
|
|||
fake.NFS_VOLUME,
|
||||
fake.EXTRA_SPECS)
|
||||
|
||||
def test_create_snapshot(self):
|
||||
|
||||
mock_clone_backing_file_for_volume = self.mock_object(
|
||||
self.driver, '_clone_backing_file_for_volume')
|
||||
|
||||
self.driver.create_snapshot(fake.SNAPSHOT)
|
||||
|
||||
mock_clone_backing_file_for_volume.assert_called_once_with(
|
||||
fake.SNAPSHOT['volume_name'], fake.SNAPSHOT['name'],
|
||||
fake.SNAPSHOT['volume_id'], is_snapshot=True)
|
||||
|
||||
def test_cleanup_volume_on_failure(self):
|
||||
path = '%s/%s' % (fake.NFS_SHARE, fake.NFS_VOLUME['name'])
|
||||
mock_local_path = self.mock_object(self.driver, 'local_path')
|
||||
|
|
|
@ -526,6 +526,27 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
|||
self.assertEqual(0, mock_get_flex_vol_name.call_count)
|
||||
self.assertEqual(0, mock_file_assign_qos.call_count)
|
||||
|
||||
@ddt.data({'share': None, 'is_snapshot': False},
|
||||
{'share': None, 'is_snapshot': True},
|
||||
{'share': 'fake_share', 'is_snapshot': False},
|
||||
{'share': 'fake_share', 'is_snapshot': True})
|
||||
@ddt.unpack
|
||||
def test_clone_backing_file_for_volume(self, share, is_snapshot):
|
||||
|
||||
mock_get_vserver_and_exp_vol = self.mock_object(
|
||||
self.driver, '_get_vserver_and_exp_vol',
|
||||
mock.Mock(return_value=(fake.VSERVER_NAME, fake.FLEXVOL)))
|
||||
|
||||
self.driver._clone_backing_file_for_volume(
|
||||
fake.FLEXVOL, 'fake_clone', fake.VOLUME_ID, share=share,
|
||||
is_snapshot=is_snapshot)
|
||||
|
||||
mock_get_vserver_and_exp_vol.assert_called_once_with(
|
||||
fake.VOLUME_ID, share)
|
||||
self.driver.zapi_client.clone_file.assert_called_once_with(
|
||||
fake.FLEXVOL, fake.FLEXVOL, 'fake_clone', fake.VSERVER_NAME,
|
||||
is_snapshot=is_snapshot)
|
||||
|
||||
def test_unmanage(self):
|
||||
mock_get_info = self.mock_object(na_utils,
|
||||
'get_valid_qos_policy_group_info')
|
||||
|
|
|
@ -194,8 +194,12 @@ class NetAppBlockStorage7modeLibrary(block_base.NetAppBlockStorageLibrary):
|
|||
|
||||
def _clone_lun(self, name, new_name, space_reserved=None,
|
||||
qos_policy_group_name=None, src_block=0, dest_block=0,
|
||||
block_count=0, source_snapshot=None):
|
||||
"""Clone LUN with the given handle to the new name."""
|
||||
block_count=0, source_snapshot=None, is_snapshot=False):
|
||||
"""Clone LUN with the given handle to the new name.
|
||||
|
||||
:param: is_snapshot Not used, present for method signature consistency
|
||||
"""
|
||||
|
||||
if not space_reserved:
|
||||
space_reserved = self.lun_space_reservation
|
||||
if qos_policy_group_name is not None:
|
||||
|
|
|
@ -275,7 +275,8 @@ class NetAppBlockStorageLibrary(object):
|
|||
vol_name = snapshot['volume_name']
|
||||
snapshot_name = snapshot['name']
|
||||
lun = self._get_lun_from_table(vol_name)
|
||||
self._clone_lun(lun.name, snapshot_name, space_reserved='false')
|
||||
self._clone_lun(lun.name, snapshot_name, space_reserved='false',
|
||||
is_snapshot=True)
|
||||
|
||||
def delete_snapshot(self, snapshot):
|
||||
"""Driver entry point for deleting a snapshot."""
|
||||
|
@ -453,7 +454,7 @@ class NetAppBlockStorageLibrary(object):
|
|||
|
||||
def _clone_lun(self, name, new_name, space_reserved='true',
|
||||
qos_policy_group_name=None, src_block=0, dest_block=0,
|
||||
block_count=0, source_snapshot=None):
|
||||
block_count=0, source_snapshot=None, is_snapshot=False):
|
||||
"""Clone LUN with the given name to the new name."""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
|
@ -138,7 +138,7 @@ class NetAppBlockStorageCmodeLibrary(block_base.NetAppBlockStorageLibrary):
|
|||
|
||||
def _clone_lun(self, name, new_name, space_reserved=None,
|
||||
qos_policy_group_name=None, src_block=0, dest_block=0,
|
||||
block_count=0, source_snapshot=None):
|
||||
block_count=0, source_snapshot=None, is_snapshot=False):
|
||||
"""Clone LUN with the given handle to the new name."""
|
||||
if not space_reserved:
|
||||
space_reserved = self.lun_space_reservation
|
||||
|
@ -149,7 +149,8 @@ class NetAppBlockStorageCmodeLibrary(block_base.NetAppBlockStorageLibrary):
|
|||
qos_policy_group_name=qos_policy_group_name,
|
||||
src_block=src_block, dest_block=dest_block,
|
||||
block_count=block_count,
|
||||
source_snapshot=source_snapshot)
|
||||
source_snapshot=source_snapshot,
|
||||
is_snapshot=is_snapshot)
|
||||
|
||||
LOG.debug("Cloned LUN with new name %s", new_name)
|
||||
lun = self.zapi_client.get_lun_by_args(vserver=self.vserver,
|
||||
|
|
|
@ -59,6 +59,7 @@ class Client(client_base.Client):
|
|||
ontapi_1_20 = ontapi_version >= (1, 20)
|
||||
ontapi_1_2x = (1, 20) <= ontapi_version < (1, 30)
|
||||
ontapi_1_30 = ontapi_version >= (1, 30)
|
||||
ontapi_1_100 = ontapi_version >= (1, 100)
|
||||
|
||||
self.features.add_feature('USER_CAPABILITY_LIST',
|
||||
supported=ontapi_1_20)
|
||||
|
@ -66,6 +67,7 @@ class Client(client_base.Client):
|
|||
self.features.add_feature('FAST_CLONE_DELETE', supported=ontapi_1_30)
|
||||
self.features.add_feature('SYSTEM_CONSTITUENT_METRICS',
|
||||
supported=ontapi_1_30)
|
||||
self.features.add_feature('BACKUP_CLONE_PARAM', supported=ontapi_1_100)
|
||||
|
||||
def _invoke_vserver_api(self, na_element, vserver):
|
||||
server = copy.copy(self.connection)
|
||||
|
@ -375,7 +377,7 @@ class Client(client_base.Client):
|
|||
|
||||
def clone_lun(self, volume, name, new_name, space_reserved='true',
|
||||
qos_policy_group_name=None, src_block=0, dest_block=0,
|
||||
block_count=0, source_snapshot=None):
|
||||
block_count=0, source_snapshot=None, is_snapshot=False):
|
||||
# zAPI can only handle 2^24 blocks per range
|
||||
bc_limit = 2 ** 24 # 8GB
|
||||
# zAPI can only handle 32 block ranges per call
|
||||
|
@ -400,6 +402,8 @@ class Client(client_base.Client):
|
|||
}
|
||||
if source_snapshot:
|
||||
zapi_args['snapshot-name'] = source_snapshot
|
||||
if is_snapshot and self.features.BACKUP_CLONE_PARAM:
|
||||
zapi_args['is-backup'] = 'true'
|
||||
clone_create = netapp_api.NaElement.create_node_with_children(
|
||||
'clone-create', **zapi_args)
|
||||
if qos_policy_group_name is not None:
|
||||
|
@ -624,16 +628,23 @@ class Client(client_base.Client):
|
|||
"%(junction)s ") % msg_fmt)
|
||||
|
||||
def clone_file(self, flex_vol, src_path, dest_path, vserver,
|
||||
dest_exists=False):
|
||||
dest_exists=False, is_snapshot=False):
|
||||
"""Clones file on vserver."""
|
||||
LOG.debug("Cloning with params volume %(volume)s, src %(src_path)s, "
|
||||
"dest %(dest_path)s, vserver %(vserver)s",
|
||||
{'volume': flex_vol, 'src_path': src_path,
|
||||
'dest_path': dest_path, 'vserver': vserver})
|
||||
|
||||
zapi_args = {
|
||||
'volume': flex_vol,
|
||||
'source-path': src_path,
|
||||
'destination-path': dest_path,
|
||||
}
|
||||
if is_snapshot and self.features.BACKUP_CLONE_PARAM:
|
||||
zapi_args['is-backup'] = 'true'
|
||||
clone_create = netapp_api.NaElement.create_node_with_children(
|
||||
'clone-create',
|
||||
**{'volume': flex_vol, 'source-path': src_path,
|
||||
'destination-path': dest_path})
|
||||
'clone-create', **zapi_args)
|
||||
|
||||
major, minor = self.connection.get_api_version()
|
||||
if major == 1 and minor >= 20 and dest_exists:
|
||||
clone_create.add_new_child('destination-exists', 'true')
|
||||
|
|
|
@ -79,8 +79,12 @@ class NetApp7modeNfsDriver(nfs_base.NetAppNfsDriver):
|
|||
super(NetApp7modeNfsDriver, self).check_for_setup_error()
|
||||
|
||||
def _clone_backing_file_for_volume(self, volume_name, clone_name,
|
||||
volume_id, share=None):
|
||||
"""Clone backing file for Cinder volume."""
|
||||
volume_id, share=None,
|
||||
is_snapshot=False):
|
||||
"""Clone backing file for Cinder volume.
|
||||
|
||||
:param: is_snapshot Not used, present for method signature consistency
|
||||
"""
|
||||
|
||||
(_host_ip, export_path) = self._get_export_ip_path(volume_id, share)
|
||||
storage_path = self.zapi_client.get_actual_path_for_export(export_path)
|
||||
|
|
|
@ -213,7 +213,8 @@ class NetAppNfsDriver(driver.ManageableVD,
|
|||
"""Creates a snapshot."""
|
||||
self._clone_backing_file_for_volume(snapshot['volume_name'],
|
||||
snapshot['name'],
|
||||
snapshot['volume_id'])
|
||||
snapshot['volume_id'],
|
||||
is_snapshot=True)
|
||||
|
||||
def delete_snapshot(self, snapshot):
|
||||
"""Deletes a snapshot."""
|
||||
|
@ -232,7 +233,8 @@ class NetAppNfsDriver(driver.ManageableVD,
|
|||
return nfs_server_ip + ':' + export_path
|
||||
|
||||
def _clone_backing_file_for_volume(self, volume_name, clone_name,
|
||||
volume_id, share=None):
|
||||
volume_id, share=None,
|
||||
is_snapshot=False):
|
||||
"""Clone backing file for Cinder volume."""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
|
@ -135,11 +135,12 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver):
|
|||
target_path)
|
||||
|
||||
def _clone_backing_file_for_volume(self, volume_name, clone_name,
|
||||
volume_id, share=None):
|
||||
volume_id, share=None,
|
||||
is_snapshot=False):
|
||||
"""Clone backing file for Cinder volume."""
|
||||
(vserver, exp_volume) = self._get_vserver_and_exp_vol(volume_id, share)
|
||||
self.zapi_client.clone_file(exp_volume, volume_name, clone_name,
|
||||
vserver)
|
||||
vserver, is_snapshot=is_snapshot)
|
||||
|
||||
def _get_vserver_and_exp_vol(self, volume_id=None, share=None):
|
||||
"""Gets the vserver and export volume for share."""
|
||||
|
|
Loading…
Reference in New Issue