nsx v3 port security plumbing
this patch includes some plumbing for the nsx v3 port security port which will come in subsequent patches. in particular this patch refactors the nsx v3 nsxlib rest client and adds a rest resource for nsx v3 switching profiles which will be leveraged in port security implementation. unit tests included. Change-Id: I210c8cc495bf30c798445c20f9604689fea37521
This commit is contained in:
parent
dba21e7045
commit
a7c909536e
@ -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
|
||||
@ -408,3 +407,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