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

NetApp ONTAP 9.4 or newer supports Adaptive QoS, which scales the
throughput according to the volume size.

In Victoria release, ONTAP Cinder driver added support for assigning
pre-existing Adaptive QoS policy groups to volumes.

This patch allows the dynamic creation of Adpative QoS, by reading new
back-end QoS specs.

Implements: blueprint netapp-ontap-dynamic-adaptive-qos

Change-Id: Ie35373c7b205ffa12c4bb710255f1baf7a836d9f
This commit is contained in:
Lucio Seki 2020-10-07 20:42:31 +00:00 committed by Felipe Rodrigues
parent fb358e45fe
commit 7d9dc8dec9
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') client_base.Client, 'do_direct_resize')
mock_set_space_reservation = self.mock_object( mock_set_space_reservation = self.mock_object(
client_base.Client, 'set_lun_space_reservation') 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 initial_size = self.fake_size
if ontap_version < '9.5': if ontap_version < '9.5':
@ -118,6 +120,7 @@ class NetAppBaseClientTestCase(test.TestCase):
self.fake_size, self.fake_size,
self.fake_metadata) self.fake_metadata)
mock_validate_qos_policy_group.assert_called_once()
mock_create_node.assert_called_with( mock_create_node.assert_called_with(
'lun-create-by-size', 'lun-create-by-size',
**{'path': expected_path, **{'path': expected_path,
@ -154,6 +157,8 @@ class NetAppBaseClientTestCase(test.TestCase):
client_base.Client, 'do_direct_resize') client_base.Client, 'do_direct_resize')
mock_set_space_reservation = self.mock_object( mock_set_space_reservation = self.mock_object(
client_base.Client, 'set_lun_space_reservation') 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 initial_size = self.fake_size
if ontap_version < '9.5': if ontap_version < '9.5':
@ -168,6 +173,7 @@ class NetAppBaseClientTestCase(test.TestCase):
self.fake_size, self.fake_size,
self.fake_metadata) self.fake_metadata)
mock_validate_qos_policy_group.assert_called_once()
mock_create_node.assert_called_with( mock_create_node.assert_called_with(
'lun-create-by-size', 'lun-create-by-size',
**{'path': expected_path, **{'path': expected_path,
@ -208,6 +214,8 @@ class NetAppBaseClientTestCase(test.TestCase):
client_base.Client, 'do_direct_resize') client_base.Client, 'do_direct_resize')
mock_set_space_reservation = self.mock_object( mock_set_space_reservation = self.mock_object(
client_base.Client, 'set_lun_space_reservation') 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 initial_size = self.fake_size
if ontap_version < '9.5': if ontap_version < '9.5':
@ -225,6 +233,7 @@ class NetAppBaseClientTestCase(test.TestCase):
self.fake_metadata, self.fake_metadata,
qos_policy_group_name=expected_qos_group_name) qos_policy_group_name=expected_qos_group_name)
mock_validate_qos_policy_group.assert_called_once()
mock_create_node.assert_called_with( mock_create_node.assert_called_with(
'lun-create-by-size', 'lun-create-by-size',
**{'path': expected_path, 'size': initial_size, **{'path': expected_path, 'size': initial_size,
@ -284,6 +293,8 @@ class NetAppBaseClientTestCase(test.TestCase):
side_effect=netapp_api.NaApiError) side_effect=netapp_api.NaApiError)
self.mock_object(self.client, 'get_ontap_version', self.mock_object(self.client, 'get_ontap_version',
return_value=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.assertRaises(netapp_api.NaApiError,
self.client.create_lun, self.client.create_lun,
@ -291,6 +302,7 @@ class NetAppBaseClientTestCase(test.TestCase):
self.fake_lun, self.fake_lun,
self.fake_size, self.fake_size,
self.fake_metadata) self.fake_metadata)
mock_validate_qos_policy_group.assert_called_once()
def test_destroy_lun(self): def test_destroy_lun(self):
path = '/vol/%s/%s' % (self.fake_volume, self.fake_lun) path = '/vol/%s/%s' % (self.fake_volume, self.fake_lun)

View File

@ -729,6 +729,67 @@ class NetAppCmodeClientTestCase(test.TestCase):
self.assertSetEqual(igroups, expected) 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): def test_clone_lun(self):
self.client.clone_lun( self.client.clone_lun(
'volume', 'fakeLUN', 'newFakeLUN', 'volume', 'fakeLUN', 'newFakeLUN',
@ -853,19 +914,38 @@ class NetAppCmodeClientTestCase(test.TestCase):
mock.call('lun-set-qos-policy-group', api_args)]) mock.call('lun-set-qos-policy-group', api_args)])
def test_provision_qos_policy_group_no_qos_policy_group_info(self): 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, self.client.provision_qos_policy_group(qos_policy_group_info=None,
qos_min_support=True) 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): 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( self.client.provision_qos_policy_group(
qos_policy_group_info=fake.QOS_POLICY_GROUP_INFO_LEGACY, qos_policy_group_info=fake.QOS_POLICY_GROUP_INFO_LEGACY,
qos_min_support=True) 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): 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, self.client.provision_qos_policy_group(fake.QOS_POLICY_GROUP_INFO,
True) True)
mock_qos_policy_group_create.assert_has_calls([ mock_qos_policy_group_create.assert_called_once_with({
mock.call({ 'policy_name': fake.QOS_POLICY_GROUP_NAME,
'policy_name': fake.QOS_POLICY_GROUP_NAME, 'min_throughput': fake.MIN_IOPS,
'min_throughput': fake.MIN_IOPS, 'max_throughput': fake.MAX_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() mock_qos_policy_group_modify.assert_not_called()
def test_provision_qos_policy_group_with_qos_spec_create_unsupported(self): 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( mock_qos_policy_group_create = self.mock_object(
self.client, 'qos_policy_group_create') self.client, 'qos_policy_group_create')
mock_qos_policy_group_modify = self.mock_object( mock_qos_policy_group_modify = self.mock_object(
@ -897,8 +1005,34 @@ class NetAppCmodeClientTestCase(test.TestCase):
self.assertRaises( self.assertRaises(
netapp_utils.NetAppDriverException, netapp_utils.NetAppDriverException,
self.client.provision_qos_policy_group, 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_create.assert_not_called()
mock_qos_policy_group_modify.assert_not_called() mock_qos_policy_group_modify.assert_not_called()
@ -963,6 +1097,33 @@ class NetAppCmodeClientTestCase(test.TestCase):
'max_throughput': fake.MAX_THROUGHPUT, '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): def test_qos_policy_group_exists(self):
self.mock_send_request.return_value = netapp_api.NaElement( self.mock_send_request.return_value = netapp_api.NaElement(
@ -1015,6 +1176,30 @@ class NetAppCmodeClientTestCase(test.TestCase):
self.mock_send_request.assert_has_calls([ self.mock_send_request.assert_has_calls([
mock.call('qos-policy-group-create', api_args, False)]) 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): def test_qos_policy_group_modify(self):
api_args = { api_args = {
@ -1032,17 +1217,28 @@ class NetAppCmodeClientTestCase(test.TestCase):
self.mock_send_request.assert_has_calls([ self.mock_send_request.assert_has_calls([
mock.call('qos-policy-group-modify', api_args, False)]) 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 = { 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( self.client.qos_adaptive_policy_group_modify({
fake.QOS_POLICY_GROUP_NAME) '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([ 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): def test_qos_policy_group_rename(self):
@ -1082,7 +1278,9 @@ class NetAppCmodeClientTestCase(test.TestCase):
self.assertEqual(0, mock_rename.call_count) self.assertEqual(0, mock_rename.call_count)
self.assertEqual(1, mock_remove.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_rename = self.mock_object(self.client, 'qos_policy_group_rename')
mock_remove = self.mock_object(self.client, 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 new_name = 'deleted_cinder_%s' % fake.QOS_POLICY_GROUP_NAME
self.client.mark_qos_policy_group_for_deletion( 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_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(0, mock_log.call_count)
self.assertEqual(1, mock_remove.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 = self.mock_object(self.client, 'qos_policy_group_rename')
mock_rename.side_effect = netapp_api.NaApiError 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 new_name = 'deleted_cinder_%s' % fake.QOS_POLICY_GROUP_NAME
self.client.mark_qos_policy_group_for_deletion( 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_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_log.call_count)
self.assertEqual(1, mock_remove.call_count) self.assertEqual(1, mock_remove.call_count)
@ -1138,15 +1340,29 @@ class NetAppCmodeClientTestCase(test.TestCase):
self.assertEqual(0, mock_log.call_count) self.assertEqual(0, mock_log.call_count)
def test_remove_unused_qos_policy_groups_api_error(self): 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') mock_log = self.mock_object(client_cmode.LOG, 'debug')
api_args = { qos_query = {
'query': { 'qos-policy-group-info': {
'qos-policy-group-info': { 'policy-group': 'deleted_cinder_*',
'policy-group': 'deleted_cinder_*', 'vserver': self.vserver,
'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, 'max-records': 3500,
'continue-on-failure': 'true', 'continue-on-failure': 'true',
'return-success-list': 'false', 'return-success-list': 'false',
@ -1157,8 +1373,12 @@ class NetAppCmodeClientTestCase(test.TestCase):
self.client.remove_unused_qos_policy_groups() self.client.remove_unused_qos_policy_groups()
self.mock_send_request.assert_has_calls([ self.mock_send_request.assert_has_calls([
mock.call('qos-policy-group-delete-iter', api_args, False)]) mock.call('qos-policy-group-delete-iter',
self.assertEqual(1, mock_log.call_count) 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', @mock.patch('cinder.volume.volume_utils.resolve_hostname',
return_value='192.168.1.101') return_value='192.168.1.101')

View File

@ -280,8 +280,28 @@ QOS_POLICY_GROUP_SPEC_MAX = {
'policy_name': QOS_POLICY_GROUP_NAME, '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 = {'legacy': None, 'spec': QOS_POLICY_GROUP_SPEC}
QOS_POLICY_GROUP_INFO_MAX = {'legacy': None, 'spec': QOS_POLICY_GROUP_SPEC_MAX} 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_NAME = 'fake_clone_source_name'
CLONE_SOURCE_ID = 'fake_clone_source_id' CLONE_SOURCE_ID = 'fake_clone_source_id'

View File

@ -21,6 +21,7 @@
"""Mock unit tests for the NetApp block storage library""" """Mock unit tests for the NetApp block storage library"""
import copy import copy
import itertools
from unittest import mock from unittest import mock
import uuid import uuid
@ -124,7 +125,7 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
self.mock_object(volume_utils, 'extract_host', self.mock_object(volume_utils, 'extract_host',
return_value=fake.POOL_NAME) return_value=fake.POOL_NAME)
self.mock_object(self.library, '_setup_qos_for_volume', 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')
self.mock_object(self.library, '_create_lun_handle') self.mock_object(self.library, '_create_lun_handle')
self.mock_object(self.library, '_add_lun_to_table') 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( self.library._create_lun.assert_called_once_with(
fake.POOL_NAME, fake.LUN_NAME, volume_size_in_bytes, 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( self.library._get_volume_model_update.assert_called_once_with(
fake.VOLUME) fake.VOLUME)
self.assertEqual( self.assertEqual(
@ -152,7 +153,7 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
self.mock_object(block_base, 'LOG') self.mock_object(block_base, 'LOG')
self.mock_object(na_utils, 'get_volume_extra_specs') self.mock_object(na_utils, 'get_volume_extra_specs')
self.mock_object(self.library, '_setup_qos_for_volume', 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, '_create_lun', side_effect=Exception)
self.mock_object(self.library, '_mark_qos_policy_group_for_deletion') 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.assertEqual(2, mock_info_log.call_count)
self.library._add_lun_to_table.assert_called_once_with(mock_lun) self.library._add_lun_to_table.assert_called_once_with(mock_lun)
@ddt.data(None, 'fake_qos_policy_group_name') @ddt.data(*itertools.product((None, 'fake_qos_policy_group_name'),
def test_manage_existing_rename_lun(self, 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 = ( expected_update = (
{'replication_status': fields.ReplicationStatus.ENABLED}) {'replication_status': fields.ReplicationStatus.ENABLED})
volume = fake_volume.fake_volume_obj(self.ctxt) 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(self.library, '_setup_qos_for_volume')
self.mock_object(na_utils, 'get_qos_policy_group_name_from_info', self.mock_object(na_utils, 'get_qos_policy_group_name_from_info',
return_value=qos_policy_group_name) 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, '_add_lun_to_table')
self.mock_object(self.library, '_get_volume_model_update', self.mock_object(self.library, '_get_volume_model_update',
return_value=expected_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) self.library._add_lun_to_table.assert_called_once_with(mock_lun)
if qos_policy_group_name: if qos_policy_group_name:
(self.zapi_client.set_lun_qos_policy_group. (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: else:
self.assertFalse( self.assertFalse(
self.zapi_client.set_lun_qos_policy_group.called) self.zapi_client.set_lun_qos_policy_group.called)

View File

@ -599,15 +599,20 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
self.zapi_client. self.zapi_client.
provision_qos_policy_group.call_count) 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, self.mock_object(self.zapi_client,
'mark_qos_policy_group_for_deletion') '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( self.library._mark_qos_policy_group_for_deletion(
fake.QOS_POLICY_GROUP_INFO) fake.QOS_POLICY_GROUP_INFO)
self.zapi_client.mark_qos_policy_group_for_deletion\ 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): def test_unmanage(self):
self.mock_object(na_utils, 'get_valid_qos_policy_group_info', 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( block_base.NetAppBlockStorageLibrary.unmanage.assert_called_once_with(
fake.VOLUME) 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', mock_lun = block_base.NetAppLun('handle', 'name', '1',
{'Path': '/vol/FAKE_CMODE_VOL1/name'}) {'Path': '/vol/FAKE_CMODE_VOL1/name'})
self.library._get_existing_vol_with_manage_ref = mock.Mock( 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.library._setup_qos_for_volume = mock.Mock()
self.mock_object(na_utils, 'get_qos_policy_group_name_from_info', self.mock_object(na_utils, 'get_qos_policy_group_name_from_info',
return_value=fake.QOS_POLICY_GROUP_NAME) 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.library._add_lun_to_table = mock.Mock()
self.zapi_client.move_lun = mock.Mock() self.zapi_client.move_lun = mock.Mock()
mock_set_lun_qos_policy_group = self.mock_object( 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.library._setup_qos_for_volume = mock.Mock()
self.mock_object(na_utils, 'get_qos_policy_group_name_from_info', self.mock_object(na_utils, 'get_qos_policy_group_name_from_info',
return_value=None) 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.library._add_lun_to_table = mock.Mock()
self.zapi_client.move_lun = 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(self.driver, '_delete_backing_file_for_volume')
self.mock_object(na_utils, self.mock_object(na_utils,
'get_valid_qos_policy_group_info', '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) self.driver.delete_volume(fake_volume)
@ -502,8 +504,10 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
fake_volume) fake_volume)
na_utils.get_valid_qos_policy_group_info.assert_called_once_with( na_utils.get_valid_qos_policy_group_info.assert_called_once_with(
fake_volume) 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. (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): def test_delete_volume_exception_path(self):
fake_provider_location = 'fake_provider_location' 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(self.driver, '_delete_backing_file_for_volume')
self.mock_object(na_utils, self.mock_object(na_utils,
'get_valid_qos_policy_group_info', '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.mock_object(
self.driver.zapi_client, self.driver.zapi_client,
'mark_qos_policy_group_for_deletion', 'mark_qos_policy_group_for_deletion',
@ -523,8 +529,10 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
fake_volume) fake_volume)
na_utils.get_valid_qos_policy_group_info.assert_called_once_with( na_utils.get_valid_qos_policy_group_info.assert_called_once_with(
fake_volume) 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. (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): def test_delete_backing_file_for_volume(self):
mock_filer_delete = self.mock_object(self.driver, '_delete_file') 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' QOS_POLICY_GROUP_NAME = 'fake_qos_policy_group_name'
LEGACY_EXTRA_SPECS = {'netapp:qos_policy_group': 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 = { LEGACY_QOS = {
'policy_name': QOS_POLICY_GROUP_NAME, '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} 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_QOS_POLICY_GROUP_INFO = {
'legacy': LEGACY_QOS, 'legacy': LEGACY_QOS,
'spec': None, 'spec': None,
} }
INVALID_QOS_POLICY_GROUP_INFO = { INVALID_QOS_POLICY_GROUP_INFO_LEGACY_AND_SPEC = {
'legacy': LEGACY_QOS, 'legacy': LEGACY_QOS,
'spec': QOS_POLICY_GROUP_SPEC, '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_SPECS_ID = 'fake_qos_specs_id'
QOS_SPEC = {'maxBPS': 21734278} QOS_SPEC = {'maxBPS': 21734278}
OUTER_BACKEND_QOS_SPEC = { OUTER_BACKEND_QOS_SPEC = {

View File

@ -45,13 +45,13 @@ class NetAppDriverUtilsTestCase(test.TestCase):
def test_validate_instantiation_proxy(self): def test_validate_instantiation_proxy(self):
kwargs = {'netapp_mode': 'proxy'} kwargs = {'netapp_mode': 'proxy'}
na_utils.validate_instantiation(**kwargs) 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()) @mock.patch.object(na_utils, 'LOG', mock.Mock())
def test_validate_instantiation_no_proxy(self): def test_validate_instantiation_no_proxy(self):
kwargs = {'netapp_mode': 'asdf'} kwargs = {'netapp_mode': 'asdf'}
na_utils.validate_instantiation(**kwargs) 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): def test_check_flags(self):
@ -228,7 +228,7 @@ class NetAppDriverUtilsTestCase(test.TestCase):
na_utils.log_extra_spec_warnings({'netapp:raid_type': 'raid4'}) 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): 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'}) 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): def test_validate_qos_spec_none(self):
qos_spec = None qos_spec = None
@ -244,6 +250,10 @@ class NetAppDriverUtilsTestCase(test.TestCase):
# Just return without raising an exception. # Just return without raising an exception.
na_utils.validate_qos_spec(qos_spec) 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): def test_validate_qos_spec_keys_weirdly_cased(self):
qos_spec = {'mAxIopS': 33000, 'mInIopS': 0} 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): def test_validate_qos_spec_bad_key_combination_miniops_miniopspergib(self):
qos_spec = {'minIOPS': 33000, 'minIOPSperGiB': 10000000} 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, self.assertRaises(exception.Invalid,
na_utils.validate_qos_spec, na_utils.validate_qos_spec,
@ -408,6 +446,101 @@ class NetAppDriverUtilsTestCase(test.TestCase):
self.assertEqual(expected, result) 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): def test_map_dict_to_lower(self):
original = {'UPperKey': 'Value'} original = {'UPperKey': 'Value'}
expected = {'upperkey': 'Value'} expected = {'upperkey': 'Value'}
@ -498,7 +631,6 @@ class NetAppDriverUtilsTestCase(test.TestCase):
mock_get_valid_qos_spec_from_volume_type = self.mock_object( mock_get_valid_qos_spec_from_volume_type = self.mock_object(
na_utils, 'get_valid_backend_qos_spec_from_volume_type') na_utils, 'get_valid_backend_qos_spec_from_volume_type')
mock_get_valid_qos_spec_from_volume_type.return_value = None 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 expected = fake.QOS_POLICY_GROUP_INFO_NONE
result = na_utils.get_valid_qos_policy_group_info(fake.VOLUME) 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( mock_get_valid_qos_spec_from_volume_type = self.mock_object(
na_utils, 'get_valid_backend_qos_spec_from_volume_type') na_utils, 'get_valid_backend_qos_spec_from_volume_type')
mock_get_valid_qos_spec_from_volume_type.return_value = None 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) 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') na_utils, 'get_valid_backend_qos_spec_from_volume_type')
mock_get_valid_qos_spec_from_volume_type.return_value =\ mock_get_valid_qos_spec_from_volume_type.return_value =\
fake.QOS_POLICY_GROUP_SPEC 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) 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( mock_get_spec = self.mock_object(
na_utils, 'get_backend_qos_spec_from_volume_type') na_utils, 'get_backend_qos_spec_from_volume_type')
mock_get_spec.return_value = None 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( result = na_utils.get_valid_backend_qos_spec_from_volume_type(
fake.VOLUME, fake.VOLUME_TYPE) fake.VOLUME, fake.VOLUME_TYPE)
self.assertIsNone(result) 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): def test_get_valid_backend_qos_spec_from_volume_type(self):
mock_get_spec = self.mock_object( mock_get_spec = self.mock_object(
na_utils, 'get_backend_qos_spec_from_volume_type') na_utils, 'get_backend_qos_spec_from_volume_type')
mock_get_spec.return_value = fake.QOS_SPEC 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( result = na_utils.get_valid_backend_qos_spec_from_volume_type(
fake.VOLUME, fake.VOLUME_TYPE) fake.VOLUME, fake.VOLUME_TYPE)
self.assertEqual(fake.QOS_POLICY_GROUP_SPEC, result) 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): def test_get_backend_qos_spec_from_volume_type_no_qos_specs_id(self):
volume_type = copy.deepcopy(fake.VOLUME_TYPE) 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) result = na_utils.get_backend_qos_spec_from_volume_type(volume_type)
self.assertIsNone(result) 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): def test_get_backend_qos_spec_from_volume_type_no_qos_spec(self):
volume_type = fake.VOLUME_TYPE volume_type = fake.VOLUME_TYPE
@ -613,11 +761,20 @@ class NetAppDriverUtilsTestCase(test.TestCase):
self.assertEqual(fake.QOS_SPEC, result) 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, self.assertRaises(exception.Invalid,
na_utils.check_for_invalid_qos_spec_combination, 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) fake.VOLUME_TYPE)
def test_get_legacy_qos_policy(self): def test_get_legacy_qos_policy(self):

View File

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

View File

@ -414,8 +414,10 @@ class NetAppBlockStorageCmodeLibrary(block_base.NetAppBlockStorageLibrary,
return {'replication_status': fields.ReplicationStatus.ENABLED} return {'replication_status': fields.ReplicationStatus.ENABLED}
def _mark_qos_policy_group_for_deletion(self, qos_policy_group_info): 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) qos_policy_group_info)
self.zapi_client.mark_qos_policy_group_for_deletion(
qos_policy_group_info, is_adaptive)
def unmanage(self, volume): def unmanage(self, volume):
"""Removes the specified volume from Cinder management. """Removes the specified volume from Cinder management.

View File

@ -109,6 +109,7 @@ class Client(object):
qos_policy_group_name=None, qos_policy_group_name=None,
qos_policy_group_is_adaptive=False): qos_policy_group_is_adaptive=False):
"""Issues API request for creating LUN on volume.""" """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) path = '/vol/%s/%s' % (volume_name, lun_name)
space_reservation = metadata['SpaceReserved'] space_reservation = metadata['SpaceReserved']
@ -314,6 +315,10 @@ class Client(object):
"""Get igroups exactly matching a set of initiators.""" """Get igroups exactly matching a set of initiators."""
raise NotImplementedError() 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): def _has_luns_mapped_to_initiator(self, initiator):
"""Checks whether any LUNs are mapped to the given initiator.""" """Checks whether any LUNs are mapped to the given initiator."""
lun_list_api = netapp_api.NaElement('lun-initiator-list-map-info') 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) self.connection.set_api_version(1, 15)
(major, minor) = self.get_ontapi_version(cached=False) (major, minor) = self.get_ontapi_version(cached=False)
self.connection.set_api_version(major, minor) self.connection.set_api_version(major, minor)
self._init_features()
ontap_version = self.get_ontap_version(cached=False) ontap_version = self.get_ontap_version(cached=False)
self.connection.set_ontap_version(ontap_version) self.connection.set_ontap_version(ontap_version)
self._init_features()
def _init_features(self): def _init_features(self):
super(Client, self)._init_features() super(Client, self)._init_features()
@ -65,6 +65,8 @@ class Client(client_base.Client):
ontapi_1_100 = ontapi_version >= (1, 100) ontapi_1_100 = ontapi_version >= (1, 100)
ontapi_1_1xx = (1, 100) <= ontapi_version < (1, 200) ontapi_1_1xx = (1, 100) <= ontapi_version < (1, 200)
ontapi_1_60 = ontapi_version >= (1, 160) 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() nodes_info = self._get_cluster_nodes_info()
for node in 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('CLUSTER_PEER_POLICY', supported=ontapi_1_30)
self.features.add_feature('FLEXVOL_ENCRYPTION', supported=ontapi_1_1xx) 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', LOG.info('Reported ONTAPI Version: %(major)s.%(minor)s',
{'major': ontapi_version[0], 'minor': ontapi_version[1]}) {'major': ontapi_version[0], 'minor': ontapi_version[1]})
@ -511,10 +519,39 @@ class Client(client_base.Client):
return igroup_list 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', def clone_lun(self, volume, name, new_name, space_reserved='true',
qos_policy_group_name=None, src_block=0, dest_block=0, qos_policy_group_name=None, src_block=0, dest_block=0,
block_count=0, source_snapshot=None, is_snapshot=False, block_count=0, source_snapshot=None, is_snapshot=False,
qos_policy_group_is_adaptive=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 # ONTAP handles only 128 MB per call as of v9.1
bc_limit = 2 ** 18 # 2^18 blocks * 512 bytes/block = 128 MB bc_limit = 2 ** 18 # 2^18 blocks * 512 bytes/block = 128 MB
z_calls = int(math.ceil(block_count / float(bc_limit))) 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 = netapp_api.NaElement.create_node_with_children(
'clone-create', **zapi_args) 'clone-create', **zapi_args)
if qos_policy_group_name is not None: if qos_policy_group_name is not None:
if qos_policy_group_is_adaptive: child_name = 'qos-%spolicy-group-name' % (
clone_create.add_new_child( 'adaptive-' if qos_policy_group_is_adaptive else '')
'qos-adaptive-policy-group-name', clone_create.add_new_child(child_name, qos_policy_group_name)
qos_policy_group_name)
else:
clone_create.add_new_child('qos-policy-group-name',
qos_policy_group_name)
if block_count > 0: if block_count > 0:
block_ranges = netapp_api.NaElement("block-ranges") block_ranges = netapp_api.NaElement("block-ranges")
segments = int(math.ceil(block_count / float(bc_limit))) 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, def file_assign_qos(self, flex_vol, qos_policy_group_name,
qos_policy_group_is_adaptive, file_path): qos_policy_group_is_adaptive, file_path):
"""Assigns the named QoS policy-group to a file.""" """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" % ( qos_arg_name = "qos-%spolicy-group-name" % (
"adaptive-" if qos_policy_group_is_adaptive else "") "adaptive-" if qos_policy_group_is_adaptive else "")
api_args = { api_args = {
@ -612,33 +647,46 @@ class Client(client_base.Client):
return return
spec = qos_policy_group_info.get('spec') spec = qos_policy_group_info.get('spec')
if spec:
if spec.get('min_throughput') and not qos_min_support: if not spec:
msg = _('QoS min_throughput is not supported by this back ' return
'end.')
raise na_utils.NetAppDriverException(msg) 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']): if not self.qos_policy_group_exists(spec['policy_name']):
self.qos_policy_group_create(spec) self.qos_policy_group_create(spec)
else: else:
self.qos_policy_group_modify(spec) 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.""" """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 = { api_args = {
'query': { 'query': {
'qos-policy-group-info': { query_name: {
'policy-group': qos_policy_group_name, 'policy-group': qos_policy_group_name,
}, },
}, },
'desired-attributes': { 'desired-attributes': {
'qos-policy-group-info': { query_name: {
'policy-group': None, 'policy-group': None,
}, },
}, },
} }
result = self.connection.send_request('qos-policy-group-get-iter', result = self.connection.send_request(request_name, api_args, False)
api_args,
False)
return self._has_records(result) return self._has_records(result)
def _qos_spec_to_api_args(self, spec, **kwargs): def _qos_spec_to_api_args(self, spec, **kwargs):
@ -656,29 +704,39 @@ class Client(client_base.Client):
return self.connection.send_request( return self.connection.send_request(
'qos-policy-group-create', api_args, False) '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): def qos_policy_group_modify(self, spec):
"""Modifies a QOS policy group.""" """Modifies a QOS policy group."""
api_args = self._qos_spec_to_api_args(spec) api_args = self._qos_spec_to_api_args(spec)
return self.connection.send_request( return self.connection.send_request(
'qos-policy-group-modify', api_args, False) 'qos-policy-group-modify', api_args, False)
def qos_policy_group_delete(self, qos_policy_group_name): def qos_adaptive_policy_group_modify(self, spec):
"""Attempts to delete a QOS policy group.""" """Modifies a QOS adaptive policy group."""
api_args = {'policy-group': qos_policy_group_name} api_args = self._qos_spec_to_api_args(spec)
return self.connection.send_request( 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.""" """Renames a QOS policy group."""
request_name = 'qos-%spolicy-group-rename' % (
'adaptive-' if is_adaptive else '')
api_args = { api_args = {
'policy-group-name': qos_policy_group_name, 'policy-group-name': qos_policy_group_name,
'new-name': new_name, 'new-name': new_name,
} }
return self.connection.send_request( return self.connection.send_request(request_name, api_args, False)
'qos-policy-group-rename', api_args, False)
def mark_qos_policy_group_for_deletion(self, qos_policy_group_info): def mark_qos_policy_group_for_deletion(self, qos_policy_group_info,
"""Do (soft) delete of backing QOS policy group for a cinder volume.""" is_adaptive=False):
"""Soft delete a QOS policy group backing a cinder volume."""
if qos_policy_group_info is None: if qos_policy_group_info is None:
return return
@ -690,11 +748,12 @@ class Client(client_base.Client):
# we instead rename the QoS policy group using a specific pattern and # 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 # later attempt on a best effort basis to delete any QoS policy groups
# matching that pattern. # matching that pattern.
if spec is not None: if spec:
current_name = spec['policy_name'] current_name = spec['policy_name']
new_name = client_base.DELETED_PREFIX + current_name new_name = client_base.DELETED_PREFIX + current_name
try: 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: except netapp_api.NaApiError as ex:
LOG.warning('Rename failure in cleanup of cDOT QOS policy ' LOG.warning('Rename failure in cleanup of cDOT QOS policy '
'group %(name)s: %(ex)s', 'group %(name)s: %(ex)s',
@ -703,11 +762,15 @@ class Client(client_base.Client):
# Attempt to delete any QoS policies named "delete-openstack-*". # Attempt to delete any QoS policies named "delete-openstack-*".
self.remove_unused_qos_policy_groups() self.remove_unused_qos_policy_groups()
def remove_unused_qos_policy_groups(self): def _send_qos_policy_group_delete_iter_request(self, is_adaptive=False):
"""Deletes all QOS policy groups that are marked for deletion.""" 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 = { api_args = {
'query': { 'query': {
'qos-policy-group-info': { query_name: {
'policy-group': '%s*' % client_base.DELETED_PREFIX, 'policy-group': '%s*' % client_base.DELETED_PREFIX,
'vserver': self.vserver, 'vserver': self.vserver,
} }
@ -719,18 +782,32 @@ class Client(client_base.Client):
} }
try: try:
self.connection.send_request( self.connection.send_request(request_name, api_args, False)
'qos-policy-group-delete-iter', api_args, False)
except netapp_api.NaApiError as ex: except netapp_api.NaApiError as ex:
msg = 'Could not delete QOS policy groups. Details: %(ex)s' msg = ('Could not delete QOS %(prefix)spolicy groups. '
msg_args = {'ex': ex} 'Details: %(ex)s')
msg_args = {
'prefix': 'adaptive ' if is_adaptive else '',
'ex': ex,
}
LOG.debug(msg, msg_args) 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.""" """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 = { api_args = {
'path': path, 'path': path,
'qos-policy-group': qos_policy_group, policy_group_key: qos_policy_group,
} }
return self.connection.send_request( return self.connection.send_request(
'lun-set-qos-policy-group', api_args) '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()) self.ssc_library.get_ssc_flexvol_names())
def _do_qos_for_volume(self, volume, extra_specs, cleanup=True): 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: try:
qos_policy_group_info = na_utils.get_valid_qos_policy_group_info( qos_policy_group_info = na_utils.get_valid_qos_policy_group_info(
volume, extra_specs) volume, extra_specs)
pool = volume_utils.extract_host(volume['host'], level='pool') pool = volume_utils.extract_host(volume['host'], level='pool')
qos_min_support = self.ssc_library.is_qos_min_supported(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, self.zapi_client.provision_qos_policy_group(qos_policy_group_info,
qos_min_support) qos_min_support)
self._set_qos_policy_group_on_volume(volume, qos_policy_group_info, self._set_qos_policy_group_on_volume(volume, qos_policy_group_info,
@ -416,8 +418,10 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver,
try: try:
qos_policy_group_info = na_utils.get_valid_qos_policy_group_info( qos_policy_group_info = na_utils.get_valid_qos_policy_group_info(
volume) volume)
self.zapi_client.mark_qos_policy_group_for_deletion( is_adaptive = na_utils.is_qos_policy_group_spec_adaptive(
qos_policy_group_info) qos_policy_group_info)
self.zapi_client.mark_qos_policy_group_for_deletion(
qos_policy_group_info, is_adaptive)
except Exception: except Exception:
# Don't blow up here if something went wrong de-provisioning the # Don't blow up here if something went wrong de-provisioning the
# QoS policy for the volume. # QoS policy for the volume.

View File

@ -60,6 +60,23 @@ MAX_QOS_KEYS = frozenset([
'maxBPS', 'maxBPS',
'maxBPSperGiB', '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']) BACKEND_QOS_CONSUMERS = frozenset(['back-end', 'both'])
# Secret length cannot be less than 96 bits. http://tools.ietf.org/html/rfc3723 # 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_min_keys = [key.lower() for key in MIN_QOS_KEYS]
normalized_max_keys = [key.lower() for key in MAX_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 = [ unrecognized_keys = [
k for k in qos_spec.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: if unrecognized_keys:
msg = _('Unrecognized QOS keywords: "%s"') % 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.') msg = _('Only one maximum limit can be set in a QoS spec.')
raise exception.Invalid(msg) 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): def get_volume_type_from_volume(volume):
"""Provides volume type associated with volume.""" """Provides volume type associated with volume."""
@ -313,6 +339,42 @@ def map_qos_spec(qos_spec, volume):
return policy 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): def map_dict_to_lower(input_dict):
"""Return an equivalent to the input dictionary with lower-case keys.""" """Return an equivalent to the input dictionary with lower-case keys."""
lower_case_dict = {} 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): def get_valid_backend_qos_spec_from_volume_type(volume, volume_type):
"""Given a volume type, return the associated Cinder QoS spec.""" """Given a volume type, return the associated Cinder QoS spec."""
spec_key_values = get_backend_qos_spec_from_volume_type(volume_type) spec_dict = get_backend_qos_spec_from_volume_type(volume_type)
if spec_key_values is None: if spec_dict is None:
return None return None
validate_qos_spec(spec_key_values) validate_qos_spec(spec_dict)
return map_qos_spec(spec_key_values, volume) 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): 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. # Front end QoS specs are handled by libvirt and we ignore them here.
if consumer not in BACKEND_QOS_CONSUMERS: if consumer not in BACKEND_QOS_CONSUMERS:
return None return None
spec_key_values = qos_spec['specs'] return qos_spec['specs']
return spec_key_values
def check_for_invalid_qos_spec_combination(info, volume_type): 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.