diff --git a/cinder/tests/unit/volume/drivers/dell_emc/powermax/powermax_data.py b/cinder/tests/unit/volume/drivers/dell_emc/powermax/powermax_data.py index 62c889921d6..7eed1f702c3 100644 --- a/cinder/tests/unit/volume/drivers/dell_emc/powermax/powermax_data.py +++ b/cinder/tests/unit/volume/drivers/dell_emc/powermax/powermax_data.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017-2019 Dell Inc. or its subsidiaries. +# Copyright (c) 2020 Dell Inc. or its subsidiaries. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -78,7 +78,7 @@ class PowerMaxData(object): rdf_group_no_2 = '71' rdf_group_no_3 = '72' rdf_group_no_4 = '73' - u4v_version = '91' + u4v_version = '92' storagegroup_name_source = 'Grp_source_sg' storagegroup_name_target = 'Grp_target_sg' group_snapshot_name = 'Grp_snapshot' @@ -988,7 +988,7 @@ class PowerMaxData(object): {'symmetrixId': array_herc, 'model': 'PowerMax 2000', 'ucode': '5978.1091.1092'}] - version_details = {'version': 'V9.1.0.1054'} + version_details = {'version': 'V9.2.0.0'} headroom = {'headroom': [{'headroomCapacity': 20348.29}]} @@ -1274,7 +1274,7 @@ class PowerMaxData(object): data_dict = {volume_id: volume_info_dict} platform = 'Linux-4.4.0-104-generic-x86_64-with-Ubuntu-16.04-xenial' - unisphere_version = u'V9.1.0.14' + unisphere_version = u'V9.2.0.0' unisphere_version_90 = "V9.0.0.1" openstack_release = '12.0.0.0b3.dev401' openstack_version = '12.0.0' @@ -1523,3 +1523,11 @@ class PowerMaxData(object): 'Emulation': 'FBA', 'Configuration': 'TDEV', 'CompressionDisabled': False}}) + + vol_create_desc1 = 'Populating Storage Group(s) with volumes : [00001]' + vol_create_desc2 = ('Refresh [Storage Group [OS-SG] ' + 'on Symmetrix [000197800123]] ') + vol_create_task = [{'execution_order': 1, + 'description': vol_create_desc1}, + {'execution_order': 2, + 'description': vol_create_desc2}] diff --git a/cinder/tests/unit/volume/drivers/dell_emc/powermax/powermax_fake_objects.py b/cinder/tests/unit/volume/drivers/dell_emc/powermax/powermax_fake_objects.py index 4056dba2b1e..bcb8736248b 100644 --- a/cinder/tests/unit/volume/drivers/dell_emc/powermax/powermax_fake_objects.py +++ b/cinder/tests/unit/volume/drivers/dell_emc/powermax/powermax_fake_objects.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017-2019 Dell Inc. or its subsidiaries. +# Copyright (c) 2020 Dell Inc. or its subsidiaries. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may diff --git a/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_common.py b/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_common.py index 40286dd8c3e..bf76618d4b3 100644 --- a/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_common.py +++ b/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_common.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017-2019 Dell Inc. or its subsidiaries. +# Copyright (c) 2020 Dell Inc. or its subsidiaries. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may diff --git a/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_fc.py b/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_fc.py index b37be14105c..628c06b170a 100644 --- a/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_fc.py +++ b/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_fc.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017-2019 Dell Inc. or its subsidiaries. +# Copyright (c) 2020 Dell Inc. or its subsidiaries. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may diff --git a/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_iscsi.py b/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_iscsi.py index ff2a15eb6ff..c973f7e2e96 100644 --- a/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_iscsi.py +++ b/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_iscsi.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017-2019 Dell Inc. or its subsidiaries. +# Copyright (c) 2020 Dell Inc. or its subsidiaries. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may diff --git a/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_masking.py b/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_masking.py index 06f0f622bf5..31f79f2ffc6 100644 --- a/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_masking.py +++ b/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_masking.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017-2019 Dell Inc. or its subsidiaries. +# Copyright (c) 2020 Dell Inc. or its subsidiaries. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may diff --git a/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_metadata.py b/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_metadata.py index 776d12fbf85..fc79a1d2245 100644 --- a/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_metadata.py +++ b/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_metadata.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017-2019 Dell Inc. or its subsidiaries. +# Copyright (c) 2020 Dell Inc. or its subsidiaries. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may diff --git a/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_provision.py b/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_provision.py index 60cfab069b9..b3a4f4493ed 100644 --- a/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_provision.py +++ b/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_provision.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017-2019 Dell Inc. or its subsidiaries. +# Copyright (c) 2020 Dell Inc. or its subsidiaries. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may diff --git a/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_replication.py b/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_replication.py index f524b7d4391..bbcfb55033f 100644 --- a/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_replication.py +++ b/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_replication.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017-2019 Dell Inc. or its subsidiaries. +# Copyright (c) 2020 Dell Inc. or its subsidiaries. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may diff --git a/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_rest.py b/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_rest.py index e2139e20809..fdb1482a75f 100644 --- a/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_rest.py +++ b/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_rest.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017-2019 Dell Inc. or its subsidiaries. +# Copyright (c) 2020 Dell Inc. or its subsidiaries. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -423,6 +423,17 @@ class PowerMaxRestTest(test.TestCase): self.data.test_volume.size, self.data.extra_specs) self.assertEqual(ref_dict, volume_dict) + @mock.patch.object(rest.PowerMaxRest, 'get_volume') + @mock.patch.object(rest.PowerMaxRest, 'wait_for_job', + return_value=tpd.PowerMaxData.vol_create_task) + def test_create_volume_from_sg_existing_volume_success( + self, mock_task, mock_get): + volume_name = self.data.volume_details[0]['volume_identifier'] + self.rest.create_volume_from_sg( + self.data.array, volume_name, self.data.defaultstoragegroup_name, + self.data.test_volume.size, self.data.extra_specs) + mock_get.assert_called_with(self.data.array, self.data.device_id) + def test_create_volume_from_sg_failed(self): volume_name = self.data.volume_details[0]['volume_identifier'] self.assertRaises( @@ -1695,7 +1706,7 @@ class PowerMaxRestTest(test.TestCase): def test_get_iterator_list(self): with mock.patch.object( - self.rest, '_get_request', side_effect=[ + self.rest, 'get_request', side_effect=[ self.data.rest_iterator_resonse_one, self.data.rest_iterator_resonse_two]): expected_response = [ @@ -1744,7 +1755,7 @@ class PowerMaxRestTest(test.TestCase): def test_get_vmax_model(self): reference = 'PowerMax_2000' - with mock.patch.object(self.rest, '_get_request', + with mock.patch.object(self.rest, 'get_request', return_value=self.data.powermax_model_details): self.assertEqual(self.rest.get_vmax_model(self.data.array), reference) @@ -2224,7 +2235,7 @@ class PowerMaxRestTest(test.TestCase): array_id, sg_name, rdf_group_no, rep_extra_specs) def test_validate_unisphere_version_unofficial_success(self): - version = 'T9.1.0.1054' + version = 'T9.2.0.1054' returned_version = {'version': version} with mock.patch.object(self.rest, "request", return_value=(200, @@ -2253,3 +2264,69 @@ class PowerMaxRestTest(test.TestCase): self.assertTrue(valid_version) request_count = mock_req.call_count self.assertEqual(1, request_count) + + @mock.patch.object(rest.PowerMaxRest, '_build_uri_kwargs') + @mock.patch.object(rest.PowerMaxRest, '_build_uri_legacy_args') + def test_build_uri_legacy(self, mck_build_legacy, mck_build_kwargs): + self.rest.build_uri('array', f_key='test') + mck_build_legacy.assert_called_once() + mck_build_kwargs.assert_not_called() + + @mock.patch.object(rest.PowerMaxRest, '_build_uri_kwargs') + @mock.patch.object(rest.PowerMaxRest, '_build_uri_legacy_args') + def test_build_uri_kwargs(self, mck_build_legacy, mck_build_kwargs): + self.rest.build_uri(array='test', f_key='test') + mck_build_legacy.assert_not_called() + mck_build_kwargs.assert_called_once() + + def test_build_uri_legacy_args_private_no_version(self): + target_uri = self.rest._build_uri_legacy_args( + self.data.array, 'sloprovisioning', 'storagegroup', + resource_name='test-sg', private=True, no_version=True) + expected_uri = ( + '/private/sloprovisioning/symmetrix/%(arr)s/storagegroup/test-sg' % + {'arr': self.data.array}) + self.assertEqual(target_uri, expected_uri) + + def test_build_uri_legacy_args_public_version(self): + target_uri = self.rest._build_uri_legacy_args( + self.data.array, 'sloprovisioning', 'storagegroup', + resource_name='test-sg') + expected_uri = ( + '/%(ver)s/sloprovisioning/symmetrix/%(arr)s/storagegroup/test-sg' % + {'ver': rest.U4V_VERSION, 'arr': self.data.array}) + self.assertEqual(target_uri, expected_uri) + + def test_build_uri_kwargs_private_no_version(self): + target_uri = self.rest._build_uri_kwargs( + no_version=True, private=True, category='test') + expected_uri = '/private/test' + self.assertEqual(target_uri, expected_uri) + + def test_build_uri_kwargs_public_version(self): + target_uri = self.rest._build_uri_kwargs(category='test') + expected_uri = '/%(ver)s/test' % {'ver': rest.U4V_VERSION} + self.assertEqual(target_uri, expected_uri) + + def test_build_uri_kwargs_full_uri(self): + target_uri = self.rest._build_uri_kwargs( + category='test-cat', + resource_level='res-level', resource_level_id='id1', + resource_type='res-type', resource_type_id='id2', + resource='res', resource_id='id3', + object_type='obj', object_type_id='id4') + expected_uri = ( + '/%(ver)s/test-cat/res-level/id1/res-type/id2/res/id3/obj/id4' % { + 'ver': rest.U4V_VERSION}) + self.assertEqual(target_uri, expected_uri) + + @mock.patch.object( + rest.PowerMaxRest, 'request', return_value=(200, {'success': True})) + def test_post_request(self, mck_request): + test_uri = '/92/test/uri' + test_op = 'performance metrics' + test_filters = {'filters': False} + response_obj = self.rest.post_request(test_uri, test_op, test_filters) + mck_request.assert_called_once_with( + test_uri, rest.POST, request_object=test_filters) + self.assertEqual(response_obj, {'success': True}) diff --git a/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_utils.py b/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_utils.py index 3a9d687081f..b4acb2f0203 100644 --- a/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_utils.py +++ b/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_utils.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017-2019 Dell Inc. or its subsidiaries. +# Copyright (c) 2020 Dell Inc. or its subsidiaries. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may diff --git a/cinder/volume/drivers/dell_emc/powermax/common.py b/cinder/volume/drivers/dell_emc/powermax/common.py index 44b73926531..72b9628e1d1 100644 --- a/cinder/volume/drivers/dell_emc/powermax/common.py +++ b/cinder/volume/drivers/dell_emc/powermax/common.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017-2018 Dell Inc. or its subsidiaries. +# Copyright (c) 2020 Dell Inc. or its subsidiaries. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -2824,17 +2824,17 @@ class PowerMaxCommon(object): :param array: the array serial number :param extra_specs: extra specifications """ - snap_name = session['snap_name'] - source = session['source_vol_id'] - generation = session['generation'] - expired = session['expired'] + snap_name = session.get('snap_name') + source = session.get('source_vol_id') + generation = session.get('generation') + expired = session.get('expired') target, cm_enabled = None, False if session.get('target_vol_id'): - target = session['target_vol_id'] - cm_enabled = session['copy_mode'] + target = session.get('target_vol_id') + cm_enabled = session.get('copy_mode') - if target: + if target and snap_name: loop = True if cm_enabled else False LOG.debug( "Unlinking source from target. Source: %(vol)s, Target: " @@ -2848,7 +2848,7 @@ class PowerMaxCommon(object): # 1. If legacy snapshot with 'EMC_SMI' in snapshot name # 2. If snapVX snapshot with copy mode enabled # 3. If snapVX snapshot with copy mode disabled and not expired - if ('EMC_SMI' in snap_name or cm_enabled or ( + if (snap_name and 'EMC_SMI' in snap_name or cm_enabled or ( not cm_enabled and not expired)): LOG.debug( "Deleting temporary snapshot. Source: %(vol)s, snap name: " diff --git a/cinder/volume/drivers/dell_emc/powermax/fc.py b/cinder/volume/drivers/dell_emc/powermax/fc.py index 8ebd10233ef..543359e5f6f 100644 --- a/cinder/volume/drivers/dell_emc/powermax/fc.py +++ b/cinder/volume/drivers/dell_emc/powermax/fc.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017-2018 Dell Inc. or its subsidiaries. +# Copyright (c) 2020 Dell Inc. or its subsidiaries. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -122,9 +122,10 @@ class PowerMaxFCDriver(san.SanDriver, driver.FibreChannelDriver): - Switch to Unisphere REST API public replication endpoints - Support for multiple replication devices - Pools bug fix allowing 'None' variants (bug #1873253) + 4.3.0 - Changing from 91 to 92 REST endpoints """ - VERSION = "4.2.0" + VERSION = "4.3.0" # ThirdPartySystems wiki CI_WIKI_NAME = "EMC_VMAX_CI" diff --git a/cinder/volume/drivers/dell_emc/powermax/iscsi.py b/cinder/volume/drivers/dell_emc/powermax/iscsi.py index 75ee3e6fddc..63a1bad7b57 100644 --- a/cinder/volume/drivers/dell_emc/powermax/iscsi.py +++ b/cinder/volume/drivers/dell_emc/powermax/iscsi.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017-2018 Dell Inc. or its subsidiaries. +# Copyright (c) 2020 Dell Inc. or its subsidiaries. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -127,9 +127,10 @@ class PowerMaxISCSIDriver(san.SanISCSIDriver): - Switch to Unisphere REST API public replication endpoints - Support for multiple replication devices - Pools bug fix allowing 'None' variants (bug #1873253) + 4.3.0 - Changing from 91 to 92 REST endpoints """ - VERSION = "4.2.0" + VERSION = "4.3.0" # ThirdPartySystems wiki CI_WIKI_NAME = "EMC_VMAX_CI" diff --git a/cinder/volume/drivers/dell_emc/powermax/masking.py b/cinder/volume/drivers/dell_emc/powermax/masking.py index e7e515055f8..c8ad70c380c 100644 --- a/cinder/volume/drivers/dell_emc/powermax/masking.py +++ b/cinder/volume/drivers/dell_emc/powermax/masking.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017-2018 Dell Inc. or its subsidiaries. +# Copyright (c) 2020 Dell Inc. or its subsidiaries. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may diff --git a/cinder/volume/drivers/dell_emc/powermax/metadata.py b/cinder/volume/drivers/dell_emc/powermax/metadata.py index e475cd51e3e..afbbde7aa2b 100644 --- a/cinder/volume/drivers/dell_emc/powermax/metadata.py +++ b/cinder/volume/drivers/dell_emc/powermax/metadata.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017-2018 Dell Inc. or its subsidiaries. +# Copyright (c) 2020 Dell Inc. or its subsidiaries. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may diff --git a/cinder/volume/drivers/dell_emc/powermax/provision.py b/cinder/volume/drivers/dell_emc/powermax/provision.py index dbe3f4da490..0547a6ebd3f 100644 --- a/cinder/volume/drivers/dell_emc/powermax/provision.py +++ b/cinder/volume/drivers/dell_emc/powermax/provision.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017-2018 Dell Inc. or its subsidiaries. +# Copyright (c) 2020 Dell Inc. or its subsidiaries. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may diff --git a/cinder/volume/drivers/dell_emc/powermax/rest.py b/cinder/volume/drivers/dell_emc/powermax/rest.py index d3326e5efe9..4098e041cc8 100644 --- a/cinder/volume/drivers/dell_emc/powermax/rest.py +++ b/cinder/volume/drivers/dell_emc/powermax/rest.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017-2018 Dell Inc. or its subsidiaries. +# Copyright (c) 2020 Dell Inc. or its subsidiaries. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -14,6 +14,7 @@ # under the License. import json +import re import sys import time @@ -35,8 +36,8 @@ LOG = logging.getLogger(__name__) SLOPROVISIONING = 'sloprovisioning' REPLICATION = 'replication' SYSTEM = 'system' -U4V_VERSION = '91' -MIN_U4P_VERSION = '9.1.0.14' +U4V_VERSION = '92' +MIN_U4P_VERSION = '9.2.0.0' UCODE_5978 = '5978' retry_exc_tuple = (exception.VolumeBackendAPIException,) u4p_failover_max_wait = 120 @@ -56,6 +57,7 @@ INCOMPLETE_LIST = ['created', 'unscheduled', 'scheduled', 'running', CREATED = 'created' SUCCEEDED = 'succeeded' CREATE_VOL_STRING = "Creating new Volumes" +POPULATE_SG_LIST = "Populating Storage Group(s) with volumes" class PowerMaxRest(object): @@ -367,7 +369,7 @@ class PowerMaxRest(object): """ complete, rc, status, result, task = False, 0, None, None, None job_url = "/%s/system/job/%s" % (U4V_VERSION, job_id) - job = self._get_request(job_url, 'job') + job = self.get_request(job_url, 'job') if job: status = job['status'] try: @@ -426,29 +428,137 @@ class PowerMaxRest(object): message=exception_message) return task - @staticmethod - def _build_uri(array, category, resource_type, - resource_name=None, private='', version=U4V_VERSION): + def build_uri(self, *args, **kwargs): """Build the target url. - :param array: the array serial number - :param category: the resource category e.g. sloprovisioning - :param resource_type: the resource type e.g. maskingview - :param resource_name: the name of a specific resource - :param private: empty string or '/private' if private url - :returns: target url, string + :param args: input args, see _build_uri_legacy_args() for input + breakdown + :param kwargs: input keyword args, see _build_uri_kwargs() for input + breakdown + :return: target uri -- str """ - target_uri = ('%(private)s/%(version)s/%(category)s/symmetrix/' - '%(array)s/%(resource_type)s' - % {'private': private, 'version': version, - 'category': category, 'array': array, - 'resource_type': resource_type}) - if resource_name: - target_uri += '/%(resource_name)s' % { - 'resource_name': resource_name} + if args: + target_uri = self._build_uri_legacy_args(*args, **kwargs) + else: + target_uri = self._build_uri_kwargs(**kwargs) + return target_uri - def _get_request(self, target_uri, resource_type, params=None): + @staticmethod + def _build_uri_legacy_args(*args, **kwargs): + """Build the target URI using legacy args & kwargs. + + Expected format: + arg[0]: the array serial number: the array serial number -- str + arg[1]: the resource category e.g. 'sloprovisioning' -- str + arg[2]: the resource type e.g. 'maskingview' -- str + kwarg resource_name: the name of a specific resource -- str + kwarg private: if endpoint is private -- bool + kwarg version: U4V REST endpoint version -- int/str + kwarg no_version: if endpoint should be versionless -- bool + + :param args: input args -- see above + :param kwargs: input keyword args -- see above + :return: target URI -- str + """ + # Extract args following legacy _build_uri() format + array_id, category, resource_type = args[0], args[1], args[2] + # Extract keyword args following legacy _build_uri() format + resource_name = kwargs.get('resource_name') + private = kwargs.get('private') + version = kwargs.get('version', U4V_VERSION) + if kwargs.get('no_version'): + version = None + + # Build URI + target_uri = '' + if private: + target_uri += '/private' + if version: + target_uri += '/%(version)s' % {'version': version} + target_uri += ( + '/{cat}/symmetrix/{array_id}/{res_type}'.format( + cat=category, array_id=array_id, res_type=resource_type)) + if resource_name: + target_uri += '/{resource_name}'.format( + resource_name=kwargs.get('resource_name')) + + return target_uri + + @staticmethod + def _build_uri_kwargs(**kwargs): + """Build the target URI using kwargs. + + Expected kwargs: + private: if endpoint is private (optional) -- bool + version: U4P REST endpoint version (optional) -- int/None + no_version: if endpoint should be versionless (optional) -- bool + + category: U4P REST category eg. 'common', 'replication'-- str + + resource_level: U4P REST resource level eg. 'symmetrix' + (optional) -- str + resource_level_id: U4P REST resource level id (optional) -- str + + resource_type: U4P REST resource type eg. 'rdf_director', 'host' + (optional) -- str + resource_type_id: U4P REST resource type id (optional) -- str + + resource: U4P REST resource eg. 'port' (optional) -- str + resource_id: U4P REST resource id (optional) -- str + + object_type: U4P REST resource eg. 'rdf_group' (optional) -- str + object_type_id: U4P REST resource id (optional) -- str + + :param kwargs: input keyword args -- see above + :return: target URI -- str + """ + version = kwargs.get('version', U4V_VERSION) + if kwargs.get('no_version'): + version = None + + target_uri = '' + + if kwargs.get('private'): + target_uri += '/private' + + if version: + target_uri += '/%(ver)s' % {'ver': version} + + target_uri += '/%(cat)s' % {'cat': kwargs.get('category')} + + if kwargs.get('resource_level'): + target_uri += '/%(res_level)s' % { + 'res_level': kwargs.get('resource_level')} + + if kwargs.get('resource_level_id'): + target_uri += '/%(res_level_id)s' % { + 'res_level_id': kwargs.get('resource_level_id')} + + if kwargs.get('resource_type'): + target_uri += '/%(res_type)s' % { + 'res_type': kwargs.get('resource_type')} + if kwargs.get('resource_type_id'): + target_uri += '/%(res_type_id)s' % { + 'res_type_id': kwargs.get('resource_type_id')} + + if kwargs.get('resource'): + target_uri += '/%(res)s' % { + 'res': kwargs.get('resource')} + if kwargs.get('resource_id'): + target_uri += '/%(res_id)s' % { + 'res_id': kwargs.get('resource_id')} + + if kwargs.get('object_type'): + target_uri += '/%(object_type)s' % { + 'object_type': kwargs.get('object_type')} + if kwargs.get('object_type_id'): + target_uri += '/%(object_type_id)s' % { + 'object_type_id': kwargs.get('object_type_id')} + + return target_uri + + def get_request(self, target_uri, resource_type, params=None): """Send a GET request to the array. :param target_uri: the target uri @@ -469,8 +579,30 @@ class PowerMaxRest(object): resource_object = self.list_pagination(resource_object) return resource_object + def post_request(self, target_uri, resource_type, request_body): + """Send a POST request to the array. + + :param target_uri: the target uri -- str + :param resource_type: the resource type -- str + :param request_body: the POST request body -- dict + :return: resource object -- dict or None + """ + resource_object = None + sc, msg = self.request(target_uri, POST, request_object=request_body) + + operation = 'POST %(res)s' % {'res': resource_type} + try: + self.check_status_code_success(operation, sc, msg) + except Exception as e: + LOG.debug("POST resource failed with %(e)s", {'e': e}) + + if sc == STATUS_200: + resource_object = msg + + return resource_object + def get_resource(self, array, category, resource_type, - resource_name=None, params=None, private='', + resource_name=None, params=None, private=False, version=U4V_VERSION): """Get resource details from array. @@ -483,12 +615,13 @@ class PowerMaxRest(object): :param version: None or specific version number if required :returns: resource object -- dict or None """ - target_uri = self._build_uri(array, category, resource_type, - resource_name, private, version=version) - return self._get_request(target_uri, resource_type, params) + target_uri = self.build_uri( + array, category, resource_type, resource_name=resource_name, + private=private, version=version) + return self.get_request(target_uri, resource_type, params) def create_resource(self, array, category, resource_type, payload, - private=''): + private=False): """Create a provisioning resource. :param array: the array serial number @@ -498,8 +631,8 @@ class PowerMaxRest(object): :param private: empty string or '/private' if private url :returns: status_code -- int, message -- string, server response """ - target_uri = self._build_uri(array, category, resource_type, - None, private) + target_uri = self.build_uri( + array, category, resource_type, private=private) status_code, message = self.request(target_uri, POST, request_object=payload) operation = 'Create %(res)s resource' % {'res': resource_type} @@ -507,8 +640,9 @@ class PowerMaxRest(object): operation, status_code, message) return status_code, message - def modify_resource(self, array, category, resource_type, payload, - version=U4V_VERSION, resource_name=None, private=''): + def modify_resource( + self, array, category, resource_type, payload, version=U4V_VERSION, + resource_name=None, private=False): """Modify a resource. :param version: the uv4 version @@ -520,8 +654,9 @@ class PowerMaxRest(object): :param private: empty string or '/private' if private url :returns: status_code -- int, message -- string (server response) """ - target_uri = self._build_uri(array, category, resource_type, - resource_name, private, version) + target_uri = self.build_uri( + array, category, resource_type, resource_name=resource_name, + private=private, version=version) status_code, message = self.request(target_uri, PUT, request_object=payload) operation = 'modify %(res)s resource' % {'res': resource_type} @@ -531,7 +666,7 @@ class PowerMaxRest(object): @retry(retry_exc_tuple, interval=2, retries=3) def delete_resource( self, array, category, resource_type, resource_name, - payload=None, private='', params=None): + payload=None, private=False, params=None): """Delete a provisioning resource. :param array: the array serial number @@ -542,8 +677,9 @@ class PowerMaxRest(object): :param private: empty string or '/private' if private url :param params: dict of optional query params """ - target_uri = self._build_uri(array, category, resource_type, - resource_name, private) + target_uri = self.build_uri( + array, category, resource_type, resource_name=resource_name, + private=private) status_code, message = self.request(target_uri, DELETE, request_object=payload, params=params) @@ -557,7 +693,7 @@ class PowerMaxRest(object): :returns: array_details -- dict or None """ target_uri = '/%s/system/symmetrix/%s' % (U4V_VERSION, array) - array_details = self._get_request(target_uri, 'system') + array_details = self.get_request(target_uri, 'system') if not array_details: LOG.error("Cannot connect to array %(array)s.", {'array': array}) @@ -570,7 +706,7 @@ class PowerMaxRest(object): :returns: tag list -- list or empty list """ target_uri = '/%s/system/tag?array_id=%s' % (U4V_VERSION, array) - array_tags = self._get_request(target_uri, 'system') + array_tags = self.get_request(target_uri, 'system') return array_tags.get('tag_name') def is_next_gen_array(self, array): @@ -967,6 +1103,12 @@ class PowerMaxRest(object): device_id = t_list[(len(t_list) - 1)] device_id = device_id[1:-1] break + elif POPULATE_SG_LIST in desc: + regex_str = (r'Populating Storage Group\(s\) ' + + r'with volumes : \[(.+)\]$') + full_str = re.compile(regex_str) + match = full_str.match(desc) + device_id = match.group(1) if match else None if device_id: self.get_volume(array, device_id) except Exception as e: @@ -1839,7 +1981,7 @@ class PowerMaxRest(object): array_capabilities = None target_uri = ("/%s/replication/capabilities/symmetrix" % U4V_VERSION) - capabilities = self._get_request( + capabilities = self.get_request( target_uri, 'replication capabilities') if capabilities: symm_list = capabilities['symmetrixCapability'] @@ -2245,19 +2387,26 @@ class PowerMaxRest(object): snap_src_dict_list.append(snap_src_dict) if snap_tgt: - snap_tgt_dict['source_vol_id'] = snap_tgt['linkSourceName'] + snap_tgt_dict['source_vol_id'] = snap_tgt.get('linkSourceName') snap_tgt_dict['target_vol_id'] = device_id - snap_tgt_dict['state'] = snap_tgt['state'] - snap_tgt_dict['copy_mode'] = snap_tgt['copy'] + snap_tgt_dict['state'] = snap_tgt.get('state') + snap_tgt_dict['copy_mode'] = snap_tgt.get('copy') vol_info = self._get_private_volume(array, device_id) - vol_tf_sessions = vol_info['timeFinderInfo']['snapVXSession'] - for session in vol_tf_sessions: - if session.get('tgtSrcSnapshotGenInfo'): - snap_tgt_link = session.get('tgtSrcSnapshotGenInfo') - snap_tgt_dict['snap_name'] = snap_tgt_link['snapshotName'] - snap_tgt_dict['expired'] = snap_tgt_link['expired'] - snap_tgt_dict['generation'] = snap_tgt_link['generation'] + if vol_info.get('timeFinderInfo'): + vol_tf_sessions = vol_info.get( + 'timeFinderInfo').get('snapVXSession') + if vol_tf_sessions: + for session in vol_tf_sessions: + if session.get('tgtSrcSnapshotGenInfo'): + snap_tgt_link = session.get( + 'tgtSrcSnapshotGenInfo') + snap_tgt_dict['snap_name'] = snap_tgt_link.get( + 'snapshotName') + snap_tgt_dict['expired'] = snap_tgt_link.get( + 'expired') + snap_tgt_dict['generation'] = snap_tgt_link.get( + 'generation') return snap_src_dict_list, snap_tgt_dict @@ -3037,8 +3186,8 @@ class PowerMaxRest(object): params = {'to': end_position, 'from': start_position} target_uri = ('/common/Iterator/%(iterator_id)s/page' % { 'iterator_id': iterator_id}) - iterator_response = self._get_request(target_uri, 'iterator', - params) + iterator_response = self.get_request(target_uri, 'iterator', + params) try: iterator_result += iterator_response['result'] start_position += max_page_size diff --git a/cinder/volume/drivers/dell_emc/powermax/utils.py b/cinder/volume/drivers/dell_emc/powermax/utils.py index 618edf75a88..b72d2de4eb4 100644 --- a/cinder/volume/drivers/dell_emc/powermax/utils.py +++ b/cinder/volume/drivers/dell_emc/powermax/utils.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017-2018 Dell Inc. or its subsidiaries. +# Copyright (c) 2020 Dell Inc. or its subsidiaries. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may diff --git a/releasenotes/notes/powermax-91-to-92-endpoints-bb467c8aca0165dd.yaml b/releasenotes/notes/powermax-91-to-92-endpoints-bb467c8aca0165dd.yaml new file mode 100644 index 00000000000..862d86fbabd --- /dev/null +++ b/releasenotes/notes/powermax-91-to-92-endpoints-bb467c8aca0165dd.yaml @@ -0,0 +1,5 @@ +--- +other: + - | + PowerMax driver - the minimum version of Unisphere for PowerMax required + for Victoria is 9.2, so all the latest 92 REST endpoints will be used.