make RedFishConnection a class
This commit is contained in:
parent
4b62406ce9
commit
2b70c34062
@ -116,8 +116,6 @@ Clients should always be prepared for:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
__author__ = 'HP'
|
|
||||||
|
|
||||||
import ssl
|
import ssl
|
||||||
import urllib2
|
import urllib2
|
||||||
from urlparse import urlparse
|
from urlparse import urlparse
|
||||||
@ -130,184 +128,190 @@ import StringIO
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|
||||||
def rest_op(operation, host, suburi, request_headers, request_body, user_name,
|
class RedFishConnection(object):
|
||||||
password, x_auth_token=None, enforce_SSL=True):
|
|
||||||
"""REST operation generic handler"""
|
|
||||||
|
|
||||||
url = urlparse('https://' + host + suburi)
|
def __init__(self):
|
||||||
|
super(RedFishConnection, self).__init__()
|
||||||
|
# XXX add members, we're going to have to cache
|
||||||
|
|
||||||
if request_headers is None:
|
def rest_op(self, operation, host, suburi, request_headers, request_body,
|
||||||
request_headers = dict()
|
user_name, password, x_auth_token=None, enforce_SSL=True):
|
||||||
|
"""REST operation generic handler"""
|
||||||
|
|
||||||
# if X-Auth-Token specified, supply it instead of basic auth
|
url = urlparse('https://' + host + suburi)
|
||||||
if x_auth_token is not None:
|
|
||||||
request_headers['X-Auth-Token'] = x_auth_token
|
|
||||||
# else use user_name/password and Basic Auth
|
|
||||||
elif user_name is not None and password is not None:
|
|
||||||
request_headers['Authorization'] = "BASIC " + base64.b64encode(user_name + ":" + password)
|
|
||||||
# TODO: add support for other types of auth
|
|
||||||
|
|
||||||
# TODO: think about redirects....
|
if request_headers is None:
|
||||||
redir_count = 4
|
request_headers = dict()
|
||||||
while redir_count:
|
|
||||||
conn = None
|
# if X-Auth-Token specified, supply it instead of basic auth
|
||||||
if url.scheme == 'https':
|
if x_auth_token is not None:
|
||||||
# New in Python 2.7.9, SSL enforcement is defaulted on, but can be opted-out of.
|
request_headers['X-Auth-Token'] = x_auth_token
|
||||||
# The below case is the Opt-Out condition and should be used with GREAT caution.
|
# else use user_name/password and Basic Auth
|
||||||
# But could be useful for debugging some things, so we're leaving it in.
|
elif user_name is not None and password is not None:
|
||||||
if( sys.version_info.major == 2 and
|
request_headers['Authorization'] = "BASIC " + base64.b64encode(user_name + ":" + password)
|
||||||
sys.version_info.minor == 7 and
|
# TODO: add support for other types of auth
|
||||||
sys.version_info.micro >= 9 and
|
|
||||||
enforce_SSL == False):
|
# TODO: think about redirects....
|
||||||
cont=ssl.SSLContext(ssl.PROTOCOL_TLSv1)
|
redir_count = 4
|
||||||
cont.verify_mode = ssl.CERT_NONE
|
while redir_count:
|
||||||
conn = httplib.HTTPSConnection(host=url.netloc, strict=True, context=cont)
|
conn = None
|
||||||
|
if url.scheme == 'https':
|
||||||
|
# New in Python 2.7.9, SSL enforcement is defaulted on, but can be opted-out of.
|
||||||
|
# The below case is the Opt-Out condition and should be used with GREAT caution.
|
||||||
|
# But could be useful for debugging some things, so we're leaving it in.
|
||||||
|
if( sys.version_info.major == 2 and
|
||||||
|
sys.version_info.minor == 7 and
|
||||||
|
sys.version_info.micro >= 9 and
|
||||||
|
enforce_SSL == False):
|
||||||
|
cont=ssl.SSLContext(ssl.PROTOCOL_TLSv1)
|
||||||
|
cont.verify_mode = ssl.CERT_NONE
|
||||||
|
conn = httplib.HTTPSConnection(host=url.netloc, strict=True, context=cont)
|
||||||
|
else:
|
||||||
|
conn = httplib.HTTPSConnection(host=url.netloc, strict=True)
|
||||||
|
elif url.scheme == 'http':
|
||||||
|
conn = httplib.HTTPConnection(host=url.netloc, strict=True)
|
||||||
else:
|
else:
|
||||||
conn = httplib.HTTPSConnection(host=url.netloc, strict=True)
|
assert(False)
|
||||||
elif url.scheme == 'http':
|
conn.request(operation, url.path, headers=request_headers, body=json.dumps(request_body))
|
||||||
conn = httplib.HTTPConnection(host=url.netloc, strict=True)
|
resp = conn.getresponse()
|
||||||
else:
|
body = resp.read()
|
||||||
assert(False)
|
|
||||||
conn.request(operation, url.path, headers=request_headers, body=json.dumps(request_body))
|
|
||||||
resp = conn.getresponse()
|
|
||||||
body = resp.read()
|
|
||||||
|
|
||||||
# NOTE: Do not assume every HTTP operation will return a JSON body.
|
# NOTE: Do not assume every HTTP operation will return a JSON body.
|
||||||
# For example, ExtendedError structures are only required for HTTP 400
|
# For example, ExtendedError structures are only required for HTTP 400
|
||||||
# errors and are optional elsewhere as they are mostly redundant for many
|
# errors and are optional elsewhere as they are mostly redundant for many
|
||||||
# of the other HTTP status code. In particular, 200 OK responses
|
# of the other HTTP status code. In particular, 200 OK responses
|
||||||
# should not have to return any body.
|
# should not have to return any body.
|
||||||
|
|
||||||
# NOTE: this makes sure the headers names are all lower cases because
|
# NOTE: this makes sure the headers names are all lower cases because
|
||||||
# HTTP says they are case insensitive
|
# HTTP says they are case insensitive
|
||||||
headers = dict((x.lower(), y) for x, y in resp.getheaders())
|
headers = dict((x.lower(), y) for x, y in resp.getheaders())
|
||||||
|
|
||||||
# Follow HTTP redirect
|
# Follow HTTP redirect
|
||||||
if resp.status == 301 and 'location' in headers:
|
if resp.status == 301 and 'location' in headers:
|
||||||
url = urlparse(headers['location'])
|
url = urlparse(headers['location'])
|
||||||
redir_count -= 1
|
redir_count -= 1
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
|
|
||||||
response = dict()
|
response = dict()
|
||||||
try:
|
|
||||||
response = json.loads(body.decode('utf-8'))
|
|
||||||
except ValueError: # if it doesn't decode as json
|
|
||||||
# NOTE: resources may return gzipped content
|
|
||||||
# try to decode as gzip (we should check the headers for Content-Encoding=gzip)
|
|
||||||
try:
|
try:
|
||||||
gzipper = gzip.GzipFile(fileobj=StringIO.StringIO(body))
|
response = json.loads(body.decode('utf-8'))
|
||||||
uncompressed_string = gzipper.read().decode('UTF-8')
|
except ValueError: # if it doesn't decode as json
|
||||||
response = json.loads(uncompressed_string)
|
# NOTE: resources may return gzipped content
|
||||||
except:
|
# try to decode as gzip (we should check the headers for Content-Encoding=gzip)
|
||||||
|
try:
|
||||||
|
gzipper = gzip.GzipFile(fileobj=StringIO.StringIO(body))
|
||||||
|
uncompressed_string = gzipper.read().decode('UTF-8')
|
||||||
|
response = json.loads(uncompressed_string)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# return empty
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# return empty
|
return resp.status, headers, response
|
||||||
pass
|
|
||||||
|
|
||||||
return resp.status, headers, response
|
|
||||||
|
|
||||||
|
|
||||||
def rest_get(host, suburi, request_headers, user_name, password):
|
def rest_get(self, host, suburi, request_headers, user_name, password):
|
||||||
"""Generic REST GET handler"""
|
"""Generic REST GET handler"""
|
||||||
# NOTE: be prepared for various HTTP responses including 500, 404, etc.
|
# NOTE: be prepared for various HTTP responses including 500, 404, etc.
|
||||||
return rest_op('GET', host, suburi, request_headers, None, user_name, password)
|
return rest_op('GET', host, suburi, request_headers, None, user_name, password)
|
||||||
|
|
||||||
|
|
||||||
def rest_patch(server, suburi, request_headers, request_body, user_name, password):
|
def rest_patch(self, server, suburi, request_headers, request_body, user_name, password):
|
||||||
"""REST PATCH"""
|
"""REST PATCH"""
|
||||||
if not isinstance(request_headers, dict): request_headers = dict()
|
if not isinstance(request_headers, dict): request_headers = dict()
|
||||||
request_headers['Content-Type'] = 'application/json'
|
request_headers['Content-Type'] = 'application/json'
|
||||||
return rest_op('PATCH', server, suburi, request_headers, request_body, user_name, password)
|
return rest_op('PATCH', server, suburi, request_headers, request_body, user_name, password)
|
||||||
# NOTE: be prepared for various HTTP responses including 500, 404, 202 etc.
|
# NOTE: be prepared for various HTTP responses including 500, 404, 202 etc.
|
||||||
|
|
||||||
|
|
||||||
def rest_put(host, suburi, request_headers, request_body, user_name, password):
|
def rest_put(self, host, suburi, request_headers, request_body, user_name, password):
|
||||||
"""REST PUT"""
|
"""REST PUT"""
|
||||||
if not isinstance(request_headers, dict): request_headers = dict()
|
if not isinstance(request_headers, dict): request_headers = dict()
|
||||||
request_headers['Content-Type'] = 'application/json'
|
request_headers['Content-Type'] = 'application/json'
|
||||||
return rest_op('PUT', host, suburi, request_headers, request_body, user_name, password)
|
return rest_op('PUT', host, suburi, request_headers, request_body, user_name, password)
|
||||||
# NOTE: be prepared for various HTTP responses including 500, 404, 202 etc.
|
# NOTE: be prepared for various HTTP responses including 500, 404, 202 etc.
|
||||||
|
|
||||||
# REST POST
|
# REST POST
|
||||||
def rest_post(host, suburi, request_headers, request_body, user_name, password):
|
def rest_post(self, host, suburi, request_headers, request_body, user_name, password):
|
||||||
if not isinstance(request_headers, dict): request_headers = dict()
|
if not isinstance(request_headers, dict): request_headers = dict()
|
||||||
request_headers['Content-Type'] = 'application/json'
|
request_headers['Content-Type'] = 'application/json'
|
||||||
return rest_op('POST', host, suburi, request_headers, request_body, user_name, password)
|
return rest_op('POST', host, suburi, request_headers, request_body, user_name, password)
|
||||||
# NOTE: don't assume any newly created resource is included in the response. Only the Location header matters.
|
# NOTE: don't assume any newly created resource is included in the response. Only the Location header matters.
|
||||||
# the response body may be the new resource, it may be an ExtendedError, or it may be empty.
|
# the response body may be the new resource, it may be an ExtendedError, or it may be empty.
|
||||||
|
|
||||||
# REST DELETE
|
# REST DELETE
|
||||||
def rest_delete(host, suburi, request_headers, user_name, password):
|
def rest_delete(self, host, suburi, request_headers, user_name, password):
|
||||||
return rest_op('DELETE', host, suburi, request_headers, None, user_name, password)
|
return rest_op('DELETE', host, suburi, request_headers, None, user_name, password)
|
||||||
# NOTE: be prepared for various HTTP responses including 500, 404, etc.
|
# NOTE: be prepared for various HTTP responses including 500, 404, etc.
|
||||||
# NOTE: response may be an ExtendedError or may be empty
|
# NOTE: response may be an ExtendedError or may be empty
|
||||||
|
|
||||||
|
|
||||||
# this is a generator that returns collection members
|
# this is a generator that returns collection members
|
||||||
def collection(host, collection_uri, request_headers, user_name, password):
|
def collection(self, host, collection_uri, request_headers, user_name, password):
|
||||||
"""
|
"""
|
||||||
collections are of two tupes:
|
collections are of two tupes:
|
||||||
- array of things that are fully expanded (details)
|
- array of things that are fully expanded (details)
|
||||||
- array of URLs (links)
|
- array of URLs (links)
|
||||||
"""
|
"""
|
||||||
# get the collection
|
# get the collection
|
||||||
status, headers, thecollection = rest_get(
|
status, headers, thecollection = rest_get(
|
||||||
host, collection_uri, request_headers, user_name, password)
|
host, collection_uri, request_headers, user_name, password)
|
||||||
|
|
||||||
# TODO: commment this
|
# TODO: commment this
|
||||||
while status < 300:
|
while status < 300:
|
||||||
# verify expected type
|
# verify expected type
|
||||||
|
|
||||||
# NOTE: Because of the Redfish standards effort, we have versioned many things at 0 in anticipation of
|
# NOTE: Because of the Redfish standards effort, we have versioned many things at 0 in anticipation of
|
||||||
# them being ratified for version 1 at some point. So this code makes the (unguarranteed) assumption
|
# them being ratified for version 1 at some point. So this code makes the (unguarranteed) assumption
|
||||||
# throughout that version 0 and 1 are both legitimate at this point. Don't write code requiring version 0 as
|
# throughout that version 0 and 1 are both legitimate at this point. Don't write code requiring version 0 as
|
||||||
# we will bump to version 1 at some point.
|
# we will bump to version 1 at some point.
|
||||||
|
|
||||||
# hint: don't limit to version 0 here as we will rev to 1.0 at some point hopefully with minimal changes
|
# hint: don't limit to version 0 here as we will rev to 1.0 at some point hopefully with minimal changes
|
||||||
assert(get_type(thecollection) == 'Collection.0' or get_type(thecollection) == 'Collection.1')
|
assert(get_type(thecollection) == 'Collection.0' or get_type(thecollection) == 'Collection.1')
|
||||||
|
|
||||||
# if this collection has inline items, return those
|
# if this collection has inline items, return those
|
||||||
|
|
||||||
# NOTE: Collections are very flexible in how the represent members. They can be inline in the collection
|
# NOTE: Collections are very flexible in how the represent members. They can be inline in the collection
|
||||||
# as members of the 'Items' array, or they may be href links in the links/Members array. The could actually
|
# as members of the 'Items' array, or they may be href links in the links/Members array. The could actually
|
||||||
# be both. We have
|
# be both. We have
|
||||||
# to render it with the href links when an array contains PATCHable items because its complex to PATCH
|
# to render it with the href links when an array contains PATCHable items because its complex to PATCH
|
||||||
# inline collection members.
|
# inline collection members.
|
||||||
# A client may wish to pass in a boolean flag favoring the href links vs. the Items in case a collection
|
# A client may wish to pass in a boolean flag favoring the href links vs. the Items in case a collection
|
||||||
# contains both.
|
# contains both.
|
||||||
|
|
||||||
if 'Items' in thecollection:
|
if 'Items' in thecollection:
|
||||||
|
|
||||||
# iterate items
|
# iterate items
|
||||||
for item in thecollection['Items']:
|
for item in thecollection['Items']:
|
||||||
# if the item has a self uri pointer, supply that for convenience
|
# if the item has a self uri pointer, supply that for convenience
|
||||||
memberuri = None
|
memberuri = None
|
||||||
if 'links' in item and 'self' in item['links']:
|
if 'links' in item and 'self' in item['links']:
|
||||||
memberuri = item['links']['self']['href']
|
memberuri = item['links']['self']['href']
|
||||||
|
|
||||||
# Read up on Python generator functions to understand what this does.
|
# Read up on Python generator functions to understand what this does.
|
||||||
yield 200, None, item, memberuri
|
yield 200, None, item, memberuri
|
||||||
|
|
||||||
# else walk the member links
|
# else walk the member links
|
||||||
elif 'links' in thecollection and 'Member' in thecollection['links']:
|
elif 'links' in thecollection and 'Member' in thecollection['links']:
|
||||||
|
|
||||||
# iterate members
|
# iterate members
|
||||||
for memberuri in thecollection['links']['Member']:
|
for memberuri in thecollection['links']['Member']:
|
||||||
# for each member return the resource indicated by the member link
|
# for each member return the resource indicated by the member link
|
||||||
status, headers, member = rest_get(host, memberuri['href'], request_headers, user_name, password)
|
status, headers, member = rest_get(host, memberuri['href'], request_headers, user_name, password)
|
||||||
|
|
||||||
# Read up on Python generator functions to understand what this does.
|
# Read up on Python generator functions to understand what this does.
|
||||||
yield status, headers, member, memberuri['href']
|
yield status, headers, member, memberuri['href']
|
||||||
|
|
||||||
# page forward if there are more pages in the collection
|
# page forward if there are more pages in the collection
|
||||||
if 'links' in thecollection and 'NextPage' in thecollection['links']:
|
if 'links' in thecollection and 'NextPage' in thecollection['links']:
|
||||||
next_link_uri = collection_uri + '?page=' + str(thecollection['links']['NextPage']['page'])
|
next_link_uri = collection_uri + '?page=' + str(thecollection['links']['NextPage']['page'])
|
||||||
status, headers, thecollection = rest_get(host, next_link_uri, request_headers, user_name, password)
|
status, headers, thecollection = rest_get(host, next_link_uri, request_headers, user_name, password)
|
||||||
|
|
||||||
# else we are finished iterating the collection
|
# else we are finished iterating the collection
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
# return the type of an object (down to the major version, skipping minor, and errata)
|
# return the type of an object (down to the major version, skipping minor, and errata)
|
||||||
|
Loading…
Reference in New Issue
Block a user