Merge "nsx v3 port security plumbing"
This commit is contained in:
commit
3c1499b839
@ -1,4 +1,4 @@
|
||||
# Copyright 2015 OpenStack Foundation
|
||||
# Copyright 2015 VMware, Inc.
|
||||
# All Rights Reserved
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
@ -12,14 +12,13 @@
|
||||
# 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
|
||||
import requests
|
||||
from requests import auth
|
||||
|
||||
from neutron.i18n import _LW
|
||||
from vmware_nsx.common import exceptions as nsx_exc
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
@ -28,88 +27,197 @@ ERRORS = {requests.codes.NOT_FOUND: nsx_exc.ResourceNotFound,
|
||||
requests.codes.PRECONDITION_FAILED: nsx_exc.StaleRevision}
|
||||
|
||||
|
||||
def _get_manager_endpoint():
|
||||
manager = _get_manager_ip()
|
||||
username = cfg.CONF.nsx_v3.nsx_user
|
||||
password = cfg.CONF.nsx_v3.nsx_password
|
||||
verify_cert = not cfg.CONF.nsx_v3.insecure
|
||||
return "https://%s" % manager, username, password, verify_cert
|
||||
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 _get_manager_ip():
|
||||
# NOTE: In future this may return the IP address from a pool
|
||||
manager = cfg.CONF.nsx_v3.nsx_manager
|
||||
return manager
|
||||
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 _validate_result(result, expected, operation):
|
||||
if result.status_code not in expected:
|
||||
if (result.status_code == requests.codes.bad):
|
||||
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()})
|
||||
else:
|
||||
LOG.warning(_LW("The HTTP request returned error code "
|
||||
"%(result)d, whereas %(expected)s response "
|
||||
"codes were expected."),
|
||||
{'result': result.status_code,
|
||||
'expected': '/'.join([str(code)
|
||||
for code in expected])})
|
||||
manager_ip = _get_manager_ip()
|
||||
'body': result.json() if result.content else ''})
|
||||
|
||||
manager_error = ERRORS.get(result.status_code, nsx_exc.ManagerError)
|
||||
raise manager_error(manager=manager_ip, operation=operation)
|
||||
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
|
||||
|
||||
|
||||
def get_resource(resource, **params):
|
||||
manager, user, password, verify = _get_manager_endpoint()
|
||||
url = manager + "/api/v1/%s" % resource
|
||||
headers = {'Accept': 'application/json'}
|
||||
result = requests.get(url, auth=auth.HTTPBasicAuth(user, password),
|
||||
verify=verify, headers=headers,
|
||||
cert=cfg.CONF.nsx_v3.ca_file,
|
||||
params=params)
|
||||
_validate_result(result, [requests.codes.ok],
|
||||
_("reading resource: %s") % resource)
|
||||
return result.json()
|
||||
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_resource(resource):
|
||||
return NSX3Client().get(resource)
|
||||
|
||||
|
||||
# NOTE(boden): tmp until all refs use client class
|
||||
def create_resource(resource, data):
|
||||
manager, user, password, verify = _get_manager_endpoint()
|
||||
url = manager + "/api/v1/%s" % resource
|
||||
headers = {'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'}
|
||||
result = requests.post(url, auth=auth.HTTPBasicAuth(user, password),
|
||||
verify=verify, headers=headers,
|
||||
data=jsonutils.dumps(data),
|
||||
cert=cfg.CONF.nsx_v3.ca_file)
|
||||
_validate_result(result, [requests.codes.created, requests.codes.ok],
|
||||
_("creating resource at: %s") % resource)
|
||||
return result.json()
|
||||
return NSX3Client(url_prefix=resource).create(body=data)
|
||||
|
||||
|
||||
# NOTE(boden): tmp until all refs use client class
|
||||
def update_resource(resource, data):
|
||||
manager, user, password, verify = _get_manager_endpoint()
|
||||
url = manager + "/api/v1/%s" % resource
|
||||
headers = {'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'}
|
||||
result = requests.put(url, auth=auth.HTTPBasicAuth(user, password),
|
||||
verify=verify, headers=headers,
|
||||
data=jsonutils.dumps(data),
|
||||
cert=cfg.CONF.nsx_v3.ca_file)
|
||||
_validate_result(result, [requests.codes.ok],
|
||||
_("updating resource: %s") % resource)
|
||||
return result.json()
|
||||
return NSX3Client().update(resource, body=data)
|
||||
|
||||
|
||||
# NOTE(boden): tmp until all refs use client class
|
||||
def delete_resource(resource):
|
||||
manager, user, password, verify = _get_manager_endpoint()
|
||||
url = manager + "/api/v1/%s" % resource
|
||||
result = requests.delete(url, auth=auth.HTTPBasicAuth(user, password),
|
||||
verify=verify, cert=cfg.CONF.nsx_v3.ca_file)
|
||||
_validate_result(result, [requests.codes.ok],
|
||||
_("deleting resource: %s") % resource)
|
||||
return NSX3Client().delete(resource)
|
||||
|
107
vmware_nsx/nsxlib/v3/resources.py
Normal file
107
vmware_nsx/nsxlib/v3/resources.py
Normal file
@ -0,0 +1,107 @@
|
||||
# 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 abc
|
||||
import six
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class AbstractRESTResource(object):
|
||||
|
||||
def __init__(self, rest_client, *args, **kwargs):
|
||||
self._client = rest_client.new_client_for(self.uri_segment)
|
||||
|
||||
@abc.abstractproperty
|
||||
def uri_segment(self):
|
||||
pass
|
||||
|
||||
def list(self):
|
||||
return self._client.list()
|
||||
|
||||
def get(self, uuid):
|
||||
return self._client.get(uuid)
|
||||
|
||||
def delete(self, uuid):
|
||||
return self._client.delete(uuid)
|
||||
|
||||
@abc.abstractmethod
|
||||
def create(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def update(self, uuid, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def find_by_display_name(self, display_name):
|
||||
found = []
|
||||
for resource in self.list()['results']:
|
||||
if resource['display_name'] == display_name:
|
||||
found.append(resource)
|
||||
return found
|
||||
|
||||
|
||||
class SwitchingProfileTypes(object):
|
||||
IP_DISCOVERY = 'IpDiscoverySwitchingProfile'
|
||||
PORT_MIRRORING = 'PortMirroringSwitchingProfile'
|
||||
QOS = 'QosSwitchingProfile'
|
||||
SPOOF_GUARD = 'SpoofGuardSwitchingProfile'
|
||||
|
||||
|
||||
class WhiteListAddressTypes(object):
|
||||
PORT = 'LPORT_BINDINGS'
|
||||
SWITCH = 'LSWITCH_BINDINGS'
|
||||
|
||||
|
||||
class SwitchingProfile(AbstractRESTResource):
|
||||
|
||||
@property
|
||||
def uri_segment(self):
|
||||
return 'switching-profiles'
|
||||
|
||||
def create(self, profile_type, display_name=None,
|
||||
description=None, **api_args):
|
||||
body = {
|
||||
'resource_type': profile_type,
|
||||
'display_name': display_name or '',
|
||||
'description': description or ''
|
||||
}
|
||||
body.update(api_args)
|
||||
|
||||
return self._client.create(body=body)
|
||||
|
||||
def update(self, uuid, profile_type, **api_args):
|
||||
body = {
|
||||
'resource_type': profile_type
|
||||
}
|
||||
body.update(api_args)
|
||||
|
||||
return self._client.update(uuid, body=body)
|
||||
|
||||
def create_spoofguard_profile(self, display_name,
|
||||
description,
|
||||
whitelist_ports=False,
|
||||
whitelist_switches=False,
|
||||
tags=None):
|
||||
whitelist_providers = []
|
||||
if whitelist_ports:
|
||||
whitelist_providers.append(WhiteListAddressTypes.PORT)
|
||||
if whitelist_switches:
|
||||
whitelist_providers.append(WhiteListAddressTypes.SWITCH)
|
||||
|
||||
return self.create(SwitchingProfileTypes.SPOOF_GUARD,
|
||||
display_name=display_name,
|
||||
description=description,
|
||||
white_list_providers=whitelist_providers,
|
||||
tags=tags or [])
|
@ -12,8 +12,7 @@
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from vmware_nsx.common import exceptions as nsx_exc
|
||||
@ -419,3 +418,12 @@ class NsxV3Mock(object):
|
||||
def update_logical_router_advertisement(self, logical_router_id, **kwargs):
|
||||
# TODO(berlin): implement this latter.
|
||||
pass
|
||||
|
||||
|
||||
class MockRequestsResponse(object):
|
||||
def __init__(self, status_code, content=None):
|
||||
self.status_code = status_code
|
||||
self.content = content
|
||||
|
||||
def json(self):
|
||||
return jsonutils.loads(self.content)
|
||||
|
369
vmware_nsx/tests/unit/vmware/nsxlib/v3/test_client.py
Normal file
369
vmware_nsx/tests/unit/vmware/nsxlib/v3/test_client.py
Normal file
@ -0,0 +1,369 @@
|
||||
# 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 mock
|
||||
|
||||
import vmware_nsx.common.exceptions as exep
|
||||
import vmware_nsx.tests.unit.vmware.nsx_v3_mocks as mocks
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
from oslo_serialization import jsonutils
|
||||
from vmware_nsx.nsxlib.v3 import client
|
||||
from vmware_nsx.tests.unit.vmware.nsxlib.v3 import nsxlib_testcase
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
CLIENT_PKG = 'vmware_nsx.nsxlib.v3.client'
|
||||
|
||||
|
||||
def assert_session_call(mock_call, url, verify, data, headers, cert):
|
||||
mock_call.assert_called_once_with(
|
||||
url, verify=verify, data=data, headers=headers, cert=cert)
|
||||
|
||||
|
||||
class BaseClientTestCase(nsxlib_testcase.NsxLibTestCase):
|
||||
nsx_manager = '1.2.3.4'
|
||||
nsx_user = 'testuser'
|
||||
nsx_password = 'pass123'
|
||||
ca_file = '/path/to/ca.pem'
|
||||
insecure = True
|
||||
|
||||
def setUp(self):
|
||||
cfg.CONF.set_override(
|
||||
'nsx_manager', BaseClientTestCase.nsx_manager, 'nsx_v3')
|
||||
cfg.CONF.set_override(
|
||||
'nsx_user', BaseClientTestCase.nsx_user, 'nsx_v3')
|
||||
cfg.CONF.set_override(
|
||||
'nsx_password', BaseClientTestCase.nsx_password, 'nsx_v3')
|
||||
cfg.CONF.set_override(
|
||||
'ca_file', BaseClientTestCase.ca_file, 'nsx_v3')
|
||||
cfg.CONF.set_override(
|
||||
'insecure', BaseClientTestCase.insecure, 'nsx_v3')
|
||||
super(BaseClientTestCase, self).setUp()
|
||||
|
||||
def new_client(
|
||||
self, clazz, host_ip=nsx_manager,
|
||||
user_name=nsx_user, password=nsx_password,
|
||||
insecure=insecure, url_prefix=None,
|
||||
default_headers=None, cert_file=ca_file):
|
||||
|
||||
return clazz(host_ip=host_ip, user_name=user_name,
|
||||
password=password, insecure=insecure,
|
||||
url_prefix=url_prefix, default_headers=default_headers,
|
||||
cert_file=cert_file)
|
||||
|
||||
|
||||
class NsxV3RESTClientTestCase(BaseClientTestCase):
|
||||
|
||||
def test_client_conf_init(self):
|
||||
api = self.new_client(client.RESTClient)
|
||||
self.assertEqual((
|
||||
BaseClientTestCase.nsx_user, BaseClientTestCase.nsx_password),
|
||||
api._session.auth)
|
||||
self.assertEqual(BaseClientTestCase.nsx_manager, api._host_ip)
|
||||
self.assertEqual(BaseClientTestCase.ca_file, api._cert_file)
|
||||
|
||||
def test_client_params_init(self):
|
||||
api = self.new_client(
|
||||
client.RESTClient, host_ip='11.12.13.14', password='mypass')
|
||||
self.assertEqual((
|
||||
BaseClientTestCase.nsx_user, 'mypass'),
|
||||
api._session.auth)
|
||||
self.assertEqual('11.12.13.14', api._host_ip)
|
||||
self.assertEqual(BaseClientTestCase.ca_file, api._cert_file)
|
||||
|
||||
@mock.patch("%s.%s" % (CLIENT_PKG, 'requests.Session.get'))
|
||||
@mock.patch(CLIENT_PKG + '.RESTClient._validate_result')
|
||||
def test_client_url_prefix(self, mock_validate, mock_get):
|
||||
mock_get.return_value = {}
|
||||
api = self.new_client(client.RESTClient, url_prefix='/cloud/api')
|
||||
api.list()
|
||||
|
||||
assert_session_call(
|
||||
mock_get,
|
||||
'https://1.2.3.4/cloud/api',
|
||||
False, None, {}, BaseClientTestCase.ca_file)
|
||||
|
||||
mock_get.reset_mock()
|
||||
|
||||
api.url_list('v1/ports')
|
||||
assert_session_call(
|
||||
mock_get,
|
||||
'https://1.2.3.4/cloud/api/v1/ports', False, None, {},
|
||||
BaseClientTestCase.ca_file)
|
||||
|
||||
@mock.patch("%s.%s" % (CLIENT_PKG, 'requests.Session.get'))
|
||||
@mock.patch(CLIENT_PKG + '.RESTClient._validate_result')
|
||||
def test_client_headers(self, mock_validate, mock_get):
|
||||
default_headers = {'Content-Type': 'application/golang'}
|
||||
|
||||
mock_get.return_value = {}
|
||||
api = self.new_client(
|
||||
client.RESTClient, default_headers=default_headers,
|
||||
url_prefix='/v1/api')
|
||||
api.list()
|
||||
|
||||
assert_session_call(
|
||||
mock_get,
|
||||
'https://1.2.3.4/v1/api',
|
||||
False, None, default_headers, BaseClientTestCase.ca_file)
|
||||
|
||||
mock_get.reset_mock()
|
||||
|
||||
method_headers = {'X-API-Key': 'strong-crypt'}
|
||||
api.url_list('ports/33', headers=method_headers)
|
||||
method_headers.update(default_headers)
|
||||
assert_session_call(
|
||||
mock_get,
|
||||
'https://1.2.3.4/v1/api/ports/33', False, None,
|
||||
method_headers,
|
||||
BaseClientTestCase.ca_file)
|
||||
|
||||
@mock.patch("%s.%s" % (CLIENT_PKG, 'requests.Session.get'))
|
||||
@mock.patch(CLIENT_PKG + '.RESTClient._validate_result')
|
||||
def test_client_for(self, mock_validate, mock_get):
|
||||
api = self.new_client(client.RESTClient, url_prefix='api/v1/')
|
||||
sub_api = api.new_client_for('switch/ports')
|
||||
sub_api.get('11a2b')
|
||||
|
||||
assert_session_call(
|
||||
mock_get,
|
||||
'https://1.2.3.4/api/v1/switch/ports/11a2b',
|
||||
False, None, {}, BaseClientTestCase.ca_file)
|
||||
|
||||
@mock.patch("%s.%s" % (CLIENT_PKG, 'requests.Session.get'))
|
||||
@mock.patch(CLIENT_PKG + '.RESTClient._validate_result')
|
||||
def test_client_list(self, mock_validate, mock_get):
|
||||
api = self.new_client(client.RESTClient, url_prefix='api/v1/ports')
|
||||
api.list()
|
||||
|
||||
assert_session_call(
|
||||
mock_get,
|
||||
'https://1.2.3.4/api/v1/ports',
|
||||
False, None, {}, BaseClientTestCase.ca_file)
|
||||
|
||||
@mock.patch("%s.%s" % (CLIENT_PKG, 'requests.Session.get'))
|
||||
@mock.patch(CLIENT_PKG + '.RESTClient._validate_result')
|
||||
def test_client_get(self, mock_validate, mock_get):
|
||||
api = self.new_client(client.RESTClient, url_prefix='api/v1/ports')
|
||||
api.get('unique-id')
|
||||
|
||||
assert_session_call(
|
||||
mock_get,
|
||||
'https://1.2.3.4/api/v1/ports/unique-id',
|
||||
False, None, {}, BaseClientTestCase.ca_file)
|
||||
|
||||
@mock.patch("%s.%s" % (CLIENT_PKG, 'requests.Session.delete'))
|
||||
@mock.patch(CLIENT_PKG + '.RESTClient._validate_result')
|
||||
def test_client_delete(self, mock_validate, mock_delete):
|
||||
api = self.new_client(client.RESTClient, url_prefix='api/v1/ports')
|
||||
api.delete('unique-id')
|
||||
|
||||
assert_session_call(
|
||||
mock_delete,
|
||||
'https://1.2.3.4/api/v1/ports/unique-id',
|
||||
False, None, {}, BaseClientTestCase.ca_file)
|
||||
|
||||
@mock.patch("%s.%s" % (CLIENT_PKG, 'requests.Session.put'))
|
||||
@mock.patch(CLIENT_PKG + '.RESTClient._validate_result')
|
||||
def test_client_update(self, mock_validate, mock_put):
|
||||
api = self.new_client(client.RESTClient, url_prefix='api/v1/ports')
|
||||
api.update('unique-id', {'name': 'a-new-name'})
|
||||
|
||||
assert_session_call(
|
||||
mock_put,
|
||||
'https://1.2.3.4/api/v1/ports/unique-id',
|
||||
False, {'name': 'a-new-name'},
|
||||
{}, BaseClientTestCase.ca_file)
|
||||
|
||||
@mock.patch("%s.%s" % (CLIENT_PKG, 'requests.Session.post'))
|
||||
@mock.patch(CLIENT_PKG + '.RESTClient._validate_result')
|
||||
def test_client_create(self, mock_validate, mock_post):
|
||||
api = self.new_client(client.RESTClient, url_prefix='api/v1/ports')
|
||||
api.create({'resource-name': 'port1'})
|
||||
|
||||
assert_session_call(
|
||||
mock_post,
|
||||
'https://1.2.3.4/api/v1/ports',
|
||||
False, {'resource-name': 'port1'},
|
||||
{}, BaseClientTestCase.ca_file)
|
||||
|
||||
@mock.patch("%s.%s" % (CLIENT_PKG, 'requests.Session.get'))
|
||||
@mock.patch(CLIENT_PKG + '.RESTClient._validate_result')
|
||||
def test_client_url_list(self, mock_validate, mock_get):
|
||||
api = self.new_client(client.RESTClient, url_prefix='api/v1/ports')
|
||||
api.url_list('/connections', {'Content-Type': 'application/json'})
|
||||
|
||||
assert_session_call(
|
||||
mock_get,
|
||||
'https://1.2.3.4/api/v1/ports/connections',
|
||||
False, None,
|
||||
{'Content-Type': 'application/json'},
|
||||
BaseClientTestCase.ca_file)
|
||||
|
||||
@mock.patch("%s.%s" % (CLIENT_PKG, 'requests.Session.get'))
|
||||
@mock.patch(CLIENT_PKG + '.RESTClient._validate_result')
|
||||
def test_client_url_get(self, mock_validate, mock_get):
|
||||
api = self.new_client(client.RESTClient, url_prefix='api/v1/ports')
|
||||
api.url_get('connections/1')
|
||||
|
||||
assert_session_call(
|
||||
mock_get,
|
||||
'https://1.2.3.4/api/v1/ports/connections/1',
|
||||
False, None, {}, BaseClientTestCase.ca_file)
|
||||
|
||||
@mock.patch("%s.%s" % (CLIENT_PKG, 'requests.Session.delete'))
|
||||
@mock.patch(CLIENT_PKG + '.RESTClient._validate_result')
|
||||
def test_client_url_delete(self, mock_validate, mock_delete):
|
||||
api = self.new_client(client.RESTClient, url_prefix='api/v1/ports')
|
||||
api.url_delete('1')
|
||||
|
||||
assert_session_call(
|
||||
mock_delete,
|
||||
'https://1.2.3.4/api/v1/ports/1',
|
||||
False, None, {}, BaseClientTestCase.ca_file)
|
||||
|
||||
@mock.patch("%s.%s" % (CLIENT_PKG, 'requests.Session.put'))
|
||||
@mock.patch(CLIENT_PKG + '.RESTClient._validate_result')
|
||||
def test_client_url_put(self, mock_validate, mock_put):
|
||||
api = self.new_client(client.RESTClient, url_prefix='api/v1/ports')
|
||||
api.url_put('connections/1', {'name': 'conn1'})
|
||||
|
||||
assert_session_call(
|
||||
mock_put,
|
||||
'https://1.2.3.4/api/v1/ports/connections/1',
|
||||
False, {'name': 'conn1'},
|
||||
{}, BaseClientTestCase.ca_file)
|
||||
|
||||
@mock.patch("%s.%s" % (CLIENT_PKG, 'requests.Session.post'))
|
||||
@mock.patch(CLIENT_PKG + '.RESTClient._validate_result')
|
||||
def test_client_url_post(self, mock_validate, mock_post):
|
||||
api = self.new_client(client.RESTClient, url_prefix='api/v1/ports')
|
||||
api.url_post('1/connections', {'name': 'conn1'})
|
||||
|
||||
assert_session_call(
|
||||
mock_post,
|
||||
'https://1.2.3.4/api/v1/ports/1/connections',
|
||||
False, {'name': 'conn1'},
|
||||
{}, BaseClientTestCase.ca_file)
|
||||
|
||||
@mock.patch("%s.%s" % (CLIENT_PKG, 'requests.Session.get'))
|
||||
@mock.patch("%s.%s" % (CLIENT_PKG, 'requests.Session.put'))
|
||||
@mock.patch("%s.%s" % (CLIENT_PKG, 'requests.Session.post'))
|
||||
@mock.patch("%s.%s" % (CLIENT_PKG, 'requests.Session.delete'))
|
||||
def test_client_validate_result(self, *args):
|
||||
|
||||
def _verb_response_code(http_verb, status_code):
|
||||
response = mocks.MockRequestsResponse(status_code, None)
|
||||
api = self.new_client(client.RESTClient)
|
||||
for mocked in args:
|
||||
mocked.return_value = response
|
||||
client_call = getattr(api, "url_%s" % http_verb)
|
||||
client_call('', None)
|
||||
|
||||
for verb in ['get', 'post', 'put', 'delete']:
|
||||
for code in client.RESTClient._VERB_RESP_CODES.get(verb):
|
||||
_verb_response_code(verb, code)
|
||||
self.assertRaises(
|
||||
exep.ManagerError, _verb_response_code, verb, 500)
|
||||
|
||||
|
||||
class NsxV3JSONClientTestCase(BaseClientTestCase):
|
||||
|
||||
@mock.patch("%s.%s" % (CLIENT_PKG, 'requests.Session.post'))
|
||||
@mock.patch(CLIENT_PKG + '.RESTClient._validate_result')
|
||||
def test_json_request(self, mock_validate, mock_post):
|
||||
mock_post.return_value = mocks.MockRequestsResponse(
|
||||
200, jsonutils.dumps({'result': {'ok': 200}}))
|
||||
|
||||
api = self.new_client(client.JSONRESTClient, url_prefix='api/v2/nat')
|
||||
resp = api.create(body={'name': 'mgmt-egress'})
|
||||
|
||||
assert_session_call(
|
||||
mock_post,
|
||||
'https://1.2.3.4/api/v2/nat',
|
||||
False, jsonutils.dumps({'name': 'mgmt-egress'}),
|
||||
client.JSONRESTClient._DEFAULT_HEADERS,
|
||||
BaseClientTestCase.ca_file)
|
||||
|
||||
self.assertEqual(resp, {'result': {'ok': 200}})
|
||||
|
||||
|
||||
class NsxV3APIClientTestCase(BaseClientTestCase):
|
||||
|
||||
@mock.patch("%s.%s" % (CLIENT_PKG, 'requests.Session.get'))
|
||||
@mock.patch(CLIENT_PKG + '.RESTClient._validate_result')
|
||||
def test_api_call(self, mock_validate, mock_get):
|
||||
api = self.new_client(client.NSX3Client)
|
||||
api.get('ports')
|
||||
|
||||
assert_session_call(
|
||||
mock_get,
|
||||
'https://1.2.3.4/api/v1/ports',
|
||||
False, None,
|
||||
client.JSONRESTClient._DEFAULT_HEADERS,
|
||||
NsxV3APIClientTestCase.ca_file)
|
||||
|
||||
|
||||
# NOTE(boden): remove this when tmp brigding removed
|
||||
class NsxV3APIClientBridgeTestCase(BaseClientTestCase):
|
||||
|
||||
@mock.patch("%s.%s" % (CLIENT_PKG, 'requests.Session.get'))
|
||||
@mock.patch(CLIENT_PKG + '.RESTClient._validate_result')
|
||||
def test_get_resource(self, mock_validate, mock_get):
|
||||
client.get_resource('ports')
|
||||
|
||||
assert_session_call(
|
||||
mock_get,
|
||||
'https://1.2.3.4/api/v1/ports',
|
||||
False, None,
|
||||
client.JSONRESTClient._DEFAULT_HEADERS,
|
||||
NsxV3APIClientTestCase.ca_file)
|
||||
|
||||
@mock.patch("%s.%s" % (CLIENT_PKG, 'requests.Session.post'))
|
||||
@mock.patch(CLIENT_PKG + '.RESTClient._validate_result')
|
||||
def test_create_resource(self, mock_validate, mock_post):
|
||||
client.create_resource('ports', {'resource-name': 'port1'})
|
||||
|
||||
assert_session_call(
|
||||
mock_post,
|
||||
'https://1.2.3.4/api/v1/ports',
|
||||
False, jsonutils.dumps({'resource-name': 'port1'}),
|
||||
client.JSONRESTClient._DEFAULT_HEADERS,
|
||||
BaseClientTestCase.ca_file)
|
||||
|
||||
@mock.patch("%s.%s" % (CLIENT_PKG, 'requests.Session.put'))
|
||||
@mock.patch(CLIENT_PKG + '.RESTClient._validate_result')
|
||||
def test_update_resource(self, mock_validate, mock_put):
|
||||
client.update_resource('ports/1', {'name': 'a-new-name'})
|
||||
|
||||
assert_session_call(
|
||||
mock_put,
|
||||
'https://1.2.3.4/api/v1/ports/1',
|
||||
False, jsonutils.dumps({'name': 'a-new-name'}),
|
||||
client.JSONRESTClient._DEFAULT_HEADERS,
|
||||
BaseClientTestCase.ca_file)
|
||||
|
||||
@mock.patch("%s.%s" % (CLIENT_PKG, 'requests.Session.delete'))
|
||||
@mock.patch(CLIENT_PKG + '.RESTClient._validate_result')
|
||||
def test_delete_resource(self, mock_validate, mock_delete):
|
||||
client.delete_resource('ports/11')
|
||||
|
||||
assert_session_call(
|
||||
mock_delete,
|
||||
'https://1.2.3.4/api/v1/ports/11',
|
||||
False, None, client.JSONRESTClient._DEFAULT_HEADERS,
|
||||
BaseClientTestCase.ca_file)
|
144
vmware_nsx/tests/unit/vmware/nsxlib/v3/test_resources.py
Normal file
144
vmware_nsx/tests/unit/vmware/nsxlib/v3/test_resources.py
Normal file
@ -0,0 +1,144 @@
|
||||
# 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 mock
|
||||
import vmware_nsx.tests.unit.vmware.nsx_v3_mocks as mocks
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
from vmware_nsx.nsxlib.v3 import client
|
||||
from vmware_nsx.nsxlib.v3 import resources
|
||||
from vmware_nsx.tests.unit.vmware.nsxlib.v3 import test_client
|
||||
|
||||
|
||||
CLIENT_PKG = test_client.CLIENT_PKG
|
||||
profile_types = resources.SwitchingProfileTypes
|
||||
|
||||
|
||||
class TestSwitchingProfileTestCase(test_client.BaseClientTestCase):
|
||||
|
||||
@mock.patch("%s.%s" % (CLIENT_PKG, 'requests.Session.post'))
|
||||
@mock.patch(CLIENT_PKG + '.RESTClient._validate_result')
|
||||
def test_switching_profile_create(self, mock_validate, mock_post):
|
||||
api = resources.SwitchingProfile(client.NSX3Client())
|
||||
api.create(profile_types.PORT_MIRRORING,
|
||||
'pm-profile', 'port mirror prof')
|
||||
|
||||
test_client.assert_session_call(
|
||||
mock_post,
|
||||
'https://1.2.3.4/api/v1/switching-profiles',
|
||||
False, jsonutils.dumps({
|
||||
'resource_type': profile_types.PORT_MIRRORING,
|
||||
'display_name': 'pm-profile',
|
||||
'description': 'port mirror prof'
|
||||
}),
|
||||
client.JSONRESTClient._DEFAULT_HEADERS,
|
||||
test_client.BaseClientTestCase.ca_file)
|
||||
|
||||
@mock.patch("%s.%s" % (CLIENT_PKG, 'requests.Session.put'))
|
||||
@mock.patch(CLIENT_PKG + '.RESTClient._validate_result')
|
||||
def test_switching_profile_update(self, mock_validate, mock_put):
|
||||
|
||||
tags = [
|
||||
{
|
||||
'scope': 'os-tid',
|
||||
'tag': 'tenant-1'
|
||||
},
|
||||
{
|
||||
'scope': 'os-api-version',
|
||||
'tag': '2.1.1.0'
|
||||
}
|
||||
]
|
||||
|
||||
api = resources.SwitchingProfile(client.NSX3Client())
|
||||
api.update('a12bc1', profile_types.PORT_MIRRORING, tags=tags)
|
||||
|
||||
test_client.assert_session_call(
|
||||
mock_put,
|
||||
'https://1.2.3.4/api/v1/switching-profiles/a12bc1',
|
||||
False, jsonutils.dumps({
|
||||
'resource_type': profile_types.PORT_MIRRORING,
|
||||
'tags': tags
|
||||
}),
|
||||
client.JSONRESTClient._DEFAULT_HEADERS,
|
||||
test_client.BaseClientTestCase.ca_file)
|
||||
|
||||
@mock.patch("%s.%s" % (CLIENT_PKG, 'requests.Session.post'))
|
||||
@mock.patch(CLIENT_PKG + '.RESTClient._validate_result')
|
||||
def test_spoofgaurd_profile_create(self, mock_validate, mock_post):
|
||||
|
||||
tags = [
|
||||
{
|
||||
'scope': 'os-tid',
|
||||
'tag': 'tenant-1'
|
||||
},
|
||||
{
|
||||
'scope': 'os-api-version',
|
||||
'tag': '2.1.1.0'
|
||||
}
|
||||
]
|
||||
|
||||
api = resources.SwitchingProfile(client.NSX3Client())
|
||||
api.create_spoofguard_profile(
|
||||
'neutron-spoof', 'spoofguard-for-neutron',
|
||||
whitelist_ports=True, tags=tags)
|
||||
|
||||
test_client.assert_session_call(
|
||||
mock_post,
|
||||
'https://1.2.3.4/api/v1/switching-profiles',
|
||||
False,
|
||||
jsonutils.dumps({
|
||||
'resource_type': profile_types.SPOOF_GUARD,
|
||||
'display_name': 'neutron-spoof',
|
||||
'description': 'spoofguard-for-neutron',
|
||||
'white_list_providers': ['LPORT_BINDINGS'],
|
||||
'tags': tags
|
||||
}),
|
||||
client.JSONRESTClient._DEFAULT_HEADERS,
|
||||
test_client.BaseClientTestCase.ca_file)
|
||||
|
||||
@mock.patch("%s.%s" % (CLIENT_PKG, 'requests.Session.get'))
|
||||
@mock.patch(CLIENT_PKG + '.RESTClient._validate_result')
|
||||
def test_find_by_display_name(self, mock_validate, mock_get):
|
||||
resp_resources = {
|
||||
'results': [
|
||||
{'display_name': 'resource-1'},
|
||||
{'display_name': 'resource-2'},
|
||||
{'display_name': 'resource-3'}
|
||||
]
|
||||
}
|
||||
mock_get.return_value = mocks.MockRequestsResponse(
|
||||
200, jsonutils.dumps(resp_resources))
|
||||
api = resources.SwitchingProfile(client.NSX3Client())
|
||||
self.assertEqual([{'display_name': 'resource-1'}],
|
||||
api.find_by_display_name('resource-1'))
|
||||
self.assertEqual([{'display_name': 'resource-2'}],
|
||||
api.find_by_display_name('resource-2'))
|
||||
self.assertEqual([{'display_name': 'resource-3'}],
|
||||
api.find_by_display_name('resource-3'))
|
||||
|
||||
mock_get.reset_mock()
|
||||
|
||||
resp_resources = {
|
||||
'results': [
|
||||
{'display_name': 'resource-1'},
|
||||
{'display_name': 'resource-1'},
|
||||
{'display_name': 'resource-1'}
|
||||
]
|
||||
}
|
||||
mock_get.return_value = mocks.MockRequestsResponse(
|
||||
200, jsonutils.dumps(resp_resources))
|
||||
api = resources.SwitchingProfile(client.NSX3Client())
|
||||
self.assertEqual(resp_resources['results'],
|
||||
api.find_by_display_name('resource-1'))
|
Loading…
x
Reference in New Issue
Block a user