Follow up NetApp ONTAP FlexGroup feature

It is a follow up to the patch [1], providing the fixes:

1. Check the ONTAP storage version for the FlexGroup feature.
The feature is only accepted whether the ONTAP is 9.8 or greater.

2. Remove multiattach support for flexgroup pools.

3. Start bumping the driver version with its documentation.

4. Update the flexgroup release notes.

[1] https://review.opendev.org/c/openstack/cinder/+/776713

Change-Id: I0636b6530a79f8ca86db52f87273ec822c3cb406
This commit is contained in:
Felipe Rodrigues 2021-03-15 17:40:47 -03:00
parent a01b5bb39e
commit bb444d4a47
9 changed files with 181 additions and 25 deletions

View File

@ -281,6 +281,8 @@ class NetAppNfsDriverTestCase(test.TestCase):
self.mock_object(self.driver, '_ensure_flexgroup_not_in_cg')
self.mock_object(self.driver, '_is_flexgroup',
return_value=is_flexgroup)
self.mock_object(self.driver, '_is_flexgroup_clone_file_supported',
return_value=not is_flexgroup)
mock_super_create = self.mock_object(
nfs.NfsDriver, 'create_volume_from_snapshot',
return_value=provider_location)
@ -311,6 +313,8 @@ class NetAppNfsDriverTestCase(test.TestCase):
self.mock_object(self.driver, '_ensure_flexgroup_not_in_cg')
self.mock_object(self.driver, '_is_flexgroup',
return_value=is_flexgroup)
self.mock_object(self.driver, '_is_flexgroup_clone_file_supported',
return_value=not is_flexgroup)
mock_super_create = self.mock_object(
nfs.NfsDriver, 'create_cloned_volume',
return_value=provider_location)
@ -368,6 +372,8 @@ class NetAppNfsDriverTestCase(test.TestCase):
def test_create_snapshot(self, is_flexgroup):
self.mock_object(self.driver, '_is_flexgroup',
return_value=is_flexgroup)
self.mock_object(self.driver, '_is_flexgroup_clone_file_supported',
return_value=not is_flexgroup)
mock_clone_backing_file_for_volume = self.mock_object(
self.driver, '_clone_backing_file_for_volume')
mock_snap_flexgroup = self.mock_object(
@ -450,6 +456,8 @@ class NetAppNfsDriverTestCase(test.TestCase):
self.mock_object(self.driver, '_delete_file')
self.mock_object(self.driver, '_is_flexgroup',
return_value=is_flexgroup)
self.mock_object(self.driver, '_is_flexgroup_clone_file_supported',
return_value=not is_flexgroup)
mock_super_delete = self.mock_object(nfs.NfsDriver,
'delete_snapshot')
@ -550,6 +558,8 @@ class NetAppNfsDriverTestCase(test.TestCase):
mock_log = self.mock_object(nfs_base, 'LOG')
self.mock_object(self.driver, '_is_flexgroup',
return_value=False)
self.mock_object(self.driver, '_is_flexgroup_clone_file_supported',
return_value=True)
self.mock_object(self.driver, '_ensure_flexgroup_not_in_cg')
mock_copy_image = self.mock_object(
remotefs.RemoteFSDriver, 'copy_image_to_volume')
@ -1147,3 +1157,7 @@ class NetAppNfsDriverTestCase(test.TestCase):
self.assertRaises(na_utils.NetAppDriverException,
self.driver._ensure_flexgroup_not_in_cg,
fake_v1)
def test__is_flexgroup_clone_file_supported(self):
self.assertRaises(NotImplementedError,
self.driver._is_flexgroup_clone_file_supported)

View File

@ -279,6 +279,7 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
'netapp_dedupe_used_percent': 0,
'consistencygroup_support': False,
'consistent_group_snapshot_enabled': False,
'multiattach': False,
})
self.assertEqual(expected, result)
@ -435,16 +436,38 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
self.assertIsNone(vserver)
def test_check_for_setup_error(self):
super_check_for_setup_error = self.mock_object(
nfs_base.NetAppNfsDriver, 'check_for_setup_error')
mock_add_looping_tasks = self.mock_object(
self.driver, '_add_looping_tasks')
mock_contains_fg = self.mock_object(
self.driver.ssc_library, 'contains_flexgroup_pool',
return_value=False)
self.driver.zapi_client = mock.Mock(features=mock.Mock(
FLEXGROUP=True))
super_check_for_setup_error = self.mock_object(
nfs_base.NetAppNfsDriver, 'check_for_setup_error')
self.driver.check_for_setup_error()
self.assertEqual(1, super_check_for_setup_error.call_count)
self.assertEqual(1, mock_add_looping_tasks.call_count)
mock_add_looping_tasks.assert_called_once_with()
mock_contains_fg.assert_called_once_with()
def test_check_for_setup_error_fail(self):
mock_add_looping_tasks = self.mock_object(
self.driver, '_add_looping_tasks')
mock_contains_fg = self.mock_object(
self.driver.ssc_library, 'contains_flexgroup_pool',
return_value=True)
self.driver.zapi_client = mock.Mock(features=mock.Mock(
FLEXGROUP=False))
self.assertRaises(
na_utils.NetAppDriverException, self.driver.check_for_setup_error)
self.assertEqual(1, mock_add_looping_tasks.call_count)
mock_add_looping_tasks.assert_called_once_with()
mock_contains_fg.assert_called_once_with()
@ddt.data({'replication_enabled': True, 'failed_over': False,
'cluster_credentials': True},
@ -558,10 +581,14 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
'delete_volume')
mock_flexgroup = self.mock_object(self.driver, '_is_flexgroup',
return_value=False)
mock_clone_file = self.mock_object(
self.driver, '_is_flexgroup_clone_file_supported',
return_value=True)
self.driver._delete_backing_file_for_volume(fake.NFS_VOLUME)
mock_flexgroup.assert_called_once_with(host=fake.NFS_VOLUME['host'])
mock_clone_file.assert_not_called()
mock_filer_delete.assert_called_once_with(
fake.NFS_VOLUME['id'], fake.NFS_VOLUME['name'])
self.assertEqual(0, mock_super_delete.call_count)
@ -570,6 +597,9 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
def test_delete_backing_file_for_volume_exception_path(self, super_exc):
mock_flexgroup = self.mock_object(self.driver, '_is_flexgroup',
return_value=False)
mock_clone_file = self.mock_object(
self.driver, '_is_flexgroup_clone_file_supported',
return_value=True)
mock_exception_log = self.mock_object(nfs_cmode.LOG, 'exception')
exception_call_count = 2 if super_exc else 1
mock_filer_delete = self.mock_object(self.driver, '_delete_file')
@ -582,6 +612,7 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
self.driver._delete_backing_file_for_volume(fake.NFS_VOLUME)
mock_flexgroup.assert_called_once_with(host=fake.NFS_VOLUME['host'])
mock_clone_file.assert_not_called()
mock_filer_delete.assert_called_once_with(
fake.NFS_VOLUME['id'], fake.NFS_VOLUME['name'])
mock_super_delete.assert_called_once_with(fake.NFS_VOLUME)
@ -593,6 +624,8 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
self.driver, '_delete_backing_file_for_snapshot')
self.mock_object(self.driver, '_is_flexgroup',
return_value=is_flexgroup)
self.mock_object(self.driver, '_is_flexgroup_clone_file_supported',
return_value=not is_flexgroup)
mock_super_delete = self.mock_object(nfs_base.NetAppNfsDriver,
'delete_snapshot')
self.driver.delete_snapshot(fake.test_snapshot)
@ -1230,6 +1263,7 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
return_value=False)
drv._copy_from_cache = mock.Mock(return_value=True)
drv._is_flexgroup = mock.Mock(return_value=False)
drv._is_flexgroup_clone_file_supported = mock.Mock(return_value=True)
drv.clone_image(context, volume, image_location, image_meta,
image_service)
@ -1242,6 +1276,9 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
def test_clone_image_flexgroup(self):
self.driver._is_flexgroup = mock.Mock(return_value=True)
mock_clone_file = self.mock_object(
self.driver, '_is_flexgroup_clone_file_supported',
return_value=False)
volume = {'host': 'openstack@nfscmode#192.128.1.1:/mnt_point'}
context = object()
model, cloned = self.driver.clone_image(
@ -1250,6 +1287,7 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
self.assertFalse(cloned)
self.assertIsNone(model)
self.driver._is_flexgroup.assert_called_once_with(host=volume['host'])
mock_clone_file.assert_called_once_with()
def test_clone_image_copyoffload_from_img_service(self):
drv = self.driver
@ -1271,6 +1309,7 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
return_value=True)
drv._copy_from_img_service = mock.Mock(return_value=True)
drv._is_flexgroup = mock.Mock(return_value=False)
drv._is_flexgroup_clone_file_supported = mock.Mock(return_value=True)
retval = drv.clone_image(
context, volume, image_location, image_meta, image_service)
@ -1298,6 +1337,7 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
return_value=False)
drv._copy_from_img_service = mock.Mock(side_effect=Exception())
drv._is_flexgroup = mock.Mock(return_value=False)
drv._is_flexgroup_clone_file_supported = mock.Mock(return_value=True)
retval = drv.clone_image(
context, volume, image_location, image_meta, image_service)
@ -1822,3 +1862,11 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
self.assertEqual('deleted', model_update['status'])
self.assertEqual('deleted', volumes[0]['status'])
mock_delete_file.assert_called_once_with(fake.VG_VOLUME)
def test__is_flexgroup_clone_file_supported(self):
self.driver.zapi_client = mock.Mock(features=mock.Mock(
FLEXGROUP_CLONE_FILE=True))
is_fg_clone = self.driver._is_flexgroup_clone_file_supported()
self.assertTrue(is_fg_clone)

View File

@ -583,3 +583,32 @@ class CapabilitiesLibraryTestCase(test.TestCase):
self.assertEqual(expected, result)
self.zapi_client.is_qos_min_supported.assert_called_once_with(False,
'node')
@ddt.data(True, False)
def test_is_flexgroup(self, is_fg):
pool_name = 'fake_pool'
self.ssc_library.ssc = {
pool_name: {
'pool_name': pool_name,
'netapp_is_flexgroup': 'true' if is_fg else 'false',
},
}
if not is_fg:
pool_name = 'no_pool'
is_fg_returned = self.ssc_library.is_flexgroup(pool_name)
self.assertEqual(is_fg_returned, is_fg)
@ddt.data(True, False)
def test_contains_flexgroup(self, contains_fg):
self.ssc_library.ssc = {
'fake_pool': {
'netapp_is_flexgroup': 'true' if contains_fg else 'false',
},
}
contains_fg_returned = self.ssc_library.contains_flexgroup_pool()
self.assertEqual(contains_fg_returned, contains_fg)

View File

@ -48,7 +48,19 @@ LOG = logging.getLogger(__name__)
@six.add_metaclass(volume_utils.TraceWrapperMetaclass)
class NetAppBlockStorageCmodeLibrary(block_base.NetAppBlockStorageLibrary,
data_motion.DataMotionMixin):
"""NetApp block storage library for Data ONTAP (Cluster-mode)."""
"""NetApp block storage library for Data ONTAP (Cluster-mode).
Version history:
.. code-block:: none
1.0.0 - Driver development before Wallaby
2.0.0 - Add support for QoS minimums specs
Add support for dynamic Adaptive QoS policy group creation
"""
VERSION = "2.0.0"
REQUIRED_CMODE_FLAGS = ['netapp_vserver']

View File

@ -67,6 +67,8 @@ class Client(client_base.Client):
ontapi_1_60 = ontapi_version >= (1, 160)
ontapi_1_40 = ontapi_version >= (1, 140)
ontapi_1_50 = ontapi_version >= (1, 150)
ontapi_1_80 = ontapi_version >= (1, 180)
ontapi_1_90 = ontapi_version >= (1, 190)
nodes_info = self._get_cluster_nodes_info()
for node in nodes_info:
@ -100,6 +102,9 @@ class Client(client_base.Client):
self.features.add_feature('BACKUP_CLONE_PARAM', supported=ontapi_1_100)
self.features.add_feature('CLUSTER_PEER_POLICY', supported=ontapi_1_30)
self.features.add_feature('FLEXVOL_ENCRYPTION', supported=ontapi_1_1xx)
self.features.add_feature('FLEXGROUP', supported=ontapi_1_80)
self.features.add_feature('FLEXGROUP_CLONE_FILE',
supported=ontapi_1_90)
self.features.add_feature('ADAPTIVE_QOS', supported=ontapi_1_40)
self.features.add_feature('ADAPTIVE_QOS_BLOCK_SIZE',

View File

@ -179,7 +179,8 @@ class NetAppNfsDriver(driver.ManageableVD,
because the ONTAP clone file is not supported by FlexGroup yet.
"""
self._ensure_flexgroup_not_in_cg(volume)
if self._is_flexgroup(vol_id=snapshot['volume_id']):
if (self._is_flexgroup(vol_id=snapshot['volume_id']) and
not self._is_flexgroup_clone_file_supported()):
model = super(NetAppNfsDriver, self).create_volume_from_snapshot(
volume, snapshot)
@ -199,7 +200,8 @@ class NetAppNfsDriver(driver.ManageableVD,
because the ONTAP clone file is not supported by FlexGroup yet.
"""
self._ensure_flexgroup_not_in_cg(volume)
if self._is_flexgroup(vol_id=src_vref['id']):
if (self._is_flexgroup(vol_id=src_vref['id']) and
not self._is_flexgroup_clone_file_supported()):
model = super(NetAppNfsDriver, self).create_cloned_volume(
volume, src_vref)
@ -306,7 +308,8 @@ class NetAppNfsDriver(driver.ManageableVD,
For a FlexGroup pool, the operation relies on the NFS generic driver
because the ONTAP clone file is not supported by FlexGroup yet.
"""
if self._is_flexgroup(vol_id=snapshot['volume_id']):
if (self._is_flexgroup(vol_id=snapshot['volume_id']) and
not self._is_flexgroup_clone_file_supported()):
self._create_snapshot_for_flexgroup(snapshot)
else:
self._clone_backing_file_for_volume(snapshot['volume_name'],
@ -360,7 +363,8 @@ class NetAppNfsDriver(driver.ManageableVD,
def delete_snapshot(self, snapshot):
"""Deletes a snapshot."""
if self._is_flexgroup(vol_id=snapshot.volume_id):
if (self._is_flexgroup(vol_id=snapshot.volume_id) and
not self._is_flexgroup_clone_file_supported()):
super(NetAppNfsDriver, self).delete_snapshot(snapshot)
else:
self._delete_file(snapshot.volume_id, snapshot.name)
@ -477,9 +481,11 @@ class NetAppNfsDriver(driver.ManageableVD,
LOG.info('Copied image to volume %s using regular download.',
volume['id'])
if not self._is_flexgroup(host=volume['host']):
# NOTE(felipe_rodrigues): FlexGroup does not support FlexClone
# file, so the NetApp image cache cannot be used.
if (not self._is_flexgroup(host=volume['host']) or
self._is_flexgroup_clone_file_supported()):
# NOTE(felipe_rodrigues): NetApp image cache relies on the
# FlexClone file, which is only available for the earliest
# versions of FlexGroup.
self._register_image_in_cache(volume, image_id)
def _register_image_in_cache(self, volume, image_id):
@ -641,10 +647,8 @@ class NetAppNfsDriver(driver.ManageableVD,
Returns a dict of volume properties eg. provider_location,
boolean indicating whether cloning occurred.
"""
if self._is_flexgroup(host=volume['host']):
# NOTE(felipe_rodrigues): FlexGroup does not support FlexClone
# file, so the clone_image cannot be used together with the Netapp
# cache. Instead, it can use the core cache implementation.
if (self._is_flexgroup(host=volume['host']) and
not self._is_flexgroup_clone_file_supported()):
return None, False
image_id = image_meta['id']
@ -1215,3 +1219,7 @@ class NetAppNfsDriver(driver.ManageableVD,
msg = _("Cannot create %s volume on FlexGroup pool with "
"consistency group.")
raise na_utils.NetAppDriverException(msg % volume['id'])
def _is_flexgroup_clone_file_supported(self):
"""Check whether storage can perform clone file for FlexGroup"""
raise NotImplementedError()

View File

@ -51,7 +51,20 @@ LOG = logging.getLogger(__name__)
@six.add_metaclass(volume_utils.TraceWrapperWithABCMetaclass)
class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver,
data_motion.DataMotionMixin):
"""NetApp NFS driver for Data ONTAP (Cluster-mode)."""
"""NetApp NFS driver for Data ONTAP (Cluster-mode).
Version history:
.. code-block:: none
1.0.0 - Driver development before Wallaby
2.0.0 - Add support for QoS minimums specs
Add support for dynamic Adaptive QoS policy group creation
Implement FlexGroup pool
"""
VERSION = "2.0.0"
REQUIRED_CMODE_FLAGS = ['netapp_vserver']
@ -101,6 +114,12 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver,
def check_for_setup_error(self):
"""Check that the driver is working and can communicate."""
self._add_looping_tasks()
if (self.ssc_library.contains_flexgroup_pool() and
not self.zapi_client.features.FLEXGROUP):
msg = _('FlexGroup pool requires Data ONTAP 9.8 or later.')
raise na_utils.NetAppDriverException(msg)
super(NetAppCmodeNfsDriver, self).check_for_setup_error()
def _add_looping_tasks(self):
@ -288,6 +307,7 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver,
if is_flexgroup:
pool['consistencygroup_support'] = False
pool['consistent_group_snapshot_enabled'] = False
pool['multiattach'] = False
# Add up-to-date capacity info
nfs_share = ssc_vol_info['pool_name']
@ -452,12 +472,14 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver,
is_flexgroup = self._is_flexgroup(host=volume['host'])
try:
LOG.debug('Deleting backing file for volume %s.', volume['id'])
if is_flexgroup:
if (is_flexgroup and
not self._is_flexgroup_clone_file_supported()):
super(NetAppCmodeNfsDriver, self).delete_volume(volume)
else:
self._delete_file(volume['id'], volume['name'])
except Exception:
if is_flexgroup:
if (is_flexgroup and
not self._is_flexgroup_clone_file_supported()):
LOG.exception('Exec of "rm" command on backing file for '
'%s was unsuccessful.', volume['id'])
else:
@ -482,7 +504,8 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver,
def delete_snapshot(self, snapshot):
"""Deletes a snapshot."""
if self._is_flexgroup(snapshot['volume_id']):
if (self._is_flexgroup(snapshot['volume_id']) and
not self._is_flexgroup_clone_file_supported()):
super(NetAppCmodeNfsDriver, self).delete_snapshot(snapshot)
else:
self._delete_backing_file_for_snapshot(snapshot)
@ -957,3 +980,7 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver,
pool_name = volume_utils.extract_host(host, level='pool')
return self.ssc_library.is_flexgroup(pool_name)
def _is_flexgroup_clone_file_supported(self):
"""Check whether storage can perform clone file for FlexGroup"""
return self.zapi_client.features.FLEXGROUP_CLONE_FILE

View File

@ -378,3 +378,11 @@ class CapabilitiesLibrary(object):
return flexvol_info['netapp_is_flexgroup'] == 'true'
return False
def contains_flexgroup_pool(self):
for __, flexvol_info in self.ssc.items():
if ('netapp_is_flexgroup' in flexvol_info and
flexvol_info['netapp_is_flexgroup'] == 'true'):
return True
return False

View File

@ -4,29 +4,34 @@ features:
NetApp ONTAP driver: added support for FlexGroup pool using the NFS
mode. There are several considerations for using the driver with it:
1. The FlexGroup pool has a different view of aggregate capabilites,
1. The FlexGroup pool is only supported using ONTAP storage 9.8 or greater.
2. The FlexGroup pool has a different view of aggregate capabilites,
changing them by a list of elements, instead of a single element. They
are ``netapp_aggregate``, ``netapp_raid_type``, ``netapp_disk_type`` and
``netapp_hybrid_aggregate``. The ``netapp_aggregate_used_percent``
capability is an average of used percent of all FlexGroup's aggregates.
2. The ``utilization`` capability is not calculated to FlexGroup pools, it is
3. The ``utilization`` capability is not calculated to FlexGroup pools, it is
always set to default of 50.
3. The driver cannot support consistency group with volumes that are over
4. The driver cannot support consistency group with volumes that are over
FlexGroup pools.
4. For volumes over the FlexGroup pool, the operations of clone volume,
5. The driver cannot support multi-attach with volumes that are over
FlexGroup pools.
6. For volumes over the FlexGroup pool, the operations of clone volume,
create snapshot and create volume from an image are implemented as the NFS
generic driver. Hence, it does not rely on the ONTAP storage to perform
those operations.
5. A driver with FlexGroup pools has snapshot support disabled by default. To
7. A driver with FlexGroup pools has snapshot support disabled by default. To
enable, you must set ``nfs_snapshot_support`` to true in the backend's configuration
section of the cinder configuration file.
6. The driver image cache is not applied for volumes over FlexGroup pools.
8. The driver image cache is not applied for volumes over FlexGroup pools.
It can use the core image cache for avoiding downloading twice, though.
7. Given that the FlexGroup pool may be on several cluster nodes, the QoS minimum
9. Given that the FlexGroup pool may be on several cluster nodes, the QoS minimum
support is only enabled if all nodes support it.