this patch contains a major refactor of the nsx v3 tests which have nsx rest api calls in their execution stack. in particular it removes the global module level patching that's current done as this type of global patching has intermittent failures when testr runs tests in parallel. these failures become more prevalent when the psec code + tests are added. thus this patch is needed for the psec drop I have. NB: as noted in the code, most of the module level patching / mocking magic can go away once we move the current functional logic to a rest resource model. Change-Id: Idec72c464a4a2771f089b840aae129d42816a35f
230 lines
8.1 KiB
Python
230 lines
8.1 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
|
|
|
|
from neutron.i18n import _LW, _
|
|
from oslo_config import cfg
|
|
from oslo_log import log
|
|
from oslo_serialization import jsonutils
|
|
|
|
from vmware_nsx.common import exceptions as nsx_exc
|
|
|
|
LOG = log.getLogger(__name__)
|
|
|
|
ERRORS = {requests.codes.NOT_FOUND: nsx_exc.ResourceNotFound,
|
|
requests.codes.PRECONDITION_FAILED: nsx_exc.StaleRevision}
|
|
|
|
|
|
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, host_ip=None, user_name=None,
|
|
password=None, insecure=None,
|
|
url_prefix=None, default_headers=None,
|
|
cert_file=None):
|
|
self._host_ip = host_ip
|
|
self._user_name = user_name
|
|
self._password = password
|
|
self._insecure = insecure if insecure is not None else False
|
|
self._url_prefix = url_prefix or ""
|
|
self._default_headers = default_headers or {}
|
|
self._cert_file = cert_file
|
|
|
|
self._session = requests.Session()
|
|
self._session.auth = (self._user_name, self._password)
|
|
if not insecure and self._cert_file:
|
|
self._session.cert = self._cert_file
|
|
|
|
def new_client_for(self, *uri_segments):
|
|
uri = "%s/%s" % (self._url_prefix, '/'.join(uri_segments))
|
|
uri = uri.replace('//', '/')
|
|
|
|
return self.__class__(
|
|
host_ip=self._host_ip, user_name=self._user_name,
|
|
password=self._password, insecure=self._insecure,
|
|
url_prefix=uri,
|
|
default_headers=self._default_headers,
|
|
cert_file=self._cert_file)
|
|
|
|
@property
|
|
def validate_certificate(self):
|
|
return not self._insecure
|
|
|
|
def list(self, headers=None):
|
|
return self.url_list('')
|
|
|
|
def get(self, uuid, headers=None):
|
|
return self.url_get(uuid, headers=headers)
|
|
|
|
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, body=None, headers=None):
|
|
return self.url_post('', body, headers=headers)
|
|
|
|
def url_list(self, url, headers=None):
|
|
return self.url_get(url, headers=headers)
|
|
|
|
def url_get(self, url, headers=None):
|
|
return self._rest_call(url, method='GET', headers=headers)
|
|
|
|
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 _validate_result(self, result, expected, operation):
|
|
if result.status_code not in expected:
|
|
LOG.warning(_LW("The HTTP request returned error code "
|
|
"%(result)d, 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.json() if result.content else ''})
|
|
|
|
manager_error = ERRORS.get(
|
|
result.status_code, nsx_exc.ManagerError)
|
|
raise manager_error(manager=self._host_ip, operation=operation)
|
|
|
|
@classmethod
|
|
def merge_headers(cls, *headers):
|
|
merged = {}
|
|
for header in headers:
|
|
if header:
|
|
merged.update(header)
|
|
return merged
|
|
|
|
def _build_url(self, uri):
|
|
uri = ("/%s/%s" % (self._url_prefix, uri)).replace('//', '/')
|
|
return ("https://%s%s" % (self._host_ip, uri)).strip('/')
|
|
|
|
def _rest_call(self, url, method='GET', body=None, headers=None):
|
|
request_headers = headers.copy() if headers else {}
|
|
request_headers.update(self._default_headers)
|
|
request_url = self._build_url(url)
|
|
|
|
do_request = getattr(self._session, method.lower())
|
|
|
|
LOG.debug("REST call: %s %s\nHeaders: %s\nBody: %s",
|
|
method, request_url, request_headers, body)
|
|
|
|
result = do_request(
|
|
request_url,
|
|
verify=self.validate_certificate,
|
|
data=body,
|
|
headers=request_headers,
|
|
cert=self._cert_file)
|
|
|
|
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, host_ip=None, user_name=None,
|
|
password=None, insecure=None,
|
|
url_prefix=None, default_headers=None,
|
|
cert_file=None):
|
|
|
|
super(JSONRESTClient, self).__init__(
|
|
host_ip=host_ip, user_name=user_name,
|
|
password=password, insecure=insecure,
|
|
url_prefix=url_prefix,
|
|
default_headers=RESTClient.merge_headers(
|
|
JSONRESTClient._DEFAULT_HEADERS, default_headers),
|
|
cert_file=cert_file)
|
|
|
|
def _rest_call(self, *args, **kwargs):
|
|
if kwargs.get('body') is not None:
|
|
kwargs['body'] = jsonutils.dumps(kwargs['body'])
|
|
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, host_ip=None, user_name=None,
|
|
password=None, insecure=None,
|
|
url_prefix=None, default_headers=None,
|
|
cert_file=None):
|
|
|
|
url_prefix = url_prefix or NSX3Client._NSX_V1_API_PREFIX
|
|
if (url_prefix and not url_prefix.startswith(
|
|
NSX3Client._NSX_V1_API_PREFIX)):
|
|
url_prefix = "%s/%s" % (NSX3Client._NSX_V1_API_PREFIX,
|
|
url_prefix or '')
|
|
host_ip = host_ip or cfg.CONF.nsx_v3.nsx_manager
|
|
user_name = user_name or cfg.CONF.nsx_v3.nsx_user
|
|
password = password or cfg.CONF.nsx_v3.nsx_password
|
|
cert_file = cert_file or cfg.CONF.nsx_v3.ca_file
|
|
insecure = (insecure if insecure is not None
|
|
else cfg.CONF.nsx_v3.insecure)
|
|
|
|
super(NSX3Client, self).__init__(
|
|
host_ip=host_ip, user_name=user_name,
|
|
password=password, insecure=insecure,
|
|
url_prefix=url_prefix,
|
|
default_headers=default_headers,
|
|
cert_file=cert_file)
|
|
|
|
|
|
# NOTE(boden): tmp until all refs use client class
|
|
def _get_client(client, *args, **kwargs):
|
|
return client or NSX3Client(*args, **kwargs)
|
|
|
|
|
|
# NOTE(boden): tmp until all refs use client class
|
|
def get_resource(resource, client=None):
|
|
return _get_client(client).get(resource)
|
|
|
|
|
|
# NOTE(boden): tmp until all refs use client class
|
|
def create_resource(resource, data, client=None):
|
|
return _get_client(client).url_post(resource, body=data)
|
|
|
|
|
|
# NOTE(boden): tmp until all refs use client class
|
|
def update_resource(resource, data, client=None):
|
|
return _get_client(client).update(resource, body=data)
|
|
|
|
|
|
# NOTE(boden): tmp until all refs use client class
|
|
def delete_resource(resource, client=None):
|
|
return _get_client(client).delete(resource)
|