From fbe36e9af7c90e12b23137ce2a57c3e031e9f9a8 Mon Sep 17 00:00:00 2001 From: Virgil Chereches Date: Tue, 27 Sep 2016 16:18:06 +0300 Subject: [PATCH] Added support for Adaptive Optimization configuration query --- hpe3parclient/client.py | 68 ++++++++++++++++++++++++++- test/HPE3ParMockServer_flask.py | 80 ++++++++++++++++++++++++++++++-- test/test_HPE3ParClient_AOCFG.py | 49 +++++++++++++++++++ 3 files changed, 190 insertions(+), 7 deletions(-) create mode 100644 test/test_HPE3ParClient_AOCFG.py diff --git a/hpe3parclient/client.py b/hpe3parclient/client.py index 85b120f6..321ccaf0 100644 --- a/hpe3parclient/client.py +++ b/hpe3parclient/client.py @@ -128,6 +128,10 @@ class HPE3ParClient(object): HPE3PAR_WS_MIN_BUILD_VERSION_VLUN_QUERY = 30201292 HPE3PAR_WS_MIN_BUILD_VERSION_VLUN_QUERY_DESC = '3.2.1 MU2' + # Minimum build version needed for AOCFG query support + HPE3PAR_WS_MIN_BUILD_VERSION_AOCFG_QUERY = 30202390 + HPE3PAR_WS_MIN_BUILD_VERSION_AOCFG_QUERY_DESC = '3.2.2 MU2' + VLUN_TYPE_EMPTY = 1 VLUN_TYPE_PORT = 2 VLUN_TYPE_HOST = 3 @@ -156,6 +160,29 @@ class HPE3ParClient(object): CPG_DISK_TYPE_NL = 2 # Near Line CPG_DISK_TYPE_SSD = 3 # SSD + + VV_PROV_TYPE_FULL = 1 + VV_PROV_TYPE_TPVV = 2 + VV_PROV_TYPE_SNP = 3 + VV_PROV_TYPE_PEER = 4 + VV_PROV_TYPE_UNKOWN = 5 + VV_PROV_TYPE_TDVV = 6 + + VV_STATE_NORMAL = 1 + VV_STATE_DEGRADED = 2 + VV_STATE_FAILED = 3 + VV_PROV_TYPE_FULL = 1 + VV_PROV_TYPE_TPVV = 2 + VV_PROV_TYPE_SNP = 3 + VV_PROV_TYPE_PEER = 4 + VV_PROV_TYPE_UNKOWN = 5 + VV_PROV_TYPE_TDVV = 6 + + VV_STATE_NORMAL = 1 + VV_STATE_DEGRADED = 2 + VV_STATE_FAILED = 3 + + HOST_EDIT_ADD = 1 HOST_EDIT_REMOVE = 2 @@ -193,6 +220,7 @@ def __init__(self, api_url, debug=False, secure=False, timeout=None, api_version = None self.ssh = None self.vlun_query_supported = False + self.aocfg_query_supported = False self.debug_rest(debug) @@ -219,13 +247,17 @@ def __init__(self, api_url, debug=False, secure=False, timeout=None, if (api_version is None or api_version['build'] < self.HPE3PAR_WS_MIN_BUILD_VERSION): raise exceptions.UnsupportedVersion( - 'Invalid 3PAR WS API, requires version, %s' % - self.HPE3PAR_WS_MIN_BUILD_VERSION_DESC) + 'Invalid 3PAR WS API: %s, requires version at least: %s' % + (api_version['build'],self.HPE3PAR_WS_MIN_BUILD_VERSION_DESC)) # Check for VLUN query support. if (api_version['build'] >= self.HPE3PAR_WS_MIN_BUILD_VERSION_VLUN_QUERY): self.vlun_query_supported = True + # Check for AOCFG query support. + if (api_version['build'] >= + self.HPE3PAR_WS_MIN_BUILD_VERSION_AOCFG_QUERY): + self.aocfg_query_supported = True def setSSHOptions(self, ip, login, password, port=22, conn_timeout=None, privatekey=None, @@ -1952,6 +1984,38 @@ def deleteCPG(self, name): """ response, body = self.http.delete('/cpgs/%s' % name) + + # AOCFG methods + def getAOCFGs(self): + """Get AOCFGs. + + :returns: Array of CPGs + + """ + if self.aocfg_query_supported: + response, body = self.http.get('/aoconfigurations') + return body + else: + raise exceptions.UnsupportedVersion( + 'Invalid 3PAR WS API %s, requires version, %s' % (self.getWsApiVersion()['build'],self.HPE3PAR_WS_MIN_BUILD_VERSION_AOCFG_QUERY_DESC)) + + def getAOCFG(self,aoconfigName): + """Get information about a CPG. + + :param aocfgName: The AO configuration to find + :type name: str + + :returns: AOCFG + :raises: :class:`~hpe3parclient.exceptions.HTTPNotFound` + - NON_EXISTENT_AOCFG - AOCFG doesn't exist + """ + if self.aocfg_query_supported: + response, body = self.http.get('/aoconfigurations/%s' % aoconfigName) + return body + else: + raise exceptions.UnsupportedVersion( + 'Invalid 3PAR WS API %s, requires version, %s' % (self.getWsApiVersion()['build'],self.HPE3PAR_WS_MIN_BUILD_VERSION_AOCFG_QUERY_DESC)) + # VLUN methods # # Virtual-LUN, or VLUN, is a pairing between a virtual volume and a diff --git a/test/HPE3ParMockServer_flask.py b/test/HPE3ParMockServer_flask.py index e57df1e3..be201087 100644 --- a/test/HPE3ParMockServer_flask.py +++ b/test/HPE3ParMockServer_flask.py @@ -15,6 +15,7 @@ INV_INPUT = 12 EXISTENT_CPG = 14 NON_EXISTENT_CPG = 15 +NON_EXISTENT_AOCFG = 293 EXISTENT_HOST = 16 NON_EXISTENT_HOST = 17 NON_EXISTENT_VLUN = 19 @@ -257,6 +258,26 @@ def get_cpg(cpg_name): throw_error(404, NON_EXISTENT_CPG, "CPG '%s' doesn't exist" % cpg_name) +# AOCFG +@app.route('/api/v1/aoconfigurations', methods=['GET']) +def get_aocfgs(): + debugRequest(flask.request) + + # should get it from global aocfgs + resp = flask.make_response(json.dumps(aocfgs), 200) + return resp + + +@app.route('/api/v1/aoconfigurations/', methods=['GET']) +def get_aocfg(aocfg_name): + debugRequest(flask.request) + + for aocfg in aocfgs['members']: + if aocfg['name'] == aocfg_name: + resp = flask.make_response(json.dumps(aocfg), 200) + return resp + + throw_error(404, NON_EXISTENT_AOCFG, "AOCFG '%s' doesn't exist" % aocfg_name) @app.route('/api/v1/spacereporter', methods=['POST']) def get_cpg_available_space(): @@ -797,11 +818,31 @@ def getPort(portPos): print(port) return port +def get_vluns_for_filter(filter): + ret = [] + regexp=re.compile('(?:(volumeWWN|remoteName|hostname|volumeName)(?:\ *EQ\ +)([a-zA-Z_0-9]+)(?:\ +OR\ +)*)') + for match in regexp.finditer(filter): + (condition,value) = match.groups() + if condition == 'volumeName': + ret.extend(get_vluns_for_volume(value)) + elif condition == 'hostname': + ret.extend(get_vluns_for_host(value)) + return ret + @app.route('/api/v1/vluns', methods=['GET']) def get_vluns(): debugRequest(flask.request) - resp = flask.make_response(json.dumps(vluns), 200) + query = flask.request.args.get('query') + if query is not None: + vollist = get_vluns_for_filter(query) + if not vollist: + throw_error(404, NON_EXISTENT_VLUN, + "The vlun(s) corresponding with filter '%s' doesn't exist" % query) + vluns_filtered = { 'total': len(vollist), 'members': vollist } + resp = flask.make_response(json.dumps(vluns_filtered), 200) + else: + resp = flask.make_response(json.dumps(vluns), 200) return resp @@ -812,6 +853,12 @@ def get_vluns_for_host(host_name): ret.append(vlun) return ret +def get_vluns_for_volume(volume_name): + ret = [] + for vlun in vluns['members']: + if vlun['volumeName'] == volume_name: + ret.append(vlun) + return ret # VOLUMES & SNAPSHOTS @@ -1466,7 +1513,7 @@ def get_wsapi_configuration(): "httpPort": 8008, "httpsState": "Enabled", "httpsPort": 8080, - "version": "1.3", + "version": "1.5", "sessionsInUse": 0, "systemResourceUsage": 144} @@ -1479,7 +1526,7 @@ def get_system(): system_info = {"id": 12345, "name": "Flask", - "systemVersion": "3.2.1.46", + "systemVersion": "3.2.2.390", "IPv4Addr": "10.10.10.10", "model": "HP_3PAR 7400", "serialNumber": "1234567", @@ -1661,8 +1708,8 @@ def get_overall_capacity(): def get_version(): debugRequest(flask.request) version = {'major': 1, - 'minor': 3, - 'build': 30201256} + 'minor': 5, + 'build': 30202390} resp = flask.make_response(json.dumps(version), 200) return resp @@ -1970,6 +2017,29 @@ def remove_key_value_pair(volume_name, key): 'uuid': 'f9b018cc-7cb6-4358-a0bf-93243f853d97'}], 'total': 2} + # fake AO configurations + global aocfgs + aocfgs = {'links': [{'href': 'http://localhost:5000/api/v1/aoconfigurations', + 'rel': 'self'}], + 'members': [{'id': 1, + 'links': [{'href': 'http://localhost:5000/api/v1/aoconfigurations/UnitTestAOCFG', + 'rel': 'self'}, + {'href': 'http://localhost:5000/api/v1/cpgs/UnitTestCPG`', + 'rel': 't0cpg'}, + {'href': 'http://localhost:5000/api/v1/cpgs/UnitTestCPG2', + 'rel': 't1cpg'}], + 'mode': 3, + 'name': 'UnitTestAOCFG', + 't0CPG': {'id': 0, + 'maxSpaceUtilizationMiB': 0, + 'minSpaceUtilizationMiB': 15728640, + 'name': 'UnitTestCPG'}, + 't1CPG': {'id': 1, + 'maxSpaceUtilizationMiB': 0, + 'minSpaceUtilizationMiB': 0, + 'name': 'UnitTestCPG1'}}], + 'total': 1} + # fake volumes global volumes volumes = {'members': diff --git a/test/test_HPE3ParClient_AOCFG.py b/test/test_HPE3ParClient_AOCFG.py new file mode 100644 index 00000000..079ed7c9 --- /dev/null +++ b/test/test_HPE3ParClient_AOCFG.py @@ -0,0 +1,49 @@ +# (c) Copyright 2015 Hewlett Packard Enterprise Development LP +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Test class of 3PAR Client for AOCFG query .""" + +from test import HPE3ParClient_base as hpe3parbase +from hpe3parclient import exceptions + +DOMAIN = 'UNIT_TEST_DOMAIN' + +class HPE3ParClientAOCFGTestCase(hpe3parbase.HPE3ParClientBaseTestCase): + + def setUp(self): + super(HPE3ParClientAOCFGTestCase, self).setUp() + + def tearDown(self): + # very last, tear down base class + super(HPE3ParClientAOCFGTestCase, self).tearDown() + + def test_1_get_AOCFG_bad(self): + self.printHeader('get_AOCFG_bad') + + self.assertRaises(exceptions.HTTPNotFound, self.cl.getAOCFG, 'BadName') + + self.printFooter('get_AOCFG_bad') + + def test_1_get_AOCFGs(self): + self.printHeader('get_AOCFGs') + + cpgs = self.cl.getAOCFGs() + self.assertGreater(len(cpgs), 0, 'getAOCFGs failed with no AOCFGs') +### We should test the links to cpgs.... + + self.printFooter('get_AOCFGs') + +# testing +# suite = unittest.TestLoader().loadTestsFromTestCase(HPE3ParClientAOCFGTestCase) +# unittest.TextTestRunner(verbosity=2).run(suite)