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
This commit is contained in:
Helen Walsh 2020-06-24 16:10:28 +01:00
parent 70beaeb788
commit d592b2ad0d
20 changed files with 326 additions and 85 deletions

View File

@ -1,4 +1,4 @@
# Copyright (c) 2017-2019 Dell Inc. or its subsidiaries. # Copyright (c) 2020 Dell Inc. or its subsidiaries.
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # 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_2 = '71'
rdf_group_no_3 = '72' rdf_group_no_3 = '72'
rdf_group_no_4 = '73' rdf_group_no_4 = '73'
u4v_version = '91' u4v_version = '92'
storagegroup_name_source = 'Grp_source_sg' storagegroup_name_source = 'Grp_source_sg'
storagegroup_name_target = 'Grp_target_sg' storagegroup_name_target = 'Grp_target_sg'
group_snapshot_name = 'Grp_snapshot' group_snapshot_name = 'Grp_snapshot'
@ -988,7 +988,7 @@ class PowerMaxData(object):
{'symmetrixId': array_herc, {'symmetrixId': array_herc,
'model': 'PowerMax 2000', 'model': 'PowerMax 2000',
'ucode': '5978.1091.1092'}] 'ucode': '5978.1091.1092'}]
version_details = {'version': 'V9.1.0.1054'} version_details = {'version': 'V9.2.0.0'}
headroom = {'headroom': [{'headroomCapacity': 20348.29}]} headroom = {'headroom': [{'headroomCapacity': 20348.29}]}
@ -1274,7 +1274,7 @@ class PowerMaxData(object):
data_dict = {volume_id: volume_info_dict} data_dict = {volume_id: volume_info_dict}
platform = 'Linux-4.4.0-104-generic-x86_64-with-Ubuntu-16.04-xenial' 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" unisphere_version_90 = "V9.0.0.1"
openstack_release = '12.0.0.0b3.dev401' openstack_release = '12.0.0.0b3.dev401'
openstack_version = '12.0.0' openstack_version = '12.0.0'
@ -1523,3 +1523,11 @@ class PowerMaxData(object):
'Emulation': 'FBA', 'Emulation': 'FBA',
'Configuration': 'TDEV', 'Configuration': 'TDEV',
'CompressionDisabled': False}}) '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}]

View File

@ -1,4 +1,4 @@
# Copyright (c) 2017-2019 Dell Inc. or its subsidiaries. # Copyright (c) 2020 Dell Inc. or its subsidiaries.
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may

View File

@ -1,4 +1,4 @@
# Copyright (c) 2017-2019 Dell Inc. or its subsidiaries. # Copyright (c) 2020 Dell Inc. or its subsidiaries.
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may

View File

@ -1,4 +1,4 @@
# Copyright (c) 2017-2019 Dell Inc. or its subsidiaries. # Copyright (c) 2020 Dell Inc. or its subsidiaries.
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may

View File

@ -1,4 +1,4 @@
# Copyright (c) 2017-2019 Dell Inc. or its subsidiaries. # Copyright (c) 2020 Dell Inc. or its subsidiaries.
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may

View File

@ -1,4 +1,4 @@
# Copyright (c) 2017-2019 Dell Inc. or its subsidiaries. # Copyright (c) 2020 Dell Inc. or its subsidiaries.
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may

View File

@ -1,4 +1,4 @@
# Copyright (c) 2017-2019 Dell Inc. or its subsidiaries. # Copyright (c) 2020 Dell Inc. or its subsidiaries.
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may

View File

@ -1,4 +1,4 @@
# Copyright (c) 2017-2019 Dell Inc. or its subsidiaries. # Copyright (c) 2020 Dell Inc. or its subsidiaries.
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may

View File

@ -1,4 +1,4 @@
# Copyright (c) 2017-2019 Dell Inc. or its subsidiaries. # Copyright (c) 2020 Dell Inc. or its subsidiaries.
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may

View File

@ -1,4 +1,4 @@
# Copyright (c) 2017-2019 Dell Inc. or its subsidiaries. # Copyright (c) 2020 Dell Inc. or its subsidiaries.
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # 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.data.test_volume.size, self.data.extra_specs)
self.assertEqual(ref_dict, volume_dict) 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): def test_create_volume_from_sg_failed(self):
volume_name = self.data.volume_details[0]['volume_identifier'] volume_name = self.data.volume_details[0]['volume_identifier']
self.assertRaises( self.assertRaises(
@ -1695,7 +1706,7 @@ class PowerMaxRestTest(test.TestCase):
def test_get_iterator_list(self): def test_get_iterator_list(self):
with mock.patch.object( 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_one,
self.data.rest_iterator_resonse_two]): self.data.rest_iterator_resonse_two]):
expected_response = [ expected_response = [
@ -1744,7 +1755,7 @@ class PowerMaxRestTest(test.TestCase):
def test_get_vmax_model(self): def test_get_vmax_model(self):
reference = 'PowerMax_2000' 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): return_value=self.data.powermax_model_details):
self.assertEqual(self.rest.get_vmax_model(self.data.array), self.assertEqual(self.rest.get_vmax_model(self.data.array),
reference) reference)
@ -2224,7 +2235,7 @@ class PowerMaxRestTest(test.TestCase):
array_id, sg_name, rdf_group_no, rep_extra_specs) array_id, sg_name, rdf_group_no, rep_extra_specs)
def test_validate_unisphere_version_unofficial_success(self): def test_validate_unisphere_version_unofficial_success(self):
version = 'T9.1.0.1054' version = 'T9.2.0.1054'
returned_version = {'version': version} returned_version = {'version': version}
with mock.patch.object(self.rest, "request", with mock.patch.object(self.rest, "request",
return_value=(200, return_value=(200,
@ -2253,3 +2264,69 @@ class PowerMaxRestTest(test.TestCase):
self.assertTrue(valid_version) self.assertTrue(valid_version)
request_count = mock_req.call_count request_count = mock_req.call_count
self.assertEqual(1, request_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})

View File

@ -1,4 +1,4 @@
# Copyright (c) 2017-2019 Dell Inc. or its subsidiaries. # Copyright (c) 2020 Dell Inc. or its subsidiaries.
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may

View File

@ -1,4 +1,4 @@
# Copyright (c) 2017-2018 Dell Inc. or its subsidiaries. # Copyright (c) 2020 Dell Inc. or its subsidiaries.
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # 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 array: the array serial number
:param extra_specs: extra specifications :param extra_specs: extra specifications
""" """
snap_name = session['snap_name'] snap_name = session.get('snap_name')
source = session['source_vol_id'] source = session.get('source_vol_id')
generation = session['generation'] generation = session.get('generation')
expired = session['expired'] expired = session.get('expired')
target, cm_enabled = None, False target, cm_enabled = None, False
if session.get('target_vol_id'): if session.get('target_vol_id'):
target = session['target_vol_id'] target = session.get('target_vol_id')
cm_enabled = session['copy_mode'] cm_enabled = session.get('copy_mode')
if target: if target and snap_name:
loop = True if cm_enabled else False loop = True if cm_enabled else False
LOG.debug( LOG.debug(
"Unlinking source from target. Source: %(vol)s, Target: " "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 # 1. If legacy snapshot with 'EMC_SMI' in snapshot name
# 2. If snapVX snapshot with copy mode enabled # 2. If snapVX snapshot with copy mode enabled
# 3. If snapVX snapshot with copy mode disabled and not expired # 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)): not cm_enabled and not expired)):
LOG.debug( LOG.debug(
"Deleting temporary snapshot. Source: %(vol)s, snap name: " "Deleting temporary snapshot. Source: %(vol)s, snap name: "

View File

@ -1,4 +1,4 @@
# Copyright (c) 2017-2018 Dell Inc. or its subsidiaries. # Copyright (c) 2020 Dell Inc. or its subsidiaries.
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # 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 - Switch to Unisphere REST API public replication endpoints
- Support for multiple replication devices - Support for multiple replication devices
- Pools bug fix allowing 'None' variants (bug #1873253) - 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 # ThirdPartySystems wiki
CI_WIKI_NAME = "EMC_VMAX_CI" CI_WIKI_NAME = "EMC_VMAX_CI"

View File

@ -1,4 +1,4 @@
# Copyright (c) 2017-2018 Dell Inc. or its subsidiaries. # Copyright (c) 2020 Dell Inc. or its subsidiaries.
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # 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 - Switch to Unisphere REST API public replication endpoints
- Support for multiple replication devices - Support for multiple replication devices
- Pools bug fix allowing 'None' variants (bug #1873253) - 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 # ThirdPartySystems wiki
CI_WIKI_NAME = "EMC_VMAX_CI" CI_WIKI_NAME = "EMC_VMAX_CI"

View File

@ -1,4 +1,4 @@
# Copyright (c) 2017-2018 Dell Inc. or its subsidiaries. # Copyright (c) 2020 Dell Inc. or its subsidiaries.
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may

View File

@ -1,4 +1,4 @@
# Copyright (c) 2017-2018 Dell Inc. or its subsidiaries. # Copyright (c) 2020 Dell Inc. or its subsidiaries.
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may

View File

@ -1,4 +1,4 @@
# Copyright (c) 2017-2018 Dell Inc. or its subsidiaries. # Copyright (c) 2020 Dell Inc. or its subsidiaries.
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may

View File

@ -1,4 +1,4 @@
# Copyright (c) 2017-2018 Dell Inc. or its subsidiaries. # Copyright (c) 2020 Dell Inc. or its subsidiaries.
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may
@ -14,6 +14,7 @@
# under the License. # under the License.
import json import json
import re
import sys import sys
import time import time
@ -35,8 +36,8 @@ LOG = logging.getLogger(__name__)
SLOPROVISIONING = 'sloprovisioning' SLOPROVISIONING = 'sloprovisioning'
REPLICATION = 'replication' REPLICATION = 'replication'
SYSTEM = 'system' SYSTEM = 'system'
U4V_VERSION = '91' U4V_VERSION = '92'
MIN_U4P_VERSION = '9.1.0.14' MIN_U4P_VERSION = '9.2.0.0'
UCODE_5978 = '5978' UCODE_5978 = '5978'
retry_exc_tuple = (exception.VolumeBackendAPIException,) retry_exc_tuple = (exception.VolumeBackendAPIException,)
u4p_failover_max_wait = 120 u4p_failover_max_wait = 120
@ -56,6 +57,7 @@ INCOMPLETE_LIST = ['created', 'unscheduled', 'scheduled', 'running',
CREATED = 'created' CREATED = 'created'
SUCCEEDED = 'succeeded' SUCCEEDED = 'succeeded'
CREATE_VOL_STRING = "Creating new Volumes" CREATE_VOL_STRING = "Creating new Volumes"
POPULATE_SG_LIST = "Populating Storage Group(s) with volumes"
class PowerMaxRest(object): class PowerMaxRest(object):
@ -367,7 +369,7 @@ class PowerMaxRest(object):
""" """
complete, rc, status, result, task = False, 0, None, None, None complete, rc, status, result, task = False, 0, None, None, None
job_url = "/%s/system/job/%s" % (U4V_VERSION, job_id) 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: if job:
status = job['status'] status = job['status']
try: try:
@ -426,29 +428,137 @@ class PowerMaxRest(object):
message=exception_message) message=exception_message)
return task return task
@staticmethod def build_uri(self, *args, **kwargs):
def _build_uri(array, category, resource_type,
resource_name=None, private='', version=U4V_VERSION):
"""Build the target url. """Build the target url.
:param array: the array serial number :param args: input args, see _build_uri_legacy_args() for input
:param category: the resource category e.g. sloprovisioning breakdown
:param resource_type: the resource type e.g. maskingview :param kwargs: input keyword args, see _build_uri_kwargs() for input
:param resource_name: the name of a specific resource breakdown
:param private: empty string or '/private' if private url :return: target uri -- str
:returns: target url, string
""" """
target_uri = ('%(private)s/%(version)s/%(category)s/symmetrix/' if args:
'%(array)s/%(resource_type)s' target_uri = self._build_uri_legacy_args(*args, **kwargs)
% {'private': private, 'version': version, else:
'category': category, 'array': array, target_uri = self._build_uri_kwargs(**kwargs)
'resource_type': resource_type})
if resource_name:
target_uri += '/%(resource_name)s' % {
'resource_name': resource_name}
return target_uri 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. """Send a GET request to the array.
:param target_uri: the target uri :param target_uri: the target uri
@ -469,8 +579,30 @@ class PowerMaxRest(object):
resource_object = self.list_pagination(resource_object) resource_object = self.list_pagination(resource_object)
return 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, def get_resource(self, array, category, resource_type,
resource_name=None, params=None, private='', resource_name=None, params=None, private=False,
version=U4V_VERSION): version=U4V_VERSION):
"""Get resource details from array. """Get resource details from array.
@ -483,12 +615,13 @@ class PowerMaxRest(object):
:param version: None or specific version number if required :param version: None or specific version number if required
:returns: resource object -- dict or None :returns: resource object -- dict or None
""" """
target_uri = self._build_uri(array, category, resource_type, target_uri = self.build_uri(
resource_name, private, version=version) array, category, resource_type, resource_name=resource_name,
return self._get_request(target_uri, resource_type, params) private=private, version=version)
return self.get_request(target_uri, resource_type, params)
def create_resource(self, array, category, resource_type, payload, def create_resource(self, array, category, resource_type, payload,
private=''): private=False):
"""Create a provisioning resource. """Create a provisioning resource.
:param array: the array serial number :param array: the array serial number
@ -498,8 +631,8 @@ class PowerMaxRest(object):
:param private: empty string or '/private' if private url :param private: empty string or '/private' if private url
:returns: status_code -- int, message -- string, server response :returns: status_code -- int, message -- string, server response
""" """
target_uri = self._build_uri(array, category, resource_type, target_uri = self.build_uri(
None, private) array, category, resource_type, private=private)
status_code, message = self.request(target_uri, POST, status_code, message = self.request(target_uri, POST,
request_object=payload) request_object=payload)
operation = 'Create %(res)s resource' % {'res': resource_type} operation = 'Create %(res)s resource' % {'res': resource_type}
@ -507,8 +640,9 @@ class PowerMaxRest(object):
operation, status_code, message) operation, status_code, message)
return status_code, message return status_code, message
def modify_resource(self, array, category, resource_type, payload, def modify_resource(
version=U4V_VERSION, resource_name=None, private=''): self, array, category, resource_type, payload, version=U4V_VERSION,
resource_name=None, private=False):
"""Modify a resource. """Modify a resource.
:param version: the uv4 version :param version: the uv4 version
@ -520,8 +654,9 @@ class PowerMaxRest(object):
:param private: empty string or '/private' if private url :param private: empty string or '/private' if private url
:returns: status_code -- int, message -- string (server response) :returns: status_code -- int, message -- string (server response)
""" """
target_uri = self._build_uri(array, category, resource_type, target_uri = self.build_uri(
resource_name, private, version) array, category, resource_type, resource_name=resource_name,
private=private, version=version)
status_code, message = self.request(target_uri, PUT, status_code, message = self.request(target_uri, PUT,
request_object=payload) request_object=payload)
operation = 'modify %(res)s resource' % {'res': resource_type} operation = 'modify %(res)s resource' % {'res': resource_type}
@ -531,7 +666,7 @@ class PowerMaxRest(object):
@retry(retry_exc_tuple, interval=2, retries=3) @retry(retry_exc_tuple, interval=2, retries=3)
def delete_resource( def delete_resource(
self, array, category, resource_type, resource_name, self, array, category, resource_type, resource_name,
payload=None, private='', params=None): payload=None, private=False, params=None):
"""Delete a provisioning resource. """Delete a provisioning resource.
:param array: the array serial number :param array: the array serial number
@ -542,8 +677,9 @@ class PowerMaxRest(object):
:param private: empty string or '/private' if private url :param private: empty string or '/private' if private url
:param params: dict of optional query params :param params: dict of optional query params
""" """
target_uri = self._build_uri(array, category, resource_type, target_uri = self.build_uri(
resource_name, private) array, category, resource_type, resource_name=resource_name,
private=private)
status_code, message = self.request(target_uri, DELETE, status_code, message = self.request(target_uri, DELETE,
request_object=payload, request_object=payload,
params=params) params=params)
@ -557,7 +693,7 @@ class PowerMaxRest(object):
:returns: array_details -- dict or None :returns: array_details -- dict or None
""" """
target_uri = '/%s/system/symmetrix/%s' % (U4V_VERSION, array) 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: if not array_details:
LOG.error("Cannot connect to array %(array)s.", LOG.error("Cannot connect to array %(array)s.",
{'array': array}) {'array': array})
@ -570,7 +706,7 @@ class PowerMaxRest(object):
:returns: tag list -- list or empty list :returns: tag list -- list or empty list
""" """
target_uri = '/%s/system/tag?array_id=%s' % (U4V_VERSION, array) 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') return array_tags.get('tag_name')
def is_next_gen_array(self, array): def is_next_gen_array(self, array):
@ -967,6 +1103,12 @@ class PowerMaxRest(object):
device_id = t_list[(len(t_list) - 1)] device_id = t_list[(len(t_list) - 1)]
device_id = device_id[1:-1] device_id = device_id[1:-1]
break 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: if device_id:
self.get_volume(array, device_id) self.get_volume(array, device_id)
except Exception as e: except Exception as e:
@ -1839,7 +1981,7 @@ class PowerMaxRest(object):
array_capabilities = None array_capabilities = None
target_uri = ("/%s/replication/capabilities/symmetrix" target_uri = ("/%s/replication/capabilities/symmetrix"
% U4V_VERSION) % U4V_VERSION)
capabilities = self._get_request( capabilities = self.get_request(
target_uri, 'replication capabilities') target_uri, 'replication capabilities')
if capabilities: if capabilities:
symm_list = capabilities['symmetrixCapability'] symm_list = capabilities['symmetrixCapability']
@ -2245,19 +2387,26 @@ class PowerMaxRest(object):
snap_src_dict_list.append(snap_src_dict) snap_src_dict_list.append(snap_src_dict)
if snap_tgt: 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['target_vol_id'] = device_id
snap_tgt_dict['state'] = snap_tgt['state'] snap_tgt_dict['state'] = snap_tgt.get('state')
snap_tgt_dict['copy_mode'] = snap_tgt['copy'] snap_tgt_dict['copy_mode'] = snap_tgt.get('copy')
vol_info = self._get_private_volume(array, device_id) vol_info = self._get_private_volume(array, device_id)
vol_tf_sessions = vol_info['timeFinderInfo']['snapVXSession'] if vol_info.get('timeFinderInfo'):
vol_tf_sessions = vol_info.get(
'timeFinderInfo').get('snapVXSession')
if vol_tf_sessions:
for session in vol_tf_sessions: for session in vol_tf_sessions:
if session.get('tgtSrcSnapshotGenInfo'): if session.get('tgtSrcSnapshotGenInfo'):
snap_tgt_link = session.get('tgtSrcSnapshotGenInfo') snap_tgt_link = session.get(
snap_tgt_dict['snap_name'] = snap_tgt_link['snapshotName'] 'tgtSrcSnapshotGenInfo')
snap_tgt_dict['expired'] = snap_tgt_link['expired'] snap_tgt_dict['snap_name'] = snap_tgt_link.get(
snap_tgt_dict['generation'] = snap_tgt_link['generation'] '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 return snap_src_dict_list, snap_tgt_dict
@ -3037,7 +3186,7 @@ class PowerMaxRest(object):
params = {'to': end_position, 'from': start_position} params = {'to': end_position, 'from': start_position}
target_uri = ('/common/Iterator/%(iterator_id)s/page' % { target_uri = ('/common/Iterator/%(iterator_id)s/page' % {
'iterator_id': iterator_id}) 'iterator_id': iterator_id})
iterator_response = self._get_request(target_uri, 'iterator', iterator_response = self.get_request(target_uri, 'iterator',
params) params)
try: try:
iterator_result += iterator_response['result'] iterator_result += iterator_response['result']

View File

@ -1,4 +1,4 @@
# Copyright (c) 2017-2018 Dell Inc. or its subsidiaries. # Copyright (c) 2020 Dell Inc. or its subsidiaries.
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may

View File

@ -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.