[NetApp] LUN space allocation support

Space allocation is an important NetApp driver specific feature.
This needs to be set when the cinder volume is created.
This is not related to thin/thick provisioning feature of cinder
volumes.It is independent of that. It enables ONTAP to reclaim
space automatically when host deletes data.This helps ONTAP
and host to see the actual space correctly when the host
deletes data.
It also helps to keep a LUN (cinder volume) online when the
LUN (cinder volume) in ontap runs out of space and containing
volume (in ONTAP) cannot automatically grow more space.

User can configure it by using volume type extra spec.
By default Space allocation value is disabled for ONTAP LUN

netapp:space_allocation: "<is> True"  # to enable space allocation
netapp:space_allocation: "<is> False" # to disable space allocation

Blueprint: netapp-space-allocation-support
Change-Id: Ib7072f3093067ecd8ad84e396aaecec8f15c49ba
This commit is contained in:
Jayaanand Borra 2023-08-30 00:21:25 +05:30
parent 0425c08f3d
commit 48d922cf62
10 changed files with 215 additions and 26 deletions

View File

@ -2347,6 +2347,9 @@ LUN_GET_ITER_RESULT = [
'OsType': LUN_GET_ITER_REST['records'][0]['os_type'],
'SpaceReserved':
LUN_GET_ITER_REST['records'][0]['space']['guarantee']['requested'],
'SpaceAllocated':
LUN_GET_ITER_REST['records'][0]['space']
['scsi_thin_provisioning_support_enabled'],
'UUID': LUN_GET_ITER_REST['records'][0]['uuid'],
},
{
@ -2360,6 +2363,9 @@ LUN_GET_ITER_RESULT = [
'OsType': LUN_GET_ITER_REST['records'][1]['os_type'],
'SpaceReserved':
LUN_GET_ITER_REST['records'][1]['space']['guarantee']['requested'],
'SpaceAllocated':
LUN_GET_ITER_REST['records'][1]['space']
['scsi_thin_provisioning_support_enabled'],
'UUID': LUN_GET_ITER_REST['records'][1]['uuid'],
},
]

View File

@ -55,7 +55,8 @@ class NetAppBaseClientTestCase(test.TestCase):
self.fake_volume = six.text_type(uuid.uuid4())
self.fake_lun = six.text_type(uuid.uuid4())
self.fake_size = '1024'
self.fake_metadata = {'OsType': 'linux', 'SpaceReserved': 'true'}
self.fake_metadata = {'OsType': 'linux', 'SpaceReserved': 'true',
'SpaceAllocated': 'true'}
self.mock_send_request = self.mock_object(
self.client.connection, 'send_request')
@ -89,14 +90,23 @@ class NetAppBaseClientTestCase(test.TestCase):
self.assertIsNone(self.client.check_is_naelement(element))
self.assertRaises(ValueError, self.client.check_is_naelement, None)
@ddt.data({'ontap_version': (9, 4, 0), 'space_reservation': 'true'},
{'ontap_version': (9, 4, 0), 'space_reservation': 'false'},
{'ontap_version': (9, 6, 0), 'space_reservation': 'true'},
{'ontap_version': (9, 6, 0), 'space_reservation': 'false'})
@ddt.data({'ontap_version': (9, 4, 0),
'space_reservation': 'true',
'space_alloc': 'true'},
{'ontap_version': (9, 4, 0),
'space_reservation': 'false',
'space_alloc': 'false'},
{'ontap_version': (9, 6, 0),
'space_reservation': 'true',
'space_alloc': 'true'},
{'ontap_version': (9, 6, 0),
'space_reservation': 'false',
'space_alloc': 'false'}, )
@ddt.unpack
def test_create_lun(self, ontap_version, space_reservation):
def test_create_lun(self, ontap_version, space_reservation, space_alloc):
expected_path = '/vol/%s/%s' % (self.fake_volume, self.fake_lun)
self.fake_metadata['SpaceReserved'] = space_reservation
self.fake_metadata['SpaceAllocated'] = space_alloc
expected_space_reservation = space_reservation
self.mock_object(self.client, 'get_ontap_version',
return_value=ontap_version)
@ -127,7 +137,9 @@ class NetAppBaseClientTestCase(test.TestCase):
'size': initial_size,
'ostype': self.fake_metadata['OsType'],
'space-reservation-enabled':
expected_space_reservation})
expected_space_reservation,
'space-allocation-enabled':
space_alloc})
self.connection.invoke_successfully.assert_called_with(
mock.ANY, True)
@ -141,15 +153,25 @@ class NetAppBaseClientTestCase(test.TestCase):
else:
mock_set_space_reservation.assert_not_called()
@ddt.data({'ontap_version': (9, 4, 0), 'space_reservation': 'true'},
{'ontap_version': (9, 4, 0), 'space_reservation': 'false'},
{'ontap_version': (9, 6, 0), 'space_reservation': 'true'},
{'ontap_version': (9, 6, 0), 'space_reservation': 'false'})
@ddt.data({'ontap_version': (9, 4, 0),
'space_reservation': 'true',
'space_alloc': 'true'},
{'ontap_version': (9, 4, 0),
'space_reservation': 'false',
'space_alloc': 'false'},
{'ontap_version': (9, 6, 0),
'space_reservation': 'true',
'space_alloc': 'true'},
{'ontap_version': (9, 6, 0),
'space_reservation': 'false',
'space_alloc': 'false'}, )
@ddt.unpack
def test_create_lun_exact_size(self, ontap_version, space_reservation):
def test_create_lun_exact_size(self, ontap_version,
space_reservation, space_alloc):
expected_path = '/vol/%s/%s' % (self.fake_volume, self.fake_lun)
self.connection.get_api_version.return_value = (1, 110)
self.fake_metadata['SpaceReserved'] = space_reservation
self.fake_metadata['SpaceAllocated'] = space_alloc
expected_space_reservation = self.fake_metadata['SpaceReserved']
self.mock_object(self.client, 'get_ontap_version',
return_value=ontap_version)
@ -181,7 +203,9 @@ class NetAppBaseClientTestCase(test.TestCase):
'ostype': self.fake_metadata['OsType'],
'use-exact-size': 'true',
'space-reservation-enabled':
expected_space_reservation})
expected_space_reservation,
'space-allocation-enabled':
space_alloc})
self.connection.invoke_successfully.assert_called_with(
mock.ANY, True)
@ -195,18 +219,27 @@ class NetAppBaseClientTestCase(test.TestCase):
else:
mock_set_space_reservation.assert_not_called()
@ddt.data({'ontap_version': (9, 4, 0), 'space_reservation': 'true'},
{'ontap_version': (9, 4, 0), 'space_reservation': 'false'},
{'ontap_version': (9, 6, 0), 'space_reservation': 'true'},
{'ontap_version': (9, 6, 0), 'space_reservation': 'false'})
@ddt.data({'ontap_version': (9, 4, 0),
'space_reservation': 'true',
'space_alloc': 'true'},
{'ontap_version': (9, 4, 0),
'space_reservation': 'false',
'space_alloc': 'false'},
{'ontap_version': (9, 6, 0),
'space_reservation': 'true',
'space_alloc': 'true'},
{'ontap_version': (9, 6, 0),
'space_reservation': 'false',
'space_alloc': 'false'}, )
@ddt.unpack
def test_create_lun_with_qos_policy_group_name(
self, ontap_version, space_reservation):
self, ontap_version, space_reservation, space_alloc):
expected_path = '/vol/%s/%s' % (self.fake_volume, self.fake_lun)
expected_qos_group_name = 'qos_1'
mock_request = mock.Mock()
self.fake_metadata['SpaceReserved'] = space_reservation
self.fake_metadata['SpaceAllocated'] = space_alloc
expected_space_reservation = self.fake_metadata['SpaceReserved']
self.mock_object(self.client, 'get_ontap_version',
return_value=ontap_version)
@ -239,7 +272,9 @@ class NetAppBaseClientTestCase(test.TestCase):
**{'path': expected_path, 'size': initial_size,
'ostype': self.fake_metadata['OsType'],
'space-reservation-enabled':
expected_space_reservation})
expected_space_reservation,
'space-allocation-enabled':
space_alloc})
mock_request.add_new_child.assert_called_with(
'qos-policy-group', expected_qos_group_name)
self.connection.invoke_successfully.assert_called_with(

View File

@ -854,6 +854,7 @@ class NetAppRestCmodeClientTestCase(test.TestCase):
'uuid': fake.UUID1,
'fields': 'svm.name,location.volume.name,space.size,'
'location.qtree.name,name,os_type,'
'space.scsi_thin_provisioning_support_enabled,'
'space.guarantee.requested,uuid'
}
@ -884,6 +885,7 @@ class NetAppRestCmodeClientTestCase(test.TestCase):
'name': path,
'fields': 'svm.name,location.volume.name,space.size,'
'location.qtree.name,name,os_type,'
'space.scsi_thin_provisioning_support_enabled,'
'space.guarantee.requested,uuid'
}
@ -1678,6 +1680,8 @@ class NetAppRestCmodeClientTestCase(test.TestCase):
'space.size': str(initial_size),
'os_type': metadata['OsType'],
'space.guarantee.requested': metadata['SpaceReserved'],
'space.scsi_thin_provisioning_support_enabled':
metadata['SpaceAllocated'],
'qos_policy.name': fake.QOS_POLICY_GROUP_NAME
}

View File

@ -64,10 +64,19 @@ JOB_UUID = 'fb132b04-6422-43ce-9451-ee819f0131a4'
LUN_METADATA = {
'OsType': None,
'SpaceReserved': 'true',
'SpaceAllocated': 'false',
'Path': PATH,
'Qtree': None,
'Volume': POOL_NAME,
}
LUN_METADATA_WITH_SPACE_ALLOCATION = {
'OsType': None,
'SpaceReserved': 'true',
'Path': PATH,
'SpaceAllocated': 'true',
'Qtree': None,
'Volume': POOL_NAME,
}
NAMESPACE_METADATA = {
'OsType': None,
'Path': PATH_NAMESPACE,
@ -239,6 +248,7 @@ ISCSI_VOLUME = {
'name': 'fake_volume',
'id': 'fake_id',
'provider_auth': 'fake provider auth',
'provider_location': 'iscsi:/dummy_path'
}
ISCSI_LUN = {'name': ISCSI_VOLUME, 'lun_id': 42}
@ -250,6 +260,7 @@ ISCSI_CONNECTION_PROPERTIES = {
'auth_method': 'fake_method',
'auth_password': 'auth',
'auth_username': 'provider',
'discard': True,
'discovery_auth_method': 'fake_method',
'discovery_auth_username': 'provider',
'discovery_auth_password': 'auth',
@ -289,7 +300,7 @@ IGROUP1 = {'initiator-group-os-type': 'linux',
'initiator-group-name': IGROUP1_NAME}
QOS_SPECS = {}
EXTRA_SPECS = {}
EXTRA_SPECS = {'netapp:space_allocation': '<is> True'}
MAX_THROUGHPUT = '21734278B/s'
MIN_IOPS = '256iops'
MAX_IOPS = '512iops'

View File

@ -136,22 +136,97 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
self.library._create_lun.assert_called_once_with(
fake.POOL_NAME, fake.LUN_NAME, volume_size_in_bytes,
fake.LUN_METADATA, fake.QOS_POLICY_GROUP_NAME, False)
fake.LUN_METADATA,
fake.QOS_POLICY_GROUP_NAME, False)
self.library._get_volume_model_update.assert_called_once_with(
fake.VOLUME)
self.assertEqual(
0, self.library. _mark_qos_policy_group_for_deletion.call_count)
self.assertEqual(0, block_base.LOG.error.call_count)
def test_create_volume_space_allocation_extra_spec_false(self):
volume_size_in_bytes = int(fake.SIZE) * units.Gi
self.mock_object(na_utils, 'get_volume_extra_specs',
return_value={
'netapp:space_allocation': '<is> False'
}
)
self.mock_object(na_utils, 'log_extra_spec_warnings')
self.mock_object(block_base, 'LOG')
self.mock_object(volume_utils, 'extract_host',
return_value=fake.POOL_NAME)
self.mock_object(self.library, '_setup_qos_for_volume',
return_value=fake.QOS_POLICY_GROUP_INFO)
self.mock_object(self.library, '_create_lun')
self.mock_object(self.library, '_create_lun_handle')
self.mock_object(self.library, '_add_lun_to_table')
self.mock_object(self.library, '_mark_qos_policy_group_for_deletion')
self.mock_object(self.library, '_get_volume_model_update')
self.library.create_volume(fake.VOLUME)
self.library._create_lun.assert_called_once_with(
fake.POOL_NAME, fake.LUN_NAME, volume_size_in_bytes,
fake.LUN_METADATA,
fake.QOS_POLICY_GROUP_NAME, False)
self.library._get_volume_model_update.assert_called_once_with(
fake.VOLUME)
self.assertEqual(
0, self.library._mark_qos_policy_group_for_deletion.call_count)
self.assertEqual(0, block_base.LOG.error.call_count)
def test_create_volume_space_allocation_extra_spec_true(self):
volume_size_in_bytes = int(fake.SIZE) * units.Gi
self.mock_object(na_utils, 'get_volume_extra_specs',
return_value={
'netapp:space_allocation': '<is> True'
}
)
self.mock_object(na_utils, 'log_extra_spec_warnings')
self.mock_object(block_base, 'LOG')
self.mock_object(volume_utils, 'extract_host',
return_value=fake.POOL_NAME)
self.mock_object(self.library, '_setup_qos_for_volume',
return_value=fake.QOS_POLICY_GROUP_INFO)
self.mock_object(self.library, '_create_lun')
self.mock_object(self.library, '_create_lun_handle')
self.mock_object(self.library, '_add_lun_to_table')
self.mock_object(self.library, '_mark_qos_policy_group_for_deletion')
self.mock_object(self.library, '_get_volume_model_update')
self.library.create_volume(fake.VOLUME)
self.library._create_lun.assert_called_once_with(
fake.POOL_NAME, fake.LUN_NAME, volume_size_in_bytes,
fake.LUN_METADATA_WITH_SPACE_ALLOCATION,
fake.QOS_POLICY_GROUP_NAME, False)
self.library._get_volume_model_update.assert_called_once_with(
fake.VOLUME)
self.assertEqual(
0, self.library._mark_qos_policy_group_for_deletion.call_count)
self.assertEqual(0, block_base.LOG.error.call_count)
def test_create_volume_no_pool(self):
self.mock_object(volume_utils, 'extract_host', return_value=None)
self.assertRaises(exception.InvalidHost, self.library.create_volume,
fake.VOLUME)
def test_space_allocation_exception_path(self):
self.mock_object(block_base, 'LOG')
self.mock_object(na_utils, 'get_volume_extra_specs',
return_value={'netapp:space_allocation': 'xyz'})
self.mock_object(self.library, '_setup_qos_for_volume',
return_value=fake.QOS_POLICY_GROUP_INFO)
self.mock_object(self.library, '_create_lun', side_effect=Exception)
self.mock_object(self.library, '_mark_qos_policy_group_for_deletion')
self.assertRaises(exception.VolumeBackendAPIException,
self.library.create_volume, fake.VOLUME)
def test_create_volume_exception_path(self):
self.mock_object(block_base, 'LOG')
self.mock_object(na_utils, 'get_volume_extra_specs')
self.mock_object(na_utils, 'get_volume_extra_specs',
return_value={})
self.mock_object(self.library, '_setup_qos_for_volume',
return_value=fake.QOS_POLICY_GROUP_INFO)
self.mock_object(self.library, '_create_lun', side_effect=Exception)
@ -767,6 +842,9 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
self.mock_object(block_base.NetAppBlockStorageLibrary,
'_get_targets_from_list',
return_value=target_details_list)
self.mock_object(block_base.NetAppBlockStorageLibrary,
'_is_space_alloc_enabled',
return_value=True)
self.zapi_client.get_iscsi_service_details.return_value = (
fake.ISCSI_SERVICE_IQN)
self.mock_object(na_utils,
@ -796,6 +874,10 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
fake.ISCSI_CONNECTION_PROPERTIES['data']
['discovery_auth_username'],
target_info['data']['discovery_auth_username'])
self.assertEqual(
fake.ISCSI_CONNECTION_PROPERTIES['data']
['discard'],
target_info['data']['discard'])
self.assertEqual(fake.ISCSI_CONNECTION_PROPERTIES, target_info)
block_base.NetAppBlockStorageLibrary._map_lun.assert_called_once_with(
@ -815,6 +897,9 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
self.zapi_client.get_iscsi_target_details.return_value = None
self.mock_object(block_base.NetAppBlockStorageLibrary,
'_get_targets_from_list')
self.mock_object(block_base.NetAppBlockStorageLibrary,
'_is_space_alloc_enabled',
return_value=True)
self.mock_object(na_utils,
'get_iscsi_connection_properties',
return_value=fake.ISCSI_CONNECTION_PROPERTIES)
@ -826,6 +911,9 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
self.assertEqual(
0, block_base.NetAppBlockStorageLibrary
._get_targets_from_list.call_count)
self.assertEqual(
0, block_base.NetAppBlockStorageLibrary
._is_space_alloc_enabled.call_count)
self.assertEqual(
0, self.zapi_client.get_iscsi_service_details.call_count)
self.assertEqual(
@ -840,6 +928,9 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
self.mock_object(block_base.NetAppBlockStorageLibrary,
'_get_targets_from_list',
return_value=None)
self.mock_object(block_base.NetAppBlockStorageLibrary,
'_is_space_alloc_enabled',
return_value=True)
self.mock_object(na_utils, 'get_iscsi_connection_properties')
self.assertRaises(exception.VolumeBackendAPIException,
@ -848,6 +939,9 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
self.assertEqual(0, self.zapi_client
.get_iscsi_service_details.call_count)
self.assertEqual(0,
block_base.NetAppBlockStorageLibrary
._is_space_alloc_enabled.call_count)
self.assertEqual(0, na_utils.get_iscsi_connection_properties
.call_count)

View File

@ -224,12 +224,17 @@ class NetAppBlockStorageLibrary(object):
extra_specs = na_utils.get_volume_extra_specs(volume)
space_allocation = volume_utils.is_boolean_str(
extra_specs.get('netapp:space_allocation')
)
LOG.debug('create_volume space_allocation %r', space_allocation)
lun_name = volume['name']
size = int(volume['size']) * units.Gi
metadata = {'OsType': self.lun_ostype,
'SpaceReserved': self.lun_space_reservation,
'SpaceAllocated': str(space_allocation).lower(),
'Path': '/vol/%s/%s' % (pool_name, lun_name)}
qos_policy_group_info = self._setup_qos_for_volume(volume, extra_specs)
@ -712,6 +717,16 @@ class NetAppBlockStorageLibrary(object):
" post clone resize LUN %s.", seg[-1])
LOG.error("Exception details: %s", e)
def _is_space_alloc_enabled(self, path):
"""Gets space allocation details for the LUN."""
LOG.debug("Getting LUN space allocation enabled.")
lun_infos = self.zapi_client.get_lun_by_args(path=path)
if not lun_infos:
seg = path.split('/')
msg = _('Failure getting LUN info for %s' % seg[-1])
raise exception.VolumeBackendAPIException(data=msg)
return lun_infos[0]['SpaceAllocated'] == "true"
def _get_lun_block_count(self, path):
"""Gets block counts for the LUN."""
LOG.debug("Getting LUN block count.")
@ -844,6 +859,7 @@ class NetAppBlockStorageLibrary(object):
"""
initiator_name = connector['initiator']
lun_path = volume['provider_location'].split(':')[1]
name = volume['name']
lun_id = self._map_lun(name, [initiator_name], 'iscsi', None)
@ -874,7 +890,7 @@ class NetAppBlockStorageLibrary(object):
properties = na_utils.get_iscsi_connection_properties(lun_id, volume,
iqn, addresses,
ports)
properties['discard'] = self._is_space_alloc_enabled(lun_path)
if self.configuration.use_chap_auth:
chap_username, chap_password = self._configure_chap(initiator_name)
self._add_chap_properties(properties, chap_username, chap_password)

View File

@ -130,6 +130,10 @@ class Client(object):
params = {'path': path, 'size': str(initial_size),
'ostype': metadata['OsType'],
'space-reservation-enabled': space_reservation}
if "SpaceAllocated" in metadata:
params['space-allocation-enabled'] = metadata['SpaceAllocated']
version = self.get_ontapi_version()
if version >= (1, 110):
params['use-exact-size'] = 'true'

View File

@ -545,6 +545,8 @@ class Client(client_base.Client):
meta_dict['OsType'] = lun.get_child_content('multiprotocol-type')
meta_dict['SpaceReserved'] = \
lun.get_child_content('is-space-reservation-enabled')
meta_dict['SpaceAllocated'] = \
lun.get_child_content('is-space-alloc-enabled')
meta_dict['UUID'] = lun.get_child_content('uuid')
meta_dict['BlockSize'] = lun.get_child_content('block-size')
return meta_dict

View File

@ -663,6 +663,7 @@ class RestClient(object):
'svm.name': self.vserver,
'fields': 'svm.name,location.volume.name,space.size,'
'location.qtree.name,name,os_type,'
'space.scsi_thin_provisioning_support_enabled,'
'space.guarantee.requested,uuid'
}
@ -683,6 +684,8 @@ class RestClient(object):
lun_info['Path'] = lun['name']
lun_info['OsType'] = lun['os_type']
lun_info['SpaceReserved'] = lun['space']['guarantee']['requested']
lun_info['SpaceAllocated'] = \
lun['space']['scsi_thin_provisioning_support_enabled']
lun_info['UUID'] = lun['uuid']
lun_list.append(lun_info)
@ -695,6 +698,7 @@ class RestClient(object):
query = {
'fields': 'svm.name,location.volume.name,space.size,'
'location.qtree.name,name,os_type,'
'space.scsi_thin_provisioning_support_enabled,'
'space.guarantee.requested,uuid'
}
@ -723,6 +727,8 @@ class RestClient(object):
lun_info['Path'] = lun['name']
lun_info['OsType'] = lun['os_type']
lun_info['SpaceReserved'] = lun['space']['guarantee']['requested']
lun_info['SpaceAllocated'] = \
lun['space']['scsi_thin_provisioning_support_enabled']
lun_info['UUID'] = lun['uuid']
# NOTE(nahimsouza): Currently, ONTAP REST API does not have the
@ -1305,13 +1311,15 @@ class RestClient(object):
path = f'/vol/{volume_name}/{lun_name}'
space_reservation = metadata['SpaceReserved']
space_allocation = metadata['SpaceAllocated']
initial_size = size
body = {
'name': path,
'space.size': str(initial_size),
'os_type': metadata['OsType'],
'space.guarantee.requested': space_reservation
'space.guarantee.requested': space_reservation,
'space.scsi_thin_provisioning_support_enabled': space_allocation
}
if qos_policy_group_name:

View File

@ -0,0 +1,9 @@
features:
- |
NetApp iSCSI/FCP drivers: NetApp space allocation feature allows ONTAP
and host to see the actual space correctly when host deletes data.
It also notifies the host when the LUN cannot accept write data due
to lack of space on the volume, and makes the LUN read-only
(rather than going offline). This feature can be enabled or
disabled on cinder volumes by using volume type extra specs with
the ``netapp:space_allocation`` property.