Merge "NetApp ONTAP: Fix extend volume for iSCSI/FCP"
This commit is contained in:
commit
926fe464b0
@ -18,6 +18,7 @@ import time
|
|||||||
from unittest import mock
|
from unittest import mock
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
import ddt
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
import six
|
import six
|
||||||
|
|
||||||
@ -38,6 +39,7 @@ CONNECTION_INFO = {'hostname': 'hostname',
|
|||||||
'api_trace_pattern': 'fake_regex'}
|
'api_trace_pattern': 'fake_regex'}
|
||||||
|
|
||||||
|
|
||||||
|
@ddt.ddt
|
||||||
class NetAppBaseClientTestCase(test.TestCase):
|
class NetAppBaseClientTestCase(test.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@ -87,8 +89,27 @@ class NetAppBaseClientTestCase(test.TestCase):
|
|||||||
self.assertIsNone(self.client.check_is_naelement(element))
|
self.assertIsNone(self.client.check_is_naelement(element))
|
||||||
self.assertRaises(ValueError, self.client.check_is_naelement, None)
|
self.assertRaises(ValueError, self.client.check_is_naelement, None)
|
||||||
|
|
||||||
def test_create_lun(self):
|
@ddt.data({'ontap_version': '9.4', 'space_reservation': 'true'},
|
||||||
|
{'ontap_version': '9.4', 'space_reservation': 'false'},
|
||||||
|
{'ontap_version': '9.6', 'space_reservation': 'true'},
|
||||||
|
{'ontap_version': '9.6', 'space_reservation': 'false'})
|
||||||
|
@ddt.unpack
|
||||||
|
def test_create_lun(self, ontap_version, space_reservation):
|
||||||
expected_path = '/vol/%s/%s' % (self.fake_volume, self.fake_lun)
|
expected_path = '/vol/%s/%s' % (self.fake_volume, self.fake_lun)
|
||||||
|
self.fake_metadata['SpaceReserved'] = space_reservation
|
||||||
|
expected_space_reservation = space_reservation
|
||||||
|
self.mock_object(self.client, 'get_ontap_version',
|
||||||
|
return_value=ontap_version)
|
||||||
|
mock_resize_lun = self.mock_object(
|
||||||
|
client_base.Client, 'do_direct_resize')
|
||||||
|
mock_set_space_reservation = self.mock_object(
|
||||||
|
client_base.Client, 'set_lun_space_reservation')
|
||||||
|
initial_size = self.fake_size
|
||||||
|
|
||||||
|
if ontap_version < '9.5':
|
||||||
|
initial_size = fake.MAX_SIZE_FOR_A_LUN
|
||||||
|
expected_space_reservation = 'false'
|
||||||
|
|
||||||
with mock.patch.object(netapp_api.NaElement,
|
with mock.patch.object(netapp_api.NaElement,
|
||||||
'create_node_with_children',
|
'create_node_with_children',
|
||||||
) as mock_create_node:
|
) as mock_create_node:
|
||||||
@ -97,19 +118,48 @@ class NetAppBaseClientTestCase(test.TestCase):
|
|||||||
self.fake_size,
|
self.fake_size,
|
||||||
self.fake_metadata)
|
self.fake_metadata)
|
||||||
|
|
||||||
mock_create_node.assert_called_once_with(
|
mock_create_node.assert_called_with(
|
||||||
'lun-create-by-size',
|
'lun-create-by-size',
|
||||||
**{'path': expected_path,
|
**{'path': expected_path,
|
||||||
'size': self.fake_size,
|
'size': initial_size,
|
||||||
'ostype': self.fake_metadata['OsType'],
|
'ostype': self.fake_metadata['OsType'],
|
||||||
'space-reservation-enabled':
|
'space-reservation-enabled':
|
||||||
self.fake_metadata['SpaceReserved']})
|
expected_space_reservation})
|
||||||
self.connection.invoke_successfully.assert_called_once_with(
|
self.connection.invoke_successfully.assert_called_with(
|
||||||
mock.ANY, True)
|
mock.ANY, True)
|
||||||
|
|
||||||
def test_create_lun_exact_size(self):
|
if ontap_version < '9.5':
|
||||||
|
mock_resize_lun.assert_called_once_with(
|
||||||
|
expected_path, self.fake_size)
|
||||||
|
|
||||||
|
if ontap_version < '9.5' and space_reservation == 'true':
|
||||||
|
mock_set_space_reservation.assert_called_once_with(
|
||||||
|
expected_path, True)
|
||||||
|
else:
|
||||||
|
mock_set_space_reservation.assert_not_called()
|
||||||
|
|
||||||
|
@ddt.data({'ontap_version': '9.4', 'space_reservation': 'true'},
|
||||||
|
{'ontap_version': '9.4', 'space_reservation': 'false'},
|
||||||
|
{'ontap_version': '9.6', 'space_reservation': 'true'},
|
||||||
|
{'ontap_version': '9.6', 'space_reservation': 'false'})
|
||||||
|
@ddt.unpack
|
||||||
|
def test_create_lun_exact_size(self, ontap_version, space_reservation):
|
||||||
expected_path = '/vol/%s/%s' % (self.fake_volume, self.fake_lun)
|
expected_path = '/vol/%s/%s' % (self.fake_volume, self.fake_lun)
|
||||||
self.connection.get_api_version.return_value = (1, 110)
|
self.connection.get_api_version.return_value = (1, 110)
|
||||||
|
self.fake_metadata['SpaceReserved'] = space_reservation
|
||||||
|
expected_space_reservation = self.fake_metadata['SpaceReserved']
|
||||||
|
self.mock_object(self.client, 'get_ontap_version',
|
||||||
|
return_value=ontap_version)
|
||||||
|
mock_resize_lun = self.mock_object(
|
||||||
|
client_base.Client, 'do_direct_resize')
|
||||||
|
mock_set_space_reservation = self.mock_object(
|
||||||
|
client_base.Client, 'set_lun_space_reservation')
|
||||||
|
initial_size = self.fake_size
|
||||||
|
|
||||||
|
if ontap_version < '9.5':
|
||||||
|
initial_size = fake.MAX_SIZE_FOR_A_LUN
|
||||||
|
expected_space_reservation = 'false'
|
||||||
|
|
||||||
with mock.patch.object(netapp_api.NaElement,
|
with mock.patch.object(netapp_api.NaElement,
|
||||||
'create_node_with_children',
|
'create_node_with_children',
|
||||||
) as mock_create_node:
|
) as mock_create_node:
|
||||||
@ -118,22 +168,52 @@ class NetAppBaseClientTestCase(test.TestCase):
|
|||||||
self.fake_size,
|
self.fake_size,
|
||||||
self.fake_metadata)
|
self.fake_metadata)
|
||||||
|
|
||||||
mock_create_node.assert_called_once_with(
|
mock_create_node.assert_called_with(
|
||||||
'lun-create-by-size',
|
'lun-create-by-size',
|
||||||
**{'path': expected_path,
|
**{'path': expected_path,
|
||||||
'size': self.fake_size,
|
'size': initial_size,
|
||||||
'ostype': self.fake_metadata['OsType'],
|
'ostype': self.fake_metadata['OsType'],
|
||||||
'use-exact-size': 'true',
|
'use-exact-size': 'true',
|
||||||
'space-reservation-enabled':
|
'space-reservation-enabled':
|
||||||
self.fake_metadata['SpaceReserved']})
|
expected_space_reservation})
|
||||||
self.connection.invoke_successfully.assert_called_once_with(
|
self.connection.invoke_successfully.assert_called_with(
|
||||||
mock.ANY, True)
|
mock.ANY, True)
|
||||||
|
|
||||||
def test_create_lun_with_qos_policy_group_name(self):
|
if ontap_version < '9.5':
|
||||||
|
mock_resize_lun.assert_called_once_with(
|
||||||
|
expected_path, self.fake_size)
|
||||||
|
|
||||||
|
if ontap_version < '9.5' and space_reservation == 'true':
|
||||||
|
mock_set_space_reservation.assert_called_once_with(
|
||||||
|
expected_path, True)
|
||||||
|
else:
|
||||||
|
mock_set_space_reservation.assert_not_called()
|
||||||
|
|
||||||
|
@ddt.data({'ontap_version': '9.4', 'space_reservation': 'true'},
|
||||||
|
{'ontap_version': '9.4', 'space_reservation': 'false'},
|
||||||
|
{'ontap_version': '9.6', 'space_reservation': 'true'},
|
||||||
|
{'ontap_version': '9.6', 'space_reservation': 'false'})
|
||||||
|
@ddt.unpack
|
||||||
|
def test_create_lun_with_qos_policy_group_name(
|
||||||
|
self, ontap_version, space_reservation):
|
||||||
expected_path = '/vol/%s/%s' % (self.fake_volume, self.fake_lun)
|
expected_path = '/vol/%s/%s' % (self.fake_volume, self.fake_lun)
|
||||||
expected_qos_group_name = 'qos_1'
|
expected_qos_group_name = 'qos_1'
|
||||||
mock_request = mock.Mock()
|
mock_request = mock.Mock()
|
||||||
|
|
||||||
|
self.fake_metadata['SpaceReserved'] = space_reservation
|
||||||
|
expected_space_reservation = self.fake_metadata['SpaceReserved']
|
||||||
|
self.mock_object(self.client, 'get_ontap_version',
|
||||||
|
return_value=ontap_version)
|
||||||
|
mock_resize_lun = self.mock_object(
|
||||||
|
client_base.Client, 'do_direct_resize')
|
||||||
|
mock_set_space_reservation = self.mock_object(
|
||||||
|
client_base.Client, 'set_lun_space_reservation')
|
||||||
|
initial_size = self.fake_size
|
||||||
|
|
||||||
|
if ontap_version < '9.5':
|
||||||
|
initial_size = fake.MAX_SIZE_FOR_A_LUN
|
||||||
|
expected_space_reservation = 'false'
|
||||||
|
|
||||||
with mock.patch.object(netapp_api.NaElement,
|
with mock.patch.object(netapp_api.NaElement,
|
||||||
'create_node_with_children',
|
'create_node_with_children',
|
||||||
return_value=mock_request
|
return_value=mock_request
|
||||||
@ -145,20 +225,65 @@ 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_create_node.assert_called_once_with(
|
mock_create_node.assert_called_with(
|
||||||
'lun-create-by-size',
|
'lun-create-by-size',
|
||||||
**{'path': expected_path, 'size': self.fake_size,
|
**{'path': expected_path, 'size': initial_size,
|
||||||
'ostype': self.fake_metadata['OsType'],
|
'ostype': self.fake_metadata['OsType'],
|
||||||
'space-reservation-enabled':
|
'space-reservation-enabled':
|
||||||
self.fake_metadata['SpaceReserved']})
|
expected_space_reservation})
|
||||||
mock_request.add_new_child.assert_called_once_with(
|
mock_request.add_new_child.assert_called_with(
|
||||||
'qos-policy-group', expected_qos_group_name)
|
'qos-policy-group', expected_qos_group_name)
|
||||||
|
self.connection.invoke_successfully.assert_called_with(
|
||||||
|
mock.ANY, True)
|
||||||
|
|
||||||
|
if ontap_version < '9.5':
|
||||||
|
mock_resize_lun.assert_called_once_with(
|
||||||
|
expected_path, self.fake_size)
|
||||||
|
|
||||||
|
if ontap_version < '9.5' and space_reservation == 'true':
|
||||||
|
mock_set_space_reservation.assert_called_once_with(
|
||||||
|
expected_path, True)
|
||||||
|
else:
|
||||||
|
mock_set_space_reservation.assert_not_called()
|
||||||
|
|
||||||
|
def test_get_ontap_version(self):
|
||||||
|
version_response = netapp_api.NaElement(
|
||||||
|
fake.SYSTEM_GET_VERSION_RESPONSE)
|
||||||
|
self.connection.invoke_successfully.return_value = version_response
|
||||||
|
|
||||||
|
result = self.client.get_ontap_version(cached=False)
|
||||||
|
|
||||||
|
self.assertEqual(('9.6'), result)
|
||||||
|
|
||||||
|
def test_get_ontap_version_cached(self):
|
||||||
|
self.connection.get_ontap_version.return_value = '9.6'
|
||||||
|
|
||||||
|
result = self.client.get_ontap_version()
|
||||||
|
|
||||||
|
self.connection.get_ontap_version.assert_called_once_with()
|
||||||
|
self.assertEqual(('9.6'), result)
|
||||||
|
|
||||||
|
def test_set_lun_space_reservation(self):
|
||||||
|
path = '/vol/%s/%s' % (self.fake_volume, self.fake_lun)
|
||||||
|
|
||||||
|
with mock.patch.object(netapp_api.NaElement,
|
||||||
|
'create_node_with_children',
|
||||||
|
) as mock_set_space_reservation:
|
||||||
|
self.client.set_lun_space_reservation(path, True)
|
||||||
|
|
||||||
|
mock_set_space_reservation.assert_called_once_with(
|
||||||
|
'lun-set-space-reservation-info',
|
||||||
|
**{'path': path,
|
||||||
|
'enable': 'True'})
|
||||||
self.connection.invoke_successfully.assert_called_once_with(
|
self.connection.invoke_successfully.assert_called_once_with(
|
||||||
mock.ANY, True)
|
mock.ANY, True)
|
||||||
|
|
||||||
def test_create_lun_raises_on_failure(self):
|
@ddt.data('9.4', '9.6')
|
||||||
|
def test_create_lun_raises_on_failure(self, ontap_version):
|
||||||
self.connection.invoke_successfully = mock.Mock(
|
self.connection.invoke_successfully = mock.Mock(
|
||||||
side_effect=netapp_api.NaApiError)
|
side_effect=netapp_api.NaApiError)
|
||||||
|
self.mock_object(self.client, 'get_ontap_version',
|
||||||
|
return_value=ontap_version)
|
||||||
|
|
||||||
self.assertRaises(netapp_api.NaApiError,
|
self.assertRaises(netapp_api.NaApiError,
|
||||||
self.client.create_lun,
|
self.client.create_lun,
|
||||||
|
@ -53,6 +53,8 @@ class NetAppCmodeClientTestCase(test.TestCase):
|
|||||||
super(NetAppCmodeClientTestCase, self).setUp()
|
super(NetAppCmodeClientTestCase, self).setUp()
|
||||||
|
|
||||||
self.mock_object(client_cmode.Client, '_init_ssh_client')
|
self.mock_object(client_cmode.Client, '_init_ssh_client')
|
||||||
|
self.mock_object(client_cmode.Client, 'get_ontap_version',
|
||||||
|
return_value='9.6')
|
||||||
with mock.patch.object(client_cmode.Client,
|
with mock.patch.object(client_cmode.Client,
|
||||||
'get_ontapi_version',
|
'get_ontapi_version',
|
||||||
return_value=(1, 20)):
|
return_value=(1, 20)):
|
||||||
|
@ -421,6 +421,21 @@ CG_VOLUME_SNAPSHOT = {
|
|||||||
'volume_id': CG_VOLUME_ID,
|
'volume_id': CG_VOLUME_ID,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SYSTEM_GET_VERSION_RESPONSE = etree.XML("""
|
||||||
|
<results status="passed">
|
||||||
|
<build-timestamp>1395426307</build-timestamp>
|
||||||
|
<is-clustered>true</is-clustered>
|
||||||
|
<version>NetApp Release 9.6P2: Fri Jul 19 06:06:59 UTC 2019</version>
|
||||||
|
<version-tuple>
|
||||||
|
<system-version-tuple>
|
||||||
|
<generation>9</generation>
|
||||||
|
<major>6</major>
|
||||||
|
<minor>0</minor>
|
||||||
|
</system-version-tuple>
|
||||||
|
</version-tuple>
|
||||||
|
</results>
|
||||||
|
""")
|
||||||
|
|
||||||
|
|
||||||
VG_VOLUME_NAME = 'fake_vg_volume'
|
VG_VOLUME_NAME = 'fake_vg_volume'
|
||||||
VG_GROUP_NAME = 'fake_volume_group'
|
VG_GROUP_NAME = 'fake_volume_group'
|
||||||
@ -435,6 +450,8 @@ VOLUME_GROUP_ID = 'fake_vg_id'
|
|||||||
VG_SNAPSHOT_ID = 'fake_vg_snapshot_id'
|
VG_SNAPSHOT_ID = 'fake_vg_snapshot_id'
|
||||||
VG_SNAPSHOT_NAME = 'snapshot-' + VG_SNAPSHOT_ID
|
VG_SNAPSHOT_NAME = 'snapshot-' + VG_SNAPSHOT_ID
|
||||||
VG_VOLUME_SNAPSHOT_ID = 'fake_vg_volume_snapshot_id'
|
VG_VOLUME_SNAPSHOT_ID = 'fake_vg_volume_snapshot_id'
|
||||||
|
MIN_SIZE_FOR_A_LUN = '4194304'
|
||||||
|
MAX_SIZE_FOR_A_LUN = '17555678822400'
|
||||||
|
|
||||||
VG_LUN_METADATA = {
|
VG_LUN_METADATA = {
|
||||||
'OsType': None,
|
'OsType': None,
|
||||||
|
@ -1203,7 +1203,8 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
|
|||||||
mock_extend_volume.assert_called_once_with(
|
mock_extend_volume.assert_called_once_with(
|
||||||
fake.VOLUME, new_size, fake.QOS_POLICY_GROUP_NAME)
|
fake.VOLUME, new_size, fake.QOS_POLICY_GROUP_NAME)
|
||||||
|
|
||||||
def test__extend_volume_direct(self):
|
@ddt.data('9.4', '9.6')
|
||||||
|
def test__extend_volume_direct(self, ontap_version):
|
||||||
|
|
||||||
current_size = fake.LUN_SIZE
|
current_size = fake.LUN_SIZE
|
||||||
current_size_bytes = current_size * units.Gi
|
current_size_bytes = current_size * units.Gi
|
||||||
@ -1212,6 +1213,9 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
|
|||||||
max_size = fake.LUN_SIZE * 10
|
max_size = fake.LUN_SIZE * 10
|
||||||
max_size_bytes = max_size * units.Gi
|
max_size_bytes = max_size * units.Gi
|
||||||
|
|
||||||
|
mock_get_ontap_version = self.mock_object(
|
||||||
|
self.library.zapi_client, 'get_ontap_version',
|
||||||
|
return_value=ontap_version)
|
||||||
fake_lun = block_base.NetAppLun(fake.LUN_HANDLE,
|
fake_lun = block_base.NetAppLun(fake.LUN_HANDLE,
|
||||||
fake.LUN_ID,
|
fake.LUN_ID,
|
||||||
current_size_bytes,
|
current_size_bytes,
|
||||||
@ -1230,16 +1234,23 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
|
|||||||
|
|
||||||
self.library._extend_volume(fake.VOLUME, new_size, 'fake_qos_policy')
|
self.library._extend_volume(fake.VOLUME, new_size, 'fake_qos_policy')
|
||||||
|
|
||||||
|
mock_get_ontap_version.assert_called_once_with(cached=True)
|
||||||
mock_get_lun_from_table.assert_called_once_with(fake.VOLUME['name'])
|
mock_get_lun_from_table.assert_called_once_with(fake.VOLUME['name'])
|
||||||
mock_get_lun_geometry.assert_called_once_with(
|
|
||||||
fake.LUN_METADATA['Path'])
|
if ontap_version < '9.5':
|
||||||
|
mock_get_lun_geometry.assert_called_once_with(
|
||||||
|
fake.LUN_METADATA['Path'])
|
||||||
|
else:
|
||||||
|
mock_get_lun_geometry.assert_not_called()
|
||||||
|
|
||||||
mock_do_direct_resize.assert_called_once_with(
|
mock_do_direct_resize.assert_called_once_with(
|
||||||
fake.LUN_METADATA['Path'], six.text_type(new_size_bytes))
|
fake.LUN_METADATA['Path'], six.text_type(new_size_bytes))
|
||||||
self.assertFalse(mock_do_sub_clone_resize.called)
|
self.assertFalse(mock_do_sub_clone_resize.called)
|
||||||
self.assertEqual(six.text_type(new_size_bytes),
|
self.assertEqual(six.text_type(new_size_bytes),
|
||||||
self.library.lun_table[fake.VOLUME['name']].size)
|
self.library.lun_table[fake.VOLUME['name']].size)
|
||||||
|
|
||||||
def test__extend_attached_volume_direct(self):
|
@ddt.data('9.4', '9.6')
|
||||||
|
def test__extend_attached_volume_direct(self, ontap_version):
|
||||||
|
|
||||||
current_size = fake.LUN_SIZE
|
current_size = fake.LUN_SIZE
|
||||||
current_size_bytes = current_size * units.Gi
|
current_size_bytes = current_size * units.Gi
|
||||||
@ -1251,6 +1262,9 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
|
|||||||
volume_copy['size'] = new_size
|
volume_copy['size'] = new_size
|
||||||
volume_copy['attach_status'] = fake.ATTACHED
|
volume_copy['attach_status'] = fake.ATTACHED
|
||||||
|
|
||||||
|
mock_get_ontap_version = self.mock_object(
|
||||||
|
self.library.zapi_client, 'get_ontap_version',
|
||||||
|
return_value=ontap_version)
|
||||||
fake_lun = block_base.NetAppLun(fake.LUN_HANDLE,
|
fake_lun = block_base.NetAppLun(fake.LUN_HANDLE,
|
||||||
fake.LUN_ID,
|
fake.LUN_ID,
|
||||||
current_size_bytes,
|
current_size_bytes,
|
||||||
@ -1265,20 +1279,27 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
|
|||||||
'do_direct_resize')
|
'do_direct_resize')
|
||||||
mock_do_sub_clone_resize = self.mock_object(self.library,
|
mock_do_sub_clone_resize = self.mock_object(self.library,
|
||||||
'_do_sub_clone_resize')
|
'_do_sub_clone_resize')
|
||||||
self.library.lun_table = {volume_copy['name']: fake_lun}
|
|
||||||
|
|
||||||
|
self.library.lun_table = {volume_copy['name']: fake_lun}
|
||||||
self.library._extend_volume(volume_copy, new_size, 'fake_qos_policy')
|
self.library._extend_volume(volume_copy, new_size, 'fake_qos_policy')
|
||||||
|
|
||||||
mock_get_lun_from_table.assert_called_once_with(volume_copy['name'])
|
mock_get_lun_from_table.assert_called_once_with(volume_copy['name'])
|
||||||
mock_get_lun_geometry.assert_called_once_with(
|
mock_get_ontap_version.assert_called_once_with(cached=True)
|
||||||
fake.LUN_METADATA['Path'])
|
|
||||||
|
if ontap_version < '9.5':
|
||||||
|
mock_get_lun_geometry.assert_called_once_with(
|
||||||
|
fake.LUN_METADATA['Path'])
|
||||||
|
else:
|
||||||
|
mock_get_lun_geometry.assert_not_called()
|
||||||
|
|
||||||
mock_do_direct_resize.assert_called_once_with(
|
mock_do_direct_resize.assert_called_once_with(
|
||||||
fake.LUN_METADATA['Path'], six.text_type(new_size_bytes))
|
fake.LUN_METADATA['Path'], six.text_type(new_size_bytes))
|
||||||
self.assertFalse(mock_do_sub_clone_resize.called)
|
self.assertFalse(mock_do_sub_clone_resize.called)
|
||||||
self.assertEqual(six.text_type(new_size_bytes),
|
self.assertEqual(six.text_type(new_size_bytes),
|
||||||
self.library.lun_table[volume_copy['name']].size)
|
self.library.lun_table[volume_copy['name']].size)
|
||||||
|
|
||||||
def test__extend_volume_clone(self):
|
@ddt.data('9.4', '9.6')
|
||||||
|
def test__extend_volume_clone(self, ontap_version):
|
||||||
|
|
||||||
current_size = fake.LUN_SIZE
|
current_size = fake.LUN_SIZE
|
||||||
current_size_bytes = current_size * units.Gi
|
current_size_bytes = current_size * units.Gi
|
||||||
@ -1287,6 +1308,9 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
|
|||||||
max_size = fake.LUN_SIZE * 10
|
max_size = fake.LUN_SIZE * 10
|
||||||
max_size_bytes = max_size * units.Gi
|
max_size_bytes = max_size * units.Gi
|
||||||
|
|
||||||
|
mock_get_ontap_version = self.mock_object(
|
||||||
|
self.library.zapi_client, 'get_ontap_version',
|
||||||
|
return_value=ontap_version)
|
||||||
fake_lun = block_base.NetAppLun(fake.LUN_HANDLE,
|
fake_lun = block_base.NetAppLun(fake.LUN_HANDLE,
|
||||||
fake.LUN_ID,
|
fake.LUN_ID,
|
||||||
current_size_bytes,
|
current_size_bytes,
|
||||||
@ -1305,29 +1329,43 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
|
|||||||
|
|
||||||
self.library._extend_volume(fake.VOLUME, new_size, 'fake_qos_policy')
|
self.library._extend_volume(fake.VOLUME, new_size, 'fake_qos_policy')
|
||||||
|
|
||||||
|
mock_get_ontap_version.assert_called_once_with(cached=True)
|
||||||
mock_get_lun_from_table.assert_called_once_with(fake.VOLUME['name'])
|
mock_get_lun_from_table.assert_called_once_with(fake.VOLUME['name'])
|
||||||
mock_get_lun_geometry.assert_called_once_with(
|
|
||||||
fake.LUN_METADATA['Path'])
|
if ontap_version < '9.5':
|
||||||
self.assertFalse(mock_do_direct_resize.called)
|
self.assertFalse(mock_do_direct_resize.called)
|
||||||
mock_do_sub_clone_resize.assert_called_once_with(
|
mock_get_lun_geometry.assert_called_once_with(
|
||||||
fake.LUN_METADATA['Path'], six.text_type(new_size_bytes),
|
fake.LUN_METADATA['Path'])
|
||||||
qos_policy_group_name='fake_qos_policy')
|
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')
|
||||||
|
else:
|
||||||
|
mock_get_lun_geometry.assert_not_called()
|
||||||
|
mock_do_sub_clone_resize.assert_not_called()
|
||||||
|
mock_do_direct_resize.assert_called_once_with(
|
||||||
|
fake.LUN_METADATA['Path'], six.text_type(new_size_bytes))
|
||||||
|
|
||||||
self.assertEqual(six.text_type(new_size_bytes),
|
self.assertEqual(six.text_type(new_size_bytes),
|
||||||
self.library.lun_table[fake.VOLUME['name']].size)
|
self.library.lun_table[fake.VOLUME['name']].size)
|
||||||
|
|
||||||
def test__extend_attached_volume_clone_error(self):
|
@ddt.data('9.4', '9.6')
|
||||||
|
def test__extend_attached_volume_clone_error(self, ontap_version):
|
||||||
|
|
||||||
current_size = fake.LUN_SIZE
|
current_size = fake.LUN_SIZE
|
||||||
current_size_bytes = current_size * units.Gi
|
current_size_bytes = current_size * units.Gi
|
||||||
new_size = fake.LUN_SIZE * 20
|
new_size = fake.LUN_SIZE * 20
|
||||||
|
new_size_bytes = new_size * units.Gi
|
||||||
max_size = fake.LUN_SIZE * 10
|
max_size = fake.LUN_SIZE * 10
|
||||||
max_size_bytes = max_size * units.Gi
|
max_size_bytes = max_size * units.Gi
|
||||||
volume_copy = copy.copy(fake.VOLUME)
|
volume_copy = copy.copy(fake.VOLUME)
|
||||||
volume_copy['attach_status'] = fake.ATTACHED
|
volume_copy['attach_status'] = fake.ATTACHED
|
||||||
|
|
||||||
|
mock_get_ontap_version = self.mock_object(
|
||||||
|
self.library.zapi_client, 'get_ontap_version',
|
||||||
|
return_value=ontap_version)
|
||||||
fake_lun = block_base.NetAppLun(fake.LUN_HANDLE,
|
fake_lun = block_base.NetAppLun(fake.LUN_HANDLE,
|
||||||
fake.LUN_ID,
|
fake.LUN_ID,
|
||||||
current_size_bytes,
|
six.text_type(current_size_bytes),
|
||||||
fake.LUN_METADATA)
|
fake.LUN_METADATA)
|
||||||
mock_get_lun_from_table = self.mock_object(
|
mock_get_lun_from_table = self.mock_object(
|
||||||
self.library, '_get_lun_from_table', return_value=fake_lun)
|
self.library, '_get_lun_from_table', return_value=fake_lun)
|
||||||
@ -1341,21 +1379,35 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
|
|||||||
'_do_sub_clone_resize')
|
'_do_sub_clone_resize')
|
||||||
self.library.lun_table = {volume_copy['name']: fake_lun}
|
self.library.lun_table = {volume_copy['name']: fake_lun}
|
||||||
|
|
||||||
self.assertRaises(exception.VolumeBackendAPIException,
|
# (throne82) This error occurs only with versions older than 9.5
|
||||||
self.library._extend_volume,
|
if ontap_version < '9.5':
|
||||||
volume_copy,
|
self.assertRaises(exception.VolumeBackendAPIException,
|
||||||
new_size,
|
self.library._extend_volume,
|
||||||
fake.QOS_POLICY_GROUP_NAME)
|
volume_copy,
|
||||||
|
new_size,
|
||||||
|
fake.QOS_POLICY_GROUP_NAME)
|
||||||
|
self.assertFalse(mock_do_direct_resize.called)
|
||||||
|
self.assertFalse(mock_do_sub_clone_resize.called)
|
||||||
|
mock_get_lun_geometry.assert_called_once_with(
|
||||||
|
fake.LUN_METADATA['Path'])
|
||||||
|
self.assertEqual(six.text_type(current_size_bytes),
|
||||||
|
self.library.lun_table[volume_copy['name']].size)
|
||||||
|
else:
|
||||||
|
self.library._extend_volume(volume_copy,
|
||||||
|
new_size, fake.QOS_POLICY_GROUP_NAME)
|
||||||
|
mock_do_direct_resize.assert_called_once_with(
|
||||||
|
fake.LUN_METADATA['Path'], six.text_type(new_size_bytes))
|
||||||
|
mock_do_sub_clone_resize.assert_not_called()
|
||||||
|
mock_get_lun_geometry.assert_not_called()
|
||||||
|
self.assertEqual(six.text_type(new_size_bytes),
|
||||||
|
self.library.lun_table[volume_copy['name']].size)
|
||||||
|
|
||||||
mock_get_lun_from_table.assert_called_once_with(volume_copy['name'])
|
mock_get_ontap_version.assert_called_once_with(cached=True)
|
||||||
mock_get_lun_geometry.assert_called_once_with(
|
mock_get_lun_from_table.assert_called_once_with(
|
||||||
fake.LUN_METADATA['Path'])
|
volume_copy['name'])
|
||||||
self.assertFalse(mock_do_direct_resize.called)
|
|
||||||
self.assertFalse(mock_do_sub_clone_resize.called)
|
|
||||||
self.assertEqual(current_size_bytes,
|
|
||||||
self.library.lun_table[volume_copy['name']].size)
|
|
||||||
|
|
||||||
def test__extend_volume_no_change(self):
|
@ddt.data('9.4', '9.6')
|
||||||
|
def test__extend_volume_no_change(self, ontap_version):
|
||||||
|
|
||||||
current_size = fake.LUN_SIZE
|
current_size = fake.LUN_SIZE
|
||||||
current_size_bytes = current_size * units.Gi
|
current_size_bytes = current_size * units.Gi
|
||||||
@ -1365,6 +1417,8 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
|
|||||||
volume_copy = copy.copy(fake.VOLUME)
|
volume_copy = copy.copy(fake.VOLUME)
|
||||||
volume_copy['size'] = new_size
|
volume_copy['size'] = new_size
|
||||||
|
|
||||||
|
mock_get_ontap_version = self.mock_object(
|
||||||
|
self.library.zapi_client, 'get_ontap_version')
|
||||||
fake_lun = block_base.NetAppLun(fake.LUN_HANDLE,
|
fake_lun = block_base.NetAppLun(fake.LUN_HANDLE,
|
||||||
fake.LUN_ID,
|
fake.LUN_ID,
|
||||||
current_size_bytes,
|
current_size_bytes,
|
||||||
@ -1387,6 +1441,7 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
|
|||||||
self.assertFalse(mock_get_lun_geometry.called)
|
self.assertFalse(mock_get_lun_geometry.called)
|
||||||
self.assertFalse(mock_do_direct_resize.called)
|
self.assertFalse(mock_do_direct_resize.called)
|
||||||
self.assertFalse(mock_do_sub_clone_resize.called)
|
self.assertFalse(mock_do_sub_clone_resize.called)
|
||||||
|
self.assertFalse(mock_get_ontap_version.called)
|
||||||
|
|
||||||
def test_do_sub_clone_resize(self):
|
def test_do_sub_clone_resize(self):
|
||||||
|
|
||||||
|
@ -91,6 +91,8 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
|
|||||||
'check_api_permissions')
|
'check_api_permissions')
|
||||||
@mock.patch.object(na_utils, 'check_flags')
|
@mock.patch.object(na_utils, 'check_flags')
|
||||||
@mock.patch.object(block_base.NetAppBlockStorageLibrary, 'do_setup')
|
@mock.patch.object(block_base.NetAppBlockStorageLibrary, 'do_setup')
|
||||||
|
@mock.patch.object(client_base.Client, 'get_ontap_version',
|
||||||
|
mock.MagicMock(return_value='9.6'))
|
||||||
def test_do_setup(self, super_do_setup, mock_check_flags,
|
def test_do_setup(self, super_do_setup, mock_check_flags,
|
||||||
mock_check_api_permissions, mock_cluster_user_supported):
|
mock_check_api_permissions, mock_cluster_user_supported):
|
||||||
self.mock_object(client_base.Client, '_init_ssh_client')
|
self.mock_object(client_base.Client, '_init_ssh_client')
|
||||||
@ -418,7 +420,7 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
|
|||||||
'netapp_raid_type': 'raid_dp',
|
'netapp_raid_type': 'raid_dp',
|
||||||
'netapp_disk_type': 'SSD',
|
'netapp_disk_type': 'SSD',
|
||||||
'replication_enabled': False,
|
'replication_enabled': False,
|
||||||
'online_extend_support': False,
|
'online_extend_support': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
expected[0].update({'QoS_support': cluster_credentials})
|
expected[0].update({'QoS_support': cluster_credentials})
|
||||||
|
@ -592,18 +592,26 @@ class NetAppBlockStorageLibrary(object):
|
|||||||
new_size_bytes = six.text_type(int(new_size) * units.Gi)
|
new_size_bytes = six.text_type(int(new_size) * units.Gi)
|
||||||
# Reused by clone scenarios.
|
# Reused by clone scenarios.
|
||||||
# Hence comparing the stored size.
|
# Hence comparing the stored size.
|
||||||
if curr_size_bytes != new_size_bytes:
|
if curr_size_bytes == new_size_bytes:
|
||||||
|
LOG.info("No need to extend volume %s"
|
||||||
|
" as it is already the requested new size.", name)
|
||||||
|
return
|
||||||
|
|
||||||
|
ontap_version = self.zapi_client.get_ontap_version(cached=True)
|
||||||
|
|
||||||
|
if ontap_version >= '9.5':
|
||||||
|
self.zapi_client.do_direct_resize(path, new_size_bytes)
|
||||||
|
else:
|
||||||
lun_geometry = self.zapi_client.get_lun_geometry(path)
|
lun_geometry = self.zapi_client.get_lun_geometry(path)
|
||||||
if (lun_geometry and lun_geometry.get("max_resize")
|
if (lun_geometry and int(lun_geometry.get("max_resize", 0)) >=
|
||||||
and int(lun_geometry.get("max_resize")) >=
|
|
||||||
int(new_size_bytes)):
|
int(new_size_bytes)):
|
||||||
self.zapi_client.do_direct_resize(path, new_size_bytes)
|
self.zapi_client.do_direct_resize(path, new_size_bytes)
|
||||||
else:
|
else:
|
||||||
if volume['attach_status'] != 'detached':
|
if volume['attach_status'] != 'detached':
|
||||||
msg = _('Volume %(vol_id)s cannot be resized from '
|
msg = _('Volume %(vol_id)s cannot be resized from '
|
||||||
'%(old_size)s to %(new_size)s, because would '
|
'%(old_size)s to %(new_size)s, because would '
|
||||||
'exceed its max geometry %(max_geo)s while not '
|
'exceed its max geometry %(max_geo)s while '
|
||||||
'being detached.')
|
'not being detached.')
|
||||||
raise exception.VolumeBackendAPIException(data=msg % {
|
raise exception.VolumeBackendAPIException(data=msg % {
|
||||||
'vol_id': name,
|
'vol_id': name,
|
||||||
'old_size': curr_size_bytes,
|
'old_size': curr_size_bytes,
|
||||||
@ -612,10 +620,7 @@ class NetAppBlockStorageLibrary(object):
|
|||||||
self._do_sub_clone_resize(
|
self._do_sub_clone_resize(
|
||||||
path, new_size_bytes,
|
path, new_size_bytes,
|
||||||
qos_policy_group_name=qos_policy_group_name)
|
qos_policy_group_name=qos_policy_group_name)
|
||||||
self.lun_table[name].size = new_size_bytes
|
self.lun_table[name].size = new_size_bytes
|
||||||
else:
|
|
||||||
LOG.info("No need to extend volume %s"
|
|
||||||
" as it is already the requested new size.", name)
|
|
||||||
|
|
||||||
def _get_vol_option(self, volume_name, option_name):
|
def _get_vol_option(self, volume_name, option_name):
|
||||||
"""Get the value for the volume option."""
|
"""Get the value for the volume option."""
|
||||||
|
@ -296,7 +296,7 @@ class NetAppBlockStorageCmodeLibrary(block_base.NetAppBlockStorageLibrary,
|
|||||||
# Add driver capabilities and config info
|
# Add driver capabilities and config info
|
||||||
pool['QoS_support'] = self.using_cluster_credentials
|
pool['QoS_support'] = self.using_cluster_credentials
|
||||||
pool['multiattach'] = True
|
pool['multiattach'] = True
|
||||||
pool['online_extend_support'] = False
|
pool['online_extend_support'] = True
|
||||||
pool['consistencygroup_support'] = True
|
pool['consistencygroup_support'] = True
|
||||||
pool['consistent_group_snapshot_enabled'] = True
|
pool['consistent_group_snapshot_enabled'] = True
|
||||||
pool['reserved_percentage'] = self.reserved_percentage
|
pool['reserved_percentage'] = self.reserved_percentage
|
||||||
|
@ -133,6 +133,12 @@ class NaServer(object):
|
|||||||
self._ns = NaServer.NETAPP_NS
|
self._ns = NaServer.NETAPP_NS
|
||||||
self._refresh_conn = True
|
self._refresh_conn = True
|
||||||
|
|
||||||
|
def set_ontap_version(self, version):
|
||||||
|
self._ontap_version = version
|
||||||
|
|
||||||
|
def get_ontap_version(self):
|
||||||
|
return self._ontap_version
|
||||||
|
|
||||||
def set_api_version(self, major, minor):
|
def set_api_version(self, major, minor):
|
||||||
"""Set the API version."""
|
"""Set the API version."""
|
||||||
try:
|
try:
|
||||||
|
@ -30,6 +30,7 @@ from cinder.volume.drivers.netapp import utils as na_utils
|
|||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
DELETED_PREFIX = 'deleted_cinder_'
|
DELETED_PREFIX = 'deleted_cinder_'
|
||||||
|
MAX_SIZE_FOR_A_LUN = '17555678822400'
|
||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(utils.TraceWrapperMetaclass)
|
@six.add_metaclass(utils.TraceWrapperMetaclass)
|
||||||
@ -60,6 +61,27 @@ class Client(object):
|
|||||||
"""Set up the repository of available Data ONTAP features."""
|
"""Set up the repository of available Data ONTAP features."""
|
||||||
self.features = na_utils.Features()
|
self.features = na_utils.Features()
|
||||||
|
|
||||||
|
def get_ontap_version(self, cached=True):
|
||||||
|
"""Gets the ONTAP version."""
|
||||||
|
|
||||||
|
if cached:
|
||||||
|
return self.connection.get_ontap_version()
|
||||||
|
|
||||||
|
ontap_version = netapp_api.NaElement("system-get-version")
|
||||||
|
result = self.connection.invoke_successfully(ontap_version, True)
|
||||||
|
|
||||||
|
version_tuple = result.get_child_by_name(
|
||||||
|
'version-tuple') or netapp_api.NaElement('none')
|
||||||
|
system_version_tuple = version_tuple.get_child_by_name(
|
||||||
|
'system-version-tuple') or netapp_api.NaElement('none')
|
||||||
|
|
||||||
|
generation = system_version_tuple.get_child_content("generation")
|
||||||
|
major = system_version_tuple.get_child_content("major")
|
||||||
|
|
||||||
|
return '%(generation)s.%(major)s' % {
|
||||||
|
'generation': generation,
|
||||||
|
'major': major}
|
||||||
|
|
||||||
def get_ontapi_version(self, cached=True):
|
def get_ontapi_version(self, cached=True):
|
||||||
"""Gets the supported ontapi version."""
|
"""Gets the supported ontapi version."""
|
||||||
|
|
||||||
@ -87,9 +109,23 @@ class Client(object):
|
|||||||
"""Issues API request for creating LUN on volume."""
|
"""Issues API request for creating LUN on volume."""
|
||||||
|
|
||||||
path = '/vol/%s/%s' % (volume_name, lun_name)
|
path = '/vol/%s/%s' % (volume_name, lun_name)
|
||||||
params = {'path': path, 'size': six.text_type(size),
|
space_reservation = metadata['SpaceReserved']
|
||||||
|
initial_size = size
|
||||||
|
ontap_version = self.get_ontap_version()
|
||||||
|
|
||||||
|
# On older ONTAP versions the extend size is limited to its
|
||||||
|
# geometry on max_resize_size. In order to remove this
|
||||||
|
# limitation we create the LUN with its maximum possible size
|
||||||
|
# and then shrink to the requested size.
|
||||||
|
if ontap_version < '9.5':
|
||||||
|
initial_size = MAX_SIZE_FOR_A_LUN
|
||||||
|
# In order to create a LUN with its maximum size (16TB),
|
||||||
|
# the space_reservation needs to be disabled
|
||||||
|
space_reservation = 'false'
|
||||||
|
|
||||||
|
params = {'path': path, 'size': str(initial_size),
|
||||||
'ostype': metadata['OsType'],
|
'ostype': metadata['OsType'],
|
||||||
'space-reservation-enabled': metadata['SpaceReserved']}
|
'space-reservation-enabled': space_reservation}
|
||||||
version = self.get_ontapi_version()
|
version = self.get_ontapi_version()
|
||||||
if version >= (1, 110):
|
if version >= (1, 110):
|
||||||
params['use-exact-size'] = 'true'
|
params['use-exact-size'] = 'true'
|
||||||
@ -109,6 +145,21 @@ class Client(object):
|
|||||||
'volume_name': volume_name,
|
'volume_name': volume_name,
|
||||||
'ex': ex})
|
'ex': ex})
|
||||||
|
|
||||||
|
if ontap_version < '9.5':
|
||||||
|
self.do_direct_resize(path, six.text_type(size))
|
||||||
|
if metadata['SpaceReserved'] == 'true':
|
||||||
|
self.set_lun_space_reservation(path, True)
|
||||||
|
|
||||||
|
def set_lun_space_reservation(self, path, flag):
|
||||||
|
"""Sets the LUN space reservation on ONTAP."""
|
||||||
|
|
||||||
|
lun_modify_space_reservation = (
|
||||||
|
netapp_api.NaElement.create_node_with_children(
|
||||||
|
'lun-set-space-reservation-info', **{
|
||||||
|
'path': path,
|
||||||
|
'enable': str(flag)}))
|
||||||
|
self.connection.invoke_successfully(lun_modify_space_reservation, True)
|
||||||
|
|
||||||
def destroy_lun(self, path, force=True):
|
def destroy_lun(self, path, force=True):
|
||||||
"""Destroys the LUN at the path."""
|
"""Destroys the LUN at the path."""
|
||||||
lun_destroy = netapp_api.NaElement.create_node_with_children(
|
lun_destroy = netapp_api.NaElement.create_node_with_children(
|
||||||
|
@ -50,6 +50,8 @@ class Client(client_base.Client):
|
|||||||
(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()
|
self._init_features()
|
||||||
|
ontap_version = self.get_ontap_version(cached=False)
|
||||||
|
self.connection.set_ontap_version(ontap_version)
|
||||||
|
|
||||||
def _init_features(self):
|
def _init_features(self):
|
||||||
super(Client, self)._init_features()
|
super(Client, self)._init_features()
|
||||||
|
@ -295,7 +295,7 @@ driver.linbit_linstor=complete
|
|||||||
driver.lvm=complete
|
driver.lvm=complete
|
||||||
driver.macrosan=complete
|
driver.macrosan=complete
|
||||||
driver.nec=complete
|
driver.nec=complete
|
||||||
driver.netapp_ontap=missing
|
driver.netapp_ontap=complete
|
||||||
driver.netapp_solidfire=complete
|
driver.netapp_solidfire=complete
|
||||||
driver.nexenta=complete
|
driver.nexenta=complete
|
||||||
driver.nfs=complete
|
driver.nfs=complete
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
fixes:
|
||||||
|
- |
|
||||||
|
Fix bug `#1874134 <https://bugs.launchpad.net/cinder/+bug/1874134>`_,
|
||||||
|
allowing an iSCSI or FCP volume to be extended to a size up to 16TB
|
||||||
|
regardless of its original size, even if it's attached to an instance.
|
Loading…
x
Reference in New Issue
Block a user