Merge "Opencontrail plug-in implementation for core resources"

This commit is contained in:
Jenkins 2014-08-15 16:38:44 +00:00 committed by Gerrit Code Review
commit 2d6a8c55d2
9 changed files with 1007 additions and 1 deletions

View 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 virtualizationSDN 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>

View File

@ -62,12 +62,14 @@ VIF_TYPE_MIDONET = 'midonet'
VIF_TYPE_MLNX_DIRECT = 'mlnx_direct'
VIF_TYPE_MLNX_HOSTDEV = 'hostdev'
VIF_TYPE_HW_VEB = 'hw_veb'
VIF_TYPE_VROUTER = 'vrouter'
VIF_TYPE_OTHER = 'other'
VIF_TYPES = [VIF_TYPE_UNBOUND, VIF_TYPE_BINDING_FAILED, VIF_TYPE_OVS,
VIF_TYPE_IVS, VIF_TYPE_BRIDGE, VIF_TYPE_802_QBG,
VIF_TYPE_802_QBH, VIF_TYPE_HYPERV, VIF_TYPE_MIDONET,
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_DIRECT = 'direct'

View File

View 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'

View 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)

View 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()

View File

@ -80,6 +80,7 @@ data_files =
etc/neutron/plugins/plumgrid = etc/neutron/plugins/plumgrid/plumgrid.ini
etc/neutron/plugins/ryu = etc/neutron/plugins/ryu/ryu.ini
etc/neutron/plugins/vmware = etc/neutron/plugins/vmware/nsx.ini
etc/neutron/plugins/opencontrail = etc/neutron/plugins/opencontrail/contrailplugin.ini
scripts =
bin/neutron-rootwrap
bin/neutron-rootwrap-xen-dom0