make RedFishConnection a class

This commit is contained in:
Devananda van der Veen 2015-03-25 13:22:04 -07:00
parent 4b62406ce9
commit 2b70c34062

View File

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