vmware-nsxlib/vmware_nsxlib/v3/client.py

251 lines
9.3 KiB
Python

# Copyright 2015 VMware, Inc.
# All Rights Reserved
#
# 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.
#
import requests
import six.moves.urllib.parse as urlparse
from oslo_log import log
from oslo_serialization import jsonutils
from vmware_nsxlib._i18n import _
from vmware_nsxlib.v3 import exceptions
from vmware_nsxlib.v3 import utils
LOG = log.getLogger(__name__)
NULL_CURSOR_PREFIX = '0000'
def http_error_to_exception(status_code, error_code):
errors = {requests.codes.NOT_FOUND: exceptions.ResourceNotFound,
requests.codes.PRECONDITION_FAILED: exceptions.StaleRevision,
requests.codes.INTERNAL_SERVER_ERROR:
{'99': exceptions.ClientCertificateNotTrusted}}
if status_code in errors:
if isinstance(errors[status_code], dict):
# choose based on error code
if error_code in errors[status_code]:
return errors[status_code][error_code]
else:
return errors[status_code]
# default exception
return exceptions.ManagerError
class RESTClient(object):
_VERB_RESP_CODES = {
'get': [requests.codes.ok],
'post': [requests.codes.created, requests.codes.ok],
'put': [requests.codes.ok],
'delete': [requests.codes.ok]
}
def __init__(self, connection, url_prefix=None,
default_headers=None,
client_obj=None):
self._conn = connection
self._url_prefix = url_prefix or ""
self._default_headers = default_headers or {}
def new_client_for(self, *uri_segments):
uri = self._build_url('/'.join(uri_segments))
return self.__class__(
self._conn,
url_prefix=uri,
default_headers=self._default_headers,
client_obj=self)
def list(self, resource='', headers=None, silent=False):
return self.url_list(resource, headers=headers, silent=silent)
def get(self, uuid, headers=None, silent=False):
return self.url_get(uuid, headers=headers, silent=silent)
def delete(self, uuid, headers=None):
return self.url_delete(uuid, headers=headers)
def update(self, uuid, body=None, headers=None):
return self.url_put(uuid, body, headers=headers)
def create(self, resource='', body=None, headers=None):
return self.url_post(resource, body, headers=headers)
def url_list(self, url, headers=None, silent=False):
concatenate_response = self.url_get(url, headers=headers)
cursor = concatenate_response.get('cursor', NULL_CURSOR_PREFIX)
op = '&' if urlparse.urlparse(url).query else '?'
url += op + 'cursor='
while cursor and not cursor.startswith(NULL_CURSOR_PREFIX):
page = self.url_get(url + cursor, headers=headers, silent=silent)
concatenate_response['results'].extend(page.get('results', []))
cursor = page.get('cursor', NULL_CURSOR_PREFIX)
return concatenate_response
def url_get(self, url, headers=None, silent=False):
return self._rest_call(url, method='GET', headers=headers,
silent=silent)
def url_delete(self, url, headers=None):
return self._rest_call(url, method='DELETE', headers=headers)
def url_put(self, url, body, headers=None):
return self._rest_call(url, method='PUT', body=body, headers=headers)
def url_post(self, url, body, headers=None):
return self._rest_call(url, method='POST', body=body, headers=headers)
def _raise_error(self, status_code, operation, result_msg,
error_code=None):
error = http_error_to_exception(status_code, error_code)
raise error(manager='', operation=operation, details=result_msg,
error_code=error_code)
def _validate_result(self, result, expected, operation):
if result.status_code not in expected:
result_msg = result.json() if result.content else ''
LOG.warning("The HTTP request returned error code "
"%(result)s, whereas %(expected)s response "
"codes were expected. Response body %(body)s",
{'result': result.status_code,
'expected': '/'.join([str(code)
for code in expected]),
'body': result_msg})
error_code = None
if isinstance(result_msg, dict) and 'error_message' in result_msg:
error_code = result_msg.get('error_code')
related_errors = [error['error_message'] for error in
result_msg.get('related_errors', [])]
result_msg = result_msg['error_message']
if related_errors:
result_msg += " relatedErrors: %s" % ' '.join(
related_errors)
self._raise_error(result.status_code, operation, result_msg,
error_code=error_code)
@classmethod
def merge_headers(cls, *headers):
merged = {}
for header in headers:
if header:
merged.update(header)
return merged
def _build_url(self, uri):
prefix = urlparse.urlparse(self._url_prefix)
uri = ("/%s/%s" % (prefix.path, uri)).replace('//', '/').strip('/')
if prefix.netloc:
uri = "%s/%s" % (prefix.netloc, uri)
if prefix.scheme:
uri = "%s://%s" % (prefix.scheme, uri)
return uri
def _rest_call(self, url, method='GET', body=None, headers=None,
silent=False):
request_headers = headers.copy() if headers else {}
request_headers.update(self._default_headers)
request_url = self._build_url(url)
do_request = getattr(self._conn, method.lower())
if not silent:
LOG.debug("REST call: %s %s\nHeaders: %s\nBody: %s",
method, request_url, request_headers, body)
result = do_request(
request_url,
data=body,
headers=request_headers)
if not silent:
LOG.debug("REST call: %s %s\nResponse: %s",
method, request_url, result.json()
if result.content else '')
self._validate_result(
result, RESTClient._VERB_RESP_CODES[method.lower()],
_("%(verb)s %(url)s") % {'verb': method, 'url': request_url})
return result
class JSONRESTClient(RESTClient):
_DEFAULT_HEADERS = {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
def __init__(self, connection, url_prefix=None,
default_headers=None,
client_obj=None):
super(JSONRESTClient, self).__init__(
connection,
url_prefix=url_prefix,
default_headers=RESTClient.merge_headers(
JSONRESTClient._DEFAULT_HEADERS, default_headers),
client_obj=None)
def _rest_call(self, *args, **kwargs):
if kwargs.get('body') is not None:
kwargs['body'] = jsonutils.dumps(kwargs['body'], sort_keys=True)
result = super(JSONRESTClient, self)._rest_call(*args, **kwargs)
return result.json() if result.content else result
class NSX3Client(JSONRESTClient):
_NSX_V1_API_PREFIX = 'api/v1/'
def __init__(self, connection, url_prefix=None,
default_headers=None,
nsx_api_managers=None,
max_attempts=utils.DEFAULT_MAX_ATTEMPTS,
client_obj=None):
# If the client obj is defined - copy configuration from it
if client_obj:
self.nsx_api_managers = client_obj.nsx_api_managers or []
self.max_attempts = client_obj.max_attempts
else:
self.nsx_api_managers = nsx_api_managers or []
self.max_attempts = max_attempts
url_prefix = url_prefix or NSX3Client._NSX_V1_API_PREFIX
if url_prefix and NSX3Client._NSX_V1_API_PREFIX not in url_prefix:
if url_prefix.startswith('http'):
url_prefix += '/' + NSX3Client._NSX_V1_API_PREFIX
else:
url_prefix = "%s/%s" % (NSX3Client._NSX_V1_API_PREFIX,
url_prefix or '')
self.max_attempts = max_attempts
super(NSX3Client, self).__init__(
connection, url_prefix=url_prefix,
default_headers=default_headers,
client_obj=client_obj)
def _raise_error(self, status_code, operation, result_msg,
error_code=None):
"""Override the Rest client errors to add the manager IPs"""
error = http_error_to_exception(status_code, error_code)
raise error(manager=self.nsx_api_managers,
operation=operation,
details=result_msg,
error_code=error_code)