Merge "Add api docs to the rest client"

This commit is contained in:
Jenkins
2015-03-13 16:20:24 +00:00
committed by Gerrit Code Review

View File

@@ -36,7 +36,27 @@ HTTP_SUCCESS = (200, 201, 202, 203, 204, 205, 206, 207)
class RestClient(object):
"""Unified OpenStack RestClient class
This class is used for building openstack api clients on top of. It is
intended to provide a base layer for wrapping outgoing http requests in
keystone auth as well as providing response code checking and error
handling.
:param auth_provider: an auth provider object used to wrap requests in auth
:param str service: The service name to use for the catalog lookup
:param str region: The region to use for the catalog lookup
:param str endpoint_type: The endpoint type to use for the catalog lookup
:param int build_interval: Time in seconds between to status checks in
wait loops
:param int build_timeout: Timeout in seconds to wait for a wait operation.
:param bool disable_ssl_certificate_validation: Set to true to disable ssl
certificate validation
:param str ca_certs: File containing the CA Bundle to use in verifying a
TLS server cert
:param str trace_request: Regex to use for specifying logging the entirety
of the request and response payload
"""
TYPE = "json"
# The version of the API this client implements
@@ -74,6 +94,18 @@ class RestClient(object):
return self.TYPE
def get_headers(self, accept_type=None, send_type=None):
"""Return the default headers which will be used with outgoing requests
:param str accept_type: The media type to use for the Accept header, if
one isn't provided the object var TYPE will be
used
:param str send_type: The media-type to use for the Content-Type
header, if one isn't provided the object var
TYPE will be used
:rtype: dict
:return: The dictionary of headers which can be used in the headers
dict for outgoing request
"""
if accept_type is None:
accept_type = self._get_type()
if send_type is None:
@@ -94,22 +126,48 @@ class RestClient(object):
@property
def user(self):
"""The username used for requests
:rtype: string
:return: The username being used for requests
"""
return self.auth_provider.credentials.username
@property
def user_id(self):
"""The user_id used for requests
:rtype: string
:return: The user id being used for requests
"""
return self.auth_provider.credentials.user_id
@property
def tenant_name(self):
"""The tenant/project being used for requests
:rtype: string
:return: The tenant/project name being used for requests
"""
return self.auth_provider.credentials.tenant_name
@property
def tenant_id(self):
"""The tenant/project id being used for requests
:rtype: string
:return: The tenant/project id being used for requests
"""
return self.auth_provider.credentials.tenant_id
@property
def password(self):
"""The password being used for requests
:rtype: string
:return: The password being used for requests
"""
return self.auth_provider.credentials.password
@property
@@ -143,6 +201,18 @@ class RestClient(object):
@classmethod
def expected_success(cls, expected_code, read_code):
"""Check expected success response code against the http response
:param int expected_code: The response code that is expected.
Optionally a list of integers can be used
to specify multiple valid success codes
:param int read_code: The response code which was returned in the
response
:raises AssertionError: if the expected_code isn't a valid http success
response code
:raises exceptions.InvalidHttpSuccessCode: if the read code isn't an
expected http success code
"""
assert_msg = ("This function only allowed to use for HTTP status"
"codes which explicitly defined in the RFC 7231 & 4918."
"{0} is not a defined Success Code!"
@@ -166,27 +236,125 @@ class RestClient(object):
raise exceptions.InvalidHttpSuccessCode(details)
def post(self, url, body, headers=None, extra_headers=False):
"""Send a HTTP POST request using keystone auth
:param str url: the relative url to send the post request to
:param dict body: the request body
:param dict headers: The headers to use for the request
:param dict extra_headers: If the headers returned by the get_headers()
method are to be used but additional headers
are needed in the request pass them in as a
dict
:return: a tuple with the first entry containing the response headers
and the second the response body
:rtype: tuple
"""
return self.request('POST', url, extra_headers, headers, body)
def get(self, url, headers=None, extra_headers=False):
"""Send a HTTP GET request using keystone service catalog and auth
:param str url: the relative url to send the post request to
:param dict headers: The headers to use for the request
:param dict extra_headers: If the headers returned by the get_headers()
method are to be used but additional headers
are needed in the request pass them in as a
dict
:return: a tuple with the first entry containing the response headers
and the second the response body
:rtype: tuple
"""
return self.request('GET', url, extra_headers, headers)
def delete(self, url, headers=None, body=None, extra_headers=False):
"""Send a HTTP DELETE request using keystone service catalog and auth
:param str url: the relative url to send the post request to
:param dict body: the request body
:param dict headers: The headers to use for the request
:param dict extra_headers: If the headers returned by the get_headers()
method are to be used but additional headers
are needed in the request pass them in as a
dict
:return: a tuple with the first entry containing the response headers
and the second the response body
:rtype: tuple
"""
return self.request('DELETE', url, extra_headers, headers, body)
def patch(self, url, body, headers=None, extra_headers=False):
"""Send a HTTP PATCH request using keystone service catalog and auth
:param str url: the relative url to send the post request to
:param dict body: the request body
:param dict headers: The headers to use for the request
:param dict extra_headers: If the headers returned by the get_headers()
method are to be used but additional headers
are needed in the request pass them in as a
dict
:return: a tuple with the first entry containing the response headers
and the second the response body
:rtype: tuple
"""
return self.request('PATCH', url, extra_headers, headers, body)
def put(self, url, body, headers=None, extra_headers=False):
"""Send a HTTP PUT request using keystone service catalog and auth
:param str url: the relative url to send the post request to
:param dict body: the request body
:param dict headers: The headers to use for the request
:param dict extra_headers: If the headers returned by the get_headers()
method are to be used but additional headers
are needed in the request pass them in as a
dict
:return: a tuple with the first entry containing the response headers
and the second the response body
:rtype: tuple
"""
return self.request('PUT', url, extra_headers, headers, body)
def head(self, url, headers=None, extra_headers=False):
"""Send a HTTP HEAD request using keystone service catalog and auth
:param str url: the relative url to send the post request to
:param dict headers: The headers to use for the request
:param dict extra_headers: If the headers returned by the get_headers()
method are to be used but additional headers
are needed in the request pass them in as a
dict
:return: a tuple with the first entry containing the response headers
and the second the response body
:rtype: tuple
"""
return self.request('HEAD', url, extra_headers, headers)
def copy(self, url, headers=None, extra_headers=False):
"""Send a HTTP COPY request using keystone service catalog and auth
:param str url: the relative url to send the post request to
:param dict headers: The headers to use for the request
:param dict extra_headers: If the headers returned by the get_headers()
method are to be used but additional headers
are needed in the request pass them in as a
dict
:return: a tuple with the first entry containing the response headers
and the second the response body
:rtype: tuple
"""
return self.request('COPY', url, extra_headers, headers)
def get_versions(self):
"""Get the versions on a endpoint from the keystone catalog
This method will make a GET request on the baseurl from the keystone
catalog to return a list of API versions. It is expected that a GET
on the endpoint in the catalog will return a list of supported API
versions.
:return tuple with response headers and list of version numbers
:rtype: tuple
"""
resp, body = self.get('')
body = self._parse_resp(body)
versions = map(lambda x: x['id'], body)
@@ -294,6 +462,20 @@ class RestClient(object):
return body
def response_checker(self, method, resp, resp_body):
"""A sanity check on the response from a HTTP request
This method does a sanity check on whether the response from an HTTP
request conforms the HTTP RFC.
:param str method: The HTTP verb of the request associated with the
response being passed in.
:param resp: The response headers
:param resp_body: The body of the response
:raises ResponseWithNonEmptyBody: If the response with the status code
is not supposed to have a body
:raises ResponseWithEntity: If the response code is 205 but has an
entity
"""
if (resp.status in set((204, 205, 304)) or resp.status < 200 or
method.upper() == 'HEAD') and resp_body:
raise exceptions.ResponseWithNonEmptyBody(status=resp.status)
@@ -343,6 +525,22 @@ class RestClient(object):
return resp, resp_body
def raw_request(self, url, method, headers=None, body=None):
"""Send a raw HTTP request without the keystone catalog or auth
This method sends a HTTP request in the same manner as the request()
method, however it does so without using keystone auth or the catalog
to determine the base url. Additionally no response handling is done
the results from the request are just returned.
:param str url: Full url to send the request
:param str method: The HTTP verb to use for the request
:param str headers: Headers to use for the request if none are specifed
the headers
:param str body: Body to to send with the request
:rtype: tuple
:return: a tuple with the first entry containing the response headers
and the second the response body
"""
if headers is None:
headers = self.get_headers()
return self.http_obj.request(url, method,
@@ -350,6 +548,55 @@ class RestClient(object):
def request(self, method, url, extra_headers=False, headers=None,
body=None):
"""Send a HTTP request with keystone auth and using the catalog
This method will send an HTTP request using keystone auth in the
headers and the catalog to determine the endpoint to use for the
baseurl to send the request to. Additionally
When a response is recieved it will check it to see if an error
response was recieved. If it was an exception will be raised to enable
it to be handled quickly.
This method will also handle rate-limiting, if a 413 response code is
recieved it will retry the request after waiting the 'retry-after'
duration from the header.
:param str url: Relative url to send the request to
:param str method: The HTTP verb to use for the request
:param dict extra_headers: If specified without the headers kwarg the
headers sent with the request will be the
combination from the get_headers() method
and this kwarg
:param dict headers: Headers to use for the request if none are
specifed the headers returned from the
get_headers() method are used. If the request
explicitly requires no headers use an empty dict.
:param str body: Body to to send with the request
:rtype: tuple
:return: a tuple with the first entry containing the response headers
and the second the response body
:raises InvalidContentType: If the content-type of the response isn't
an expect type or a 415 response code is
recieved
:raises Unauthorized: If a 401 response code is recieved
:raises Forbidden: If a 403 response code is recieved
:raises NotFound: If a 404 response code is recieved
:raises BadRequest: If a 400 response code is recieved
:raises Conflict: If a 409 response code is recieved
:raies Overlimit: If a 413 response code is recieved and over_limit is
not in the response body
:raises RateLimitExceeded: If a 413 response code is recieved and
over_limit is in the response body
:raises UnprocessableEntity: If a 422 response code is recieved
:raises InvalidHTTPResponseBody: The response body wasn't valid JSON
and couldn't be parsed
:raises NotImplemented: If a 501 response code is recieved
:raises ServerFault: If a 500 response code is recieved
:raises UnexpectedResponseCode: If a response code above 400 is
recieved and it doesn't fall into any
of the handled checks
"""
# if extra_headers is True
# default headers would be added to headers
retry = 0
@@ -499,7 +746,16 @@ class RestClient(object):
return 'exceed' in over_limit.get('message', 'blabla')
def wait_for_resource_deletion(self, id):
"""Waits for a resource to be deleted."""
"""Waits for a resource to be deleted
This method will loop over is_resource_deleted until either
is_resource_deleted returns True or the build timeout is reached. This
depends on is_resource_deleted being implemented
:param str id: The id of the resource to check
:raises TimeoutException: If the build_timeout has elapsed and the
resource still hasn't been deleted
"""
start_time = int(time.time())
while True:
if self.is_resource_deleted(id):