From d592b2ad0d4eeab2fbbf25427d63b80fd0c5d72b Mon Sep 17 00:00:00 2001 From: Helen Walsh Date: Wed, 24 Jun 2020 16:10:28 +0100 Subject: [PATCH] PowerMax Driver - Changing from 91 to 92 REST endpoints Changing unisphere for powermax REST endpoint from 91 to 92. Improvements have been made to the method by with URIs are created to allow for full coverage of all possible Unisphere REST API endpoints. Change-Id: I6b3f826dee5c901b50159656a72ad2399818fcc5 --- .../dell_emc/powermax/powermax_data.py | 16 +- .../powermax/powermax_fake_objects.py | 2 +- .../dell_emc/powermax/test_powermax_common.py | 2 +- .../dell_emc/powermax/test_powermax_fc.py | 2 +- .../dell_emc/powermax/test_powermax_iscsi.py | 2 +- .../powermax/test_powermax_masking.py | 2 +- .../powermax/test_powermax_metadata.py | 2 +- .../powermax/test_powermax_provision.py | 2 +- .../powermax/test_powermax_replication.py | 2 +- .../dell_emc/powermax/test_powermax_rest.py | 85 +++++- .../dell_emc/powermax/test_powermax_utils.py | 2 +- .../drivers/dell_emc/powermax/common.py | 18 +- cinder/volume/drivers/dell_emc/powermax/fc.py | 5 +- .../volume/drivers/dell_emc/powermax/iscsi.py | 5 +- .../drivers/dell_emc/powermax/masking.py | 2 +- .../drivers/dell_emc/powermax/metadata.py | 2 +- .../drivers/dell_emc/powermax/provision.py | 2 +- .../volume/drivers/dell_emc/powermax/rest.py | 251 ++++++++++++++---- .../volume/drivers/dell_emc/powermax/utils.py | 2 +- ...x-91-to-92-endpoints-bb467c8aca0165dd.yaml | 5 + 20 files changed, 326 insertions(+), 85 deletions(-) create mode 100644 releasenotes/notes/powermax-91-to-92-endpoints-bb467c8aca0165dd.yaml 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.