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:
Clinton Knight 2016-07-18 10:31:38 -04:00
parent 6156a45591
commit 3af88ab4e6
16 changed files with 177 additions and 26 deletions

View File

@ -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()

View File

@ -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'

View File

@ -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,

View File

@ -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',

View File

@ -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')

View File

@ -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],

View File

@ -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

View File

@ -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')

View File

@ -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')

View File

@ -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:

View File

@ -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()

View File

@ -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,

View File

@ -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')

View File

@ -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)

View File

@ -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()

View File

@ -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."""