Merge "Add variable QoS to NetApp cDOT drivers"
This commit is contained in:
commit
d7f46bbba0
|
@ -92,6 +92,12 @@ FAKE_RESULT_SUCCESS.add_attr('status', 'passed')
|
|||
|
||||
FAKE_HTTP_OPENER = urllib.request.build_opener()
|
||||
|
||||
NO_RECORDS_RESPONSE = etree.XML("""
|
||||
<results status="passed">
|
||||
<num-records>0</num-records>
|
||||
</results>
|
||||
""")
|
||||
|
||||
GET_OPERATIONAL_NETWORK_INTERFACE_ADDRESSES_RESPONSE = etree.XML("""
|
||||
<results status="passed">
|
||||
<num-records>2</num-records>
|
||||
|
@ -106,6 +112,23 @@ GET_OPERATIONAL_NETWORK_INTERFACE_ADDRESSES_RESPONSE = etree.XML("""
|
|||
</results>
|
||||
""" % {"address1": "1.2.3.4", "address2": "99.98.97.96"})
|
||||
|
||||
QOS_POLICY_GROUP_GET_ITER_RESPONSE = etree.XML("""
|
||||
<results status="passed">
|
||||
<attributes-list>
|
||||
<qos-policy-group-info>
|
||||
<max-throughput>30KB/S</max-throughput>
|
||||
<num-workloads>1</num-workloads>
|
||||
<pgid>53</pgid>
|
||||
<policy-group>fake_qos_policy_group_name</policy-group>
|
||||
<policy-group-class>user_defined</policy-group-class>
|
||||
<uuid>12496028-b641-11e5-abbd-123478563412</uuid>
|
||||
<vserver>cinder-iscsi</vserver>
|
||||
</qos-policy-group-info>
|
||||
</attributes-list>
|
||||
<num-records>1</num-records>
|
||||
</results>
|
||||
""")
|
||||
|
||||
VOLUME_LIST_INFO_RESPONSE = etree.XML("""
|
||||
<results status="passed">
|
||||
<volumes>
|
||||
|
|
|
@ -58,6 +58,20 @@ class NetAppCmodeClientTestCase(test.TestCase):
|
|||
def tearDown(self):
|
||||
super(NetAppCmodeClientTestCase, self).tearDown()
|
||||
|
||||
def test_has_records(self):
|
||||
|
||||
result = self.client._has_records(netapp_api.NaElement(
|
||||
fake_client.QOS_POLICY_GROUP_GET_ITER_RESPONSE))
|
||||
|
||||
self.assertTrue(result)
|
||||
|
||||
def test_has_records_not_found(self):
|
||||
|
||||
result = self.client._has_records(
|
||||
netapp_api.NaElement(fake_client.NO_RECORDS_RESPONSE))
|
||||
|
||||
self.assertFalse(result)
|
||||
|
||||
def test_get_iscsi_target_details_no_targets(self):
|
||||
response = netapp_api.NaElement(
|
||||
etree.XML("""<results status="passed">
|
||||
|
@ -525,14 +539,67 @@ class NetAppCmodeClientTestCase(test.TestCase):
|
|||
|
||||
self.assertEqual(0, self.connection.qos_policy_group_create.call_count)
|
||||
|
||||
def test_provision_qos_policy_group_with_qos_spec(self):
|
||||
def test_provision_qos_policy_group_with_qos_spec_create(self):
|
||||
|
||||
self.mock_object(self.client,
|
||||
'qos_policy_group_exists',
|
||||
mock.Mock(return_value=False))
|
||||
self.mock_object(self.client, 'qos_policy_group_create')
|
||||
self.mock_object(self.client, 'qos_policy_group_modify')
|
||||
|
||||
self.client.provision_qos_policy_group(fake.QOS_POLICY_GROUP_INFO)
|
||||
|
||||
self.client.qos_policy_group_create.assert_has_calls([
|
||||
mock.call(fake.QOS_POLICY_GROUP_NAME, fake.MAX_THROUGHPUT)])
|
||||
self.assertFalse(self.client.qos_policy_group_modify.called)
|
||||
|
||||
def test_provision_qos_policy_group_with_qos_spec_modify(self):
|
||||
|
||||
self.mock_object(self.client,
|
||||
'qos_policy_group_exists',
|
||||
mock.Mock(return_value=True))
|
||||
self.mock_object(self.client, 'qos_policy_group_create')
|
||||
self.mock_object(self.client, 'qos_policy_group_modify')
|
||||
|
||||
self.client.provision_qos_policy_group(fake.QOS_POLICY_GROUP_INFO)
|
||||
|
||||
self.assertFalse(self.client.qos_policy_group_create.called)
|
||||
self.client.qos_policy_group_modify.assert_has_calls([
|
||||
mock.call(fake.QOS_POLICY_GROUP_NAME, fake.MAX_THROUGHPUT)])
|
||||
|
||||
def test_qos_policy_group_exists(self):
|
||||
|
||||
self.mock_send_request.return_value = netapp_api.NaElement(
|
||||
fake_client.QOS_POLICY_GROUP_GET_ITER_RESPONSE)
|
||||
|
||||
result = self.client.qos_policy_group_exists(
|
||||
fake.QOS_POLICY_GROUP_NAME)
|
||||
|
||||
api_args = {
|
||||
'query': {
|
||||
'qos-policy-group-info': {
|
||||
'policy-group': fake.QOS_POLICY_GROUP_NAME,
|
||||
},
|
||||
},
|
||||
'desired-attributes': {
|
||||
'qos-policy-group-info': {
|
||||
'policy-group': None,
|
||||
},
|
||||
},
|
||||
}
|
||||
self.mock_send_request.assert_has_calls([
|
||||
mock.call('qos-policy-group-get-iter', api_args, False)])
|
||||
self.assertTrue(result)
|
||||
|
||||
def test_qos_policy_group_exists_not_found(self):
|
||||
|
||||
self.mock_send_request.return_value = netapp_api.NaElement(
|
||||
fake_client.NO_RECORDS_RESPONSE)
|
||||
|
||||
result = self.client.qos_policy_group_exists(
|
||||
fake.QOS_POLICY_GROUP_NAME)
|
||||
|
||||
self.assertFalse(result)
|
||||
|
||||
def test_qos_policy_group_create(self):
|
||||
|
||||
|
@ -548,6 +615,19 @@ class NetAppCmodeClientTestCase(test.TestCase):
|
|||
self.mock_send_request.assert_has_calls([
|
||||
mock.call('qos-policy-group-create', api_args, False)])
|
||||
|
||||
def test_qos_policy_group_modify(self):
|
||||
|
||||
api_args = {
|
||||
'policy-group': fake.QOS_POLICY_GROUP_NAME,
|
||||
'max-throughput': fake.MAX_THROUGHPUT,
|
||||
}
|
||||
|
||||
self.client.qos_policy_group_modify(
|
||||
fake.QOS_POLICY_GROUP_NAME, fake.MAX_THROUGHPUT)
|
||||
|
||||
self.mock_send_request.assert_has_calls([
|
||||
mock.call('qos-policy-group-modify', api_args, False)])
|
||||
|
||||
def test_qos_policy_group_delete(self):
|
||||
|
||||
api_args = {
|
||||
|
|
|
@ -206,8 +206,11 @@ CLONE_DESTINATION = {
|
|||
'id': CLONE_DESTINATION_ID,
|
||||
}
|
||||
|
||||
SNAPSHOT_NAME = 'fake_snapshot_name'
|
||||
SNAPSHOT_LUN_HANDLE = 'fake_snapshot_lun_handle'
|
||||
|
||||
SNAPSHOT = {
|
||||
'name': 'fake_snapshot_name',
|
||||
'name': SNAPSHOT_NAME,
|
||||
'volume_size': SIZE,
|
||||
'volume_id': 'fake_volume_id',
|
||||
}
|
||||
|
|
|
@ -612,3 +612,23 @@ class NetAppBlockStorage7modeLibraryTestCase(test.TestCase):
|
|||
pools = self.library._get_pool_stats()
|
||||
|
||||
self.assertListEqual([], pools)
|
||||
|
||||
def test_delete_volume(self):
|
||||
self.library.vol_refresh_voluntary = False
|
||||
mock_super_delete_volume = self.mock_object(
|
||||
block_base.NetAppBlockStorageLibrary, 'delete_volume')
|
||||
|
||||
self.library.delete_volume(fake.VOLUME)
|
||||
|
||||
mock_super_delete_volume.assert_called_once_with(fake.VOLUME)
|
||||
self.assertTrue(self.library.vol_refresh_voluntary)
|
||||
|
||||
def test_delete_snapshot(self):
|
||||
self.library.vol_refresh_voluntary = False
|
||||
mock_super_delete_snapshot = self.mock_object(
|
||||
block_base.NetAppBlockStorageLibrary, 'delete_snapshot')
|
||||
|
||||
self.library.delete_snapshot(fake.SNAPSHOT)
|
||||
|
||||
mock_super_delete_snapshot.assert_called_once_with(fake.SNAPSHOT)
|
||||
self.assertTrue(self.library.vol_refresh_voluntary)
|
||||
|
|
|
@ -25,6 +25,7 @@ import uuid
|
|||
|
||||
import mock
|
||||
from oslo_utils import units
|
||||
import six
|
||||
|
||||
from cinder import exception
|
||||
from cinder.i18n import _
|
||||
|
@ -694,24 +695,31 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
|
|||
['lun1'])
|
||||
|
||||
def test_delete_volume(self):
|
||||
mock_delete_lun = self.mock_object(self.library, '_delete_lun')
|
||||
|
||||
self.library.delete_volume(fake.VOLUME)
|
||||
|
||||
mock_delete_lun.assert_called_once_with(fake.LUN_NAME)
|
||||
|
||||
def test_delete_lun(self):
|
||||
mock_get_lun_attr = self.mock_object(self.library, '_get_lun_attr')
|
||||
mock_get_lun_attr.return_value = fake.LUN_METADATA
|
||||
self.library.zapi_client = mock.Mock()
|
||||
self.library.lun_table = fake.LUN_TABLE
|
||||
|
||||
self.library.delete_volume(fake.VOLUME)
|
||||
self.library._delete_lun(fake.LUN_NAME)
|
||||
|
||||
mock_get_lun_attr.assert_called_once_with(
|
||||
fake.LUN_NAME, 'metadata')
|
||||
self.library.zapi_client.destroy_lun.assert_called_once_with(fake.PATH)
|
||||
|
||||
def test_delete_volume_no_metadata(self):
|
||||
def test_delete_lun_no_metadata(self):
|
||||
self.mock_object(self.library, '_get_lun_attr', mock.Mock(
|
||||
return_value=None))
|
||||
self.library.zapi_client = mock.Mock()
|
||||
self.mock_object(self.library, 'zapi_client')
|
||||
|
||||
self.library.delete_volume(fake.VOLUME)
|
||||
self.library._delete_lun(fake.LUN_NAME)
|
||||
|
||||
self.library._get_lun_attr.assert_called_once_with(
|
||||
fake.LUN_NAME, 'metadata')
|
||||
|
@ -720,13 +728,20 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
|
|||
self.zapi_client.
|
||||
mark_qos_policy_group_for_deletion.call_count)
|
||||
|
||||
def test_delete_snapshot(self):
|
||||
mock_delete_lun = self.mock_object(self.library, '_delete_lun')
|
||||
|
||||
self.library.delete_snapshot(fake.SNAPSHOT)
|
||||
|
||||
mock_delete_lun.assert_called_once_with(fake.SNAPSHOT_NAME)
|
||||
|
||||
def test_clone_source_to_destination(self):
|
||||
self.mock_object(na_utils, 'get_volume_extra_specs', mock.Mock(
|
||||
return_value=fake.EXTRA_SPECS))
|
||||
self.mock_object(self.library, '_setup_qos_for_volume', mock.Mock(
|
||||
return_value=fake.QOS_POLICY_GROUP_INFO))
|
||||
self.mock_object(self.library, '_clone_lun')
|
||||
self.mock_object(self.library, 'extend_volume')
|
||||
self.mock_object(self.library, '_extend_volume')
|
||||
self.mock_object(self.library, 'delete_volume')
|
||||
self.mock_object(self.library, '_mark_qos_policy_group_for_deletion')
|
||||
self.library.lun_space_reservation = 'false'
|
||||
|
@ -742,9 +757,9 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
|
|||
fake.CLONE_SOURCE_NAME, fake.CLONE_DESTINATION_NAME,
|
||||
space_reserved='false',
|
||||
qos_policy_group_name=fake.QOS_POLICY_GROUP_NAME)
|
||||
self.library.extend_volume.assert_called_once_with(
|
||||
self.library._extend_volume.assert_called_once_with(
|
||||
fake.CLONE_DESTINATION, fake.CLONE_DESTINATION_SIZE,
|
||||
qos_policy_group_name=fake.QOS_POLICY_GROUP_NAME)
|
||||
fake.QOS_POLICY_GROUP_NAME)
|
||||
self.assertEqual(0, self.library.delete_volume.call_count)
|
||||
self.assertEqual(0, self.library.
|
||||
_mark_qos_policy_group_for_deletion.call_count)
|
||||
|
@ -755,7 +770,7 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
|
|||
self.mock_object(self.library, '_setup_qos_for_volume', mock.Mock(
|
||||
return_value=fake.QOS_POLICY_GROUP_INFO))
|
||||
self.mock_object(self.library, '_clone_lun')
|
||||
self.mock_object(self.library, 'extend_volume', mock.Mock(
|
||||
self.mock_object(self.library, '_extend_volume', mock.Mock(
|
||||
side_effect=Exception))
|
||||
self.mock_object(self.library, 'delete_volume')
|
||||
self.mock_object(self.library, '_mark_qos_policy_group_for_deletion')
|
||||
|
@ -773,9 +788,9 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
|
|||
fake.CLONE_SOURCE_NAME, fake.CLONE_DESTINATION_NAME,
|
||||
space_reserved='true',
|
||||
qos_policy_group_name=fake.QOS_POLICY_GROUP_NAME)
|
||||
self.library.extend_volume.assert_called_once_with(
|
||||
self.library._extend_volume.assert_called_once_with(
|
||||
fake.CLONE_DESTINATION, fake.CLONE_DESTINATION_SIZE,
|
||||
qos_policy_group_name=fake.QOS_POLICY_GROUP_NAME)
|
||||
fake.QOS_POLICY_GROUP_NAME)
|
||||
self.assertEqual(1, self.library.delete_volume.call_count)
|
||||
self.assertEqual(1, self.library.
|
||||
_mark_qos_policy_group_for_deletion.call_count)
|
||||
|
@ -819,3 +834,167 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
|
|||
|
||||
mock_do_clone.assert_has_calls([
|
||||
mock.call(source, fake.VOLUME)])
|
||||
|
||||
def test_extend_volume(self):
|
||||
|
||||
new_size = 100
|
||||
volume_copy = copy.copy(fake.VOLUME)
|
||||
volume_copy['size'] = new_size
|
||||
|
||||
mock_get_volume_extra_specs = self.mock_object(
|
||||
na_utils, 'get_volume_extra_specs',
|
||||
mock.Mock(return_value=fake.EXTRA_SPECS))
|
||||
mock_setup_qos_for_volume = self.mock_object(
|
||||
self.library, '_setup_qos_for_volume',
|
||||
mock.Mock(return_value=fake.QOS_POLICY_GROUP_INFO))
|
||||
mock_extend_volume = self.mock_object(self.library, '_extend_volume')
|
||||
|
||||
self.library.extend_volume(fake.VOLUME, new_size)
|
||||
|
||||
mock_get_volume_extra_specs.assert_called_once_with(fake.VOLUME)
|
||||
mock_setup_qos_for_volume.assert_called_once_with(volume_copy,
|
||||
fake.EXTRA_SPECS)
|
||||
mock_extend_volume.assert_called_once_with(fake.VOLUME,
|
||||
new_size,
|
||||
fake.QOS_POLICY_GROUP_NAME)
|
||||
|
||||
def test_extend_volume_api_error(self):
|
||||
|
||||
new_size = 100
|
||||
volume_copy = copy.copy(fake.VOLUME)
|
||||
volume_copy['size'] = new_size
|
||||
|
||||
mock_get_volume_extra_specs = self.mock_object(
|
||||
na_utils, 'get_volume_extra_specs',
|
||||
mock.Mock(return_value=fake.EXTRA_SPECS))
|
||||
mock_setup_qos_for_volume = self.mock_object(
|
||||
self.library, '_setup_qos_for_volume',
|
||||
mock.Mock(return_value=fake.QOS_POLICY_GROUP_INFO))
|
||||
mock_extend_volume = self.mock_object(
|
||||
self.library, '_extend_volume',
|
||||
mock.Mock(side_effect=netapp_api.NaApiError))
|
||||
|
||||
self.assertRaises(netapp_api.NaApiError,
|
||||
self.library.extend_volume,
|
||||
fake.VOLUME,
|
||||
new_size)
|
||||
|
||||
mock_get_volume_extra_specs.assert_called_once_with(fake.VOLUME)
|
||||
mock_setup_qos_for_volume.assert_has_calls([
|
||||
mock.call(volume_copy, fake.EXTRA_SPECS),
|
||||
mock.call(fake.VOLUME, fake.EXTRA_SPECS)])
|
||||
mock_extend_volume.assert_called_once_with(
|
||||
fake.VOLUME, new_size, fake.QOS_POLICY_GROUP_NAME)
|
||||
|
||||
def test__extend_volume_direct(self):
|
||||
|
||||
current_size = fake.LUN_SIZE
|
||||
current_size_bytes = current_size * units.Gi
|
||||
new_size = fake.LUN_SIZE * 2
|
||||
new_size_bytes = new_size * units.Gi
|
||||
max_size = fake.LUN_SIZE * 10
|
||||
max_size_bytes = max_size * units.Gi
|
||||
fake_volume = copy.copy(fake.VOLUME)
|
||||
fake_volume['size'] = new_size
|
||||
|
||||
fake_lun = block_base.NetAppLun(fake.LUN_HANDLE,
|
||||
fake.LUN_ID,
|
||||
current_size_bytes,
|
||||
fake.LUN_METADATA)
|
||||
mock_get_lun_from_table = self.mock_object(
|
||||
self.library, '_get_lun_from_table',
|
||||
mock.Mock(return_value=fake_lun))
|
||||
fake_lun_geometry = {'max_resize': six.text_type(max_size_bytes)}
|
||||
mock_get_lun_geometry = self.mock_object(
|
||||
self.library.zapi_client, 'get_lun_geometry',
|
||||
mock.Mock(return_value=fake_lun_geometry))
|
||||
mock_do_direct_resize = self.mock_object(self.library.zapi_client,
|
||||
'do_direct_resize')
|
||||
mock_do_sub_clone_resize = self.mock_object(self.library,
|
||||
'_do_sub_clone_resize')
|
||||
self.library.lun_table = {fake.VOLUME['name']: fake_lun}
|
||||
|
||||
self.library._extend_volume(fake.VOLUME, new_size, 'fake_qos_policy')
|
||||
|
||||
mock_get_lun_from_table.assert_called_once_with(fake.VOLUME['name'])
|
||||
mock_get_lun_geometry.assert_called_once_with(
|
||||
fake.LUN_METADATA['Path'])
|
||||
mock_do_direct_resize.assert_called_once_with(
|
||||
fake.LUN_METADATA['Path'], six.text_type(new_size_bytes))
|
||||
self.assertFalse(mock_do_sub_clone_resize.called)
|
||||
self.assertEqual(six.text_type(new_size_bytes),
|
||||
self.library.lun_table[fake.VOLUME['name']].size)
|
||||
|
||||
def test__extend_volume_clone(self):
|
||||
|
||||
current_size = fake.LUN_SIZE
|
||||
current_size_bytes = current_size * units.Gi
|
||||
new_size = fake.LUN_SIZE * 20
|
||||
new_size_bytes = new_size * units.Gi
|
||||
max_size = fake.LUN_SIZE * 10
|
||||
max_size_bytes = max_size * units.Gi
|
||||
fake_volume = copy.copy(fake.VOLUME)
|
||||
fake_volume['size'] = new_size
|
||||
|
||||
fake_lun = block_base.NetAppLun(fake.LUN_HANDLE,
|
||||
fake.LUN_ID,
|
||||
current_size_bytes,
|
||||
fake.LUN_METADATA)
|
||||
mock_get_lun_from_table = self.mock_object(
|
||||
self.library, '_get_lun_from_table',
|
||||
mock.Mock(return_value=fake_lun))
|
||||
fake_lun_geometry = {'max_resize': six.text_type(max_size_bytes)}
|
||||
mock_get_lun_geometry = self.mock_object(
|
||||
self.library.zapi_client, 'get_lun_geometry',
|
||||
mock.Mock(return_value=fake_lun_geometry))
|
||||
mock_do_direct_resize = self.mock_object(self.library.zapi_client,
|
||||
'do_direct_resize')
|
||||
mock_do_sub_clone_resize = self.mock_object(self.library,
|
||||
'_do_sub_clone_resize')
|
||||
self.library.lun_table = {fake.VOLUME['name']: fake_lun}
|
||||
|
||||
self.library._extend_volume(fake.VOLUME, new_size, 'fake_qos_policy')
|
||||
|
||||
mock_get_lun_from_table.assert_called_once_with(fake.VOLUME['name'])
|
||||
mock_get_lun_geometry.assert_called_once_with(
|
||||
fake.LUN_METADATA['Path'])
|
||||
self.assertFalse(mock_do_direct_resize.called)
|
||||
mock_do_sub_clone_resize.assert_called_once_with(
|
||||
fake.LUN_METADATA['Path'], six.text_type(new_size_bytes),
|
||||
qos_policy_group_name='fake_qos_policy')
|
||||
self.assertEqual(six.text_type(new_size_bytes),
|
||||
self.library.lun_table[fake.VOLUME['name']].size)
|
||||
|
||||
def test__extend_volume_no_change(self):
|
||||
|
||||
current_size = fake.LUN_SIZE
|
||||
current_size_bytes = current_size * units.Gi
|
||||
new_size = fake.LUN_SIZE
|
||||
max_size = fake.LUN_SIZE * 10
|
||||
max_size_bytes = max_size * units.Gi
|
||||
fake_volume = copy.copy(fake.VOLUME)
|
||||
fake_volume['size'] = new_size
|
||||
|
||||
fake_lun = block_base.NetAppLun(fake.LUN_HANDLE,
|
||||
fake.LUN_ID,
|
||||
current_size_bytes,
|
||||
fake.LUN_METADATA)
|
||||
mock_get_lun_from_table = self.mock_object(
|
||||
self.library, '_get_lun_from_table',
|
||||
mock.Mock(return_value=fake_lun))
|
||||
fake_lun_geometry = {'max_resize': six.text_type(max_size_bytes)}
|
||||
mock_get_lun_geometry = self.mock_object(
|
||||
self.library.zapi_client, 'get_lun_geometry',
|
||||
mock.Mock(return_value=fake_lun_geometry))
|
||||
mock_do_direct_resize = self.mock_object(self.library.zapi_client,
|
||||
'do_direct_resize')
|
||||
mock_do_sub_clone_resize = self.mock_object(self.library,
|
||||
'_do_sub_clone_resize')
|
||||
self.library.lun_table = {fake_volume['name']: fake_lun}
|
||||
|
||||
self.library._extend_volume(fake_volume, new_size, 'fake_qos_policy')
|
||||
|
||||
mock_get_lun_from_table.assert_called_once_with(fake_volume['name'])
|
||||
self.assertFalse(mock_get_lun_geometry.called)
|
||||
self.assertFalse(mock_do_direct_resize.called)
|
||||
self.assertFalse(mock_do_sub_clone_resize.called)
|
||||
|
|
|
@ -50,8 +50,13 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
|
|||
self.library.ssc_vols = None
|
||||
self.fake_lun = block_base.NetAppLun(fake.LUN_HANDLE, fake.LUN_NAME,
|
||||
fake.SIZE, None)
|
||||
self.fake_snapshot_lun = block_base.NetAppLun(
|
||||
fake.SNAPSHOT_LUN_HANDLE, fake.SNAPSHOT_NAME, fake.SIZE, None)
|
||||
self.mock_object(self.library, 'lun_table')
|
||||
self.library.lun_table = {fake.LUN_NAME: self.fake_lun}
|
||||
self.library.lun_table = {
|
||||
fake.LUN_NAME: self.fake_lun,
|
||||
fake.SNAPSHOT_NAME: self.fake_snapshot_lun,
|
||||
}
|
||||
self.mock_object(block_base.NetAppBlockStorageLibrary, 'delete_volume')
|
||||
|
||||
def tearDown(self):
|
||||
|
@ -439,6 +444,30 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
|
|||
.assert_called_once_with(None)
|
||||
self.assertEqual(1, self.library._update_stale_vols.call_count)
|
||||
|
||||
def test_delete_snapshot(self):
|
||||
self.mock_object(block_base.NetAppLun, 'get_metadata_property',
|
||||
mock.Mock(return_value=fake.NETAPP_VOLUME))
|
||||
mock_super_delete_snapshot = self.mock_object(
|
||||
block_base.NetAppBlockStorageLibrary, 'delete_snapshot')
|
||||
mock_update_stale_vols = self.mock_object(self.library,
|
||||
'_update_stale_vols')
|
||||
self.library.delete_snapshot(fake.SNAPSHOT)
|
||||
|
||||
mock_super_delete_snapshot.assert_called_once_with(fake.SNAPSHOT)
|
||||
self.assertTrue(mock_update_stale_vols.called)
|
||||
|
||||
def test_delete_snapshot_no_netapp_vol(self):
|
||||
self.mock_object(block_base.NetAppLun, 'get_metadata_property',
|
||||
mock.Mock(return_value=None))
|
||||
mock_super_delete_snapshot = self.mock_object(
|
||||
block_base.NetAppBlockStorageLibrary, 'delete_snapshot')
|
||||
mock_update_stale_vols = self.mock_object(self.library,
|
||||
'_update_stale_vols')
|
||||
self.library.delete_snapshot(fake.SNAPSHOT)
|
||||
|
||||
mock_super_delete_snapshot.assert_called_once_with(fake.SNAPSHOT)
|
||||
self.assertFalse(mock_update_stale_vols.called)
|
||||
|
||||
def test_setup_qos_for_volume(self):
|
||||
self.mock_object(na_utils, 'get_valid_qos_policy_group_info',
|
||||
mock.Mock(
|
||||
|
|
|
@ -29,6 +29,7 @@ from cinder import exception
|
|||
from cinder import test
|
||||
from cinder.tests.unit.volume.drivers.netapp.dataontap import fakes as fake
|
||||
from cinder import utils
|
||||
from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api
|
||||
from cinder.volume.drivers.netapp.dataontap import nfs_base
|
||||
from cinder.volume.drivers.netapp import utils as na_utils
|
||||
from cinder.volume.drivers import nfs
|
||||
|
@ -351,6 +352,90 @@ class NetAppNfsDriverTestCase(test.TestCase):
|
|||
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_extend_volume(self):
|
||||
|
||||
new_size = 100
|
||||
volume_copy = copy.copy(fake.VOLUME)
|
||||
volume_copy['size'] = new_size
|
||||
|
||||
path = '%s/%s' % (fake.NFS_SHARE, fake.NFS_VOLUME['name'])
|
||||
self.mock_object(self.driver,
|
||||
'local_path',
|
||||
mock.Mock(return_value=path))
|
||||
mock_resize_image_file = self.mock_object(self.driver,
|
||||
'_resize_image_file')
|
||||
mock_get_volume_extra_specs = self.mock_object(
|
||||
na_utils, 'get_volume_extra_specs',
|
||||
mock.Mock(return_value=fake.EXTRA_SPECS))
|
||||
mock_do_qos_for_volume = self.mock_object(self.driver,
|
||||
'_do_qos_for_volume')
|
||||
|
||||
self.driver.extend_volume(fake.VOLUME, new_size)
|
||||
|
||||
mock_resize_image_file.assert_called_once_with(path, new_size)
|
||||
mock_get_volume_extra_specs.assert_called_once_with(fake.VOLUME)
|
||||
mock_do_qos_for_volume.assert_called_once_with(volume_copy,
|
||||
fake.EXTRA_SPECS,
|
||||
cleanup=False)
|
||||
|
||||
def test_extend_volume_resize_error(self):
|
||||
|
||||
new_size = 100
|
||||
volume_copy = copy.copy(fake.VOLUME)
|
||||
volume_copy['size'] = new_size
|
||||
|
||||
path = '%s/%s' % (fake.NFS_SHARE, fake.NFS_VOLUME['name'])
|
||||
self.mock_object(self.driver,
|
||||
'local_path',
|
||||
mock.Mock(return_value=path))
|
||||
mock_resize_image_file = self.mock_object(
|
||||
self.driver, '_resize_image_file',
|
||||
mock.Mock(side_effect=netapp_api.NaApiError))
|
||||
mock_get_volume_extra_specs = self.mock_object(
|
||||
na_utils, 'get_volume_extra_specs',
|
||||
mock.Mock(return_value=fake.EXTRA_SPECS))
|
||||
mock_do_qos_for_volume = self.mock_object(self.driver,
|
||||
'_do_qos_for_volume')
|
||||
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.driver.extend_volume,
|
||||
fake.VOLUME,
|
||||
new_size)
|
||||
|
||||
mock_resize_image_file.assert_called_once_with(path, new_size)
|
||||
self.assertFalse(mock_get_volume_extra_specs.called)
|
||||
self.assertFalse(mock_do_qos_for_volume.called)
|
||||
|
||||
def test_extend_volume_qos_error(self):
|
||||
|
||||
new_size = 100
|
||||
volume_copy = copy.copy(fake.VOLUME)
|
||||
volume_copy['size'] = new_size
|
||||
|
||||
path = '%s/%s' % (fake.NFS_SHARE, fake.NFS_VOLUME['name'])
|
||||
self.mock_object(self.driver,
|
||||
'local_path',
|
||||
mock.Mock(return_value=path))
|
||||
mock_resize_image_file = self.mock_object(self.driver,
|
||||
'_resize_image_file')
|
||||
mock_get_volume_extra_specs = self.mock_object(
|
||||
na_utils, 'get_volume_extra_specs',
|
||||
mock.Mock(return_value=fake.EXTRA_SPECS))
|
||||
mock_do_qos_for_volume = self.mock_object(
|
||||
self.driver, '_do_qos_for_volume',
|
||||
mock.Mock(side_effect=netapp_api.NaApiError))
|
||||
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.driver.extend_volume,
|
||||
fake.VOLUME,
|
||||
new_size)
|
||||
|
||||
mock_resize_image_file.assert_called_once_with(path, new_size)
|
||||
mock_get_volume_extra_specs.assert_called_once_with(fake.VOLUME)
|
||||
mock_do_qos_for_volume.assert_called_once_with(volume_copy,
|
||||
fake.EXTRA_SPECS,
|
||||
cleanup=False)
|
||||
|
||||
def test_is_share_clone_compatible(self):
|
||||
self.assertRaises(NotImplementedError,
|
||||
self.driver._is_share_clone_compatible,
|
||||
|
|
|
@ -56,6 +56,16 @@ VOLUME = {
|
|||
'volume_type_id': VOLUME_TYPE_ID,
|
||||
}
|
||||
|
||||
SNAPSHOT_NAME = 'fake_snapshot_name'
|
||||
SNAPSHOT_ID = 'fake_snapshot_id'
|
||||
|
||||
SNAPSHOT = {
|
||||
'name': SNAPSHOT_NAME,
|
||||
'id': SNAPSHOT_ID,
|
||||
'volume_id': VOLUME_ID,
|
||||
'volume_name': VOLUME_NAME,
|
||||
'volume_size': 42,
|
||||
}
|
||||
|
||||
QOS_SPECS = {}
|
||||
|
||||
|
|
|
@ -238,6 +238,19 @@ class NetAppDriverUtilsTestCase(test.TestCase):
|
|||
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_map_qos_spec_maxiopspergib(self):
|
||||
qos_spec = {'maxIOPSperGiB': 1000}
|
||||
mock_get_name = self.mock_object(na_utils, 'get_qos_policy_group_name')
|
||||
mock_get_name.return_value = 'fake_qos_policy'
|
||||
expected = {
|
||||
'policy_name': 'fake_qos_policy',
|
||||
'max_throughput': '42000iops',
|
||||
}
|
||||
|
||||
result = na_utils.map_qos_spec(qos_spec, fake.VOLUME)
|
||||
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_map_qos_spec_maxbps(self):
|
||||
qos_spec = {'maxBPS': 1000000}
|
||||
mock_get_name = self.mock_object(na_utils, 'get_qos_policy_group_name')
|
||||
|
@ -251,6 +264,19 @@ class NetAppDriverUtilsTestCase(test.TestCase):
|
|||
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_map_qos_spec_maxbpspergib(self):
|
||||
qos_spec = {'maxBPSperGiB': 100000}
|
||||
mock_get_name = self.mock_object(na_utils, 'get_qos_policy_group_name')
|
||||
mock_get_name.return_value = 'fake_qos_policy'
|
||||
expected = {
|
||||
'policy_name': 'fake_qos_policy',
|
||||
'max_throughput': '4200000B/s',
|
||||
}
|
||||
|
||||
result = na_utils.map_qos_spec(qos_spec, fake.VOLUME)
|
||||
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_map_qos_spec_no_key_present(self):
|
||||
qos_spec = {}
|
||||
mock_get_name = self.mock_object(na_utils, 'get_qos_policy_group_name')
|
||||
|
|
|
@ -384,6 +384,11 @@ class NetAppBlockStorage7modeLibrary(block_base.NetAppBlockStorageLibrary):
|
|||
self.vol_refresh_voluntary = True
|
||||
LOG.debug('Deleted LUN with name %s', volume['name'])
|
||||
|
||||
def delete_snapshot(self, snapshot):
|
||||
"""Driver entry point for deleting a snapshot."""
|
||||
super(NetAppBlockStorage7modeLibrary, self).delete_snapshot(snapshot)
|
||||
self.vol_refresh_voluntary = True
|
||||
|
||||
def _is_lun_valid_on_storage(self, lun):
|
||||
"""Validate LUN specific to storage system."""
|
||||
if self.volume_list:
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
Volume driver library for NetApp 7/C-mode block storage systems.
|
||||
"""
|
||||
|
||||
import copy
|
||||
import math
|
||||
import sys
|
||||
import uuid
|
||||
|
@ -228,14 +229,18 @@ class NetAppBlockStorageLibrary(object):
|
|||
|
||||
def delete_volume(self, volume):
|
||||
"""Driver entry point for destroying existing volumes."""
|
||||
name = volume['name']
|
||||
metadata = self._get_lun_attr(name, 'metadata')
|
||||
if not metadata:
|
||||
self._delete_lun(volume['name'])
|
||||
|
||||
def _delete_lun(self, lun_name):
|
||||
"""Helper method to delete LUN backing a volume or snapshot."""
|
||||
|
||||
metadata = self._get_lun_attr(lun_name, 'metadata')
|
||||
if metadata:
|
||||
self.zapi_client.destroy_lun(metadata['Path'])
|
||||
self.lun_table.pop(lun_name)
|
||||
else:
|
||||
LOG.warning(_LW("No entry in LUN table for volume/snapshot"
|
||||
" %(name)s."), {'name': name})
|
||||
return
|
||||
self.zapi_client.destroy_lun(metadata['Path'])
|
||||
self.lun_table.pop(name)
|
||||
" %(name)s."), {'name': lun_name})
|
||||
|
||||
def ensure_export(self, context, volume):
|
||||
"""Driver entry point to get the export info for an existing volume."""
|
||||
|
@ -270,7 +275,7 @@ class NetAppBlockStorageLibrary(object):
|
|||
|
||||
def delete_snapshot(self, snapshot):
|
||||
"""Driver entry point for deleting a snapshot."""
|
||||
self.delete_volume(snapshot)
|
||||
self._delete_lun(snapshot['name'])
|
||||
LOG.debug("Snapshot %s deletion successful", snapshot['name'])
|
||||
|
||||
def create_volume_from_snapshot(self, volume, snapshot):
|
||||
|
@ -305,9 +310,9 @@ class NetAppBlockStorageLibrary(object):
|
|||
if destination_size != source_size:
|
||||
|
||||
try:
|
||||
self.extend_volume(
|
||||
destination_volume, destination_size,
|
||||
qos_policy_group_name=qos_policy_group_name)
|
||||
self._extend_volume(destination_volume,
|
||||
destination_size,
|
||||
qos_policy_group_name)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.error(
|
||||
|
@ -479,7 +484,29 @@ class NetAppBlockStorageLibrary(object):
|
|||
def _update_volume_stats(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def extend_volume(self, volume, new_size, qos_policy_group_name=None):
|
||||
def extend_volume(self, volume, new_size):
|
||||
"""Driver entry point to increase the size of a volume."""
|
||||
|
||||
extra_specs = na_utils.get_volume_extra_specs(volume)
|
||||
|
||||
# Create volume copy with new size for size-dependent QOS specs
|
||||
volume_copy = copy.copy(volume)
|
||||
volume_copy['size'] = new_size
|
||||
|
||||
qos_policy_group_info = self._setup_qos_for_volume(volume_copy,
|
||||
extra_specs)
|
||||
qos_policy_group_name = (
|
||||
na_utils.get_qos_policy_group_name_from_info(
|
||||
qos_policy_group_info))
|
||||
|
||||
try:
|
||||
self._extend_volume(volume, new_size, qos_policy_group_name)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
# If anything went wrong, revert QoS settings
|
||||
self._setup_qos_for_volume(volume, extra_specs)
|
||||
|
||||
def _extend_volume(self, volume, new_size, qos_policy_group_name):
|
||||
"""Extend an existing volume to the new size."""
|
||||
name = volume['name']
|
||||
lun = self._get_lun_from_table(name)
|
||||
|
|
|
@ -311,6 +311,17 @@ class NetAppBlockStorageCmodeLibrary(block_base.NetAppBlockStorageLibrary):
|
|||
msg = 'Deleted LUN with name %(name)s and QoS info %(qos)s'
|
||||
LOG.debug(msg, {'name': volume['name'], 'qos': qos_policy_group_info})
|
||||
|
||||
def delete_snapshot(self, snapshot):
|
||||
"""Driver entry point for deleting a snapshot."""
|
||||
lun = self.lun_table.get(snapshot['name'])
|
||||
netapp_vol = lun.get_metadata_property('Volume') if lun else None
|
||||
|
||||
super(NetAppBlockStorageCmodeLibrary, self).delete_snapshot(snapshot)
|
||||
|
||||
if netapp_vol:
|
||||
self._update_stale_vols(
|
||||
volume=ssc_cmode.NetAppVolume(netapp_vol, self.vserver))
|
||||
|
||||
def _check_volume_type_for_lun(self, volume, lun, existing_ref,
|
||||
extra_specs):
|
||||
"""Check if LUN satisfies volume type."""
|
||||
|
|
|
@ -61,6 +61,10 @@ class Client(client_base.Client):
|
|||
result = server.invoke_successfully(na_element, True)
|
||||
return result
|
||||
|
||||
def _has_records(self, api_result_element):
|
||||
num_records = api_result_element.get_child_content('num-records')
|
||||
return bool(num_records and '0' != num_records)
|
||||
|
||||
def set_vserver(self, vserver):
|
||||
self.connection.set_vserver(vserver)
|
||||
|
||||
|
@ -335,8 +339,31 @@ class Client(client_base.Client):
|
|||
|
||||
spec = qos_policy_group_info.get('spec')
|
||||
if spec is not None:
|
||||
self.qos_policy_group_create(spec['policy_name'],
|
||||
spec['max_throughput'])
|
||||
if not self.qos_policy_group_exists(spec['policy_name']):
|
||||
self.qos_policy_group_create(spec['policy_name'],
|
||||
spec['max_throughput'])
|
||||
else:
|
||||
self.qos_policy_group_modify(spec['policy_name'],
|
||||
spec['max_throughput'])
|
||||
|
||||
def qos_policy_group_exists(self, qos_policy_group_name):
|
||||
"""Checks if a QOS policy group exists."""
|
||||
api_args = {
|
||||
'query': {
|
||||
'qos-policy-group-info': {
|
||||
'policy-group': qos_policy_group_name,
|
||||
},
|
||||
},
|
||||
'desired-attributes': {
|
||||
'qos-policy-group-info': {
|
||||
'policy-group': None,
|
||||
},
|
||||
},
|
||||
}
|
||||
result = self.send_request('qos-policy-group-get-iter',
|
||||
api_args,
|
||||
False)
|
||||
return self._has_records(result)
|
||||
|
||||
def qos_policy_group_create(self, qos_policy_group_name, max_throughput):
|
||||
"""Creates a QOS policy group."""
|
||||
|
@ -347,11 +374,17 @@ class Client(client_base.Client):
|
|||
}
|
||||
return self.send_request('qos-policy-group-create', api_args, False)
|
||||
|
||||
def qos_policy_group_delete(self, qos_policy_group_name):
|
||||
"""Attempts to delete a QOS policy group."""
|
||||
def qos_policy_group_modify(self, qos_policy_group_name, max_throughput):
|
||||
"""Modifies a QOS policy group."""
|
||||
api_args = {
|
||||
'policy-group': qos_policy_group_name,
|
||||
'max-throughput': max_throughput,
|
||||
}
|
||||
return self.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}
|
||||
return self.send_request('qos-policy-group-delete', api_args, False)
|
||||
|
||||
def qos_policy_group_rename(self, qos_policy_group_name, new_name):
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
Volume driver for NetApp NFS storage.
|
||||
"""
|
||||
|
||||
import copy
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
|
@ -691,9 +692,33 @@ class NetAppNfsDriver(driver.ManageableVD,
|
|||
|
||||
def extend_volume(self, volume, new_size):
|
||||
"""Extend an existing volume to the new size."""
|
||||
|
||||
LOG.info(_LI('Extending volume %s.'), volume['name'])
|
||||
path = self.local_path(volume)
|
||||
self._resize_image_file(path, new_size)
|
||||
|
||||
try:
|
||||
path = self.local_path(volume)
|
||||
self._resize_image_file(path, new_size)
|
||||
except Exception as err:
|
||||
exception_msg = (_("Failed to extend volume "
|
||||
"%(name)s, Error msg: %(msg)s.") %
|
||||
{'name': volume['name'],
|
||||
'msg': six.text_type(err)})
|
||||
raise exception.VolumeBackendAPIException(data=exception_msg)
|
||||
|
||||
try:
|
||||
extra_specs = na_utils.get_volume_extra_specs(volume)
|
||||
|
||||
# Create volume copy with new size for size-dependent QOS specs
|
||||
volume_copy = copy.copy(volume)
|
||||
volume_copy['size'] = new_size
|
||||
|
||||
self._do_qos_for_volume(volume_copy, extra_specs, cleanup=False)
|
||||
except Exception as err:
|
||||
exception_msg = (_("Failed to set QoS for existing volume "
|
||||
"%(name)s, Error msg: %(msg)s.") %
|
||||
{'name': volume['name'],
|
||||
'msg': six.text_type(err)})
|
||||
raise exception.VolumeBackendAPIException(data=exception_msg)
|
||||
|
||||
def _is_share_clone_compatible(self, volume, share):
|
||||
"""Checks if share is compatible with volume to host its clone."""
|
||||
|
|
|
@ -50,7 +50,7 @@ DEPRECATED_SSC_SPECS = {'netapp_unmirrored': 'netapp_mirrored',
|
|||
'netapp_nodedup': 'netapp_dedup',
|
||||
'netapp_nocompression': 'netapp_compression',
|
||||
'netapp_thick_provisioned': 'netapp_thin_provisioned'}
|
||||
QOS_KEYS = frozenset(['maxIOPS', 'maxBPS'])
|
||||
QOS_KEYS = frozenset(['maxIOPS', 'maxIOPSperGiB', 'maxBPS', 'maxBPSperGiB'])
|
||||
BACKEND_QOS_CONSUMERS = frozenset(['back-end', 'both'])
|
||||
|
||||
|
||||
|
@ -199,14 +199,23 @@ def map_qos_spec(qos_spec, volume):
|
|||
"""Map Cinder QOS spec to limit/throughput-value as used in client API."""
|
||||
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),
|
||||
max_throughput=None)
|
||||
# IOPS and BPS specifications are exclusive of one another.
|
||||
|
||||
# QoS specs are exclusive of one another.
|
||||
if 'maxiops' in qos_spec:
|
||||
spec['max_throughput'] = '%siops' % qos_spec['maxiops']
|
||||
elif 'maxiopspergib' in qos_spec:
|
||||
spec['max_throughput'] = '%siops' % six.text_type(
|
||||
int(qos_spec['maxiopspergib']) * int(volume['size']))
|
||||
elif 'maxbps' in qos_spec:
|
||||
spec['max_throughput'] = '%sB/s' % qos_spec['maxbps']
|
||||
elif 'maxbpspergib' in qos_spec:
|
||||
spec['max_throughput'] = '%sB/s' % six.text_type(
|
||||
int(qos_spec['maxbpspergib']) * int(volume['size']))
|
||||
|
||||
return spec
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue