[Pure Storage] Remove all REST version checks

Enforce a minimum FlashArray Purity version that will make all
REST version checking renudant as the enforced minimum version
supports all the features that are currently version-gated.

Remove any code that checks for different REST responses based on
REST response due to old REST code.

Enforced minimum Purity version matches the minimum supported
Purity version for FlashArray as of December 2021.

No change required to the purestorage pypi package used by this
driver.

Change-Id: I9fd03186b77ca442b62b1bfa28eed9026d91949b
This commit is contained in:
Simon Dodsley 2021-11-08 16:14:41 -05:00
parent f2620a1fe4
commit 8bb216dc16
3 changed files with 85 additions and 317 deletions

View File

@ -582,7 +582,6 @@ class PureDriverTestCase(test.TestCase):
self.async_array2.get.return_value = GET_ARRAY_SECONDARY self.async_array2.get.return_value = GET_ARRAY_SECONDARY
self.async_array2.replication_type = 'async' self.async_array2.replication_type = 'async'
self.purestorage_module = pure.purestorage self.purestorage_module = pure.purestorage
self.purestorage_module.VERSION = '1.4.0'
self.purestorage_module.PureHTTPError = FakePureStorageHTTPError self.purestorage_module.PureHTTPError = FakePureStorageHTTPError
def fake_get_array(self, *args, **kwargs): def fake_get_array(self, *args, **kwargs):
@ -627,15 +626,7 @@ class PureBaseSharedDriverTestCase(PureDriverTestCase):
self.driver._array = self.array self.driver._array = self.array
self.driver._replication_pod_name = 'cinder-pod' self.driver._replication_pod_name = 'cinder-pod'
self.driver._replication_pg_name = 'cinder-group' self.driver._replication_pg_name = 'cinder-group'
self.array._list_available_rest_versions.return_value = [
'1.0', '1.1', '1.2', '1.3', '1.4', '1.5', '1.6', '1.7', '1.8',
'1.9', '1.10', '1.11', '1.12', '1.13', '1.14', '1.15', '1.16',
'1.17', '1.18', '1.19']
self.purestorage_module.FlashArray.side_effect = None self.purestorage_module.FlashArray.side_effect = None
self.async_array2._list_available_rest_versions.return_value = [
'1.0', '1.1', '1.2', '1.3', '1.4', '1.5', '1.6', '1.7', '1.8',
'1.9', '1.10', '1.11', '1.12', '1.13', '1.14', '1.15', '1.16',
'1.17', '1.18', '1.19']
def new_fake_vol(self, set_provider_id=True, fake_context=None, def new_fake_vol(self, set_provider_id=True, fake_context=None,
spec=None, type_extra_specs=None, type_qos_specs_id=None, spec=None, type_extra_specs=None, type_qos_specs_id=None,
@ -808,10 +799,6 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase):
] ]
mock_target = mock.MagicMock() mock_target = mock.MagicMock()
mock_target.get.return_value = GET_ARRAY_PRIMARY mock_target.get.return_value = GET_ARRAY_PRIMARY
mock_target._list_available_rest_versions.return_value = [
'1.0', '1.1', '1.2', '1.3', '1.4', '1.5', '1.6', '1.7', '1.8',
'1.9', '1.10', '1.11', '1.12', '1.13', '1.14', '1.15', '1.16',
'1.17', '1.18', '1.19']
self.purestorage_module.FlashArray.return_value = mock_target self.purestorage_module.FlashArray.return_value = mock_target
self.driver.parse_replication_configs() self.driver.parse_replication_configs()
@ -848,10 +835,6 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase):
] ]
mock_target = mock.MagicMock() mock_target = mock.MagicMock()
mock_target.get.return_value = GET_ARRAY_PRIMARY mock_target.get.return_value = GET_ARRAY_PRIMARY
mock_target._list_available_rest_versions.return_value = [
'1.0', '1.1', '1.2', '1.3', '1.4', '1.5', '1.6', '1.7', '1.8',
'1.9', '1.10', '1.11', '1.12', '1.13', '1.14', '1.15', '1.16',
'1.17', '1.18', '1.19']
self.purestorage_module.FlashArray.return_value = mock_target self.purestorage_module.FlashArray.return_value = mock_target
self.driver.parse_replication_configs() self.driver.parse_replication_configs()
@ -912,15 +895,7 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase):
] ]
mock_sync_target = mock.MagicMock() mock_sync_target = mock.MagicMock()
mock_sync_target.get.return_value = GET_ARRAY_SECONDARY mock_sync_target.get.return_value = GET_ARRAY_SECONDARY
mock_sync_target._list_available_rest_versions.return_value = [
'1.0', '1.1', '1.2', '1.3', '1.4', '1.5', '1.6', '1.7', '1.8',
'1.9', '1.10', '1.11', '1.12', '1.13', '1.14', '1.15', '1.16',
'1.17', '1.18', '1.19']
self.array.get.return_value = GET_ARRAY_PRIMARY self.array.get.return_value = GET_ARRAY_PRIMARY
self.array._list_available_rest_versions.return_value = [
'1.0', '1.1', '1.2', '1.3', '1.4', '1.5', '1.6', '1.7', '1.8',
'1.9', '1.10', '1.11', '1.12', '1.13', '1.14', '1.15', '1.16',
'1.17', '1.18', '1.19']
self.purestorage_module.FlashArray.side_effect = [self.array, self.purestorage_module.FlashArray.side_effect = [self.array,
mock_sync_target] mock_sync_target]
self.driver.do_setup(None) self.driver.do_setup(None)
@ -1287,10 +1262,6 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase):
vol, vol_name = self.new_fake_vol(type_extra_specs=type_spec) vol, vol_name = self.new_fake_vol(type_extra_specs=type_spec)
self.array.list_volume_private_connections.return_value = [] self.array.list_volume_private_connections.return_value = []
# Set the array to be in a sync-rep enabled version # Set the array to be in a sync-rep enabled version
self.array._list_available_rest_versions.return_value = [
'1.0', '1.1', '1.2', '1.3', '1.4', '1.5', '1.6', '1.7', '1.8',
'1.9', '1.10', '1.11', '1.12', '1.13', '1.14', '1.15', '1.16',
'1.17', '1.18', '1.19']
self.driver.delete_volume(vol) self.driver.delete_volume(vol)
@ -1330,14 +1301,9 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase):
] ]
# Set the array to be in a sync-rep enabled version # Set the array to be in a sync-rep enabled version
self.array._list_available_rest_versions.return_value = [
'1.0', '1.1', '1.2', '1.3', '1.4', '1.5', '1.6', '1.7', '1.8',
'1.9', '1.10', '1.11', '1.12', '1.13', '1.14', '1.15', '1.16',
'1.17', '1.18', '1.19']
self.driver.delete_volume(vol) self.driver.delete_volume(vol)
expected = [ expected = [
mock.call._list_available_rest_versions(),
mock.call.list_volume_private_connections(vol_name, remote=True), mock.call.list_volume_private_connections(vol_name, remote=True),
mock.call.disconnect_host(host_name_a, vol_name), mock.call.disconnect_host(host_name_a, vol_name),
mock.call.list_host_connections(host_name_a, private=True), mock.call.list_host_connections(host_name_a, private=True),
@ -2203,14 +2169,6 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase):
self.driver.manage_existing_snapshot, self.driver.manage_existing_snapshot,
snap, snap_ref) snap, snap_ref)
def test_manage_existing_snapshot_bad_api_version(self):
self.array._list_available_rest_versions.return_value = ['1.0', '1.1',
'1.2']
snap, _ = self.new_fake_snap()
self.assertRaises(pure.PureDriverException,
self.driver.manage_existing_snapshot,
snap, {'name': PURE_SNAPSHOT['name']})
def test_manage_existing_snapshot_get_size(self): def test_manage_existing_snapshot_get_size(self):
ref_name = PURE_SNAPSHOT['name'] ref_name = PURE_SNAPSHOT['name']
snap_ref = {'name': ref_name} snap_ref = {'name': ref_name}
@ -2262,14 +2220,6 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase):
self.driver.manage_existing_snapshot_get_size, self.driver.manage_existing_snapshot_get_size,
snap, {'name': 'non-existing-volume.snap1'}) snap, {'name': 'non-existing-volume.snap1'})
def test_manage_existing_snapshot_get_size_bad_api_version(self):
snap, _ = self.new_fake_snap()
self.array._list_available_rest_versions.return_value = ['1.0', '1.1',
'1.2']
self.assertRaises(pure.PureDriverException,
self.driver.manage_existing_snapshot_get_size,
snap, {'name': PURE_SNAPSHOT['name']})
@ddt.data( @ddt.data(
# 96 chars, will exceed allowable length # 96 chars, will exceed allowable length
'volume-1e5177e7-95e5-4a0f-b170-e45f4b469f6a-cinder.' 'volume-1e5177e7-95e5-4a0f-b170-e45f4b469f6a-cinder.'
@ -2312,14 +2262,6 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase):
self.array.rename_volume.assert_called_with(snap_name, self.array.rename_volume.assert_called_with(snap_name,
unmanaged_snap_name) unmanaged_snap_name)
def test_unmanage_snapshot_bad_api_version(self):
snap, _ = self.new_fake_snap()
self.array._list_available_rest_versions.return_value = ['1.0', '1.1',
'1.2']
self.assertRaises(pure.PureDriverException,
self.driver.unmanage_snapshot,
snap)
def _test_get_manageable_things(self, def _test_get_manageable_things(self,
pure_objs=MANAGEABLE_PURE_VOLS, pure_objs=MANAGEABLE_PURE_VOLS,
expected_refs=MANAGEABLE_PURE_VOL_REFS, expected_refs=MANAGEABLE_PURE_VOL_REFS,
@ -2922,12 +2864,11 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase):
self.async_array2.backend_id = secondary_device_id self.async_array2.backend_id = secondary_device_id
self.driver._replication_target_arrays = [self.async_array2] self.driver._replication_target_arrays = [self.async_array2]
array2_v1_3 = mock.Mock() array2 = mock.Mock()
array2_v1_3.backend_id = secondary_device_id array2.backend_id = secondary_device_id
array2_v1_3.array_name = GET_ARRAY_SECONDARY['array_name'] array2.array_name = GET_ARRAY_SECONDARY['array_name']
array2_v1_3.array_id = GET_ARRAY_SECONDARY['id'] array2.array_id = GET_ARRAY_SECONDARY['id']
array2_v1_3.version = '1.3' mock_get_array.return_value = array2
mock_get_array.return_value = array2_v1_3
target_array = self.async_array2 target_array = self.async_array2
target_array.copy_volume = mock.Mock() target_array.copy_volume = mock.Mock()
@ -2937,7 +2878,7 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase):
REPLICATED_PGSNAPS[1] REPLICATED_PGSNAPS[1]
) )
array2_v1_3.get_volume.return_value = REPLICATED_VOLUME_SNAPS array2.get_volume.return_value = REPLICATED_VOLUME_SNAPS
context = mock.MagicMock() context = mock.MagicMock()
new_active_id, volume_updates, __ = self.driver.failover_host( new_active_id, volume_updates, __ = self.driver.failover_host(
@ -3070,17 +3011,16 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase):
REPLICATED_PGSNAPS[1] REPLICATED_PGSNAPS[1]
) )
array2_v1_3 = mock.Mock() array2 = mock.Mock()
array2_v1_3.array_name = GET_ARRAY_SECONDARY['array_name'] array2.array_name = GET_ARRAY_SECONDARY['array_name']
array2_v1_3.array_id = GET_ARRAY_SECONDARY['id'] array2.array_id = GET_ARRAY_SECONDARY['id']
array2_v1_3.version = '1.3' mock_get_array.return_value = array2
mock_get_array.return_value = array2_v1_3
array2_v1_3.get_volume.return_value = REPLICATED_VOLUME_SNAPS array2.get_volume.return_value = REPLICATED_VOLUME_SNAPS
self.assert_error_propagates( self.assert_error_propagates(
[mock_find_failover_target, [mock_find_failover_target,
mock_get_array, mock_get_array,
array2_v1_3.get_volume, array2.get_volume,
self.async_array2.copy_volume], self.async_array2.copy_volume],
self.driver.failover_host, self.driver.failover_host,
mock.Mock(), REPLICATED_VOLUME_OBJS, None mock.Mock(), REPLICATED_VOLUME_OBJS, None
@ -3129,43 +3069,7 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase):
verify_https=True, verify_https=True,
ssl_cert=cert_path, ssl_cert=cert_path,
user_agent=self.driver._user_agent, user_agent=self.driver._user_agent,
) request_kwargs=None,
def test_get_flasharray_with_request_kwargs_success(self):
san_ip = '1.2.3.4'
api_token = 'abcdef'
self.purestorage_module.FlashArray.return_value = mock.MagicMock()
self.purestorage_module.VERSION = "1.17.0"
self.driver._get_flasharray(san_ip,
api_token,
request_kwargs={"some": "arg"})
self.purestorage_module.FlashArray.assert_called_with(
san_ip,
api_token=api_token,
rest_version=None,
verify_https=None,
ssl_cert=None,
user_agent=self.driver._user_agent,
request_kwargs={"some": "arg"}
)
def test_get_flasharray_with_request_kwargs_version_too_old(self):
san_ip = '1.2.3.4'
api_token = 'abcdef'
self.purestorage_module.FlashArray.return_value = mock.MagicMock()
self.purestorage_module.VERSION = "1.10.0"
self.driver._get_flasharray(san_ip,
api_token,
request_kwargs={"some": "arg"})
self.purestorage_module.FlashArray.assert_called_with(
san_ip,
api_token=api_token,
rest_version=None,
verify_https=None,
ssl_cert=None,
user_agent=self.driver._user_agent
) )
def test_get_wwn(self): def test_get_wwn(self):
@ -3244,10 +3148,6 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase):
type_qos_specs_id=qos.id) type_qos_specs_id=qos.id)
mock_get_volume_type.return_value = vol.volume_type mock_get_volume_type.return_value = vol.volume_type
self.array._list_available_rest_versions.return_value = [
'1.0', '1.1', '1.2', '1.3', '1.4', '1.5', '1.6', '1.7', '1.8',
'1.9', '1.10', '1.11', '1.12', '1.13', '1.14', '1.15', '1.16',
'1.17', '1.18', '1.19']
mock_get_qos_specs.return_value = qos mock_get_qos_specs.return_value = qos
mock_get_repl_type.return_value = None mock_get_repl_type.return_value = None
@ -3277,10 +3177,6 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase):
type_qos_specs_id=qos.id) type_qos_specs_id=qos.id)
mock_get_volume_type.return_value = vol.volume_type mock_get_volume_type.return_value = vol.volume_type
self.array._list_available_rest_versions.return_value = [
'1.0', '1.1', '1.2', '1.3', '1.4', '1.5', '1.6', '1.7', '1.8',
'1.9', '1.10', '1.11', '1.12', '1.13', '1.14', '1.15', '1.16',
'1.17', '1.18', '1.19']
mock_get_qos_specs.return_value = qos mock_get_qos_specs.return_value = qos
mock_get_repl_type.return_value = None mock_get_repl_type.return_value = None
@ -3310,10 +3206,6 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase):
mock_get_volume_type.return_value = vol.volume_type mock_get_volume_type.return_value = vol.volume_type
mock_get_qos_specs.return_value = qos mock_get_qos_specs.return_value = qos
self.array.list_volume_private_connections.return_value = [] self.array.list_volume_private_connections.return_value = []
self.array._list_available_rest_versions.return_value = [
'1.0', '1.1', '1.2', '1.3', '1.4', '1.5', '1.6', '1.7', '1.8',
'1.9', '1.10', '1.11', '1.12', '1.13', '1.14', '1.15', '1.16',
'1.17', '1.18', '1.19']
self.driver.manage_existing(vol, volume_ref) self.driver.manage_existing(vol, volume_ref)
self.array.list_volume_private_connections.assert_called_with(ref_name) self.array.list_volume_private_connections.assert_called_with(ref_name)
@ -3330,10 +3222,6 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase):
new_type = fake_volume.fake_volume_type_obj(mock_context) new_type = fake_volume.fake_volume_type_obj(mock_context)
new_type.qos_specs_id = qos.id new_type.qos_specs_id = qos.id
self.array._list_available_rest_versions.return_value = [
'1.0', '1.1', '1.2', '1.3', '1.4', '1.5', '1.6', '1.7', '1.8',
'1.9', '1.10', '1.11', '1.12', '1.13', '1.14', '1.15', '1.16',
'1.17', '1.18', '1.19']
get_voltype = "cinder.objects.volume_type.VolumeType.get_by_name_or_id" get_voltype = "cinder.objects.volume_type.VolumeType.get_by_name_or_id"
with mock.patch(get_voltype) as mock_get_vol_type: with mock.patch(get_voltype) as mock_get_vol_type:
mock_get_vol_type.return_value = new_type mock_get_vol_type.return_value = new_type
@ -3357,10 +3245,6 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase):
vol, vol_name = self.new_fake_vol() vol, vol_name = self.new_fake_vol()
new_type = fake_volume.fake_volume_type_obj(mock_context) new_type = fake_volume.fake_volume_type_obj(mock_context)
self.array._list_available_rest_versions.return_value = [
'1.0', '1.1', '1.2', '1.3', '1.4', '1.5', '1.6', '1.7', '1.8',
'1.9', '1.10', '1.11', '1.12', '1.13', '1.14', '1.15', '1.16',
'1.17', '1.18', '1.19']
get_voltype = "cinder.objects.volume_type.VolumeType.get_by_name_or_id" get_voltype = "cinder.objects.volume_type.VolumeType.get_by_name_or_id"
with mock.patch(get_voltype) as mock_get_vol_type: with mock.patch(get_voltype) as mock_get_vol_type:
mock_get_vol_type.return_value = new_type mock_get_vol_type.return_value = new_type
@ -3739,20 +3623,6 @@ class PureISCSIDriverTestCase(PureBaseSharedDriverTestCase):
self.mock_config.use_chap_auth = False self.mock_config.use_chap_auth = False
self.mock_config.safe_get.return_value = 'oracle-vm-server' self.mock_config.safe_get.return_value = 'oracle-vm-server'
self.array._list_available_rest_versions.return_value = [
'1.0', '1.1', '1.2', '1.3', '1.4', '1.5', '1.6', '1.7', '1.8',
'1.9', '1.10', '1.11']
# Branch where we fail due to invalid version for setting personality
self.assertRaises(pure.PureDriverException, self.driver._connect,
self.array, vol_name, ISCSI_CONNECTOR, None, None)
self.assertFalse(self.array.create_host.called)
self.assertFalse(self.array.set_host.called)
self.array._list_available_rest_versions.return_value = [
'1.0', '1.1', '1.2', '1.3', '1.4', '1.5', '1.6', '1.7', '1.8',
'1.9', '1.10', '1.11', '1.12', '1.13', '1.14', '1.15', '1.16',
'1.17', '1.18', '1.19']
# Branch where personality is set # Branch where personality is set
self.driver._connect(self.array, vol_name, ISCSI_CONNECTOR, self.driver._connect(self.array, vol_name, ISCSI_CONNECTOR,
None, None) None, None)
@ -4009,20 +3879,6 @@ class PureFCDriverTestCase(PureBaseSharedDriverTestCase):
self.driver._connect, self.array, vol_name, FC_CONNECTOR) self.driver._connect, self.array, vol_name, FC_CONNECTOR)
self.mock_config.safe_get.return_value = 'oracle-vm-server' self.mock_config.safe_get.return_value = 'oracle-vm-server'
self.array._list_available_rest_versions.return_value = [
'1.0', '1.1', '1.2', '1.3', '1.4', '1.5', '1.6', '1.7', '1.8',
'1.9', '1.10', '1.11']
# Branch where we fail due to invalid version for setting personality
self.assertRaises(pure.PureDriverException, self.driver._connect,
self.array, vol_name, FC_CONNECTOR)
self.assertTrue(self.array.create_host.called)
self.assertFalse(self.array.set_host.called)
self.array._list_available_rest_versions.return_value = [
'1.0', '1.1', '1.2', '1.3', '1.4', '1.5', '1.6', '1.7', '1.8',
'1.9', '1.10', '1.11', '1.12', '1.13', '1.14', '1.15', '1.16',
'1.17', '1.18', '1.19']
# Branch where personality is set # Branch where personality is set
self.driver._connect(self.array, vol_name, FC_CONNECTOR) self.driver._connect(self.array, vol_name, FC_CONNECTOR)

View File

@ -137,11 +137,6 @@ EXTRA_SPECS_REPL_TYPE = "replication_type"
MAX_VOL_LENGTH = 63 MAX_VOL_LENGTH = 63
MAX_SNAP_LENGTH = 96 MAX_SNAP_LENGTH = 96
UNMANAGED_SUFFIX = '-unmanaged' UNMANAGED_SUFFIX = '-unmanaged'
QOS_REQUIRED_API_VERSION = '1.17'
SYNC_REPLICATION_REQUIRED_API_VERSION = '1.13'
ASYNC_REPLICATION_REQUIRED_API_VERSION = '1.3'
MANAGE_SNAP_REQUIRED_API_VERSION = '1.4'
PERSONALITY_REQUIRED_API_VERSION = '1.14'
REPL_SETTINGS_PROPAGATE_RETRY_INTERVAL = 5 # 5 seconds REPL_SETTINGS_PROPAGATE_RETRY_INTERVAL = 5 # 5 seconds
REPL_SETTINGS_PROPAGATE_MAX_RETRIES = 36 # 36 * 5 = 180 seconds REPL_SETTINGS_PROPAGATE_MAX_RETRIES = 36 # 36 * 5 = 180 seconds
@ -279,24 +274,6 @@ class PureBaseVolumeDriver(san.SanDriver):
ssl_cert_path=ssl_cert_path ssl_cert_path=ssl_cert_path
) )
api_versions = target_array._list_available_rest_versions()
if repl_type == REPLICATION_TYPE_ASYNC:
req_api_version = ASYNC_REPLICATION_REQUIRED_API_VERSION
elif repl_type == REPLICATION_TYPE_SYNC:
req_api_version = SYNC_REPLICATION_REQUIRED_API_VERSION
else:
msg = _('Invalid replication type specified:') % repl_type
raise PureDriverException(reason=msg)
if req_api_version not in api_versions:
msg = _('Unable to do replication with Purity REST '
'API version, requires minimum of '
'%(required_version)s.') % {
'required_version': req_api_version
}
raise PureDriverException(reason=msg)
target_array_info = target_array.get() target_array_info = target_array.get()
target_array.array_name = target_array_info["array_name"] target_array.array_name = target_array_info["array_name"]
target_array.array_id = target_array_info["id"] target_array.array_id = target_array_info["id"]
@ -378,6 +355,13 @@ class PureBaseVolumeDriver(san.SanDriver):
) )
array_info = self._array.get() array_info = self._array.get()
if version.LooseVersion(array_info["version"]) < version.LooseVersion(
'5.3.0'
):
msg = _("FlashArray Purity version less than 5.3.0 unsupported."
" Please upgrade your backend to a supported version.")
raise PureDriverException(msg)
self._array.array_name = array_info["array_name"] self._array.array_name = array_info["array_name"]
self._array.array_id = array_info["id"] self._array.array_id = array_info["id"]
self._array.replication_type = None self._array.replication_type = None
@ -484,11 +468,9 @@ class PureBaseVolumeDriver(san.SanDriver):
ctxt = context.get_admin_context() ctxt = context.get_admin_context()
type_id = volume.get('volume_type_id') type_id = volume.get('volume_type_id')
current_array = self._get_current_array() current_array = self._get_current_array()
rest_versions = current_array._list_available_rest_versions()
if type_id is not None: if type_id is not None:
volume_type = volume_types.get_volume_type(ctxt, type_id) volume_type = volume_types.get_volume_type(ctxt, type_id)
if QOS_REQUIRED_API_VERSION in rest_versions: qos = self._get_qos_settings(volume_type)
qos = self._get_qos_settings(volume_type)
if qos is not None: if qos is not None:
self.create_with_qos(current_array, vol_name, vol_size, qos) self.create_with_qos(current_array, vol_name, vol_size, qos)
else: else:
@ -509,11 +491,9 @@ class PureBaseVolumeDriver(san.SanDriver):
current_array = self._get_current_array() current_array = self._get_current_array()
ctxt = context.get_admin_context() ctxt = context.get_admin_context()
type_id = volume.get('volume_type_id') type_id = volume.get('volume_type_id')
rest_versions = current_array._list_available_rest_versions()
if type_id is not None: if type_id is not None:
volume_type = volume_types.get_volume_type(ctxt, type_id) volume_type = volume_types.get_volume_type(ctxt, type_id)
if QOS_REQUIRED_API_VERSION in rest_versions: qos = self._get_qos_settings(volume_type)
qos = self._get_qos_settings(volume_type)
current_array.copy_volume(snap_name, vol_name) current_array.copy_volume(snap_name, vol_name)
self._extend_if_needed(current_array, self._extend_if_needed(current_array,
@ -606,16 +586,11 @@ class PureBaseVolumeDriver(san.SanDriver):
"""Disconnect all hosts and delete the volume""" """Disconnect all hosts and delete the volume"""
vol_name = self._get_vol_name(volume) vol_name = self._get_vol_name(volume)
current_array = self._get_current_array() current_array = self._get_current_array()
rest_versions = current_array._list_available_rest_versions()
try: try:
# Do a pass over remaining connections on the current array, if # Do a pass over remaining connections on the current array, if
# we can try and remove any remote connections too. # we can try and remove any remote connections too.
if SYNC_REPLICATION_REQUIRED_API_VERSION in rest_versions: hosts = current_array.list_volume_private_connections(
hosts = current_array.list_volume_private_connections( vol_name, remote=True)
vol_name, remote=True)
else:
hosts = current_array.list_volume_private_connections(
vol_name)
for host_info in hosts: for host_info in hosts:
host_name = host_info["host"] host_name = host_info["host"]
self._disconnect_host(current_array, host_name, vol_name) self._disconnect_host(current_array, host_name, vol_name)
@ -710,18 +685,13 @@ class PureBaseVolumeDriver(san.SanDriver):
Returns True if it was the hosts last connection. Returns True if it was the hosts last connection.
""" """
vol_name = self._get_vol_name(volume) vol_name = self._get_vol_name(volume)
rest_versions = array._list_available_rest_versions()
if connector is None: if connector is None:
# If no connector was provided it is a force-detach, remove all # If no connector was provided it is a force-detach, remove all
# host connections for the volume # host connections for the volume
LOG.warning("Removing ALL host connections for volume %s", LOG.warning("Removing ALL host connections for volume %s",
vol_name) vol_name)
if SYNC_REPLICATION_REQUIRED_API_VERSION in rest_versions: connections = array.list_volume_private_connections(
# Remote connections are only allowed in newer API versions vol_name, remote=True)
connections = array.list_volume_private_connections(
vol_name, remote=True)
else:
connections = array.list_volume_private_connections(vol_name)
for connection in connections: for connection in connections:
self._disconnect_host(array, connection['host'], vol_name) self._disconnect_host(array, connection['host'], vol_name)
@ -1332,15 +1302,13 @@ class PureBaseVolumeDriver(san.SanDriver):
# Check if the volume_type has QoS settings and if so # Check if the volume_type has QoS settings and if so
# apply them to the newly managed volume # apply them to the newly managed volume
qos = None qos = None
rest_versions = current_array._list_available_rest_versions() qos = self._get_qos_settings(volume.volume_type)
if QOS_REQUIRED_API_VERSION in rest_versions: if qos is not None:
qos = self._get_qos_settings(volume.volume_type) self.set_qos(current_array, new_vol_name, qos)
if qos is not None: else:
self.set_qos(current_array, new_vol_name, qos) current_array.set_volume(new_vol_name,
else: iops_limit='',
current_array.set_volume(new_vol_name, bandwidth_limit='')
iops_limit='',
bandwidth_limit='')
volume.provider_id = new_vol_name volume.provider_id = new_vol_name
async_enabled = self._enable_async_replication_if_needed(current_array, async_enabled = self._enable_async_replication_if_needed(current_array,
volume) volume)
@ -1400,24 +1368,12 @@ class PureBaseVolumeDriver(san.SanDriver):
{"ref_name": vol_name, "new_name": unmanaged_vol_name}) {"ref_name": vol_name, "new_name": unmanaged_vol_name})
self._rename_volume_object(vol_name, unmanaged_vol_name) self._rename_volume_object(vol_name, unmanaged_vol_name)
def _verify_manage_snap_api_requirements(self):
current_array = self._get_current_array()
rest_versions = current_array._list_available_rest_versions()
if MANAGE_SNAP_REQUIRED_API_VERSION not in rest_versions:
msg = _('Unable to do manage snapshot operations with Purity REST '
'API version, requires '
'%(required_version)s.') % {
'required_version': MANAGE_SNAP_REQUIRED_API_VERSION
}
raise PureDriverException(reason=msg)
def manage_existing_snapshot(self, snapshot, existing_ref): def manage_existing_snapshot(self, snapshot, existing_ref):
"""Brings an existing backend storage object under Cinder management. """Brings an existing backend storage object under Cinder management.
We expect a snapshot name in the existing_ref that matches one in We expect a snapshot name in the existing_ref that matches one in
Purity. Purity.
""" """
self._verify_manage_snap_api_requirements()
self._validate_manage_existing_ref(existing_ref, is_snap=True) self._validate_manage_existing_ref(existing_ref, is_snap=True)
ref_snap_name = existing_ref['name'] ref_snap_name = existing_ref['name']
new_snap_name = self._get_snap_name(snapshot) new_snap_name = self._get_snap_name(snapshot)
@ -1435,7 +1391,6 @@ class PureBaseVolumeDriver(san.SanDriver):
We expect a snapshot name in the existing_ref that matches one in We expect a snapshot name in the existing_ref that matches one in
Purity. Purity.
""" """
self._verify_manage_snap_api_requirements()
snap_info = self._validate_manage_existing_ref(existing_ref, snap_info = self._validate_manage_existing_ref(existing_ref,
is_snap=True) is_snap=True)
size = self._round_bytes_to_gib(snap_info['size']) size = self._round_bytes_to_gib(snap_info['size'])
@ -1449,7 +1404,6 @@ class PureBaseVolumeDriver(san.SanDriver):
We expect a snapshot name in the existing_ref that matches one in We expect a snapshot name in the existing_ref that matches one in
Purity. Purity.
""" """
self._verify_manage_snap_api_requirements()
snap_name = self._get_snap_name(snapshot) snap_name = self._get_snap_name(snapshot)
if len(snap_name + UNMANAGED_SUFFIX) > MAX_SNAP_LENGTH: if len(snap_name + UNMANAGED_SUFFIX) > MAX_SNAP_LENGTH:
unmanaged_snap_name = snap_name[:-len(UNMANAGED_SUFFIX)] + \ unmanaged_snap_name = snap_name[:-len(UNMANAGED_SUFFIX)] + \
@ -1573,28 +1527,13 @@ class PureBaseVolumeDriver(san.SanDriver):
verify_https=None, ssl_cert_path=None, verify_https=None, ssl_cert_path=None,
request_kwargs=None): request_kwargs=None):
if (version.LooseVersion(purestorage.VERSION) < array = purestorage.FlashArray(san_ip,
version.LooseVersion('1.17.0')): api_token=api_token,
if request_kwargs is not None: rest_version=rest_version,
LOG.warning("Unable to specify request_kwargs='%s' on " verify_https=verify_https,
"purestorage.FlashArray using 'purestorage' " ssl_cert=ssl_cert_path,
"python module <1.17.0. Current version: %s", user_agent=self._user_agent,
request_kwargs, request_kwargs=request_kwargs)
purestorage.VERSION)
array = purestorage.FlashArray(san_ip,
api_token=api_token,
rest_version=rest_version,
verify_https=verify_https,
ssl_cert=ssl_cert_path,
user_agent=self._user_agent)
else:
array = purestorage.FlashArray(san_ip,
api_token=api_token,
rest_version=rest_version,
verify_https=verify_https,
ssl_cert=ssl_cert_path,
user_agent=self._user_agent,
request_kwargs=request_kwargs)
array_info = array.get() array_info = array.get()
array.array_name = array_info["array_name"] array.array_name = array_info["array_name"]
array.array_id = array_info["id"] array.array_id = array_info["id"]
@ -1928,16 +1867,14 @@ class PureBaseVolumeDriver(san.SanDriver):
# make sure the volume gets the correct new QoS settings. # make sure the volume gets the correct new QoS settings.
# This could mean removing existing QoS settings. # This could mean removing existing QoS settings.
current_array = self._get_current_array() current_array = self._get_current_array()
rest_versions = current_array._list_available_rest_versions() qos = self._get_qos_settings(new_type)
if QOS_REQUIRED_API_VERSION in rest_versions: vol_name = self._generate_purity_vol_name(volume)
qos = self._get_qos_settings(new_type) if qos is not None:
vol_name = self._generate_purity_vol_name(volume) self.set_qos(current_array, vol_name, qos)
if qos is not None: else:
self.set_qos(current_array, vol_name, qos) current_array.set_volume(vol_name,
else: iops_limit='',
current_array.set_volume(vol_name, bandwidth_limit='')
iops_limit='',
bandwidth_limit='')
return True, model_update return True, model_update
@ -2078,23 +2015,6 @@ class PureBaseVolumeDriver(san.SanDriver):
return secondary_array.backend_id, model_updates, [] return secondary_array.backend_id, model_updates, []
@pure_driver_debug_trace
def get_check_personality(self, array):
personality = self.configuration.safe_get('pure_host_personality')
rest_versions = array._list_available_rest_versions()
if personality:
if PERSONALITY_REQUIRED_API_VERSION not in rest_versions:
# Continuing here would mean creating a host not according
# to specificiations, possibly leading to unexpected
# behavior later on.
msg = _('Unable to set host personality with Purity REST '
'API version, requires '
'%(required_version)s.') % {
'required_version': PERSONALITY_REQUIRED_API_VERSION
}
raise PureDriverException(reason=msg)
return personality
@pure_driver_debug_trace @pure_driver_debug_trace
def set_personality(self, array, host_name, personality): def set_personality(self, array, host_name, personality):
try: try:
@ -2446,28 +2366,12 @@ class PureBaseVolumeDriver(san.SanDriver):
return secondary_array return secondary_array
def _async_failover_host(self, volumes, secondary_array, pg_snap): def _async_failover_host(self, volumes, secondary_array, pg_snap):
# NOTE(patrickeast): This currently requires a call with REST API 1.3. # Try to copy the flasharray as close as we can.
# If we need to, create a temporary FlashArray for this operation. target_array = self._get_flasharray(
api_version = secondary_array.get_rest_version() secondary_array._target,
LOG.debug("Current REST API for array id %(id)s is %(api_version)s", api_token=secondary_array._api_token,
{"id": secondary_array.array_id, "api_version": api_version}) request_kwargs=secondary_array._request_kwargs,
if api_version != '1.3': )
# Try to copy the flasharray as close as we can..
if hasattr(secondary_array, '_request_kwargs'):
target_array = self._get_flasharray(
secondary_array._target,
api_token=secondary_array._api_token,
rest_version='1.3',
request_kwargs=secondary_array._request_kwargs,
)
else:
target_array = self._get_flasharray(
secondary_array._target,
api_token=secondary_array._api_token,
rest_version='1.3',
)
else:
target_array = secondary_array
volume_snaps = target_array.get_volume(pg_snap['name'], volume_snaps = target_array.get_volume(pg_snap['name'],
snap=True, snap=True,
@ -2579,11 +2483,7 @@ class PureISCSIDriver(PureBaseVolumeDriver, san.SanISCSIDriver):
def _get_host(self, array, connector, remote=False): def _get_host(self, array, connector, remote=False):
"""Return dict describing existing Purity host object or None.""" """Return dict describing existing Purity host object or None."""
rest_versions = array._list_available_rest_versions() hosts = array.list_hosts(remote=remote)
if remote and SYNC_REPLICATION_REQUIRED_API_VERSION in rest_versions:
hosts = array.list_hosts(remote=True)
else:
hosts = array.list_hosts()
matching_hosts = [] matching_hosts = []
for host in hosts: for host in hosts:
if connector["initiator"] in host["iqn"]: if connector["initiator"] in host["iqn"]:
@ -2746,7 +2646,7 @@ class PureISCSIDriver(PureBaseVolumeDriver, san.SanISCSIDriver):
reason=_("Unable to re-use host with unknown CHAP " reason=_("Unable to re-use host with unknown CHAP "
"credentials configured.")) "credentials configured."))
else: else:
personality = self.get_check_personality(array) personality = self.configuration.safe_get('pure_host_personality')
host_name = self._generate_purity_host_name(connector["host"]) host_name = self._generate_purity_host_name(connector["host"])
LOG.info("Creating host object %(host_name)r with IQN:" LOG.info("Creating host object %(host_name)r with IQN:"
" %(iqn)s.", {"host_name": host_name, "iqn": iqn}) " %(iqn)s.", {"host_name": host_name, "iqn": iqn})
@ -2806,11 +2706,7 @@ class PureFCDriver(PureBaseVolumeDriver, driver.FibreChannelDriver):
def _get_host(self, array, connector, remote=False): def _get_host(self, array, connector, remote=False):
"""Return dict describing existing Purity host object or None.""" """Return dict describing existing Purity host object or None."""
rest_versions = array._list_available_rest_versions() hosts = array.list_hosts(remote=remote)
if remote and SYNC_REPLICATION_REQUIRED_API_VERSION in rest_versions:
hosts = array.list_hosts(remote=True)
else:
hosts = array.list_hosts()
matching_hosts = [] matching_hosts = []
for host in hosts: for host in hosts:
for wwn in connector["wwpns"]: for wwn in connector["wwpns"]:
@ -2821,18 +2717,18 @@ class PureFCDriver(PureBaseVolumeDriver, driver.FibreChannelDriver):
@staticmethod @staticmethod
def _get_array_wwns(array): def _get_array_wwns(array):
"""Return list of wwns from the array""" """Return list of wwns from the array
Ensure that only true scsi FC ports are selected
and not any that are enabled for NVMe-based FC with
an associated NQN.
"""
ports = array.list_ports() ports = array.list_ports()
try: valid_ports = [
valid_ports = [ port["wwn"]
port["wwn"] for port in ports
for port in ports if port["wwn"] and not port["nqn"]
if port["wwn"] and not port["nqn"] ]
]
except KeyError: # Older array code versions will not return nqn
valid_ports = [
port["wwn"] for port in ports if port["wwn"]
]
return valid_ports return valid_ports
@pure_driver_debug_trace @pure_driver_debug_trace
@ -2888,7 +2784,7 @@ class PureFCDriver(PureBaseVolumeDriver, driver.FibreChannelDriver):
LOG.info("Re-using existing purity host %(host_name)r", LOG.info("Re-using existing purity host %(host_name)r",
{"host_name": host_name}) {"host_name": host_name})
else: else:
personality = self.get_check_personality(array) personality = self.configuration.safe_get('pure_host_personality')
host_name = self._generate_purity_host_name(connector["host"]) host_name = self._generate_purity_host_name(connector["host"])
LOG.info("Creating host object %(host_name)r with WWN:" LOG.info("Creating host object %(host_name)r with WWN:"
" %(wwn)s.", {"host_name": host_name, "wwn": wwns}) " %(wwn)s.", {"host_name": host_name, "wwn": wwns})

View File

@ -0,0 +1,16 @@
---
upgrade:
- |
Pure Storage: Minimum supported FlashArray Purity//FA is changed to 5.3.0.
All FlashArray backends must be at at least this minimum version or the
driver will not initialize.
fixes:
- |
Pure Storage: Remove all API version checks in driver as the new minimum
FlashArray Purity//FA version supports all previously version-gated
features and functionality support.
other:
- |
Pure Storage: FlashArray minimum Purity//FA version is increased to 5.3.0.
All FlashArray backends must be at at least this minimum version or the
driver will not initialize.