Merge "NetApp ONTAP: Add support for dynamic Adaptive QoS policy group creation"

This commit is contained in:
Zuul 2021-03-13 00:54:22 +00:00 committed by Gerrit Code Review
commit e75c5e5b49
15 changed files with 768 additions and 113 deletions

View File

@ -104,6 +104,8 @@ class NetAppBaseClientTestCase(test.TestCase):
client_base.Client, 'do_direct_resize')
mock_set_space_reservation = self.mock_object(
client_base.Client, 'set_lun_space_reservation')
mock_validate_qos_policy_group = self.mock_object(
client_base.Client, '_validate_qos_policy_group')
initial_size = self.fake_size
if ontap_version < '9.5':
@ -118,6 +120,7 @@ class NetAppBaseClientTestCase(test.TestCase):
self.fake_size,
self.fake_metadata)
mock_validate_qos_policy_group.assert_called_once()
mock_create_node.assert_called_with(
'lun-create-by-size',
**{'path': expected_path,
@ -154,6 +157,8 @@ class NetAppBaseClientTestCase(test.TestCase):
client_base.Client, 'do_direct_resize')
mock_set_space_reservation = self.mock_object(
client_base.Client, 'set_lun_space_reservation')
mock_validate_qos_policy_group = self.mock_object(
client_base.Client, '_validate_qos_policy_group')
initial_size = self.fake_size
if ontap_version < '9.5':
@ -168,6 +173,7 @@ class NetAppBaseClientTestCase(test.TestCase):
self.fake_size,
self.fake_metadata)
mock_validate_qos_policy_group.assert_called_once()
mock_create_node.assert_called_with(
'lun-create-by-size',
**{'path': expected_path,
@ -208,6 +214,8 @@ class NetAppBaseClientTestCase(test.TestCase):
client_base.Client, 'do_direct_resize')
mock_set_space_reservation = self.mock_object(
client_base.Client, 'set_lun_space_reservation')
mock_validate_qos_policy_group = self.mock_object(
client_base.Client, '_validate_qos_policy_group')
initial_size = self.fake_size
if ontap_version < '9.5':
@ -225,6 +233,7 @@ class NetAppBaseClientTestCase(test.TestCase):
self.fake_metadata,
qos_policy_group_name=expected_qos_group_name)
mock_validate_qos_policy_group.assert_called_once()
mock_create_node.assert_called_with(
'lun-create-by-size',
**{'path': expected_path, 'size': initial_size,
@ -284,6 +293,8 @@ class NetAppBaseClientTestCase(test.TestCase):
side_effect=netapp_api.NaApiError)
self.mock_object(self.client, 'get_ontap_version',
return_value=ontap_version)
mock_validate_qos_policy_group = self.mock_object(
client_base.Client, '_validate_qos_policy_group')
self.assertRaises(netapp_api.NaApiError,
self.client.create_lun,
@ -291,6 +302,7 @@ class NetAppBaseClientTestCase(test.TestCase):
self.fake_lun,
self.fake_size,
self.fake_metadata)
mock_validate_qos_policy_group.assert_called_once()
def test_destroy_lun(self):
path = '/vol/%s/%s' % (self.fake_volume, self.fake_lun)

View File

@ -729,6 +729,67 @@ class NetAppCmodeClientTestCase(test.TestCase):
self.assertSetEqual(igroups, expected)
@ddt.data(True, False)
def test__validate_qos_policy_group_none_adaptive(self, is_adaptive):
self.client.features.add_feature('ADAPTIVE_QOS', supported=True)
self.client._validate_qos_policy_group(
is_adaptive=is_adaptive, spec=None)
def test__validate_qos_policy_group_none_adaptive_no_support(self):
self.client.features.add_feature('ADAPTIVE_QOS', supported=False)
self.assertRaises(
netapp_utils.NetAppDriverException,
self.client._validate_qos_policy_group,
is_adaptive=True,
spec=None)
@ddt.data(True, False)
def test__validate_qos_policy_group_no_qos_min_support(self, is_adaptive):
spec = {'min_throughput': '10'}
self.assertRaises(
netapp_utils.NetAppDriverException,
self.client._validate_qos_policy_group,
is_adaptive=is_adaptive,
spec=spec,
qos_min_support=False)
def test__validate_qos_policy_group_no_block_size_support(self):
self.client.features.add_feature(
'ADAPTIVE_QOS_BLOCK_SIZE', supported=False)
spec = {'block_size': '4K'}
self.assertRaises(
netapp_utils.NetAppDriverException,
self.client._validate_qos_policy_group,
is_adaptive=True,
spec=spec)
def test__validate_qos_policy_group_no_expected_iops_allocation_support(
self):
self.client.features.add_feature(
'ADAPTIVE_QOS_EXPECTED_IOPS_ALLOCATION', supported=False)
spec = {'expected_iops_allocation': 'used-space'}
self.assertRaises(
netapp_utils.NetAppDriverException,
self.client._validate_qos_policy_group,
is_adaptive=True,
spec=spec)
def test__validate_qos_policy_group_adaptive_qos_spec(self):
self.client.features.add_feature('ADAPTIVE_QOS', supported=True)
self.client.features.add_feature(
'ADAPTIVE_QOS_BLOCK_SIZE', supported=True)
self.client.features.add_feature(
'ADAPTIVE_QOS_EXPECTED_IOPS_ALLOCATION', supported=True)
spec = {
'expected_iops': '128IOPS/GB',
'peak_iops': '512IOPS/GB',
'expected_iops_allocation': 'used-space',
'peak_iops_allocation': 'used-space',
'absolute_min_iops': '64IOPS',
'block_size': '4K',
}
self.client._validate_qos_policy_group(is_adaptive=True, spec=spec)
def test_clone_lun(self):
self.client.clone_lun(
'volume', 'fakeLUN', 'newFakeLUN',
@ -853,19 +914,38 @@ class NetAppCmodeClientTestCase(test.TestCase):
mock.call('lun-set-qos-policy-group', api_args)])
def test_provision_qos_policy_group_no_qos_policy_group_info(self):
mock_qos_policy_group_create = self.mock_object(
self.client, 'qos_policy_group_create')
self.client.provision_qos_policy_group(qos_policy_group_info=None,
qos_min_support=True)
self.assertEqual(0, self.connection.qos_policy_group_create.call_count)
mock_qos_policy_group_create.assert_not_called()
def test_provision_qos_policy_group_no_legacy_no_spec(self):
mock_qos_policy_group_exists = self.mock_object(
self.client, 'qos_policy_group_exists')
mock_qos_policy_group_create = self.mock_object(
self.client, 'qos_policy_group_create')
mock_qos_policy_group_modify = self.mock_object(
self.client, 'qos_policy_group_modify')
self.client.provision_qos_policy_group(qos_policy_group_info={},
qos_min_support=False)
mock_qos_policy_group_exists.assert_not_called()
mock_qos_policy_group_create.assert_not_called()
mock_qos_policy_group_modify.assert_not_called()
def test_provision_qos_policy_group_legacy_qos_policy_group_info(self):
mock_qos_policy_group_create = self.mock_object(
self.client, 'qos_policy_group_create')
self.client.provision_qos_policy_group(
qos_policy_group_info=fake.QOS_POLICY_GROUP_INFO_LEGACY,
qos_min_support=True)
self.assertEqual(0, self.connection.qos_policy_group_create.call_count)
mock_qos_policy_group_create.assert_not_called()
def test_provision_qos_policy_group_with_qos_spec_create_with_min(self):
@ -880,15 +960,43 @@ class NetAppCmodeClientTestCase(test.TestCase):
self.client.provision_qos_policy_group(fake.QOS_POLICY_GROUP_INFO,
True)
mock_qos_policy_group_create.assert_has_calls([
mock.call({
'policy_name': fake.QOS_POLICY_GROUP_NAME,
'min_throughput': fake.MIN_IOPS,
'max_throughput': fake.MAX_IOPS,
})])
mock_qos_policy_group_create.assert_called_once_with({
'policy_name': fake.QOS_POLICY_GROUP_NAME,
'min_throughput': fake.MIN_IOPS,
'max_throughput': fake.MAX_IOPS,
})
mock_qos_policy_group_modify.assert_not_called()
def test_provision_qos_policy_group_with_qos_spec_create_with_aqos(self):
self.client.features.add_feature('ADAPTIVE_QOS', supported=True)
self.client.features.add_feature(
'ADAPTIVE_QOS_BLOCK_SIZE', supported=True)
self.client.features.add_feature(
'ADAPTIVE_QOS_EXPECTED_IOPS_ALLOCATION', supported=True)
self.mock_object(self.client,
'qos_policy_group_exists',
return_value=False)
mock_qos_policy_group_create = self.mock_object(
self.client, 'qos_policy_group_create')
mock_qos_policy_group_modify = self.mock_object(
self.client, 'qos_policy_group_modify')
mock_qos_adaptive_policy_group_create = self.mock_object(
self.client, 'qos_adaptive_policy_group_create')
mock_qos_adaptive_policy_group_modify = self.mock_object(
self.client, 'qos_adaptive_policy_group_modify')
self.client.provision_qos_policy_group(
fake.ADAPTIVE_QOS_POLICY_GROUP_INFO, False)
mock_qos_adaptive_policy_group_create.assert_called_once_with(
fake.ADAPTIVE_QOS_SPEC)
mock_qos_adaptive_policy_group_modify.assert_not_called()
mock_qos_policy_group_create.assert_not_called()
mock_qos_policy_group_modify.assert_not_called()
def test_provision_qos_policy_group_with_qos_spec_create_unsupported(self):
mock_qos_policy_group_exists = self.mock_object(
self.client, 'qos_policy_group_exists')
mock_qos_policy_group_create = self.mock_object(
self.client, 'qos_policy_group_create')
mock_qos_policy_group_modify = self.mock_object(
@ -897,8 +1005,34 @@ class NetAppCmodeClientTestCase(test.TestCase):
self.assertRaises(
netapp_utils.NetAppDriverException,
self.client.provision_qos_policy_group,
fake.QOS_POLICY_GROUP_INFO, False)
fake.QOS_POLICY_GROUP_INFO,
False)
mock_qos_policy_group_exists.assert_not_called()
mock_qos_policy_group_create.assert_not_called()
mock_qos_policy_group_modify.assert_not_called()
def test_provision_qos_policy_group_with_invalid_qos_spec(self):
self.mock_object(self.client, '_validate_qos_policy_group',
side_effect=netapp_utils.NetAppDriverException)
mock_policy_group_spec_is_adaptive = self.mock_object(
netapp_utils, 'is_qos_policy_group_spec_adaptive')
mock_qos_policy_group_exists = self.mock_object(
self.client, 'qos_policy_group_exists')
mock_qos_policy_group_create = self.mock_object(
self.client, 'qos_policy_group_create')
mock_qos_policy_group_modify = self.mock_object(
self.client, 'qos_policy_group_modify')
self.assertRaises(
netapp_utils.NetAppDriverException,
self.client.provision_qos_policy_group,
fake.QOS_POLICY_GROUP_INFO,
False)
mock_policy_group_spec_is_adaptive.assert_called_once_with(
fake.QOS_POLICY_GROUP_INFO)
mock_qos_policy_group_exists.assert_not_called()
mock_qos_policy_group_create.assert_not_called()
mock_qos_policy_group_modify.assert_not_called()
@ -963,6 +1097,33 @@ class NetAppCmodeClientTestCase(test.TestCase):
'max_throughput': fake.MAX_THROUGHPUT,
})])
def test_provision_qos_policy_group_with_qos_spec_modify_with_aqos(self):
self.client.features.add_feature('ADAPTIVE_QOS', supported=True)
self.client.features.add_feature(
'ADAPTIVE_QOS_BLOCK_SIZE', supported=True)
self.client.features.add_feature(
'ADAPTIVE_QOS_EXPECTED_IOPS_ALLOCATION', supported=True)
self.mock_object(self.client,
'qos_policy_group_exists',
return_value=True)
mock_qos_policy_group_create = self.mock_object(
self.client, 'qos_policy_group_create')
mock_qos_policy_group_modify = self.mock_object(
self.client, 'qos_policy_group_modify')
mock_qos_adaptive_policy_group_create = self.mock_object(
self.client, 'qos_adaptive_policy_group_create')
mock_qos_adaptive_policy_group_modify = self.mock_object(
self.client, 'qos_adaptive_policy_group_modify')
self.client.provision_qos_policy_group(
fake.ADAPTIVE_QOS_POLICY_GROUP_INFO, False)
mock_qos_adaptive_policy_group_modify.assert_called_once_with(
fake.ADAPTIVE_QOS_SPEC)
mock_qos_adaptive_policy_group_create.assert_not_called()
mock_qos_policy_group_create.assert_not_called()
mock_qos_policy_group_modify.assert_not_called()
def test_qos_policy_group_exists(self):
self.mock_send_request.return_value = netapp_api.NaElement(
@ -1015,6 +1176,30 @@ class NetAppCmodeClientTestCase(test.TestCase):
self.mock_send_request.assert_has_calls([
mock.call('qos-policy-group-create', api_args, False)])
def test_qos_adaptive_policy_group_create(self):
api_args = {
'policy-group': fake.QOS_POLICY_GROUP_NAME,
'expected-iops': '%sIOPS/GB' % fake.EXPECTED_IOPS_PER_GB,
'peak-iops': '%sIOPS/GB' % fake.PEAK_IOPS_PER_GB,
'expected-iops-allocation': fake.EXPECTED_IOPS_ALLOCATION,
'peak-iops-allocation': fake.PEAK_IOPS_ALLOCATION,
'block-size': fake.BLOCK_SIZE,
'vserver': self.vserver,
}
self.client.qos_adaptive_policy_group_create({
'policy_name': fake.QOS_POLICY_GROUP_NAME,
'expected_iops': '%sIOPS/GB' % fake.EXPECTED_IOPS_PER_GB,
'peak_iops': '%sIOPS/GB' % fake.PEAK_IOPS_PER_GB,
'expected_iops_allocation': fake.EXPECTED_IOPS_ALLOCATION,
'peak_iops_allocation': fake.PEAK_IOPS_ALLOCATION,
'block_size': fake.BLOCK_SIZE,
})
self.mock_send_request.assert_has_calls([
mock.call('qos-adaptive-policy-group-create', api_args, False)])
def test_qos_policy_group_modify(self):
api_args = {
@ -1032,17 +1217,28 @@ class NetAppCmodeClientTestCase(test.TestCase):
self.mock_send_request.assert_has_calls([
mock.call('qos-policy-group-modify', api_args, False)])
def test_qos_policy_group_delete(self):
def test_qos_adaptive_policy_group_modify(self):
api_args = {
'policy-group': fake.QOS_POLICY_GROUP_NAME
'policy-group': fake.QOS_POLICY_GROUP_NAME,
'expected-iops': '%sIOPS/GB' % fake.EXPECTED_IOPS_PER_GB,
'peak-iops': '%sIOPS/GB' % fake.PEAK_IOPS_PER_GB,
'expected-iops-allocation': fake.EXPECTED_IOPS_ALLOCATION,
'peak-iops-allocation': fake.PEAK_IOPS_ALLOCATION,
'block-size': fake.BLOCK_SIZE,
}
self.client.qos_policy_group_delete(
fake.QOS_POLICY_GROUP_NAME)
self.client.qos_adaptive_policy_group_modify({
'policy_name': fake.QOS_POLICY_GROUP_NAME,
'expected_iops': '%sIOPS/GB' % fake.EXPECTED_IOPS_PER_GB,
'peak_iops': '%sIOPS/GB' % fake.PEAK_IOPS_PER_GB,
'expected_iops_allocation': fake.EXPECTED_IOPS_ALLOCATION,
'peak_iops_allocation': fake.PEAK_IOPS_ALLOCATION,
'block_size': fake.BLOCK_SIZE,
})
self.mock_send_request.assert_has_calls([
mock.call('qos-policy-group-delete', api_args, False)])
mock.call('qos-adaptive-policy-group-modify', api_args, False)])
def test_qos_policy_group_rename(self):
@ -1082,7 +1278,9 @@ class NetAppCmodeClientTestCase(test.TestCase):
self.assertEqual(0, mock_rename.call_count)
self.assertEqual(1, mock_remove.call_count)
def test_mark_qos_policy_group_for_deletion_w_qos_spec(self):
@ddt.data(True, False)
def test_mark_qos_policy_group_for_deletion_w_qos_spec(self,
is_adaptive):
mock_rename = self.mock_object(self.client, 'qos_policy_group_rename')
mock_remove = self.mock_object(self.client,
@ -1091,14 +1289,17 @@ class NetAppCmodeClientTestCase(test.TestCase):
new_name = 'deleted_cinder_%s' % fake.QOS_POLICY_GROUP_NAME
self.client.mark_qos_policy_group_for_deletion(
qos_policy_group_info=fake.QOS_POLICY_GROUP_INFO_MAX)
qos_policy_group_info=fake.QOS_POLICY_GROUP_INFO_MAX,
is_adaptive=is_adaptive)
mock_rename.assert_has_calls([
mock.call(fake.QOS_POLICY_GROUP_NAME, new_name)])
mock.call(fake.QOS_POLICY_GROUP_NAME, new_name, is_adaptive)])
self.assertEqual(0, mock_log.call_count)
self.assertEqual(1, mock_remove.call_count)
def test_mark_qos_policy_group_for_deletion_exception_path(self):
@ddt.data(True, False)
def test_mark_qos_policy_group_for_deletion_exception_path(self,
is_adaptive):
mock_rename = self.mock_object(self.client, 'qos_policy_group_rename')
mock_rename.side_effect = netapp_api.NaApiError
@ -1108,10 +1309,11 @@ class NetAppCmodeClientTestCase(test.TestCase):
new_name = 'deleted_cinder_%s' % fake.QOS_POLICY_GROUP_NAME
self.client.mark_qos_policy_group_for_deletion(
qos_policy_group_info=fake.QOS_POLICY_GROUP_INFO_MAX)
qos_policy_group_info=fake.QOS_POLICY_GROUP_INFO_MAX,
is_adaptive=is_adaptive)
mock_rename.assert_has_calls([
mock.call(fake.QOS_POLICY_GROUP_NAME, new_name)])
mock.call(fake.QOS_POLICY_GROUP_NAME, new_name, is_adaptive)])
self.assertEqual(1, mock_log.call_count)
self.assertEqual(1, mock_remove.call_count)
@ -1138,15 +1340,29 @@ class NetAppCmodeClientTestCase(test.TestCase):
self.assertEqual(0, mock_log.call_count)
def test_remove_unused_qos_policy_groups_api_error(self):
self.client.features.add_feature('ADAPTIVE_QOS', supported=True)
mock_log = self.mock_object(client_cmode.LOG, 'debug')
api_args = {
'query': {
'qos-policy-group-info': {
'policy-group': 'deleted_cinder_*',
'vserver': self.vserver,
}
},
qos_query = {
'qos-policy-group-info': {
'policy-group': 'deleted_cinder_*',
'vserver': self.vserver,
}
}
adaptive_qos_query = {
'qos-adaptive-policy-group-info': {
'policy-group': 'deleted_cinder_*',
'vserver': self.vserver,
}
}
qos_api_args = {
'query': qos_query,
'max-records': 3500,
'continue-on-failure': 'true',
'return-success-list': 'false',
'return-failure-list': 'false',
}
adaptive_qos_api_args = {
'query': adaptive_qos_query,
'max-records': 3500,
'continue-on-failure': 'true',
'return-success-list': 'false',
@ -1157,8 +1373,12 @@ class NetAppCmodeClientTestCase(test.TestCase):
self.client.remove_unused_qos_policy_groups()
self.mock_send_request.assert_has_calls([
mock.call('qos-policy-group-delete-iter', api_args, False)])
self.assertEqual(1, mock_log.call_count)
mock.call('qos-policy-group-delete-iter',
qos_api_args, False),
mock.call('qos-adaptive-policy-group-delete-iter',
adaptive_qos_api_args, False),
])
self.assertEqual(2, mock_log.call_count)
@mock.patch('cinder.volume.volume_utils.resolve_hostname',
return_value='192.168.1.101')

View File

@ -280,8 +280,28 @@ QOS_POLICY_GROUP_SPEC_MAX = {
'policy_name': QOS_POLICY_GROUP_NAME,
}
EXPECTED_IOPS_PER_GB = '128'
PEAK_IOPS_PER_GB = '512'
EXPECTED_IOPS_ALLOCATION = 'used-space'
PEAK_IOPS_ALLOCATION = 'used-space'
ABSOLUTE_MIN_IOPS = '75'
BLOCK_SIZE = 'ANY'
ADAPTIVE_QOS_SPEC = {
'policy_name': QOS_POLICY_GROUP_NAME,
'expected_iops': EXPECTED_IOPS_PER_GB,
'peak_iops': PEAK_IOPS_PER_GB,
'expected_iops_allocation': EXPECTED_IOPS_ALLOCATION,
'peak_iops_allocation': PEAK_IOPS_ALLOCATION,
'absolute_min_iops': ABSOLUTE_MIN_IOPS,
'block_size': BLOCK_SIZE,
}
QOS_POLICY_GROUP_INFO = {'legacy': None, 'spec': QOS_POLICY_GROUP_SPEC}
QOS_POLICY_GROUP_INFO_MAX = {'legacy': None, 'spec': QOS_POLICY_GROUP_SPEC_MAX}
ADAPTIVE_QOS_POLICY_GROUP_INFO = {
'legacy': None,
'spec': ADAPTIVE_QOS_SPEC,
}
CLONE_SOURCE_NAME = 'fake_clone_source_name'
CLONE_SOURCE_ID = 'fake_clone_source_id'

View File

@ -21,6 +21,7 @@
"""Mock unit tests for the NetApp block storage library"""
import copy
import itertools
from unittest import mock
import uuid
@ -124,7 +125,7 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
self.mock_object(volume_utils, 'extract_host',
return_value=fake.POOL_NAME)
self.mock_object(self.library, '_setup_qos_for_volume',
return_value=None)
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')
@ -135,7 +136,7 @@ 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, None, False)
fake.LUN_METADATA, fake.QOS_POLICY_GROUP_NAME, False)
self.library._get_volume_model_update.assert_called_once_with(
fake.VOLUME)
self.assertEqual(
@ -152,7 +153,7 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
self.mock_object(block_base, 'LOG')
self.mock_object(na_utils, 'get_volume_extra_specs')
self.mock_object(self.library, '_setup_qos_for_volume',
return_value=None)
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')
@ -692,8 +693,11 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
self.assertEqual(2, mock_info_log.call_count)
self.library._add_lun_to_table.assert_called_once_with(mock_lun)
@ddt.data(None, 'fake_qos_policy_group_name')
def test_manage_existing_rename_lun(self, qos_policy_group_name):
@ddt.data(*itertools.product((None, 'fake_qos_policy_group_name'),
(True, False)))
@ddt.unpack
def test_manage_existing_rename_lun(self, qos_policy_group_name,
is_qos_policy_group_spec_adaptive):
expected_update = (
{'replication_status': fields.ReplicationStatus.ENABLED})
volume = fake_volume.fake_volume_obj(self.ctxt)
@ -710,6 +714,8 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
self.mock_object(self.library, '_setup_qos_for_volume')
self.mock_object(na_utils, 'get_qos_policy_group_name_from_info',
return_value=qos_policy_group_name)
self.mock_object(na_utils, 'is_qos_policy_group_spec_adaptive',
return_value=is_qos_policy_group_spec_adaptive)
self.mock_object(self.library, '_add_lun_to_table')
self.mock_object(self.library, '_get_volume_model_update',
return_value=expected_update)
@ -724,7 +730,8 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
self.library._add_lun_to_table.assert_called_once_with(mock_lun)
if qos_policy_group_name:
(self.zapi_client.set_lun_qos_policy_group.
assert_called_once_with(expected_new_path, qos_policy_group_name))
assert_called_once_with(expected_new_path, qos_policy_group_name,
is_qos_policy_group_spec_adaptive))
else:
self.assertFalse(
self.zapi_client.set_lun_qos_policy_group.called)

View File

@ -599,15 +599,20 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
self.zapi_client.
provision_qos_policy_group.call_count)
def test_mark_qos_policy_group_for_deletion(self):
@ddt.data(True, False)
def test_mark_qos_policy_group_for_deletion(self, is_adaptive):
self.mock_object(self.zapi_client,
'mark_qos_policy_group_for_deletion')
self.mock_object(na_utils,
'is_qos_policy_group_spec_adaptive',
return_value=is_adaptive)
self.library._mark_qos_policy_group_for_deletion(
fake.QOS_POLICY_GROUP_INFO)
self.zapi_client.mark_qos_policy_group_for_deletion\
.assert_called_once_with(fake.QOS_POLICY_GROUP_INFO)
.assert_called_once_with(fake.QOS_POLICY_GROUP_INFO,
is_adaptive)
def test_unmanage(self):
self.mock_object(na_utils, 'get_valid_qos_policy_group_info',
@ -639,7 +644,8 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
block_base.NetAppBlockStorageLibrary.unmanage.assert_called_once_with(
fake.VOLUME)
def test_manage_existing_lun_same_name(self):
@ddt.data(True, False)
def test_manage_existing_lun_same_name(self, is_adaptive):
mock_lun = block_base.NetAppLun('handle', 'name', '1',
{'Path': '/vol/FAKE_CMODE_VOL1/name'})
self.library._get_existing_vol_with_manage_ref = mock.Mock(
@ -650,6 +656,8 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
self.library._setup_qos_for_volume = mock.Mock()
self.mock_object(na_utils, 'get_qos_policy_group_name_from_info',
return_value=fake.QOS_POLICY_GROUP_NAME)
self.mock_object(na_utils, 'is_qos_policy_group_spec_adaptive',
return_value=is_adaptive)
self.library._add_lun_to_table = mock.Mock()
self.zapi_client.move_lun = mock.Mock()
mock_set_lun_qos_policy_group = self.mock_object(
@ -675,6 +683,8 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
self.library._setup_qos_for_volume = mock.Mock()
self.mock_object(na_utils, 'get_qos_policy_group_name_from_info',
return_value=None)
self.mock_object(na_utils, 'is_qos_policy_group_spec_adaptive',
return_value=False)
self.library._add_lun_to_table = mock.Mock()
self.zapi_client.move_lun = mock.Mock()

View File

@ -494,7 +494,9 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
self.mock_object(self.driver, '_delete_backing_file_for_volume')
self.mock_object(na_utils,
'get_valid_qos_policy_group_info',
return_value='fake_qos_policy_group_info')
return_value=fake.QOS_POLICY_GROUP_INFO)
self.mock_object(na_utils, 'is_qos_policy_group_spec_adaptive',
return_value=False)
self.driver.delete_volume(fake_volume)
@ -502,8 +504,10 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
fake_volume)
na_utils.get_valid_qos_policy_group_info.assert_called_once_with(
fake_volume)
na_utils.is_qos_policy_group_spec_adaptive.assert_called_once_with(
fake.QOS_POLICY_GROUP_INFO)
(self.driver.zapi_client.mark_qos_policy_group_for_deletion.
assert_called_once_with('fake_qos_policy_group_info'))
assert_called_once_with(fake.QOS_POLICY_GROUP_INFO, False))
def test_delete_volume_exception_path(self):
fake_provider_location = 'fake_provider_location'
@ -511,7 +515,9 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
self.mock_object(self.driver, '_delete_backing_file_for_volume')
self.mock_object(na_utils,
'get_valid_qos_policy_group_info',
return_value='fake_qos_policy_group_info')
return_value=fake.QOS_POLICY_GROUP_INFO)
self.mock_object(na_utils, 'is_qos_policy_group_spec_adaptive',
return_value=False)
self.mock_object(
self.driver.zapi_client,
'mark_qos_policy_group_for_deletion',
@ -523,8 +529,10 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
fake_volume)
na_utils.get_valid_qos_policy_group_info.assert_called_once_with(
fake_volume)
na_utils.is_qos_policy_group_spec_adaptive.assert_called_once_with(
fake.QOS_POLICY_GROUP_INFO)
(self.driver.zapi_client.mark_qos_policy_group_for_deletion.
assert_called_once_with('fake_qos_policy_group_info'))
assert_called_once_with(fake.QOS_POLICY_GROUP_INFO, False))
def test_delete_backing_file_for_volume(self):
mock_filer_delete = self.mock_object(self.driver, '_delete_file')

View File

@ -98,6 +98,21 @@ MAX_THROUGHPUT_BPS = '21734278B/s'
QOS_POLICY_GROUP_NAME = 'fake_qos_policy_group_name'
LEGACY_EXTRA_SPECS = {'netapp:qos_policy_group': QOS_POLICY_GROUP_NAME}
EXPECTED_IOPS_PER_GB = '128'
PEAK_IOPS_PER_GB = '512'
EXPECTED_IOPS_ALLOCATION = 'used-space'
PEAK_IOPS_ALLOCATION = 'used-space'
ABSOLUTE_MIN_IOPS = '75'
BLOCK_SIZE = 'ANY'
ADAPTIVE_QOS_SPEC = {
'expectedIOPSperGiB': EXPECTED_IOPS_PER_GB,
'peakIOPSperGiB': PEAK_IOPS_PER_GB,
'expectedIOPSAllocation': EXPECTED_IOPS_ALLOCATION,
'peakIOPSAllocation': PEAK_IOPS_ALLOCATION,
'absoluteMinIOPS': ABSOLUTE_MIN_IOPS,
'blockSize': BLOCK_SIZE,
}
LEGACY_QOS = {
'policy_name': QOS_POLICY_GROUP_NAME,
}
@ -111,16 +126,31 @@ QOS_POLICY_GROUP_INFO_NONE = {'legacy': None, 'spec': None}
QOS_POLICY_GROUP_INFO = {'legacy': None, 'spec': QOS_POLICY_GROUP_SPEC}
ADAPTIVE_QOS_POLICY_GROUP_SPEC = {
'expected_iops': '128IOPS/GB',
'peak_iops': '512IOPS/GB',
'expected_iops_allocation': 'used-space',
'peak_iops_allocation': 'used-space',
'absolute_min_iops': '75IOPS',
'block_size': 'ANY',
'policy_name': 'openstack-%s' % VOLUME_ID,
}
LEGACY_QOS_POLICY_GROUP_INFO = {
'legacy': LEGACY_QOS,
'spec': None,
}
INVALID_QOS_POLICY_GROUP_INFO = {
INVALID_QOS_POLICY_GROUP_INFO_LEGACY_AND_SPEC = {
'legacy': LEGACY_QOS,
'spec': QOS_POLICY_GROUP_SPEC,
}
INVALID_QOS_POLICY_GROUP_INFO_STANDARD_AND_ADAPTIVE = {
'legacy': None,
'spec': {**QOS_POLICY_GROUP_SPEC, **ADAPTIVE_QOS_SPEC},
}
QOS_SPECS_ID = 'fake_qos_specs_id'
QOS_SPEC = {'maxBPS': 21734278}
OUTER_BACKEND_QOS_SPEC = {

View File

@ -45,13 +45,13 @@ class NetAppDriverUtilsTestCase(test.TestCase):
def test_validate_instantiation_proxy(self):
kwargs = {'netapp_mode': 'proxy'}
na_utils.validate_instantiation(**kwargs)
self.assertEqual(0, na_utils.LOG.warning.call_count)
na_utils.LOG.warning.assert_not_called()
@mock.patch.object(na_utils, 'LOG', mock.Mock())
def test_validate_instantiation_no_proxy(self):
kwargs = {'netapp_mode': 'asdf'}
na_utils.validate_instantiation(**kwargs)
self.assertEqual(1, na_utils.LOG.warning.call_count)
na_utils.LOG.warning.assert_called_once()
def test_check_flags(self):
@ -228,7 +228,7 @@ class NetAppDriverUtilsTestCase(test.TestCase):
na_utils.log_extra_spec_warnings({'netapp:raid_type': 'raid4'})
self.assertEqual(1, mock_log.call_count)
mock_log.assert_called_once()
def test_log_extra_spec_warnings_deprecated_specs(self):
@ -236,7 +236,13 @@ class NetAppDriverUtilsTestCase(test.TestCase):
na_utils.log_extra_spec_warnings({'netapp_thick_provisioned': 'true'})
self.assertEqual(1, mock_log.call_count)
mock_log.assert_called_once()
def test_validate_qos_spec(self):
qos_spec = fake.QOS_SPEC
# Just return without raising an exception.
na_utils.validate_qos_spec(qos_spec)
def test_validate_qos_spec_none(self):
qos_spec = None
@ -244,6 +250,10 @@ class NetAppDriverUtilsTestCase(test.TestCase):
# Just return without raising an exception.
na_utils.validate_qos_spec(qos_spec)
def test_validate_qos_spec_adaptive(self):
# Just return without raising an exception.
na_utils.validate_qos_spec(fake.ADAPTIVE_QOS_SPEC)
def test_validate_qos_spec_keys_weirdly_cased(self):
qos_spec = {'mAxIopS': 33000, 'mInIopS': 0}
@ -280,6 +290,34 @@ class NetAppDriverUtilsTestCase(test.TestCase):
def test_validate_qos_spec_bad_key_combination_miniops_miniopspergib(self):
qos_spec = {'minIOPS': 33000, 'minIOPSperGiB': 10000000}
self.assertRaises(exception.Invalid,
na_utils.validate_qos_spec,
qos_spec)
def test_validate_qos_spec_bad_key_combination_aqos_qos_max(self):
qos_spec = {'peakIOPSperGiB': 33000, 'maxIOPS': 33000}
self.assertRaises(exception.Invalid,
na_utils.validate_qos_spec,
qos_spec)
def test_validate_qos_spec_bad_key_combination_aqos_qos_min(self):
qos_spec = {'absoluteMinIOPS': 33000, 'minIOPS': 33000}
self.assertRaises(exception.Invalid,
na_utils.validate_qos_spec,
qos_spec)
def test_validate_qos_spec_bad_key_combination_aqos_qos_min_max(self):
qos_spec = {
'expectedIOPSperGiB': 33000,
'minIOPS': 33000,
'maxIOPS': 33000,
}
self.assertRaises(exception.Invalid,
na_utils.validate_qos_spec,
qos_spec)
def test_validate_qos_spec_adaptive_and_non_adaptive(self):
qos_spec = fake.INVALID_QOS_POLICY_GROUP_INFO_STANDARD_AND_ADAPTIVE
self.assertRaises(exception.Invalid,
na_utils.validate_qos_spec,
@ -408,6 +446,101 @@ class NetAppDriverUtilsTestCase(test.TestCase):
self.assertEqual(expected, result)
def test_map_aqos_spec(self):
qos_spec = {
'expectedIOPSperGiB': '128',
'peakIOPSperGiB': '512',
'expectedIOPSAllocation': 'used-space',
'peakIOPSAllocation': 'used-space',
'absoluteMinIOPS': '75',
'blockSize': 'ANY',
}
mock_get_name = self.mock_object(na_utils, 'get_qos_policy_group_name')
mock_get_name.return_value = 'fake_qos_policy'
expected = {
'expected_iops': '128IOPS/GB',
'peak_iops': '512IOPS/GB',
'expected_iops_allocation': 'used-space',
'peak_iops_allocation': 'used-space',
'absolute_min_iops': '75IOPS',
'block_size': 'ANY',
'policy_name': 'fake_qos_policy',
}
result = na_utils.map_aqos_spec(qos_spec, fake.VOLUME)
self.assertEqual(expected, result)
@ddt.data({'expectedIOPSperGiB': '528', 'peakIOPSperGiB': '128'},
{'expectedIOPSperGiB': '528'})
def test_map_aqos_spec_error(self, qos_spec):
mock_get_name = self.mock_object(na_utils, 'get_qos_policy_group_name')
mock_get_name.return_value = 'fake_qos_policy'
self.assertRaises(exception.Invalid, na_utils.map_aqos_spec, qos_spec,
fake.VOLUME)
def test_is_qos_adaptive_adaptive_spec(self):
aqos_spec = fake.ADAPTIVE_QOS_SPEC
self.assertTrue(na_utils.is_qos_adaptive(aqos_spec))
def test_is_qos_adaptive_weirdly_cased_adaptive_spec(self):
aqos_spec = {'expecTEDiopsPERgib': '128IOPS/GB'}
self.assertTrue(na_utils.is_qos_adaptive(aqos_spec))
def test_is_qos_adaptive_non_adaptive_spec(self):
qos_spec = fake.QOS_SPEC
self.assertFalse(na_utils.is_qos_adaptive(qos_spec))
def test_is_qos_policy_group_spec_adaptive_adaptive_spec(self):
aqos_spec = {
'spec': {
'expected_iops': '128IOPS/GB',
'peak_iops': '512IOPS/GB',
'expected_iops_allocation': 'used-space',
'absolute_min_iops': '75IOPS',
'block_size': 'ANY',
'policy_name': 'fake_policy_name',
}
}
self.assertTrue(na_utils.is_qos_policy_group_spec_adaptive(aqos_spec))
def test_is_qos_policy_group_spec_adaptive_none(self):
qos_spec = None
self.assertFalse(na_utils.is_qos_policy_group_spec_adaptive(qos_spec))
def test_is_qos_policy_group_spec_adaptive_legacy(self):
qos_spec = {
'legacy': fake.LEGACY_QOS,
}
self.assertFalse(na_utils.is_qos_policy_group_spec_adaptive(qos_spec))
def test_is_qos_policy_group_spec_adaptive_non_adaptive_spec(self):
qos_spec = {
'spec': {
'max_throughput': '21834289B/s',
'policy_name': 'fake_policy_name',
}
}
self.assertFalse(na_utils.is_qos_policy_group_spec_adaptive(qos_spec))
def test_policy_group_qos_spec_is_adaptive_invalid_spec(self):
qos_spec = {
'spec': {
'max_flops': '512',
'policy_name': 'fake_policy_name',
}
}
self.assertFalse(na_utils.is_qos_policy_group_spec_adaptive(qos_spec))
def test_map_dict_to_lower(self):
original = {'UPperKey': 'Value'}
expected = {'upperkey': 'Value'}
@ -498,7 +631,6 @@ class NetAppDriverUtilsTestCase(test.TestCase):
mock_get_valid_qos_spec_from_volume_type = self.mock_object(
na_utils, 'get_valid_backend_qos_spec_from_volume_type')
mock_get_valid_qos_spec_from_volume_type.return_value = None
self.mock_object(na_utils, 'check_for_invalid_qos_spec_combination')
expected = fake.QOS_POLICY_GROUP_INFO_NONE
result = na_utils.get_valid_qos_policy_group_info(fake.VOLUME)
@ -516,7 +648,6 @@ class NetAppDriverUtilsTestCase(test.TestCase):
mock_get_valid_qos_spec_from_volume_type = self.mock_object(
na_utils, 'get_valid_backend_qos_spec_from_volume_type')
mock_get_valid_qos_spec_from_volume_type.return_value = None
self.mock_object(na_utils, 'check_for_invalid_qos_spec_combination')
result = na_utils.get_valid_qos_policy_group_info(fake.VOLUME)
@ -533,7 +664,6 @@ class NetAppDriverUtilsTestCase(test.TestCase):
na_utils, 'get_valid_backend_qos_spec_from_volume_type')
mock_get_valid_qos_spec_from_volume_type.return_value =\
fake.QOS_POLICY_GROUP_SPEC
self.mock_object(na_utils, 'check_for_invalid_qos_spec_combination')
result = na_utils.get_valid_qos_policy_group_info(fake.VOLUME)
@ -543,25 +673,43 @@ class NetAppDriverUtilsTestCase(test.TestCase):
mock_get_spec = self.mock_object(
na_utils, 'get_backend_qos_spec_from_volume_type')
mock_get_spec.return_value = None
mock_validate = self.mock_object(na_utils, 'validate_qos_spec')
mock_map_qos_spec = self.mock_object(
na_utils, 'map_qos_spec')
mock_map_aqos_spec = self.mock_object(
na_utils, 'map_aqos_spec')
result = na_utils.get_valid_backend_qos_spec_from_volume_type(
fake.VOLUME, fake.VOLUME_TYPE)
self.assertIsNone(result)
self.assertEqual(0, mock_validate.call_count)
mock_map_qos_spec.assert_not_called()
mock_map_aqos_spec.assert_not_called()
def test_get_valid_backend_qos_spec_from_volume_type(self):
mock_get_spec = self.mock_object(
na_utils, 'get_backend_qos_spec_from_volume_type')
mock_get_spec.return_value = fake.QOS_SPEC
mock_validate = self.mock_object(na_utils, 'validate_qos_spec')
mock_map_aqos_spec = self.mock_object(
na_utils, 'map_aqos_spec')
result = na_utils.get_valid_backend_qos_spec_from_volume_type(
fake.VOLUME, fake.VOLUME_TYPE)
self.assertEqual(fake.QOS_POLICY_GROUP_SPEC, result)
self.assertEqual(1, mock_validate.call_count)
mock_map_aqos_spec.assert_not_called()
def test_get_valid_backend_qos_spec_from_volume_type_adaptive(self):
mock_get_spec = self.mock_object(
na_utils, 'get_backend_qos_spec_from_volume_type')
mock_get_spec.return_value = fake.ADAPTIVE_QOS_SPEC
mock_map_qos_spec = self.mock_object(
na_utils, 'map_qos_spec')
result = na_utils.get_valid_backend_qos_spec_from_volume_type(
fake.VOLUME, fake.VOLUME_TYPE)
self.assertEqual(fake.ADAPTIVE_QOS_POLICY_GROUP_SPEC, result)
mock_map_qos_spec.assert_not_called()
def test_get_backend_qos_spec_from_volume_type_no_qos_specs_id(self):
volume_type = copy.deepcopy(fake.VOLUME_TYPE)
@ -571,7 +719,7 @@ class NetAppDriverUtilsTestCase(test.TestCase):
result = na_utils.get_backend_qos_spec_from_volume_type(volume_type)
self.assertIsNone(result)
self.assertEqual(0, mock_get_context.call_count)
mock_get_context.assert_not_called()
def test_get_backend_qos_spec_from_volume_type_no_qos_spec(self):
volume_type = fake.VOLUME_TYPE
@ -613,11 +761,20 @@ class NetAppDriverUtilsTestCase(test.TestCase):
self.assertEqual(fake.QOS_SPEC, result)
def test_check_for_invalid_qos_spec_combination(self):
def test_check_for_invalid_qos_spec_combination_legacy(self):
na_utils.check_for_invalid_qos_spec_combination(
fake.LEGACY_QOS_POLICY_GROUP_INFO,
fake.VOLUME_TYPE)
def test_check_for_invalid_qos_spec_combination_spec(self):
na_utils.check_for_invalid_qos_spec_combination(
fake.QOS_POLICY_GROUP_INFO,
fake.VOLUME_TYPE)
def test_check_for_invalid_qos_spec_combination_legacy_and_spec(self):
self.assertRaises(exception.Invalid,
na_utils.check_for_invalid_qos_spec_combination,
fake.INVALID_QOS_POLICY_GROUP_INFO,
fake.INVALID_QOS_POLICY_GROUP_INFO_LEGACY_AND_SPEC,
fake.VOLUME_TYPE)
def test_get_legacy_qos_policy(self):

View File

@ -236,8 +236,10 @@ class NetAppBlockStorageLibrary(object):
qos_policy_group_name = (
na_utils.get_qos_policy_group_name_from_info(
qos_policy_group_info))
qos_policy_group_is_adaptive = volume_utils.is_boolean_str(
extra_specs.get('netapp:qos_policy_group_is_adaptive'))
qos_policy_group_is_adaptive = (volume_utils.is_boolean_str(
extra_specs.get('netapp:qos_policy_group_is_adaptive')) or
na_utils.is_qos_policy_group_spec_adaptive(
qos_policy_group_info))
try:
self._create_lun(pool_name, lun_name, size, metadata,
@ -359,8 +361,10 @@ class NetAppBlockStorageLibrary(object):
qos_policy_group_name = (
na_utils.get_qos_policy_group_name_from_info(
qos_policy_group_info))
qos_policy_group_is_adaptive = volume_utils.is_boolean_str(
extra_specs.get('netapp:qos_policy_group_is_adaptive'))
qos_policy_group_is_adaptive = (volume_utils.is_boolean_str(
extra_specs.get('netapp:qos_policy_group_is_adaptive')) or
na_utils.is_qos_policy_group_spec_adaptive(
qos_policy_group_info))
try:
self._clone_lun(
@ -741,6 +745,8 @@ class NetAppBlockStorageLibrary(object):
qos_policy_group_name = (
na_utils.get_qos_policy_group_name_from_info(
qos_policy_group_info))
is_adaptive = na_utils.is_qos_policy_group_spec_adaptive(
qos_policy_group_info)
path = lun.get_metadata_property('Path')
if lun.name == volume['name']:
@ -756,7 +762,8 @@ class NetAppBlockStorageLibrary(object):
if qos_policy_group_name is not None:
self.zapi_client.set_lun_qos_policy_group(new_path,
qos_policy_group_name)
qos_policy_group_name,
is_adaptive)
self._add_lun_to_table(lun)
LOG.info("Manage operation completed for LUN with new path"
" %(path)s and uuid %(uuid)s.",

View File

@ -414,8 +414,10 @@ class NetAppBlockStorageCmodeLibrary(block_base.NetAppBlockStorageLibrary,
return {'replication_status': fields.ReplicationStatus.ENABLED}
def _mark_qos_policy_group_for_deletion(self, qos_policy_group_info):
self.zapi_client.mark_qos_policy_group_for_deletion(
is_adaptive = na_utils.is_qos_policy_group_spec_adaptive(
qos_policy_group_info)
self.zapi_client.mark_qos_policy_group_for_deletion(
qos_policy_group_info, is_adaptive)
def unmanage(self, volume):
"""Removes the specified volume from Cinder management.

View File

@ -109,6 +109,7 @@ class Client(object):
qos_policy_group_name=None,
qos_policy_group_is_adaptive=False):
"""Issues API request for creating LUN on volume."""
self._validate_qos_policy_group(qos_policy_group_is_adaptive)
path = '/vol/%s/%s' % (volume_name, lun_name)
space_reservation = metadata['SpaceReserved']
@ -314,6 +315,10 @@ class Client(object):
"""Get igroups exactly matching a set of initiators."""
raise NotImplementedError()
def _validate_qos_policy_group(self, is_adaptive, spec=None, is_nfs=False):
"""Raises an exception if the backend doesn't support the QoS spec."""
raise NotImplementedError()
def _has_luns_mapped_to_initiator(self, initiator):
"""Checks whether any LUNs are mapped to the given initiator."""
lun_list_api = netapp_api.NaElement('lun-initiator-list-map-info')

View File

@ -50,9 +50,9 @@ class Client(client_base.Client):
self.connection.set_api_version(1, 15)
(major, minor) = self.get_ontapi_version(cached=False)
self.connection.set_api_version(major, minor)
self._init_features()
ontap_version = self.get_ontap_version(cached=False)
self.connection.set_ontap_version(ontap_version)
self._init_features()
def _init_features(self):
super(Client, self)._init_features()
@ -65,6 +65,8 @@ class Client(client_base.Client):
ontapi_1_100 = ontapi_version >= (1, 100)
ontapi_1_1xx = (1, 100) <= ontapi_version < (1, 200)
ontapi_1_60 = ontapi_version >= (1, 160)
ontapi_1_40 = ontapi_version >= (1, 140)
ontapi_1_50 = ontapi_version >= (1, 150)
nodes_info = self._get_cluster_nodes_info()
for node in nodes_info:
@ -99,6 +101,12 @@ class Client(client_base.Client):
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('ADAPTIVE_QOS', supported=ontapi_1_40)
self.features.add_feature('ADAPTIVE_QOS_BLOCK_SIZE',
supported=ontapi_1_50)
self.features.add_feature('ADAPTIVE_QOS_EXPECTED_IOPS_ALLOCATION',
supported=ontapi_1_50)
LOG.info('Reported ONTAPI Version: %(major)s.%(minor)s',
{'major': ontapi_version[0], 'minor': ontapi_version[1]})
@ -511,10 +519,39 @@ class Client(client_base.Client):
return igroup_list
def _validate_qos_policy_group(self, is_adaptive, spec=None,
qos_min_support=False):
if is_adaptive and not self.features.ADAPTIVE_QOS:
msg = _("Adaptive QoS feature requires ONTAP 9.4 or later.")
raise na_utils.NetAppDriverException(msg)
if not spec:
return
qos_spec_support = [
{'key': 'min_throughput',
'support': qos_min_support,
'reason': _('is not supported by this back end.')},
{'key': 'block_size',
'support': self.features.ADAPTIVE_QOS_BLOCK_SIZE,
'reason': _('requires ONTAP >= 9.5.')},
{'key': 'expected_iops_allocation',
'support': self.features.ADAPTIVE_QOS_EXPECTED_IOPS_ALLOCATION,
'reason': _('requires ONTAP >= 9.5.')},
]
for feature in qos_spec_support:
if feature['key'] in spec and not feature['support']:
msg = '%(key)s %(reason)s'
raise na_utils.NetAppDriverException(msg % {
'key': feature['key'],
'reason': feature['reason']})
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, is_snapshot=False,
qos_policy_group_is_adaptive=False):
self._validate_qos_policy_group(qos_policy_group_is_adaptive)
# ONTAP handles only 128 MB per call as of v9.1
bc_limit = 2 ** 18 # 2^18 blocks * 512 bytes/block = 128 MB
z_calls = int(math.ceil(block_count / float(bc_limit)))
@ -541,13 +578,9 @@ class Client(client_base.Client):
clone_create = netapp_api.NaElement.create_node_with_children(
'clone-create', **zapi_args)
if qos_policy_group_name is not None:
if qos_policy_group_is_adaptive:
clone_create.add_new_child(
'qos-adaptive-policy-group-name',
qos_policy_group_name)
else:
clone_create.add_new_child('qos-policy-group-name',
qos_policy_group_name)
child_name = 'qos-%spolicy-group-name' % (
'adaptive-' if qos_policy_group_is_adaptive else '')
clone_create.add_new_child(child_name, qos_policy_group_name)
if block_count > 0:
block_ranges = netapp_api.NaElement("block-ranges")
segments = int(math.ceil(block_count / float(bc_limit)))
@ -589,6 +622,8 @@ class Client(client_base.Client):
def file_assign_qos(self, flex_vol, qos_policy_group_name,
qos_policy_group_is_adaptive, file_path):
"""Assigns the named QoS policy-group to a file."""
self._validate_qos_policy_group(qos_policy_group_is_adaptive)
qos_arg_name = "qos-%spolicy-group-name" % (
"adaptive-" if qos_policy_group_is_adaptive else "")
api_args = {
@ -612,33 +647,46 @@ class Client(client_base.Client):
return
spec = qos_policy_group_info.get('spec')
if spec:
if spec.get('min_throughput') and not qos_min_support:
msg = _('QoS min_throughput is not supported by this back '
'end.')
raise na_utils.NetAppDriverException(msg)
if not spec:
return
is_adaptive = na_utils.is_qos_policy_group_spec_adaptive(
qos_policy_group_info)
self._validate_qos_policy_group(is_adaptive, spec=spec,
qos_min_support=qos_min_support)
if is_adaptive:
if not self.qos_policy_group_exists(spec['policy_name'],
is_adaptive=True):
self.qos_adaptive_policy_group_create(spec)
else:
self.qos_adaptive_policy_group_modify(spec)
else:
if not self.qos_policy_group_exists(spec['policy_name']):
self.qos_policy_group_create(spec)
else:
self.qos_policy_group_modify(spec)
def qos_policy_group_exists(self, qos_policy_group_name):
def qos_policy_group_exists(self, qos_policy_group_name,
is_adaptive=False):
"""Checks if a QOS policy group exists."""
query_name = 'qos-%spolicy-group-info' % (
'adaptive-' if is_adaptive else '')
request_name = 'qos-%spolicy-group-get-iter' % (
'adaptive-' if is_adaptive else '')
api_args = {
'query': {
'qos-policy-group-info': {
query_name: {
'policy-group': qos_policy_group_name,
},
},
'desired-attributes': {
'qos-policy-group-info': {
query_name: {
'policy-group': None,
},
},
}
result = self.connection.send_request('qos-policy-group-get-iter',
api_args,
False)
result = self.connection.send_request(request_name, api_args, False)
return self._has_records(result)
def _qos_spec_to_api_args(self, spec, **kwargs):
@ -656,29 +704,39 @@ class Client(client_base.Client):
return self.connection.send_request(
'qos-policy-group-create', api_args, False)
def qos_adaptive_policy_group_create(self, spec):
"""Creates a QOS adaptive policy group."""
api_args = self._qos_spec_to_api_args(
spec, vserver=self.vserver)
return self.connection.send_request(
'qos-adaptive-policy-group-create', api_args, False)
def qos_policy_group_modify(self, spec):
"""Modifies a QOS policy group."""
api_args = self._qos_spec_to_api_args(spec)
return self.connection.send_request(
'qos-policy-group-modify', api_args, False)
def qos_policy_group_delete(self, qos_policy_group_name):
"""Attempts to delete a QOS policy group."""
api_args = {'policy-group': qos_policy_group_name}
def qos_adaptive_policy_group_modify(self, spec):
"""Modifies a QOS adaptive policy group."""
api_args = self._qos_spec_to_api_args(spec)
return self.connection.send_request(
'qos-policy-group-delete', api_args, False)
'qos-adaptive-policy-group-modify', api_args, False)
def qos_policy_group_rename(self, qos_policy_group_name, new_name):
def qos_policy_group_rename(self, qos_policy_group_name, new_name,
is_adaptive=False):
"""Renames a QOS policy group."""
request_name = 'qos-%spolicy-group-rename' % (
'adaptive-' if is_adaptive else '')
api_args = {
'policy-group-name': qos_policy_group_name,
'new-name': new_name,
}
return self.connection.send_request(
'qos-policy-group-rename', api_args, False)
return self.connection.send_request(request_name, api_args, False)
def mark_qos_policy_group_for_deletion(self, qos_policy_group_info):
"""Do (soft) delete of backing QOS policy group for a cinder volume."""
def mark_qos_policy_group_for_deletion(self, qos_policy_group_info,
is_adaptive=False):
"""Soft delete a QOS policy group backing a cinder volume."""
if qos_policy_group_info is None:
return
@ -690,11 +748,12 @@ class Client(client_base.Client):
# we instead rename the QoS policy group using a specific pattern and
# later attempt on a best effort basis to delete any QoS policy groups
# matching that pattern.
if spec is not None:
if spec:
current_name = spec['policy_name']
new_name = client_base.DELETED_PREFIX + current_name
try:
self.qos_policy_group_rename(current_name, new_name)
self.qos_policy_group_rename(current_name, new_name,
is_adaptive)
except netapp_api.NaApiError as ex:
LOG.warning('Rename failure in cleanup of cDOT QOS policy '
'group %(name)s: %(ex)s',
@ -703,11 +762,15 @@ class Client(client_base.Client):
# Attempt to delete any QoS policies named "delete-openstack-*".
self.remove_unused_qos_policy_groups()
def remove_unused_qos_policy_groups(self):
"""Deletes all QOS policy groups that are marked for deletion."""
def _send_qos_policy_group_delete_iter_request(self, is_adaptive=False):
request_name = 'qos-%spolicy-group-delete-iter' % (
'adaptive-' if is_adaptive else '')
query_name = 'qos-%spolicy-group-info' % (
'adaptive-' if is_adaptive else '')
api_args = {
'query': {
'qos-policy-group-info': {
query_name: {
'policy-group': '%s*' % client_base.DELETED_PREFIX,
'vserver': self.vserver,
}
@ -719,18 +782,32 @@ class Client(client_base.Client):
}
try:
self.connection.send_request(
'qos-policy-group-delete-iter', api_args, False)
self.connection.send_request(request_name, api_args, False)
except netapp_api.NaApiError as ex:
msg = 'Could not delete QOS policy groups. Details: %(ex)s'
msg_args = {'ex': ex}
msg = ('Could not delete QOS %(prefix)spolicy groups. '
'Details: %(ex)s')
msg_args = {
'prefix': 'adaptive ' if is_adaptive else '',
'ex': ex,
}
LOG.debug(msg, msg_args)
def set_lun_qos_policy_group(self, path, qos_policy_group):
def remove_unused_qos_policy_groups(self):
"""Deletes all QOS policy groups that are marked for deletion."""
self._send_qos_policy_group_delete_iter_request()
if self.features.ADAPTIVE_QOS:
self._send_qos_policy_group_delete_iter_request(is_adaptive=True)
def set_lun_qos_policy_group(self, path, qos_policy_group,
is_adaptive=False):
"""Sets qos_policy_group on a LUN."""
self._validate_qos_policy_group(is_adaptive)
policy_group_key = 'qos-%spolicy-group' % (
'adaptive-' if is_adaptive else '')
api_args = {
'path': path,
'qos-policy-group': qos_policy_group,
policy_group_key: qos_policy_group,
}
return self.connection.send_request(
'lun-set-qos-policy-group', api_args)

View File

@ -160,13 +160,15 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver,
self.ssc_library.get_ssc_flexvol_names())
def _do_qos_for_volume(self, volume, extra_specs, cleanup=True):
qos_policy_group_is_adaptive = volume_utils.is_boolean_str(
extra_specs.get('netapp:qos_policy_group_is_adaptive'))
try:
qos_policy_group_info = na_utils.get_valid_qos_policy_group_info(
volume, extra_specs)
pool = volume_utils.extract_host(volume['host'], level='pool')
qos_min_support = self.ssc_library.is_qos_min_supported(pool)
qos_policy_group_is_adaptive = (volume_utils.is_boolean_str(
extra_specs.get('netapp:qos_policy_group_is_adaptive')) or
na_utils.is_qos_policy_group_spec_adaptive(
qos_policy_group_info))
self.zapi_client.provision_qos_policy_group(qos_policy_group_info,
qos_min_support)
self._set_qos_policy_group_on_volume(volume, qos_policy_group_info,
@ -416,8 +418,10 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver,
try:
qos_policy_group_info = na_utils.get_valid_qos_policy_group_info(
volume)
self.zapi_client.mark_qos_policy_group_for_deletion(
is_adaptive = na_utils.is_qos_policy_group_spec_adaptive(
qos_policy_group_info)
self.zapi_client.mark_qos_policy_group_for_deletion(
qos_policy_group_info, is_adaptive)
except Exception:
# Don't blow up here if something went wrong de-provisioning the
# QoS policy for the volume.

View File

@ -60,6 +60,23 @@ MAX_QOS_KEYS = frozenset([
'maxBPS',
'maxBPSperGiB',
])
ADAPTIVE_QOS_KEYS = frozenset([
'expectedIOPSperGiB',
'peakIOPSperGiB',
'expectedIOPSAllocation',
'peakIOPSAllocation',
'absoluteMinIOPS',
'blockSize',
])
QOS_ADAPTIVE_POLICY_GROUP_SPEC_KEYS = frozenset([
'expected_iops',
'peak_iops',
'expected_iops_allocation',
'peak_iops_allocation',
'absolute_min_iops',
'block_size',
'policy_name',
])
BACKEND_QOS_CONSUMERS = frozenset(['back-end', 'both'])
# Secret length cannot be less than 96 bits. http://tools.ietf.org/html/rfc3723
@ -219,10 +236,12 @@ def validate_qos_spec(qos_spec):
normalized_min_keys = [key.lower() for key in MIN_QOS_KEYS]
normalized_max_keys = [key.lower() for key in MAX_QOS_KEYS]
normalized_aqos_keys = [key.lower() for key in ADAPTIVE_QOS_KEYS]
unrecognized_keys = [
k for k in qos_spec.keys()
if k.lower() not in normalized_max_keys + normalized_min_keys]
if k.lower() not in
normalized_max_keys + normalized_min_keys + normalized_aqos_keys]
if unrecognized_keys:
msg = _('Unrecognized QOS keywords: "%s"') % unrecognized_keys
@ -240,6 +259,13 @@ def validate_qos_spec(qos_spec):
msg = _('Only one maximum limit can be set in a QoS spec.')
raise exception.Invalid(msg)
aqos_dict = {k: v for k, v in qos_spec.items()
if k.lower() in normalized_aqos_keys}
if aqos_dict and (min_dict or max_dict):
msg = _('Adaptive QoS specs and non-adaptive QoS specs '
'cannot be used together.')
raise exception.Invalid(msg)
def get_volume_type_from_volume(volume):
"""Provides volume type associated with volume."""
@ -313,6 +339,42 @@ def map_qos_spec(qos_spec, volume):
return policy
def map_aqos_spec(qos_spec, volume):
"""Map Cinder QOS spec to Adaptive QoS values."""
if qos_spec is None:
return None
qos_spec = map_dict_to_lower(qos_spec)
spec = dict(policy_name=get_qos_policy_group_name(volume))
# Adaptive QoS specs
if 'expectediopspergib' in qos_spec:
spec['expected_iops'] = (
'%sIOPS/GB' % qos_spec['expectediopspergib'])
if 'peakiopspergib' in qos_spec:
spec['peak_iops'] = '%sIOPS/GB' % qos_spec['peakiopspergib']
if 'expectediopsallocation' in qos_spec:
spec['expected_iops_allocation'] = qos_spec['expectediopsallocation']
if 'peakiopsallocation' in qos_spec:
spec['peak_iops_allocation'] = qos_spec['peakiopsallocation']
if 'absoluteminiops' in qos_spec:
spec['absolute_min_iops'] = '%sIOPS' % qos_spec['absoluteminiops']
if 'blocksize' in qos_spec:
spec['block_size'] = qos_spec['blocksize']
if 'peak_iops' not in spec or 'expected_iops' not in spec:
msg = _('Adaptive QoS requires the expected property and '
'the peak property set together.')
raise exception.Invalid(msg)
if spec['peak_iops'] < spec['expected_iops']:
msg = _('Adaptive maximum limit should be greater than or equal to '
'the adaptive minimum limit.')
raise exception.Invalid(msg)
return spec
def map_dict_to_lower(input_dict):
"""Return an equivalent to the input dictionary with lower-case keys."""
lower_case_dict = {}
@ -389,11 +451,35 @@ def get_valid_qos_policy_group_info(volume, extra_specs=None):
def get_valid_backend_qos_spec_from_volume_type(volume, volume_type):
"""Given a volume type, return the associated Cinder QoS spec."""
spec_key_values = get_backend_qos_spec_from_volume_type(volume_type)
if spec_key_values is None:
spec_dict = get_backend_qos_spec_from_volume_type(volume_type)
if spec_dict is None:
return None
validate_qos_spec(spec_key_values)
return map_qos_spec(spec_key_values, volume)
validate_qos_spec(spec_dict)
map_spec = (map_aqos_spec
if is_qos_adaptive(spec_dict)
else map_qos_spec)
return map_spec(spec_dict, volume)
def is_qos_adaptive(spec_dict):
if not spec_dict:
return False
normalized_aqos_keys = [key.lower() for key in ADAPTIVE_QOS_KEYS]
return all(key in normalized_aqos_keys
for key in map_dict_to_lower(spec_dict).keys())
def is_qos_policy_group_spec_adaptive(policy):
if not policy:
return False
spec = policy.get('spec')
if not spec:
return False
return all(key in QOS_ADAPTIVE_POLICY_GROUP_SPEC_KEYS
for key in map_dict_to_lower(spec).keys())
def get_backend_qos_spec_from_volume_type(volume_type):
@ -408,8 +494,7 @@ def get_backend_qos_spec_from_volume_type(volume_type):
# Front end QoS specs are handled by libvirt and we ignore them here.
if consumer not in BACKEND_QOS_CONSUMERS:
return None
spec_key_values = qos_spec['specs']
return spec_key_values
return qos_spec['specs']
def check_for_invalid_qos_spec_combination(info, volume_type):

View File

@ -0,0 +1,11 @@
---
features:
- |
NetApp ONTAP driver: Added support for Adaptive QoS specs. The driver now
accepts ``expectedIOPSperGiB``, ``peakIOPSperGiB``, ``expectedIOPSAllocation``,
``peakIOPSAllocation``, ``absoluteMinIOPS`` and ``blockSize``. The field
``peakIOPSperGiB`` and the field ``expectedIOPSperGiB`` are required together.
The ``expectedIOPSperGiB`` and ``absoluteMinIOPS`` specs are only guaranteed
by ONTAP AFF systems. All specs can only be used with ONTAP version equal
or greater than 9.4, excepting the ``expectedIOPSAllocation`` and
``blockSize`` specs which require at least 9.5.