Opencontrail plug-in implementation for core resources
Adds Opencontrail plug-in implementation with unit tests This patch has no dependency on any other blueprints The link below describes how to install VIF driver for opencontrail https://github.com/Juniper/contrail-controller/wiki/ OpenContrail-bring-up-and-provisioning - The contrail_plugin_core.py is the main interface for neutron common infrastructure. It relays API requests to the opencontrail controller DocImpact Change-Id: I501bf669b2a999a171f9a3ee3e9893d4ead50e3b Implements: blueprint juniper-plugin-with-extensions
This commit is contained in:
parent
266ab8ddaf
commit
5f47367d30
26
etc/neutron/plugins/opencontrail/contrailplugin.ini
Normal file
26
etc/neutron/plugins/opencontrail/contrailplugin.ini
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# OpenContrail is an Apache 2.0-licensed project that is built using
|
||||||
|
# standards-based protocols and provides all the necessary components for
|
||||||
|
# network virtualization–SDN controller, virtual router, analytics engine,
|
||||||
|
# and published northbound APIs
|
||||||
|
# For more information visit: http://opencontrail.org
|
||||||
|
|
||||||
|
# Opencontrail plugin specific configuration
|
||||||
|
[CONTRAIL]
|
||||||
|
# (StrOpt) IP address to connect to opencontrail controller.
|
||||||
|
# Uncomment this line for specifying the IP address of the opencontrail
|
||||||
|
# Api-Server.
|
||||||
|
# Default value is local host(127.0.0.1).
|
||||||
|
# api_server_ip='127.0.0.1'
|
||||||
|
|
||||||
|
# (IntOpt) port to connect to opencontrail controller.
|
||||||
|
# Uncomment this line for the specifying the Port of the opencontrail
|
||||||
|
# Api-Server.
|
||||||
|
# Default value is 8082
|
||||||
|
# api_server_port=8082
|
||||||
|
|
||||||
|
# (DictOpt) enable opencontrail extensions
|
||||||
|
# Opencontrail in future would support extension such as ipam, policy,
|
||||||
|
# these extensions can be configured as shown below. Plugin will then
|
||||||
|
# load the specified extensions.
|
||||||
|
# Default value is None, it wont load any extension
|
||||||
|
# contrail_extensions=ipam:<classpath>,policy:<classpath>
|
@ -62,12 +62,14 @@ VIF_TYPE_MIDONET = 'midonet'
|
|||||||
VIF_TYPE_MLNX_DIRECT = 'mlnx_direct'
|
VIF_TYPE_MLNX_DIRECT = 'mlnx_direct'
|
||||||
VIF_TYPE_MLNX_HOSTDEV = 'hostdev'
|
VIF_TYPE_MLNX_HOSTDEV = 'hostdev'
|
||||||
VIF_TYPE_HW_VEB = 'hw_veb'
|
VIF_TYPE_HW_VEB = 'hw_veb'
|
||||||
|
VIF_TYPE_VROUTER = 'vrouter'
|
||||||
VIF_TYPE_OTHER = 'other'
|
VIF_TYPE_OTHER = 'other'
|
||||||
VIF_TYPES = [VIF_TYPE_UNBOUND, VIF_TYPE_BINDING_FAILED, VIF_TYPE_OVS,
|
VIF_TYPES = [VIF_TYPE_UNBOUND, VIF_TYPE_BINDING_FAILED, VIF_TYPE_OVS,
|
||||||
VIF_TYPE_IVS, VIF_TYPE_BRIDGE, VIF_TYPE_802_QBG,
|
VIF_TYPE_IVS, VIF_TYPE_BRIDGE, VIF_TYPE_802_QBG,
|
||||||
VIF_TYPE_802_QBH, VIF_TYPE_HYPERV, VIF_TYPE_MIDONET,
|
VIF_TYPE_802_QBH, VIF_TYPE_HYPERV, VIF_TYPE_MIDONET,
|
||||||
VIF_TYPE_MLNX_DIRECT, VIF_TYPE_MLNX_HOSTDEV, VIF_TYPE_HW_VEB,
|
VIF_TYPE_MLNX_DIRECT, VIF_TYPE_MLNX_HOSTDEV, VIF_TYPE_HW_VEB,
|
||||||
VIF_TYPE_DVS, VIF_TYPE_OTHER, VIF_TYPE_DISTRIBUTED]
|
VIF_TYPE_DVS, VIF_TYPE_OTHER, VIF_TYPE_DISTRIBUTED,
|
||||||
|
VIF_TYPE_VROUTER]
|
||||||
|
|
||||||
VNIC_NORMAL = 'normal'
|
VNIC_NORMAL = 'normal'
|
||||||
VNIC_DIRECT = 'direct'
|
VNIC_DIRECT = 'direct'
|
||||||
|
0
neutron/plugins/opencontrail/__init__.py
Normal file
0
neutron/plugins/opencontrail/__init__.py
Normal file
0
neutron/plugins/opencontrail/common/__init__.py
Normal file
0
neutron/plugins/opencontrail/common/__init__.py
Normal file
40
neutron/plugins/opencontrail/common/exceptions.py
Normal file
40
neutron/plugins/opencontrail/common/exceptions.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# Copyright 2014 Juniper Networks. 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
from neutron.common import exceptions as exc
|
||||||
|
|
||||||
|
|
||||||
|
class ContrailError(exc.NeutronException):
|
||||||
|
message = '%(msg)s'
|
||||||
|
|
||||||
|
|
||||||
|
class ContrailNotFoundError(exc.NotFound):
|
||||||
|
message = '%(msg)s'
|
||||||
|
|
||||||
|
|
||||||
|
class ContrailConflictError(exc.Conflict):
|
||||||
|
message = '%(msg)s'
|
||||||
|
|
||||||
|
|
||||||
|
class ContrailBadRequestError(exc.BadRequest):
|
||||||
|
message = '%(msg)s'
|
||||||
|
|
||||||
|
|
||||||
|
class ContrailServiceUnavailableError(exc.ServiceUnavailable):
|
||||||
|
message = '%(msg)s'
|
||||||
|
|
||||||
|
|
||||||
|
class ContrailNotAuthorizedError(exc.NotAuthorized):
|
||||||
|
message = '%(msg)s'
|
622
neutron/plugins/opencontrail/contrail_plugin.py
Normal file
622
neutron/plugins/opencontrail/contrail_plugin.py
Normal file
@ -0,0 +1,622 @@
|
|||||||
|
# Copyright 2014 Juniper Networks. 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
from oslo.config import cfg
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from neutron.api.v2 import attributes as attr
|
||||||
|
from neutron.common import exceptions as exc
|
||||||
|
from neutron.db import portbindings_base
|
||||||
|
from neutron.extensions import external_net
|
||||||
|
from neutron.extensions import portbindings
|
||||||
|
from neutron.extensions import securitygroup
|
||||||
|
from neutron import neutron_plugin_base_v2
|
||||||
|
from neutron.openstack.common import jsonutils
|
||||||
|
from neutron.openstack.common import log as logging
|
||||||
|
from neutron.plugins.opencontrail.common import exceptions as c_exc
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
opencontrail_opts = [
|
||||||
|
cfg.StrOpt('api_server_ip', default='127.0.0.1',
|
||||||
|
help='IP address to connect to opencontrail controller'),
|
||||||
|
cfg.IntOpt('api_server_port', default=8082,
|
||||||
|
help='Port to connect to opencontrail controller'),
|
||||||
|
]
|
||||||
|
|
||||||
|
cfg.CONF.register_opts(opencontrail_opts, 'CONTRAIL')
|
||||||
|
|
||||||
|
CONTRAIL_EXCEPTION_MAP = {
|
||||||
|
requests.codes.not_found: c_exc.ContrailNotFoundError,
|
||||||
|
requests.codes.conflict: c_exc.ContrailConflictError,
|
||||||
|
requests.codes.bad_request: c_exc.ContrailBadRequestError,
|
||||||
|
requests.codes.service_unavailable: c_exc.ContrailServiceUnavailableError,
|
||||||
|
requests.codes.unauthorized: c_exc.ContrailNotAuthorizedError,
|
||||||
|
requests.codes.internal_server_error: c_exc.ContrailError,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class NeutronPluginContrailCoreV2(neutron_plugin_base_v2.NeutronPluginBaseV2,
|
||||||
|
securitygroup.SecurityGroupPluginBase,
|
||||||
|
portbindings_base.PortBindingBaseMixin,
|
||||||
|
external_net.External_net):
|
||||||
|
|
||||||
|
supported_extension_aliases = ["security-group", "router",
|
||||||
|
"port-security", "binding", "agent",
|
||||||
|
"quotas", "external-net"]
|
||||||
|
PLUGIN_URL_PREFIX = '/neutron'
|
||||||
|
__native_bulk_support = False
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialize the plugin class."""
|
||||||
|
|
||||||
|
super(NeutronPluginContrailCoreV2, self).__init__()
|
||||||
|
portbindings_base.register_port_dict_function()
|
||||||
|
self.base_binding_dict = self._get_base_binding_dict()
|
||||||
|
|
||||||
|
def _get_base_binding_dict(self):
|
||||||
|
"""return VIF type and details."""
|
||||||
|
|
||||||
|
binding = {
|
||||||
|
portbindings.VIF_TYPE: portbindings.VIF_TYPE_VROUTER,
|
||||||
|
portbindings.VIF_DETAILS: {
|
||||||
|
# TODO(praneetb): Replace with new VIF security details
|
||||||
|
portbindings.CAP_PORT_FILTER:
|
||||||
|
'security-group' in self.supported_extension_aliases
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return binding
|
||||||
|
|
||||||
|
def _request_api_server(self, url, data=None, headers=None):
|
||||||
|
"""Send received request to api server."""
|
||||||
|
|
||||||
|
return requests.post(url, data=data, headers=headers)
|
||||||
|
|
||||||
|
def _relay_request(self, url_path, data=None):
|
||||||
|
"""Send received request to api server."""
|
||||||
|
|
||||||
|
url = "http://%s:%d%s" % (cfg.CONF.CONTRAIL.api_server_ip,
|
||||||
|
cfg.CONF.CONTRAIL.api_server_port,
|
||||||
|
url_path)
|
||||||
|
|
||||||
|
return self._request_api_server(
|
||||||
|
url, data=data, headers={'Content-type': 'application/json'})
|
||||||
|
|
||||||
|
def _request_backend(self, context, data_dict, obj_name, action):
|
||||||
|
"""Relays request to the controller."""
|
||||||
|
|
||||||
|
context_dict = self._encode_context(context, action, obj_name)
|
||||||
|
data = jsonutils.dumps({'context': context_dict, 'data': data_dict})
|
||||||
|
|
||||||
|
url_path = "%s/%s" % (self.PLUGIN_URL_PREFIX, obj_name)
|
||||||
|
response = self._relay_request(url_path, data=data)
|
||||||
|
if response.content:
|
||||||
|
return response.status_code, response.json()
|
||||||
|
else:
|
||||||
|
return response.status_code, response.content
|
||||||
|
|
||||||
|
def _encode_context(self, context, operation, apitype):
|
||||||
|
"""Encode the context to be sent to the controller."""
|
||||||
|
|
||||||
|
cdict = {'user_id': getattr(context, 'user_id', ''),
|
||||||
|
'is_admin': getattr(context, 'is_admin', False),
|
||||||
|
'operation': operation,
|
||||||
|
'type': apitype,
|
||||||
|
'tenant_id': getattr(context, 'tenant_id', None)}
|
||||||
|
if context.roles:
|
||||||
|
cdict['roles'] = context.roles
|
||||||
|
if context.tenant:
|
||||||
|
cdict['tenant'] = context.tenant
|
||||||
|
return cdict
|
||||||
|
|
||||||
|
def _encode_resource(self, resource_id=None, resource=None, fields=None,
|
||||||
|
filters=None):
|
||||||
|
"""Encode a resource to be sent to the controller."""
|
||||||
|
|
||||||
|
resource_dict = {}
|
||||||
|
if resource_id:
|
||||||
|
resource_dict['id'] = resource_id
|
||||||
|
if resource:
|
||||||
|
resource_dict['resource'] = resource
|
||||||
|
resource_dict['filters'] = filters
|
||||||
|
resource_dict['fields'] = fields
|
||||||
|
return resource_dict
|
||||||
|
|
||||||
|
def _prune(self, resource_dict, fields):
|
||||||
|
"""Prune the resource dictionary based in the fields."""
|
||||||
|
|
||||||
|
if fields:
|
||||||
|
return dict(((key, item) for key, item in resource_dict.items()
|
||||||
|
if key in fields))
|
||||||
|
return resource_dict
|
||||||
|
|
||||||
|
def _transform_response(self, status_code, info=None, obj_name=None,
|
||||||
|
fields=None):
|
||||||
|
"""Transform the response for a Resource API."""
|
||||||
|
|
||||||
|
if status_code == requests.codes.ok:
|
||||||
|
if not isinstance(info, list):
|
||||||
|
return self._prune(info, fields)
|
||||||
|
else:
|
||||||
|
return [self._prune(items, fields) for items in info]
|
||||||
|
self._raise_contrail_error(status_code, info, obj_name)
|
||||||
|
|
||||||
|
def _raise_contrail_error(self, status_code, info, obj_name):
|
||||||
|
"""Raises an error in handling of a Resource.
|
||||||
|
|
||||||
|
This method converts return error code into neutron exception.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if status_code == requests.codes.bad_request:
|
||||||
|
raise c_exc.ContrailBadRequestError(
|
||||||
|
msg=info['message'], resource=obj_name)
|
||||||
|
error_class = CONTRAIL_EXCEPTION_MAP.get(status_code,
|
||||||
|
c_exc.ContrailError)
|
||||||
|
raise error_class(msg=info['message'])
|
||||||
|
|
||||||
|
def _create_resource(self, res_type, context, res_data):
|
||||||
|
"""Create a resource in API server.
|
||||||
|
|
||||||
|
This method encodes neutron model, and sends it to the
|
||||||
|
contrail api server.
|
||||||
|
"""
|
||||||
|
|
||||||
|
for key, value in res_data[res_type].items():
|
||||||
|
if value == attr.ATTR_NOT_SPECIFIED:
|
||||||
|
res_data[res_type][key] = None
|
||||||
|
|
||||||
|
res_dict = self._encode_resource(resource=res_data[res_type])
|
||||||
|
status_code, res_info = self._request_backend(context, res_dict,
|
||||||
|
res_type, 'CREATE')
|
||||||
|
res_dicts = self._transform_response(status_code, info=res_info,
|
||||||
|
obj_name=res_type)
|
||||||
|
LOG.debug("create_%(res_type)s(): %(res_dicts)s",
|
||||||
|
{'res_type': res_type, 'res_dicts': res_dicts})
|
||||||
|
|
||||||
|
return res_dicts
|
||||||
|
|
||||||
|
def _get_resource(self, res_type, context, res_id, fields):
|
||||||
|
"""Get a resource from API server.
|
||||||
|
|
||||||
|
This method gets a resource from the contrail api server
|
||||||
|
"""
|
||||||
|
|
||||||
|
res_dict = self._encode_resource(resource_id=res_id, fields=fields)
|
||||||
|
status_code, res_info = self._request_backend(context, res_dict,
|
||||||
|
res_type, 'READ')
|
||||||
|
res_dicts = self._transform_response(status_code, info=res_info,
|
||||||
|
fields=fields, obj_name=res_type)
|
||||||
|
LOG.debug("get_%(res_type)s(): %(res_dicts)s",
|
||||||
|
{'res_type': res_type, 'res_dicts': res_dicts})
|
||||||
|
|
||||||
|
return res_dicts
|
||||||
|
|
||||||
|
def _update_resource(self, res_type, context, res_id, res_data):
|
||||||
|
"""Update a resource in API server.
|
||||||
|
|
||||||
|
This method updates a resource in the contrail api server
|
||||||
|
"""
|
||||||
|
|
||||||
|
res_dict = self._encode_resource(resource_id=res_id,
|
||||||
|
resource=res_data[res_type])
|
||||||
|
status_code, res_info = self._request_backend(context, res_dict,
|
||||||
|
res_type, 'UPDATE')
|
||||||
|
res_dicts = self._transform_response(status_code, info=res_info,
|
||||||
|
obj_name=res_type)
|
||||||
|
LOG.debug("update_%(res_type)s(): %(res_dicts)s",
|
||||||
|
{'res_type': res_type, 'res_dicts': res_dicts})
|
||||||
|
|
||||||
|
return res_dicts
|
||||||
|
|
||||||
|
def _delete_resource(self, res_type, context, res_id):
|
||||||
|
"""Delete a resource in API server
|
||||||
|
|
||||||
|
This method deletes a resource in the contrail api server
|
||||||
|
"""
|
||||||
|
|
||||||
|
res_dict = self._encode_resource(resource_id=res_id)
|
||||||
|
LOG.debug("delete_%(res_type)s(): %(res_id)s",
|
||||||
|
{'res_type': res_type, 'res_id': res_id})
|
||||||
|
status_code, res_info = self._request_backend(context, res_dict,
|
||||||
|
res_type, 'DELETE')
|
||||||
|
if status_code != requests.codes.ok:
|
||||||
|
self._raise_contrail_error(status_code, info=res_info,
|
||||||
|
obj_name=res_type)
|
||||||
|
|
||||||
|
def _list_resource(self, res_type, context, filters, fields):
|
||||||
|
"""Get the list of a Resource."""
|
||||||
|
|
||||||
|
res_dict = self._encode_resource(filters=filters, fields=fields)
|
||||||
|
status_code, res_info = self._request_backend(context, res_dict,
|
||||||
|
res_type, 'READALL')
|
||||||
|
res_dicts = self._transform_response(status_code, info=res_info,
|
||||||
|
fields=fields, obj_name=res_type)
|
||||||
|
LOG.debug(
|
||||||
|
"get_%(res_type)s(): filters: %(filters)r data: %(res_dicts)r",
|
||||||
|
{'res_type': res_type, 'filters': filters,
|
||||||
|
'res_dicts': res_dicts})
|
||||||
|
|
||||||
|
return res_dicts
|
||||||
|
|
||||||
|
def _count_resource(self, res_type, context, filters):
|
||||||
|
"""Get the count of a Resource."""
|
||||||
|
|
||||||
|
res_dict = self._encode_resource(filters=filters)
|
||||||
|
_, res_count = self._request_backend(context, res_dict, res_type,
|
||||||
|
'READCOUNT')
|
||||||
|
LOG.debug("get_%(res_type)s_count(): %(res_count)r",
|
||||||
|
{'res_type': res_type, 'res_count': res_count})
|
||||||
|
return res_count
|
||||||
|
|
||||||
|
def _get_network(self, context, res_id, fields=None):
|
||||||
|
"""Get the attributes of a Virtual Network."""
|
||||||
|
|
||||||
|
return self._get_resource('network', context, res_id, fields)
|
||||||
|
|
||||||
|
def create_network(self, context, network):
|
||||||
|
"""Creates a new Virtual Network."""
|
||||||
|
|
||||||
|
return self._create_resource('network', context, network)
|
||||||
|
|
||||||
|
def get_network(self, context, network_id, fields=None):
|
||||||
|
"""Get the attributes of a particular Virtual Network."""
|
||||||
|
|
||||||
|
return self._get_network(context, network_id, fields)
|
||||||
|
|
||||||
|
def update_network(self, context, network_id, network):
|
||||||
|
"""Updates the attributes of a particular Virtual Network."""
|
||||||
|
|
||||||
|
return self._update_resource('network', context, network_id,
|
||||||
|
network)
|
||||||
|
|
||||||
|
def delete_network(self, context, network_id):
|
||||||
|
"""Deletes the network with the specified network identifier."""
|
||||||
|
|
||||||
|
self._delete_resource('network', context, network_id)
|
||||||
|
|
||||||
|
def get_networks(self, context, filters=None, fields=None):
|
||||||
|
"""Get the list of Virtual Networks."""
|
||||||
|
|
||||||
|
return self._list_resource('network', context, filters,
|
||||||
|
fields)
|
||||||
|
|
||||||
|
def get_networks_count(self, context, filters=None):
|
||||||
|
"""Get the count of Virtual Network."""
|
||||||
|
|
||||||
|
networks_count = self._count_resource('network', context, filters)
|
||||||
|
return networks_count['count']
|
||||||
|
|
||||||
|
def create_subnet(self, context, subnet):
|
||||||
|
"""Creates a new subnet, and assigns it a symbolic name."""
|
||||||
|
|
||||||
|
if subnet['subnet']['gateway_ip'] is None:
|
||||||
|
subnet['subnet']['gateway_ip'] = '0.0.0.0'
|
||||||
|
|
||||||
|
if subnet['subnet']['host_routes'] != attr.ATTR_NOT_SPECIFIED:
|
||||||
|
if (len(subnet['subnet']['host_routes']) >
|
||||||
|
cfg.CONF.max_subnet_host_routes):
|
||||||
|
raise exc.HostRoutesExhausted(subnet_id=subnet[
|
||||||
|
'subnet'].get('id', _('new subnet')),
|
||||||
|
quota=cfg.CONF.max_subnet_host_routes)
|
||||||
|
|
||||||
|
subnet_created = self._create_resource('subnet', context, subnet)
|
||||||
|
return self._make_subnet_dict(subnet_created)
|
||||||
|
|
||||||
|
def _make_subnet_dict(self, subnet):
|
||||||
|
"""Fixes subnet attributes."""
|
||||||
|
|
||||||
|
if subnet.get('gateway_ip') == '0.0.0.0':
|
||||||
|
subnet['gateway_ip'] = None
|
||||||
|
return subnet
|
||||||
|
|
||||||
|
def _get_subnet(self, context, subnet_id, fields=None):
|
||||||
|
"""Get the attributes of a subnet."""
|
||||||
|
|
||||||
|
subnet = self._get_resource('subnet', context, subnet_id, fields)
|
||||||
|
return self._make_subnet_dict(subnet)
|
||||||
|
|
||||||
|
def get_subnet(self, context, subnet_id, fields=None):
|
||||||
|
"""Get the attributes of a particular subnet."""
|
||||||
|
|
||||||
|
return self._get_subnet(context, subnet_id, fields)
|
||||||
|
|
||||||
|
def update_subnet(self, context, subnet_id, subnet):
|
||||||
|
"""Updates the attributes of a particular subnet."""
|
||||||
|
|
||||||
|
subnet = self._update_resource('subnet', context, subnet_id, subnet)
|
||||||
|
return self._make_subnet_dict(subnet)
|
||||||
|
|
||||||
|
def delete_subnet(self, context, subnet_id):
|
||||||
|
"""
|
||||||
|
Deletes the subnet with the specified subnet identifier
|
||||||
|
belonging to the specified tenant.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self._delete_resource('subnet', context, subnet_id)
|
||||||
|
|
||||||
|
def get_subnets(self, context, filters=None, fields=None):
|
||||||
|
"""Get the list of subnets."""
|
||||||
|
|
||||||
|
return [self._make_subnet_dict(s)
|
||||||
|
for s in self._list_resource(
|
||||||
|
'subnet', context, filters, fields)]
|
||||||
|
|
||||||
|
def get_subnets_count(self, context, filters=None):
|
||||||
|
"""Get the count of subnets."""
|
||||||
|
|
||||||
|
subnets_count = self._count_resource('subnet', context, filters)
|
||||||
|
return subnets_count['count']
|
||||||
|
|
||||||
|
def _make_port_dict(self, port, fields=None):
|
||||||
|
"""filters attributes of a port based on fields."""
|
||||||
|
|
||||||
|
if not fields:
|
||||||
|
port.update(self.base_binding_dict)
|
||||||
|
else:
|
||||||
|
for key in self.base_binding_dict:
|
||||||
|
if key in fields:
|
||||||
|
port.update(self.base_binding_dict[key])
|
||||||
|
return port
|
||||||
|
|
||||||
|
def _get_port(self, context, res_id, fields=None):
|
||||||
|
"""Get the attributes of a port."""
|
||||||
|
|
||||||
|
port = self._get_resource('port', context, res_id, fields)
|
||||||
|
return self._make_port_dict(port, fields)
|
||||||
|
|
||||||
|
def _update_ips_for_port(self, context, original_ips, new_ips):
|
||||||
|
"""Add or remove IPs from the port."""
|
||||||
|
|
||||||
|
# These ips are still on the port and haven't been removed
|
||||||
|
prev_ips = []
|
||||||
|
|
||||||
|
# the new_ips contain all of the fixed_ips that are to be updated
|
||||||
|
if len(new_ips) > cfg.CONF.max_fixed_ips_per_port:
|
||||||
|
msg = _('Exceeded maximim amount of fixed ips per port')
|
||||||
|
raise exc.InvalidInput(error_message=msg)
|
||||||
|
|
||||||
|
# Remove all of the intersecting elements
|
||||||
|
for original_ip in original_ips[:]:
|
||||||
|
for new_ip in new_ips[:]:
|
||||||
|
if ('ip_address' in new_ip and
|
||||||
|
original_ip['ip_address'] == new_ip['ip_address']):
|
||||||
|
original_ips.remove(original_ip)
|
||||||
|
new_ips.remove(new_ip)
|
||||||
|
prev_ips.append(original_ip)
|
||||||
|
|
||||||
|
return new_ips, prev_ips
|
||||||
|
|
||||||
|
def create_port(self, context, port):
|
||||||
|
"""Creates a port on the specified Virtual Network."""
|
||||||
|
|
||||||
|
port = self._create_resource('port', context, port)
|
||||||
|
return self._make_port_dict(port)
|
||||||
|
|
||||||
|
def get_port(self, context, port_id, fields=None):
|
||||||
|
"""Get the attributes of a particular port."""
|
||||||
|
|
||||||
|
return self._get_port(context, port_id, fields)
|
||||||
|
|
||||||
|
def update_port(self, context, port_id, port):
|
||||||
|
"""Updates a port.
|
||||||
|
|
||||||
|
Updates the attributes of a port on the specified Virtual
|
||||||
|
Network.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if 'fixed_ips' in port['port']:
|
||||||
|
original = self._get_port(context, port_id)
|
||||||
|
added_ips, prev_ips = self._update_ips_for_port(
|
||||||
|
context, original['fixed_ips'], port['port']['fixed_ips'])
|
||||||
|
port['port']['fixed_ips'] = prev_ips + added_ips
|
||||||
|
|
||||||
|
port = self._update_resource('port', context, port_id, port)
|
||||||
|
return self._make_port_dict(port)
|
||||||
|
|
||||||
|
def delete_port(self, context, port_id):
|
||||||
|
"""Deletes a port.
|
||||||
|
|
||||||
|
Deletes a port on a specified Virtual Network,
|
||||||
|
if the port contains a remote interface attachment,
|
||||||
|
the remote interface is first un-plugged and then the port
|
||||||
|
is deleted.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self._delete_resource('port', context, port_id)
|
||||||
|
|
||||||
|
def get_ports(self, context, filters=None, fields=None):
|
||||||
|
"""Get all ports.
|
||||||
|
|
||||||
|
Retrieves all port identifiers belonging to the
|
||||||
|
specified Virtual Network with the specfied filter.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return [self._make_port_dict(p, fields)
|
||||||
|
for p in self._list_resource('port', context, filters, fields)]
|
||||||
|
|
||||||
|
def get_ports_count(self, context, filters=None):
|
||||||
|
"""Get the count of ports."""
|
||||||
|
|
||||||
|
ports_count = self._count_resource('port', context, filters)
|
||||||
|
return ports_count['count']
|
||||||
|
|
||||||
|
# Router API handlers
|
||||||
|
def create_router(self, context, router):
|
||||||
|
"""Creates a router.
|
||||||
|
|
||||||
|
Creates a new Logical Router, and assigns it
|
||||||
|
a symbolic name.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self._create_resource('router', context, router)
|
||||||
|
|
||||||
|
def get_router(self, context, router_id, fields=None):
|
||||||
|
"""Get the attributes of a router."""
|
||||||
|
|
||||||
|
return self._get_resource('router', context, router_id, fields)
|
||||||
|
|
||||||
|
def update_router(self, context, router_id, router):
|
||||||
|
"""Updates the attributes of a router."""
|
||||||
|
|
||||||
|
return self._update_resource('router', context, router_id,
|
||||||
|
router)
|
||||||
|
|
||||||
|
def delete_router(self, context, router_id):
|
||||||
|
"""Deletes a router."""
|
||||||
|
|
||||||
|
self._delete_resource('router', context, router_id)
|
||||||
|
|
||||||
|
def get_routers(self, context, filters=None, fields=None):
|
||||||
|
"""Retrieves all router identifiers."""
|
||||||
|
|
||||||
|
return self._list_resource('router', context, filters, fields)
|
||||||
|
|
||||||
|
def get_routers_count(self, context, filters=None):
|
||||||
|
"""Get the count of routers."""
|
||||||
|
|
||||||
|
routers_count = self._count_resource('router', context, filters)
|
||||||
|
return routers_count['count']
|
||||||
|
|
||||||
|
def _validate_router_interface_request(self, interface_info):
|
||||||
|
"""Validates parameters to the router interface requests."""
|
||||||
|
|
||||||
|
port_id_specified = interface_info and 'port_id' in interface_info
|
||||||
|
subnet_id_specified = interface_info and 'subnet_id' in interface_info
|
||||||
|
if not (port_id_specified or subnet_id_specified):
|
||||||
|
msg = _("Either subnet_id or port_id must be specified")
|
||||||
|
raise exc.BadRequest(resource='router', msg=msg)
|
||||||
|
|
||||||
|
def add_router_interface(self, context, router_id, interface_info):
|
||||||
|
"""Add interface to a router."""
|
||||||
|
|
||||||
|
self._validate_router_interface_request(interface_info)
|
||||||
|
|
||||||
|
if 'port_id' in interface_info:
|
||||||
|
if 'subnet_id' in interface_info:
|
||||||
|
msg = _("Cannot specify both subnet-id and port-id")
|
||||||
|
raise exc.BadRequest(resource='router', msg=msg)
|
||||||
|
|
||||||
|
res_dict = self._encode_resource(resource_id=router_id,
|
||||||
|
resource=interface_info)
|
||||||
|
status_code, res_info = self._request_backend(context, res_dict,
|
||||||
|
'router', 'ADDINTERFACE')
|
||||||
|
if status_code != requests.codes.ok:
|
||||||
|
self._raise_contrail_error(status_code, info=res_info,
|
||||||
|
obj_name='add_router_interface')
|
||||||
|
return res_info
|
||||||
|
|
||||||
|
def remove_router_interface(self, context, router_id, interface_info):
|
||||||
|
"""Delete interface from a router."""
|
||||||
|
|
||||||
|
self._validate_router_interface_request(interface_info)
|
||||||
|
|
||||||
|
res_dict = self._encode_resource(resource_id=router_id,
|
||||||
|
resource=interface_info)
|
||||||
|
status_code, res_info = self._request_backend(context, res_dict,
|
||||||
|
'router', 'DELINTERFACE')
|
||||||
|
if status_code != requests.codes.ok:
|
||||||
|
self._raise_contrail_error(status_code, info=res_info,
|
||||||
|
obj_name='remove_router_interface')
|
||||||
|
return res_info
|
||||||
|
|
||||||
|
# Floating IP API handlers
|
||||||
|
def create_floatingip(self, context, floatingip):
|
||||||
|
"""Creates a floating IP."""
|
||||||
|
|
||||||
|
return self._create_resource('floatingip', context, floatingip)
|
||||||
|
|
||||||
|
def update_floatingip(self, context, fip_id, floatingip):
|
||||||
|
"""Updates the attributes of a floating IP."""
|
||||||
|
|
||||||
|
return self._update_resource('floatingip', context, fip_id,
|
||||||
|
floatingip)
|
||||||
|
|
||||||
|
def get_floatingip(self, context, fip_id, fields=None):
|
||||||
|
"""Get the attributes of a floating ip."""
|
||||||
|
|
||||||
|
return self._get_resource('floatingip', context, fip_id, fields)
|
||||||
|
|
||||||
|
def delete_floatingip(self, context, fip_id):
|
||||||
|
"""Deletes a floating IP."""
|
||||||
|
|
||||||
|
self._delete_resource('floatingip', context, fip_id)
|
||||||
|
|
||||||
|
def get_floatingips(self, context, filters=None, fields=None):
|
||||||
|
"""Retrieves all floating ips identifiers."""
|
||||||
|
|
||||||
|
return self._list_resource('floatingip', context, filters, fields)
|
||||||
|
|
||||||
|
def get_floatingips_count(self, context, filters=None):
|
||||||
|
"""Get the count of floating IPs."""
|
||||||
|
|
||||||
|
fips_count = self._count_resource('floatingip', context, filters)
|
||||||
|
return fips_count['count']
|
||||||
|
|
||||||
|
# Security Group handlers
|
||||||
|
def create_security_group(self, context, security_group):
|
||||||
|
"""Creates a Security Group."""
|
||||||
|
|
||||||
|
return self._create_resource('security_group', context,
|
||||||
|
security_group)
|
||||||
|
|
||||||
|
def get_security_group(self, context, sg_id, fields=None, tenant_id=None):
|
||||||
|
"""Get the attributes of a security group."""
|
||||||
|
|
||||||
|
return self._get_resource('security_group', context, sg_id, fields)
|
||||||
|
|
||||||
|
def update_security_group(self, context, sg_id, security_group):
|
||||||
|
"""Updates the attributes of a security group."""
|
||||||
|
|
||||||
|
return self._update_resource('security_group', context, sg_id,
|
||||||
|
security_group)
|
||||||
|
|
||||||
|
def delete_security_group(self, context, sg_id):
|
||||||
|
"""Deletes a security group."""
|
||||||
|
|
||||||
|
self._delete_resource('security_group', context, sg_id)
|
||||||
|
|
||||||
|
def get_security_groups(self, context, filters=None, fields=None,
|
||||||
|
sorts=None, limit=None, marker=None,
|
||||||
|
page_reverse=False):
|
||||||
|
"""Retrieves all security group identifiers."""
|
||||||
|
|
||||||
|
return self._list_resource('security_group', context,
|
||||||
|
filters, fields)
|
||||||
|
|
||||||
|
def create_security_group_rule(self, context, security_group_rule):
|
||||||
|
"""Creates a security group rule."""
|
||||||
|
|
||||||
|
return self._create_resource('security_group_rule', context,
|
||||||
|
security_group_rule)
|
||||||
|
|
||||||
|
def delete_security_group_rule(self, context, sg_rule_id):
|
||||||
|
"""Deletes a security group rule."""
|
||||||
|
|
||||||
|
self._delete_resource('security_group_rule', context, sg_rule_id)
|
||||||
|
|
||||||
|
def get_security_group_rule(self, context, sg_rule_id, fields=None):
|
||||||
|
"""Get the attributes of a security group rule."""
|
||||||
|
|
||||||
|
return self._get_resource('security_group_rule', context,
|
||||||
|
sg_rule_id, fields)
|
||||||
|
|
||||||
|
def get_security_group_rules(self, context, filters=None, fields=None,
|
||||||
|
sorts=None, limit=None, marker=None,
|
||||||
|
page_reverse=False):
|
||||||
|
"""Retrieves all security group rules."""
|
||||||
|
|
||||||
|
return self._list_resource('security_group_rule', context,
|
||||||
|
filters, fields)
|
0
neutron/tests/unit/opencontrail/__init__.py
Normal file
0
neutron/tests/unit/opencontrail/__init__.py
Normal file
315
neutron/tests/unit/opencontrail/test_contrail_plugin.py
Normal file
315
neutron/tests/unit/opencontrail/test_contrail_plugin.py
Normal file
@ -0,0 +1,315 @@
|
|||||||
|
# Copyright 2014 Juniper Networks. 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 datetime
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
import mock
|
||||||
|
import netaddr
|
||||||
|
from oslo.config import cfg
|
||||||
|
from testtools import matchers
|
||||||
|
import webob.exc
|
||||||
|
|
||||||
|
from neutron.api import extensions
|
||||||
|
from neutron.api.v2 import attributes as attr
|
||||||
|
from neutron.api.v2 import base as api_base
|
||||||
|
from neutron.common import exceptions as exc
|
||||||
|
from neutron import context as neutron_context
|
||||||
|
from neutron.db import api as db
|
||||||
|
from neutron.db import db_base_plugin_v2
|
||||||
|
from neutron.db import external_net_db
|
||||||
|
from neutron.db import l3_db
|
||||||
|
from neutron.db import quota_db # noqa
|
||||||
|
from neutron.db import securitygroups_db
|
||||||
|
from neutron.extensions import portbindings
|
||||||
|
from neutron.extensions import securitygroup as ext_sg
|
||||||
|
from neutron.openstack.common import jsonutils
|
||||||
|
from neutron.tests.unit import _test_extension_portbindings as test_bindings
|
||||||
|
from neutron.tests.unit import test_db_plugin as test_plugin
|
||||||
|
from neutron.tests.unit import test_extension_security_group as test_sg
|
||||||
|
from neutron.tests.unit import test_extensions
|
||||||
|
from neutron.tests.unit import test_l3_plugin
|
||||||
|
|
||||||
|
|
||||||
|
CONTRAIL_PKG_PATH = "neutron.plugins.opencontrail.contrail_plugin"
|
||||||
|
|
||||||
|
|
||||||
|
class FakeServer(db_base_plugin_v2.NeutronDbPluginV2,
|
||||||
|
external_net_db.External_net_db_mixin,
|
||||||
|
securitygroups_db.SecurityGroupDbMixin,
|
||||||
|
l3_db.L3_NAT_db_mixin):
|
||||||
|
"""FakeServer for contrail api server.
|
||||||
|
|
||||||
|
This class mocks behaviour of contrail API server.
|
||||||
|
"""
|
||||||
|
supported_extension_aliases = ['external-net', 'router', 'floatingip']
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _core_plugin(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def create_port(self, context, port):
|
||||||
|
self._ensure_default_security_group_on_port(context, port)
|
||||||
|
sgids = self._get_security_groups_on_port(context, port)
|
||||||
|
result = super(FakeServer, self).create_port(context, port)
|
||||||
|
self._process_port_create_security_group(context, result, sgids)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def update_port(self, context, id, port):
|
||||||
|
original_port = self.get_port(context, id)
|
||||||
|
updated_port = super(FakeServer, self).update_port(context, id, port)
|
||||||
|
port_updates = port['port']
|
||||||
|
if ext_sg.SECURITYGROUPS in port_updates:
|
||||||
|
port_updates[ext_sg.SECURITYGROUPS] = (
|
||||||
|
self._get_security_groups_on_port(context, port))
|
||||||
|
self._delete_port_security_group_bindings(context, id)
|
||||||
|
self._process_port_create_security_group(
|
||||||
|
context,
|
||||||
|
updated_port,
|
||||||
|
port_updates[ext_sg.SECURITYGROUPS])
|
||||||
|
else:
|
||||||
|
updated_port[ext_sg.SECURITYGROUPS] = (
|
||||||
|
original_port[ext_sg.SECURITYGROUPS])
|
||||||
|
|
||||||
|
return updated_port
|
||||||
|
|
||||||
|
def delete_port(self, context, id, l3_port_check=True):
|
||||||
|
if l3_port_check:
|
||||||
|
self.prevent_l3_port_deletion(context, id)
|
||||||
|
self.disassociate_floatingips(context, id)
|
||||||
|
super(FakeServer, self).delete_port(context, id)
|
||||||
|
|
||||||
|
def create_subnet(self, context, subnet):
|
||||||
|
subnet_data = subnet['subnet']
|
||||||
|
if subnet_data['gateway_ip'] == '0.0.0.0':
|
||||||
|
subnet_data['gateway_ip'] = None
|
||||||
|
return super(FakeServer, self).create_subnet(context, subnet)
|
||||||
|
|
||||||
|
def create_network(self, context, network):
|
||||||
|
net_data = network['network']
|
||||||
|
tenant_id = self._get_tenant_id_for_create(context, net_data)
|
||||||
|
self._ensure_default_security_group(context, tenant_id)
|
||||||
|
result = super(FakeServer, self).create_network(context, network)
|
||||||
|
self._process_l3_create(context, result, network['network'])
|
||||||
|
return result
|
||||||
|
|
||||||
|
def update_network(self, context, id, network):
|
||||||
|
with context.session.begin(subtransactions=True):
|
||||||
|
result = super(
|
||||||
|
FakeServer, self).update_network(context, id, network)
|
||||||
|
self._process_l3_update(context, result, network['network'])
|
||||||
|
return result
|
||||||
|
|
||||||
|
def delete_network(self, context, id):
|
||||||
|
self.delete_disassociated_floatingips(context, id)
|
||||||
|
super(FakeServer, self).delete_network(context, id)
|
||||||
|
|
||||||
|
def request(self, *args, **kwargs):
|
||||||
|
request_data = jsonutils.loads(kwargs['data'])
|
||||||
|
context_dict = request_data['context']
|
||||||
|
context = neutron_context.Context.from_dict(context_dict)
|
||||||
|
resource_type = context_dict['type']
|
||||||
|
operation = context_dict['operation']
|
||||||
|
data = request_data['data']
|
||||||
|
resource = None
|
||||||
|
if data.get('resource'):
|
||||||
|
body = data['resource']
|
||||||
|
if resource_type not in [
|
||||||
|
'security_group_rule', 'router', 'floatingip']:
|
||||||
|
for key, value in body.items():
|
||||||
|
if value is None:
|
||||||
|
body[key] = attr.ATTR_NOT_SPECIFIED
|
||||||
|
resource = {resource_type: body}
|
||||||
|
|
||||||
|
obj = {}
|
||||||
|
code = webob.exc.HTTPOk.code
|
||||||
|
try:
|
||||||
|
if operation == 'READ':
|
||||||
|
func = getattr(self, 'get_%s' % resource_type)
|
||||||
|
obj = func(context, data['id'])
|
||||||
|
if operation == 'READALL':
|
||||||
|
func = getattr(self, 'get_%ss' % resource_type)
|
||||||
|
obj = func(context, filters=data.get('filters'))
|
||||||
|
if operation == 'READCOUNT':
|
||||||
|
func = getattr(self, 'get_%ss_count' % resource_type)
|
||||||
|
count = func(context, filters=data.get('filters'))
|
||||||
|
obj = {'count': count}
|
||||||
|
if operation == 'CREATE':
|
||||||
|
func = getattr(self, 'create_%s' % resource_type)
|
||||||
|
obj = func(context, resource)
|
||||||
|
if operation == 'UPDATE':
|
||||||
|
func = getattr(self, 'update_%s' % resource_type)
|
||||||
|
obj = func(context, data['id'], resource)
|
||||||
|
if operation == 'DELETE':
|
||||||
|
func = getattr(self, 'delete_%s' % resource_type)
|
||||||
|
obj = func(context, data['id'])
|
||||||
|
if operation == 'ADDINTERFACE':
|
||||||
|
obj = self.add_router_interface(
|
||||||
|
context, data['id'], data['resource'])
|
||||||
|
if operation == 'DELINTERFACE':
|
||||||
|
obj = self.remove_router_interface(
|
||||||
|
context, data['id'], data['resource'])
|
||||||
|
except (exc.NeutronException,
|
||||||
|
netaddr.AddrFormatError) as error:
|
||||||
|
for fault in api_base.FAULT_MAP:
|
||||||
|
if isinstance(error, fault):
|
||||||
|
mapped_exc = api_base.FAULT_MAP[fault]
|
||||||
|
code = mapped_exc.code
|
||||||
|
obj = {'type': error.__class__.__name__,
|
||||||
|
'message': error.msg, 'detail': ''}
|
||||||
|
if data.get('id'):
|
||||||
|
obj['id'] = data.get('id')
|
||||||
|
response = mock.MagicMock()
|
||||||
|
response.status_code = code
|
||||||
|
|
||||||
|
def return_obj():
|
||||||
|
return obj
|
||||||
|
response.json = return_obj
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
FAKE_SERVER = FakeServer()
|
||||||
|
|
||||||
|
|
||||||
|
class Context(object):
|
||||||
|
def __init__(self, tenant_id=''):
|
||||||
|
self.read_only = False
|
||||||
|
self.show_deleted = False
|
||||||
|
self.roles = [u'admin', u'KeystoneServiceAdmin', u'KeystoneAdmin']
|
||||||
|
self._read_deleted = 'no'
|
||||||
|
self.timestamp = datetime.datetime.now()
|
||||||
|
self.auth_token = None
|
||||||
|
self._session = None
|
||||||
|
self._is_admin = True
|
||||||
|
self.admin = uuid.uuid4().hex.decode()
|
||||||
|
self.request_id = 'req-' + str(uuid.uuid4())
|
||||||
|
self.tenant = tenant_id
|
||||||
|
|
||||||
|
|
||||||
|
class KeyStoneInfo(object):
|
||||||
|
"""To generate Keystone Authentication information
|
||||||
|
Contrail Driver expects Keystone auth info for testing purpose.
|
||||||
|
"""
|
||||||
|
auth_protocol = 'http'
|
||||||
|
auth_host = 'host'
|
||||||
|
auth_port = 5000
|
||||||
|
admin_user = 'neutron'
|
||||||
|
admin_password = 'neutron'
|
||||||
|
admin_token = 'neutron'
|
||||||
|
admin_tenant_name = 'neutron'
|
||||||
|
|
||||||
|
|
||||||
|
class ContrailPluginTestCase(test_plugin.NeutronDbPluginV2TestCase):
|
||||||
|
_plugin_name = ('%s.NeutronPluginContrailCoreV2' % CONTRAIL_PKG_PATH)
|
||||||
|
|
||||||
|
def setUp(self, plugin=None, ext_mgr=None):
|
||||||
|
|
||||||
|
cfg.CONF.keystone_authtoken = KeyStoneInfo()
|
||||||
|
mock.patch('requests.post').start().side_effect = FAKE_SERVER.request
|
||||||
|
db.configure_db()
|
||||||
|
super(ContrailPluginTestCase, self).setUp(self._plugin_name)
|
||||||
|
|
||||||
|
|
||||||
|
class TestContrailNetworksV2(test_plugin.TestNetworksV2,
|
||||||
|
ContrailPluginTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(TestContrailNetworksV2, self).setUp()
|
||||||
|
|
||||||
|
|
||||||
|
class TestContrailSubnetsV2(test_plugin.TestSubnetsV2,
|
||||||
|
ContrailPluginTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(TestContrailSubnetsV2, self).setUp()
|
||||||
|
|
||||||
|
# Support ipv6 in contrail is planned in Juno
|
||||||
|
def test_update_subnet_ipv6_attributes(self):
|
||||||
|
self.skipTest("Contrail isn't supporting ipv6 yet")
|
||||||
|
|
||||||
|
def test_update_subnet_ipv6_inconsistent_address_attribute(self):
|
||||||
|
self.skipTest("Contrail isn't supporting ipv6 yet")
|
||||||
|
|
||||||
|
def test_update_subnet_ipv6_inconsistent_enable_dhcp(self):
|
||||||
|
self.skipTest("Contrail isn't supporting ipv6 yet")
|
||||||
|
|
||||||
|
def test_update_subnet_ipv6_inconsistent_ra_attribute(self):
|
||||||
|
self.skipTest("Contrail isn't supporting ipv6 yet")
|
||||||
|
|
||||||
|
def test_delete_subnet_dhcp_port_associated_with_other_subnets(self):
|
||||||
|
self.skipTest("There is no dhcp port in contrail")
|
||||||
|
|
||||||
|
def _helper_test_validate_subnet(self, option, exception):
|
||||||
|
cfg.CONF.set_override(option, 0)
|
||||||
|
with self.network() as network:
|
||||||
|
subnet = {'network_id': network['network']['id'],
|
||||||
|
'cidr': '10.0.2.0/24',
|
||||||
|
'ip_version': 4,
|
||||||
|
'tenant_id': network['network']['tenant_id'],
|
||||||
|
'gateway_ip': '10.0.2.1',
|
||||||
|
'dns_nameservers': ['8.8.8.8'],
|
||||||
|
'host_routes': [{'destination': '135.207.0.0/16',
|
||||||
|
'nexthop': '1.2.3.4'}]}
|
||||||
|
error = self.assertRaises(exception,
|
||||||
|
FAKE_SERVER._validate_subnet,
|
||||||
|
neutron_context.get_admin_context(
|
||||||
|
load_admin_roles=False),
|
||||||
|
subnet)
|
||||||
|
self.assertThat(
|
||||||
|
str(error),
|
||||||
|
matchers.Not(matchers.Contains('built-in function id')))
|
||||||
|
|
||||||
|
|
||||||
|
class TestContrailPortsV2(test_plugin.TestPortsV2,
|
||||||
|
ContrailPluginTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(TestContrailPortsV2, self).setUp()
|
||||||
|
|
||||||
|
def test_delete_ports_by_device_id(self):
|
||||||
|
self.skipTest("This method tests rpc API of "
|
||||||
|
"which contrail isn't using")
|
||||||
|
|
||||||
|
def test_delete_ports_by_device_id_second_call_failure(self):
|
||||||
|
self.skipTest("This method tests rpc API of "
|
||||||
|
"which contrail isn't using")
|
||||||
|
|
||||||
|
def test_delete_ports_ignores_port_not_found(self):
|
||||||
|
self.skipTest("This method tests private method of "
|
||||||
|
"which contrail isn't using")
|
||||||
|
|
||||||
|
|
||||||
|
class TestContrailSecurityGroups(test_sg.TestSecurityGroups,
|
||||||
|
ContrailPluginTestCase):
|
||||||
|
def setUp(self, plugin=None, ext_mgr=None):
|
||||||
|
super(TestContrailSecurityGroups, self).setUp(self._plugin_name,
|
||||||
|
ext_mgr)
|
||||||
|
ext_mgr = extensions.PluginAwareExtensionManager.get_instance()
|
||||||
|
self.ext_api = test_extensions.setup_extensions_middleware(ext_mgr)
|
||||||
|
|
||||||
|
|
||||||
|
class TestContrailPortBinding(ContrailPluginTestCase,
|
||||||
|
test_bindings.PortBindingsTestCase):
|
||||||
|
VIF_TYPE = portbindings.VIF_TYPE_VROUTER
|
||||||
|
HAS_PORT_FILTER = True
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestContrailPortBinding, self).setUp()
|
||||||
|
|
||||||
|
|
||||||
|
class TestContrailL3NatTestCase(ContrailPluginTestCase,
|
||||||
|
test_l3_plugin.L3NatDBIntTestCase):
|
||||||
|
mock_rescheduling = False
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestContrailL3NatTestCase, self).setUp()
|
@ -80,6 +80,7 @@ data_files =
|
|||||||
etc/neutron/plugins/plumgrid = etc/neutron/plugins/plumgrid/plumgrid.ini
|
etc/neutron/plugins/plumgrid = etc/neutron/plugins/plumgrid/plumgrid.ini
|
||||||
etc/neutron/plugins/ryu = etc/neutron/plugins/ryu/ryu.ini
|
etc/neutron/plugins/ryu = etc/neutron/plugins/ryu/ryu.ini
|
||||||
etc/neutron/plugins/vmware = etc/neutron/plugins/vmware/nsx.ini
|
etc/neutron/plugins/vmware = etc/neutron/plugins/vmware/nsx.ini
|
||||||
|
etc/neutron/plugins/opencontrail = etc/neutron/plugins/opencontrail/contrailplugin.ini
|
||||||
scripts =
|
scripts =
|
||||||
bin/neutron-rootwrap
|
bin/neutron-rootwrap
|
||||||
bin/neutron-rootwrap-xen-dom0
|
bin/neutron-rootwrap-xen-dom0
|
||||||
|
Loading…
x
Reference in New Issue
Block a user