Merge "Add OpenDaylight ML2 MechanismDriver"
This commit is contained in:
commit
8c1886aee9
30
etc/neutron/plugins/ml2/ml2_conf_odl.ini
Normal file
30
etc/neutron/plugins/ml2/ml2_conf_odl.ini
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# Configuration for the OpenDaylight MechanismDriver
|
||||||
|
|
||||||
|
[ml2_odl]
|
||||||
|
# (StrOpt) OpenDaylight REST URL
|
||||||
|
# If this is not set then no HTTP requests will be made.
|
||||||
|
#
|
||||||
|
# url =
|
||||||
|
# Example: url = http://192.168.56.1:8080/controller/nb/v2/neutron
|
||||||
|
|
||||||
|
# (StrOpt) Username for HTTP basic authentication to ODL.
|
||||||
|
#
|
||||||
|
# username =
|
||||||
|
# Example: username = admin
|
||||||
|
|
||||||
|
# (StrOpt) Password for HTTP basic authentication to ODL.
|
||||||
|
#
|
||||||
|
# password =
|
||||||
|
# Example: password = admin
|
||||||
|
|
||||||
|
# (IntOpt) Timeout in seconds to wait for ODL HTTP request completion.
|
||||||
|
# This is an optional parameter, default value is 10 seconds.
|
||||||
|
#
|
||||||
|
# timeout = 10
|
||||||
|
# Example: timeout = 15
|
||||||
|
|
||||||
|
# (IntOpt) Timeout in minutes to wait for a Tomcat session timeout.
|
||||||
|
# This is an optional parameter, default value is 30 minutes.
|
||||||
|
#
|
||||||
|
# session_timeout = 30
|
||||||
|
# Example: session_timeout = 60
|
41
neutron/plugins/ml2/drivers/README.odl
Normal file
41
neutron/plugins/ml2/drivers/README.odl
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
OpenDaylight ML2 MechanismDriver
|
||||||
|
================================
|
||||||
|
OpenDaylight is an Open Source SDN Controller developed by a plethora of
|
||||||
|
companies and hosted by the Linux Foundation. The OpenDaylight website
|
||||||
|
contains more information on the capabilities OpenDaylight provides:
|
||||||
|
|
||||||
|
http://www.opendaylight.org
|
||||||
|
|
||||||
|
Theory of operation
|
||||||
|
===================
|
||||||
|
The OpenStack Neutron integration with OpenDaylight consists of the ML2
|
||||||
|
MechanismDriver which acts as a REST proxy and passess all Neutron API
|
||||||
|
calls into OpenDaylight. OpenDaylight contains a NB REST service (called
|
||||||
|
the NeutronAPIService) which caches data from these proxied API calls and
|
||||||
|
makes it available to other services inside of OpenDaylight. One current
|
||||||
|
user of the SB side of the NeutronAPIService is the OVSDB code in
|
||||||
|
OpenDaylight. OVSDB uses the neutron information to isolate tenant networks
|
||||||
|
using GRE or VXLAN tunnels.
|
||||||
|
|
||||||
|
How to use the OpenDaylight ML2 MechanismDriver
|
||||||
|
===============================================
|
||||||
|
To use the ML2 MechanismDriver, you need to ensure you have it configured
|
||||||
|
as one of the "mechanism_drivers" in ML2:
|
||||||
|
|
||||||
|
mechanism_drivers=opendaylight
|
||||||
|
|
||||||
|
The next step is to setup the "[ml2_odl]" section in either the ml2_conf.ini
|
||||||
|
file or in a separate ml2_conf_odl.ini file. An example is shown below:
|
||||||
|
|
||||||
|
[ml2_odl]
|
||||||
|
password = admin
|
||||||
|
username = admin
|
||||||
|
url = http://192.168.100.1:8080/controller/nb/v2/neutron
|
||||||
|
|
||||||
|
When starting OpenDaylight, ensure you have the SimpleForwarding application
|
||||||
|
disabled or remove the .jar file from the plugins directory. Also ensure you
|
||||||
|
start OpenDaylight before you start OpenStack Neutron.
|
||||||
|
|
||||||
|
There is devstack support for this which will automatically pull down OpenDaylight
|
||||||
|
and start it as part of devstack as well. The patch for this will likely merge
|
||||||
|
around the same time as this patch merges.
|
367
neutron/plugins/ml2/drivers/mechanism_odl.py
Normal file
367
neutron/plugins/ml2/drivers/mechanism_odl.py
Normal file
@ -0,0 +1,367 @@
|
|||||||
|
# Copyright (c) 2013-2014 OpenStack Foundation
|
||||||
|
# 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.
|
||||||
|
# @author: Kyle Mestery, Cisco Systems, Inc.
|
||||||
|
# @author: Dave Tucker, Hewlett-Packard Development Company L.P.
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
from oslo.config import cfg
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from neutron.common import exceptions as n_exc
|
||||||
|
from neutron.common import utils
|
||||||
|
from neutron.extensions import portbindings
|
||||||
|
from neutron.openstack.common import excutils
|
||||||
|
from neutron.openstack.common import jsonutils
|
||||||
|
from neutron.openstack.common import log
|
||||||
|
from neutron.plugins.common import constants
|
||||||
|
from neutron.plugins.ml2 import driver_api as api
|
||||||
|
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
ODL_NETWORK = 'network'
|
||||||
|
ODL_NETWORKS = 'networks'
|
||||||
|
ODL_SUBNET = 'subnet'
|
||||||
|
ODL_SUBNETS = 'subnets'
|
||||||
|
ODL_PORT = 'port'
|
||||||
|
ODL_PORTS = 'ports'
|
||||||
|
|
||||||
|
not_found_exception_map = {ODL_NETWORKS: n_exc.NetworkNotFound,
|
||||||
|
ODL_SUBNETS: n_exc.SubnetNotFound,
|
||||||
|
ODL_PORTS: n_exc.PortNotFound}
|
||||||
|
|
||||||
|
odl_opts = [
|
||||||
|
cfg.StrOpt('url',
|
||||||
|
help=_("HTTP URL of OpenDaylight REST interface.")),
|
||||||
|
cfg.StrOpt('username',
|
||||||
|
help=_("HTTP username for authentication")),
|
||||||
|
cfg.StrOpt('password', secret=True,
|
||||||
|
help=_("HTTP password for authentication")),
|
||||||
|
cfg.IntOpt('timeout', default=10,
|
||||||
|
help=_("HTTP timeout in seconds.")),
|
||||||
|
cfg.IntOpt('session_timeout', default=30,
|
||||||
|
help=_("Tomcat session timeout in minutes.")),
|
||||||
|
]
|
||||||
|
|
||||||
|
cfg.CONF.register_opts(odl_opts, "ml2_odl")
|
||||||
|
|
||||||
|
|
||||||
|
def try_del(d, keys):
|
||||||
|
"""Ignore key errors when deleting from a dictionary."""
|
||||||
|
for key in keys:
|
||||||
|
try:
|
||||||
|
del d[key]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class JsessionId(requests.auth.AuthBase):
|
||||||
|
|
||||||
|
"""Attaches the JSESSIONID and JSESSIONIDSSO cookies to an HTTP Request.
|
||||||
|
|
||||||
|
If the cookies are not available or when the session expires, a new
|
||||||
|
set of cookies are obtained.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, url, username, password):
|
||||||
|
"""Initialization function for JsessionId."""
|
||||||
|
|
||||||
|
# NOTE(kmestery) The 'limit' paramater is intended to limit how much
|
||||||
|
# data is returned from ODL. This is not implemented in the Hydrogen
|
||||||
|
# release of OpenDaylight, but will be implemented in the Helium
|
||||||
|
# timeframe. Hydrogen will silently ignore this value.
|
||||||
|
self.url = str(url) + '/' + ODL_NETWORKS + '?limit=1'
|
||||||
|
self.username = username
|
||||||
|
self.password = password
|
||||||
|
self.auth_cookies = None
|
||||||
|
self.last_request = None
|
||||||
|
self.expired = None
|
||||||
|
self.session_timeout = cfg.CONF.ml2_odl.session_timeout * 60
|
||||||
|
self.session_deadline = 0
|
||||||
|
|
||||||
|
def obtain_auth_cookies(self):
|
||||||
|
"""Make a REST call to obtain cookies for ODL authenticiation."""
|
||||||
|
|
||||||
|
r = requests.get(self.url, auth=(self.username, self.password))
|
||||||
|
r.raise_for_status()
|
||||||
|
jsessionid = r.cookies.get('JSESSIONID')
|
||||||
|
jsessionidsso = r.cookies.get('JSESSIONIDSSO')
|
||||||
|
if jsessionid and jsessionidsso:
|
||||||
|
self.auth_cookies = dict(JSESSIONID=jsessionid,
|
||||||
|
JSESSIONIDSSO=jsessionidsso)
|
||||||
|
|
||||||
|
def __call__(self, r):
|
||||||
|
"""Verify timestamp for Tomcat session timeout."""
|
||||||
|
|
||||||
|
if time.time() > self.session_deadline:
|
||||||
|
self.obtain_auth_cookies()
|
||||||
|
self.session_deadline = time.time() + self.session_timeout
|
||||||
|
r.prepare_cookies(self.auth_cookies)
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
class OpenDaylightMechanismDriver(api.MechanismDriver):
|
||||||
|
|
||||||
|
"""Mechanism Driver for OpenDaylight.
|
||||||
|
|
||||||
|
This driver was a port from the Tail-F NCS MechanismDriver. The API
|
||||||
|
exposed by ODL is slightly different from the API exposed by NCS,
|
||||||
|
but the general concepts are the same.
|
||||||
|
"""
|
||||||
|
auth = None
|
||||||
|
out_of_sync = True
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
self.url = cfg.CONF.ml2_odl.url
|
||||||
|
self.timeout = cfg.CONF.ml2_odl.timeout
|
||||||
|
self.username = cfg.CONF.ml2_odl.username
|
||||||
|
self.password = cfg.CONF.ml2_odl.password
|
||||||
|
self.auth = JsessionId(self.url, self.username, self.password)
|
||||||
|
self.vif_type = portbindings.VIF_TYPE_OVS
|
||||||
|
self.vif_details = {portbindings.CAP_PORT_FILTER: True}
|
||||||
|
|
||||||
|
# Postcommit hooks are used to trigger synchronization.
|
||||||
|
|
||||||
|
def create_network_postcommit(self, context):
|
||||||
|
self.synchronize('create', ODL_NETWORKS, context)
|
||||||
|
|
||||||
|
def update_network_postcommit(self, context):
|
||||||
|
self.synchronize('update', ODL_NETWORKS, context)
|
||||||
|
|
||||||
|
def delete_network_postcommit(self, context):
|
||||||
|
self.synchronize('delete', ODL_NETWORKS, context)
|
||||||
|
|
||||||
|
def create_subnet_postcommit(self, context):
|
||||||
|
self.synchronize('create', ODL_SUBNETS, context)
|
||||||
|
|
||||||
|
def update_subnet_postcommit(self, context):
|
||||||
|
self.synchronize('update', ODL_SUBNETS, context)
|
||||||
|
|
||||||
|
def delete_subnet_postcommit(self, context):
|
||||||
|
self.synchronize('delete', ODL_SUBNETS, context)
|
||||||
|
|
||||||
|
def create_port_postcommit(self, context):
|
||||||
|
self.synchronize('create', ODL_PORTS, context)
|
||||||
|
|
||||||
|
def update_port_postcommit(self, context):
|
||||||
|
self.synchronize('update', ODL_PORTS, context)
|
||||||
|
|
||||||
|
def delete_port_postcommit(self, context):
|
||||||
|
self.synchronize('delete', ODL_PORTS, context)
|
||||||
|
|
||||||
|
def synchronize(self, operation, object_type, context):
|
||||||
|
"""Synchronize ODL with Neutron following a configuration change."""
|
||||||
|
if self.out_of_sync:
|
||||||
|
self.sync_full(context)
|
||||||
|
else:
|
||||||
|
self.sync_object(operation, object_type, context)
|
||||||
|
|
||||||
|
def filter_create_network_attributes(self, network, context, dbcontext):
|
||||||
|
"""Filter out network attributes not required for a create."""
|
||||||
|
try_del(network, ['status', 'subnets'])
|
||||||
|
|
||||||
|
def filter_create_subnet_attributes(self, subnet, context, dbcontext):
|
||||||
|
"""Filter out subnet attributes not required for a create."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def filter_create_port_attributes(self, port, context, dbcontext):
|
||||||
|
"""Filter out port attributes not required for a create."""
|
||||||
|
self.add_security_groups(context, dbcontext, port)
|
||||||
|
# TODO(kmestery): Converting to uppercase due to ODL bug
|
||||||
|
# https://bugs.opendaylight.org/show_bug.cgi?id=477
|
||||||
|
port['mac_address'] = port['mac_address'].upper()
|
||||||
|
try_del(port, ['status'])
|
||||||
|
|
||||||
|
def sync_resources(self, resource_name, collection_name, resources,
|
||||||
|
context, dbcontext, attr_filter):
|
||||||
|
"""Sync objects from Neutron over to OpenDaylight.
|
||||||
|
|
||||||
|
This will handle syncing networks, subnets, and ports from Neutron to
|
||||||
|
OpenDaylight. It also filters out the requisite items which are not
|
||||||
|
valid for create API operations.
|
||||||
|
"""
|
||||||
|
to_be_synced = []
|
||||||
|
for resource in resources:
|
||||||
|
try:
|
||||||
|
urlpath = collection_name + '/' + resource['id']
|
||||||
|
self.sendjson('get', urlpath, None)
|
||||||
|
except requests.exceptions.HTTPError as e:
|
||||||
|
if e.response.status_code == 404:
|
||||||
|
attr_filter(resource, context, dbcontext)
|
||||||
|
to_be_synced.append(resource)
|
||||||
|
|
||||||
|
key = resource_name if len(to_be_synced) == 1 else collection_name
|
||||||
|
|
||||||
|
# 400 errors are returned if an object exists, which we ignore.
|
||||||
|
self.sendjson('post', collection_name, {key: to_be_synced}, [400])
|
||||||
|
|
||||||
|
@utils.synchronized('odl-sync-full')
|
||||||
|
def sync_full(self, context):
|
||||||
|
"""Resync the entire database to ODL.
|
||||||
|
|
||||||
|
Transition to the in-sync state on success.
|
||||||
|
Note: we only allow a single thead in here at a time.
|
||||||
|
"""
|
||||||
|
if not self.out_of_sync:
|
||||||
|
return
|
||||||
|
dbcontext = context._plugin_context
|
||||||
|
networks = context._plugin.get_networks(dbcontext)
|
||||||
|
subnets = context._plugin.get_subnets(dbcontext)
|
||||||
|
ports = context._plugin.get_ports(dbcontext)
|
||||||
|
|
||||||
|
self.sync_resources(ODL_NETWORK, ODL_NETWORKS, networks,
|
||||||
|
context, dbcontext,
|
||||||
|
self.filter_create_network_attributes)
|
||||||
|
self.sync_resources(ODL_SUBNET, ODL_SUBNETS, subnets,
|
||||||
|
context, dbcontext,
|
||||||
|
self.filter_create_subnet_attributes)
|
||||||
|
self.sync_resources(ODL_PORT, ODL_PORTS, ports,
|
||||||
|
context, dbcontext,
|
||||||
|
self.filter_create_port_attributes)
|
||||||
|
self.out_of_sync = False
|
||||||
|
|
||||||
|
def filter_update_network_attributes(self, network, context, dbcontext):
|
||||||
|
"""Filter out network attributes for an update operation."""
|
||||||
|
try_del(network, ['id', 'status', 'subnets', 'tenant_id'])
|
||||||
|
|
||||||
|
def filter_update_subnet_attributes(self, subnet, context, dbcontext):
|
||||||
|
"""Filter out subnet attributes for an update operation."""
|
||||||
|
try_del(subnet, ['id', 'network_id', 'ip_version', 'cidr',
|
||||||
|
'allocation_pools', 'tenant_id'])
|
||||||
|
|
||||||
|
def filter_update_port_attributes(self, port, context, dbcontext):
|
||||||
|
"""Filter out port attributes for an update operation."""
|
||||||
|
self.add_security_groups(context, dbcontext, port)
|
||||||
|
try_del(port, ['network_id', 'id', 'status', 'mac_address',
|
||||||
|
'tenant_id', 'fixed_ips'])
|
||||||
|
|
||||||
|
create_object_map = {ODL_NETWORKS: filter_create_network_attributes,
|
||||||
|
ODL_SUBNETS: filter_create_subnet_attributes,
|
||||||
|
ODL_PORTS: filter_create_port_attributes}
|
||||||
|
|
||||||
|
update_object_map = {ODL_NETWORKS: filter_update_network_attributes,
|
||||||
|
ODL_SUBNETS: filter_update_subnet_attributes,
|
||||||
|
ODL_PORTS: filter_update_port_attributes}
|
||||||
|
|
||||||
|
def sync_single_resource(self, operation, object_type, obj_id,
|
||||||
|
context, attr_filter_create, attr_filter_update):
|
||||||
|
"""Sync over a single resource from Neutron to OpenDaylight.
|
||||||
|
|
||||||
|
Handle syncing a single operation over to OpenDaylight, and correctly
|
||||||
|
filter attributes out which are not required for the requisite
|
||||||
|
operation (create or update) being handled.
|
||||||
|
"""
|
||||||
|
dbcontext = context._plugin_context
|
||||||
|
if operation == 'create':
|
||||||
|
urlpath = object_type
|
||||||
|
method = 'post'
|
||||||
|
else:
|
||||||
|
urlpath = object_type + '/' + obj_id
|
||||||
|
method = 'put'
|
||||||
|
|
||||||
|
try:
|
||||||
|
obj_getter = getattr(context._plugin, 'get_%s' % object_type[:-1])
|
||||||
|
resource = obj_getter(dbcontext, obj_id)
|
||||||
|
except not_found_exception_map[object_type]:
|
||||||
|
LOG.debug(_('%(object_type)s not found (%(obj_id)s)'),
|
||||||
|
{'object_type': object_type.capitalize(),
|
||||||
|
'obj_id': obj_id})
|
||||||
|
else:
|
||||||
|
if operation == 'create':
|
||||||
|
attr_filter_create(self, resource, context, dbcontext)
|
||||||
|
elif operation == 'update':
|
||||||
|
attr_filter_update(self, resource, context, dbcontext)
|
||||||
|
try:
|
||||||
|
# 400 errors are returned if an object exists, which we ignore.
|
||||||
|
self.sendjson(method, urlpath, {object_type[:-1]: resource},
|
||||||
|
[400])
|
||||||
|
except Exception:
|
||||||
|
with excutils.save_and_reraise_exception():
|
||||||
|
self.out_of_sync = True
|
||||||
|
|
||||||
|
def sync_object(self, operation, object_type, context):
|
||||||
|
"""Synchronize the single modified record to ODL."""
|
||||||
|
obj_id = context.current['id']
|
||||||
|
|
||||||
|
self.sync_single_resource(operation, object_type, obj_id, context,
|
||||||
|
self.create_object_map[object_type],
|
||||||
|
self.update_object_map[object_type])
|
||||||
|
|
||||||
|
def add_security_groups(self, context, dbcontext, port):
|
||||||
|
"""Populate the 'security_groups' field with entire records."""
|
||||||
|
groups = [context._plugin.get_security_group(dbcontext, sg)
|
||||||
|
for sg in port['security_groups']]
|
||||||
|
port['security_groups'] = groups
|
||||||
|
|
||||||
|
def sendjson(self, method, urlpath, obj, ignorecodes=[]):
|
||||||
|
"""Send json to the OpenDaylight controller."""
|
||||||
|
|
||||||
|
headers = {'Content-Type': 'application/json'}
|
||||||
|
data = jsonutils.dumps(obj, indent=2) if obj else None
|
||||||
|
if self.url:
|
||||||
|
url = '/'.join([self.url, urlpath])
|
||||||
|
LOG.debug(_('ODL-----> sending URL (%s) <-----ODL') % url)
|
||||||
|
LOG.debug(_('ODL-----> sending JSON (%s) <-----ODL') % obj)
|
||||||
|
r = requests.request(method, url=url,
|
||||||
|
headers=headers, data=data,
|
||||||
|
auth=self.auth, timeout=self.timeout)
|
||||||
|
|
||||||
|
# ignorecodes contains a list of HTTP error codes to ignore.
|
||||||
|
if r.status_code in ignorecodes:
|
||||||
|
return
|
||||||
|
r.raise_for_status()
|
||||||
|
|
||||||
|
def bind_port(self, context):
|
||||||
|
LOG.debug(_("Attempting to bind port %(port)s on "
|
||||||
|
"network %(network)s"),
|
||||||
|
{'port': context.current['id'],
|
||||||
|
'network': context.network.current['id']})
|
||||||
|
for segment in context.network.network_segments:
|
||||||
|
if self.check_segment(segment):
|
||||||
|
context.set_binding(segment[api.ID],
|
||||||
|
self.vif_type,
|
||||||
|
self.vif_details)
|
||||||
|
LOG.debug(_("Bound using segment: %s"), segment)
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
LOG.debug(_("Refusing to bind port for segment ID %(id)s, "
|
||||||
|
"segment %(seg)s, phys net %(physnet)s, and "
|
||||||
|
"network type %(nettype)s"),
|
||||||
|
{'id': segment[api.ID],
|
||||||
|
'seg': segment[api.SEGMENTATION_ID],
|
||||||
|
'physnet': segment[api.PHYSICAL_NETWORK],
|
||||||
|
'nettype': segment[api.NETWORK_TYPE]})
|
||||||
|
|
||||||
|
def validate_port_binding(self, context):
|
||||||
|
if self.check_segment(context.bound_segment):
|
||||||
|
LOG.debug(_('Binding valid.'))
|
||||||
|
return True
|
||||||
|
LOG.warning(_("Binding invalid for port: %s"), context.current)
|
||||||
|
|
||||||
|
def unbind_port(self, context):
|
||||||
|
LOG.debug(_("Unbinding port %(port)s on "
|
||||||
|
"network %(network)s"),
|
||||||
|
{'port': context.current['id'],
|
||||||
|
'network': context.network.current['id']})
|
||||||
|
|
||||||
|
def check_segment(self, segment):
|
||||||
|
"""Verify a segment is valid for the OpenDaylight MechanismDriver.
|
||||||
|
|
||||||
|
Verify the requested segment is supported by ODL and return True or
|
||||||
|
False to indicate this to callers.
|
||||||
|
"""
|
||||||
|
network_type = segment[api.NETWORK_TYPE]
|
||||||
|
return network_type in [constants.TYPE_LOCAL, constants.TYPE_GRE,
|
||||||
|
constants.TYPE_VXLAN]
|
79
neutron/tests/unit/ml2/test_mechanism_odl.py
Normal file
79
neutron/tests/unit/ml2/test_mechanism_odl.py
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
# Copyright (c) 2013-2014 OpenStack Foundation
|
||||||
|
# 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.
|
||||||
|
# @author: Kyle Mestery, Cisco Systems, Inc.
|
||||||
|
|
||||||
|
from neutron.plugins.common import constants
|
||||||
|
from neutron.plugins.ml2 import config as config
|
||||||
|
from neutron.plugins.ml2 import driver_api as api
|
||||||
|
from neutron.plugins.ml2.drivers import mechanism_odl
|
||||||
|
from neutron.tests.unit import test_db_plugin as test_plugin
|
||||||
|
|
||||||
|
PLUGIN_NAME = 'neutron.plugins.ml2.plugin.Ml2Plugin'
|
||||||
|
|
||||||
|
|
||||||
|
class OpenDaylightTestCase(test_plugin.NeutronDbPluginV2TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
# Enable the test mechanism driver to ensure that
|
||||||
|
# we can successfully call through to all mechanism
|
||||||
|
# driver apis.
|
||||||
|
config.cfg.CONF.set_override('mechanism_drivers',
|
||||||
|
['logger', 'opendaylight'],
|
||||||
|
'ml2')
|
||||||
|
super(OpenDaylightTestCase, self).setUp(PLUGIN_NAME)
|
||||||
|
self.port_create_status = 'DOWN'
|
||||||
|
self.segment = {'api.NETWORK_TYPE': ""}
|
||||||
|
self.mech = mechanism_odl.OpenDaylightMechanismDriver()
|
||||||
|
mechanism_odl.OpenDaylightMechanismDriver.sendjson = (
|
||||||
|
self.check_sendjson)
|
||||||
|
|
||||||
|
def check_sendjson(self, method, urlpath, obj, ignorecodes=[]):
|
||||||
|
self.assertFalse(urlpath.startswith("http://"))
|
||||||
|
|
||||||
|
def test_check_segment(self):
|
||||||
|
"""Validate the check_segment call."""
|
||||||
|
self.segment[api.NETWORK_TYPE] = constants.TYPE_LOCAL
|
||||||
|
self.assertTrue(self.mech.check_segment(self.segment))
|
||||||
|
self.segment[api.NETWORK_TYPE] = constants.TYPE_FLAT
|
||||||
|
self.assertFalse(self.mech.check_segment(self.segment))
|
||||||
|
self.segment[api.NETWORK_TYPE] = constants.TYPE_VLAN
|
||||||
|
self.assertFalse(self.mech.check_segment(self.segment))
|
||||||
|
self.segment[api.NETWORK_TYPE] = constants.TYPE_GRE
|
||||||
|
self.assertTrue(self.mech.check_segment(self.segment))
|
||||||
|
self.segment[api.NETWORK_TYPE] = constants.TYPE_VXLAN
|
||||||
|
self.assertTrue(self.mech.check_segment(self.segment))
|
||||||
|
# Validate a network type not currently supported
|
||||||
|
self.segment[api.NETWORK_TYPE] = 'mpls'
|
||||||
|
self.assertFalse(self.mech.check_segment(self.segment))
|
||||||
|
|
||||||
|
|
||||||
|
class OpenDaylightMechanismTestBasicGet(test_plugin.TestBasicGet,
|
||||||
|
OpenDaylightTestCase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class OpenDaylightMechanismTestNetworksV2(test_plugin.TestNetworksV2,
|
||||||
|
OpenDaylightTestCase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class OpenDaylightMechanismTestSubnetsV2(test_plugin.TestSubnetsV2,
|
||||||
|
OpenDaylightTestCase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class OpenDaylightMechanismTestPortsV2(test_plugin.TestPortsV2,
|
||||||
|
OpenDaylightTestCase):
|
||||||
|
pass
|
@ -63,6 +63,7 @@ data_files =
|
|||||||
etc/neutron/plugins/ml2/ml2_conf_arista.ini
|
etc/neutron/plugins/ml2/ml2_conf_arista.ini
|
||||||
etc/neutron/plugins/ml2/ml2_conf_brocade.ini
|
etc/neutron/plugins/ml2/ml2_conf_brocade.ini
|
||||||
etc/neutron/plugins/ml2/ml2_conf_cisco.ini
|
etc/neutron/plugins/ml2/ml2_conf_cisco.ini
|
||||||
|
etc/neutron/plugins/ml2/ml2_conf_odl.ini
|
||||||
etc/neutron/plugins/bigswitch/restproxy.ini
|
etc/neutron/plugins/bigswitch/restproxy.ini
|
||||||
etc/neutron/plugins/ml2/ml2_conf_ofa.ini
|
etc/neutron/plugins/ml2/ml2_conf_ofa.ini
|
||||||
etc/neutron/plugins/mlnx = etc/neutron/plugins/mlnx/mlnx_conf.ini
|
etc/neutron/plugins/mlnx = etc/neutron/plugins/mlnx/mlnx_conf.ini
|
||||||
@ -162,6 +163,7 @@ neutron.ml2.type_drivers =
|
|||||||
gre = neutron.plugins.ml2.drivers.type_gre:GreTypeDriver
|
gre = neutron.plugins.ml2.drivers.type_gre:GreTypeDriver
|
||||||
vxlan = neutron.plugins.ml2.drivers.type_vxlan:VxlanTypeDriver
|
vxlan = neutron.plugins.ml2.drivers.type_vxlan:VxlanTypeDriver
|
||||||
neutron.ml2.mechanism_drivers =
|
neutron.ml2.mechanism_drivers =
|
||||||
|
opendaylight = neutron.plugins.ml2.drivers.mechanism_odl:OpenDaylightMechanismDriver
|
||||||
logger = neutron.tests.unit.ml2.drivers.mechanism_logger:LoggerMechanismDriver
|
logger = neutron.tests.unit.ml2.drivers.mechanism_logger:LoggerMechanismDriver
|
||||||
test = neutron.tests.unit.ml2.drivers.mechanism_test:TestMechanismDriver
|
test = neutron.tests.unit.ml2.drivers.mechanism_test:TestMechanismDriver
|
||||||
bulkless = neutron.tests.unit.ml2.drivers.mechanism_bulkless:BulklessMechanismDriver
|
bulkless = neutron.tests.unit.ml2.drivers.mechanism_bulkless:BulklessMechanismDriver
|
||||||
|
Loading…
x
Reference in New Issue
Block a user