Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for Adaptive Optimization configuration query #26

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 66 additions & 2 deletions hpe3parclient/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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)

Expand All @@ -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,
Expand Down Expand Up @@ -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
Expand Down
80 changes: 75 additions & 5 deletions test/HPE3ParMockServer_flask.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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/<aocfg_name>', 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():
Expand Down Expand Up @@ -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


Expand All @@ -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

Expand Down Expand Up @@ -1466,7 +1513,7 @@ def get_wsapi_configuration():
"httpPort": 8008,
"httpsState": "Enabled",
"httpsPort": 8080,
"version": "1.3",
"version": "1.5",
"sessionsInUse": 0,
"systemResourceUsage": 144}

Expand All @@ -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",
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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':
Expand Down
49 changes: 49 additions & 0 deletions test/test_HPE3ParClient_AOCFG.py
Original file line number Diff line number Diff line change
@@ -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)