Add VXLAN tunneling support for the ML2 plugin
Add an ML2 Type Driver for VXLAN networks to allow the creation of VXLAN networks with the OVS agent. Change-Id: I7fcc384cc44d5adc752510847d8ba5f46bbb79fb Implements: blueprint ml2-vxlan
This commit is contained in:
parent
ba948fe6c1
commit
c95d3441e1
@ -3,15 +3,22 @@
|
|||||||
# (ListOpt) List of network type driver entrypoints to be loaded from
|
# (ListOpt) List of network type driver entrypoints to be loaded from
|
||||||
# the quantum.ml2.type_drivers namespace.
|
# the quantum.ml2.type_drivers namespace.
|
||||||
#
|
#
|
||||||
# type_drivers = local,flat,vlan,gre
|
# type_drivers = local,flat,vlan,gre,vxlan
|
||||||
# Example: type_drivers = flat,vlan,gre
|
# Example: type_drivers = flat,vlan,gre,vxlan
|
||||||
|
|
||||||
# (ListOpt) Ordered list of network_types to allocate as tenant
|
# (ListOpt) Ordered list of network_types to allocate as tenant
|
||||||
# networks. The default value 'local' is useful for single-box testing
|
# networks. The default value 'local' is useful for single-box testing
|
||||||
# but provides no connectivity between hosts.
|
# but provides no connectivity between hosts.
|
||||||
#
|
#
|
||||||
# tenant_network_types = local
|
# tenant_network_types = local
|
||||||
# Example: tenant_network_types = vlan,gre
|
# Example: tenant_network_types = vlan,gre,vxlan
|
||||||
|
|
||||||
|
# (StrOpt) Multicast group for the VXLAN interface. When configured, will
|
||||||
|
# enable sending all broadcast traffic to this multicast group. When left
|
||||||
|
# unconfigured, will disable multicast VXLAN mode.
|
||||||
|
#
|
||||||
|
# vxlan_group =
|
||||||
|
# Example: vxlan_group = 239.1.1.1
|
||||||
|
|
||||||
# (ListOpt) Ordered list of networking mechanism driver entrypoints
|
# (ListOpt) Ordered list of networking mechanism driver entrypoints
|
||||||
# to be loaded from the neutron.ml2.mechanism_drivers namespace.
|
# to be loaded from the neutron.ml2.mechanism_drivers namespace.
|
||||||
|
@ -0,0 +1,69 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
#
|
||||||
|
# Copyright 2013 OpenStack Foundation
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
"""DB Migration for ML2 VXLAN Type Driver
|
||||||
|
|
||||||
|
Revision ID: 477a4488d3f4
|
||||||
|
Revises: 20ae61555e95
|
||||||
|
Create Date: 2013-07-09 14:14:33.158502
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '477a4488d3f4'
|
||||||
|
down_revision = '20ae61555e95'
|
||||||
|
|
||||||
|
# Change to ['*'] if this migration applies to all plugins
|
||||||
|
|
||||||
|
migration_for_plugins = [
|
||||||
|
'neutron.plugins.ml2.plugin.Ml2Plugin'
|
||||||
|
]
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
from neutron.db import migration
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade(active_plugin=None, options=None):
|
||||||
|
if not migration.should_run(active_plugin, migration_for_plugins):
|
||||||
|
return
|
||||||
|
|
||||||
|
op.create_table(
|
||||||
|
'ml2_vxlan_allocations',
|
||||||
|
sa.Column('vxlan_vni', sa.Integer, nullable=False,
|
||||||
|
autoincrement=False),
|
||||||
|
sa.Column('allocated', sa.Boolean, nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint('vxlan_vni')
|
||||||
|
)
|
||||||
|
|
||||||
|
op.create_table(
|
||||||
|
'ml2_vxlan_endpoints',
|
||||||
|
sa.Column('ip_address', sa.String(length=64)),
|
||||||
|
sa.Column('udp_port', sa.Integer(), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint('ip_address'),
|
||||||
|
sa.PrimaryKeyConstraint('udp_port')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade(active_plugin=None, options=None):
|
||||||
|
if not migration.should_run(active_plugin, migration_for_plugins):
|
||||||
|
return
|
||||||
|
|
||||||
|
op.drop_table('ml2_vxlan_allocations')
|
||||||
|
op.drop_table('ml2_vxlan_endpoint')
|
@ -20,7 +20,7 @@ from neutron import scheduler
|
|||||||
|
|
||||||
ml2_opts = [
|
ml2_opts = [
|
||||||
cfg.ListOpt('type_drivers',
|
cfg.ListOpt('type_drivers',
|
||||||
default=['local', 'flat', 'vlan', 'gre'],
|
default=['local', 'flat', 'vlan', 'gre', 'vxlan'],
|
||||||
help=_("List of network type driver entrypoints to be loaded "
|
help=_("List of network type driver entrypoints to be loaded "
|
||||||
"from the neutron.ml2.type_drivers namespace.")),
|
"from the neutron.ml2.type_drivers namespace.")),
|
||||||
cfg.ListOpt('tenant_network_types',
|
cfg.ListOpt('tenant_network_types',
|
||||||
|
@ -26,6 +26,8 @@ from neutron.plugins.ml2.drivers import type_tunnel
|
|||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
TYPE_GRE = 'gre'
|
||||||
|
|
||||||
gre_opts = [
|
gre_opts = [
|
||||||
cfg.ListOpt('tunnel_id_ranges',
|
cfg.ListOpt('tunnel_id_ranges',
|
||||||
default=[],
|
default=[],
|
||||||
@ -60,11 +62,15 @@ class GreTypeDriver(api.TypeDriver,
|
|||||||
type_tunnel.TunnelTypeDriver):
|
type_tunnel.TunnelTypeDriver):
|
||||||
|
|
||||||
def get_type(self):
|
def get_type(self):
|
||||||
return type_tunnel.TYPE_GRE
|
return TYPE_GRE
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
self.gre_id_ranges = []
|
self.gre_id_ranges = []
|
||||||
self._parse_gre_id_ranges()
|
self._parse_tunnel_ranges(
|
||||||
|
cfg.CONF.ml2_type_gre.tunnel_id_ranges,
|
||||||
|
self.gre_id_ranges,
|
||||||
|
TYPE_GRE
|
||||||
|
)
|
||||||
self._sync_gre_allocations()
|
self._sync_gre_allocations()
|
||||||
|
|
||||||
def validate_provider_segment(self, segment):
|
def validate_provider_segment(self, segment):
|
||||||
@ -109,7 +115,7 @@ class GreTypeDriver(api.TypeDriver,
|
|||||||
LOG.debug(_("Allocating gre tunnel id %(gre_id)s"),
|
LOG.debug(_("Allocating gre tunnel id %(gre_id)s"),
|
||||||
{'gre_id': alloc.gre_id})
|
{'gre_id': alloc.gre_id})
|
||||||
alloc.allocated = True
|
alloc.allocated = True
|
||||||
return {api.NETWORK_TYPE: type_tunnel.TYPE_GRE,
|
return {api.NETWORK_TYPE: TYPE_GRE,
|
||||||
api.PHYSICAL_NETWORK: None,
|
api.PHYSICAL_NETWORK: None,
|
||||||
api.SEGMENTATION_ID: alloc.gre_id}
|
api.SEGMENTATION_ID: alloc.gre_id}
|
||||||
|
|
||||||
@ -134,21 +140,6 @@ class GreTypeDriver(api.TypeDriver,
|
|||||||
except sa_exc.NoResultFound:
|
except sa_exc.NoResultFound:
|
||||||
LOG.warning(_("gre_id %s not found"), gre_id)
|
LOG.warning(_("gre_id %s not found"), gre_id)
|
||||||
|
|
||||||
def _parse_gre_id_ranges(self):
|
|
||||||
for entry in cfg.CONF.ml2_type_gre.tunnel_id_ranges:
|
|
||||||
entry = entry.strip()
|
|
||||||
try:
|
|
||||||
tun_min, tun_max = entry.split(':')
|
|
||||||
tun_min = tun_min.strip()
|
|
||||||
tun_max = tun_max.strip()
|
|
||||||
self.gre_id_ranges.append((int(tun_min), int(tun_max)))
|
|
||||||
except ValueError as ex:
|
|
||||||
LOG.error(_("Invalid tunnel ID range: '%(range)s' - %(e)s. "
|
|
||||||
"Agent terminated!"),
|
|
||||||
{'range': cfg.CONF.ml2_type_gre.tunnel_id_ranges,
|
|
||||||
'e': ex})
|
|
||||||
LOG.info(_("gre ID ranges: %s"), self.gre_id_ranges)
|
|
||||||
|
|
||||||
def _sync_gre_allocations(self):
|
def _sync_gre_allocations(self):
|
||||||
"""Synchronize gre_allocations table with configured tunnel ranges."""
|
"""Synchronize gre_allocations table with configured tunnel ranges."""
|
||||||
|
|
||||||
|
@ -22,8 +22,6 @@ LOG = log.getLogger(__name__)
|
|||||||
|
|
||||||
TUNNEL = 'tunnel'
|
TUNNEL = 'tunnel'
|
||||||
|
|
||||||
TYPE_GRE = 'gre'
|
|
||||||
|
|
||||||
|
|
||||||
class TunnelTypeDriver(object):
|
class TunnelTypeDriver(object):
|
||||||
"""Define stable abstract interface for ML2 type drivers.
|
"""Define stable abstract interface for ML2 type drivers.
|
||||||
@ -50,6 +48,21 @@ class TunnelTypeDriver(object):
|
|||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def _parse_tunnel_ranges(self, tunnel_ranges, current_range, tunnel_type):
|
||||||
|
for entry in tunnel_ranges:
|
||||||
|
entry = entry.strip()
|
||||||
|
try:
|
||||||
|
tun_min, tun_max = entry.split(':')
|
||||||
|
tun_min = tun_min.strip()
|
||||||
|
tun_max = tun_max.strip()
|
||||||
|
current_range.append((int(tun_min), int(tun_max)))
|
||||||
|
except ValueError as ex:
|
||||||
|
LOG.error(_("Invalid tunnel ID range: '%(range)s' - %(e)s. "
|
||||||
|
"Agent terminated!"),
|
||||||
|
{'range': tunnel_ranges, 'e': ex})
|
||||||
|
LOG.info(_("%(type)s ID ranges: %(range)s"),
|
||||||
|
{'type': tunnel_type, 'range': current_range})
|
||||||
|
|
||||||
|
|
||||||
class TunnelRpcCallbackMixin(object):
|
class TunnelRpcCallbackMixin(object):
|
||||||
|
|
||||||
|
215
neutron/plugins/ml2/drivers/type_vxlan.py
Normal file
215
neutron/plugins/ml2/drivers/type_vxlan.py
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
# Copyright (c) 2013 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 oslo.config import cfg
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.orm import exc as sa_exc
|
||||||
|
|
||||||
|
from neutron.common import exceptions as exc
|
||||||
|
from neutron.db import api as db_api
|
||||||
|
from neutron.db import model_base
|
||||||
|
from neutron.openstack.common import log
|
||||||
|
from neutron.plugins.ml2 import driver_api as api
|
||||||
|
from neutron.plugins.ml2.drivers import type_tunnel
|
||||||
|
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
TYPE_VXLAN = 'vxlan'
|
||||||
|
VXLAN_UDP_PORT = 4789
|
||||||
|
MAX_VXLAN_VNI = 16777215
|
||||||
|
|
||||||
|
vxlan_opts = [
|
||||||
|
cfg.ListOpt('vni_ranges',
|
||||||
|
default=[],
|
||||||
|
help=_("Comma-separated list of <vni_min>:<vni_max> tuples "
|
||||||
|
"enumerating ranges of VXLAN VNI IDs that are "
|
||||||
|
"available for tenant network allocation")),
|
||||||
|
cfg.StrOpt('vxlan_group', default=None,
|
||||||
|
help=_("Multicast group for VXLAN. If unset, disables VXLAN "
|
||||||
|
"multicast mode.")),
|
||||||
|
]
|
||||||
|
|
||||||
|
cfg.CONF.register_opts(vxlan_opts, "ml2_type_vxlan")
|
||||||
|
|
||||||
|
|
||||||
|
class VxlanAllocation(model_base.BASEV2):
|
||||||
|
|
||||||
|
__tablename__ = 'ml2_vxlan_allocations'
|
||||||
|
|
||||||
|
vxlan_vni = sa.Column(sa.Integer, nullable=False, primary_key=True,
|
||||||
|
autoincrement=False)
|
||||||
|
allocated = sa.Column(sa.Boolean, nullable=False, default=False)
|
||||||
|
|
||||||
|
|
||||||
|
class VxlanEndpoints(model_base.BASEV2):
|
||||||
|
"""Represents tunnel endpoint in RPC mode."""
|
||||||
|
__tablename__ = 'ml2_vxlan_endpoints'
|
||||||
|
|
||||||
|
ip_address = sa.Column(sa.String(64), primary_key=True)
|
||||||
|
udp_port = sa.Column(sa.Integer, primary_key=True, nullable=False)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<VxlanTunnelEndpoint(%s)>" % self.ip_address
|
||||||
|
|
||||||
|
|
||||||
|
class VxlanTypeDriver(api.TypeDriver,
|
||||||
|
type_tunnel.TunnelTypeDriver):
|
||||||
|
|
||||||
|
def get_type(self):
|
||||||
|
return TYPE_VXLAN
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
self.vxlan_vni_ranges = []
|
||||||
|
self._parse_tunnel_ranges(
|
||||||
|
cfg.CONF.ml2_type_vxlan.vni_ranges,
|
||||||
|
self.vxlan_vni_ranges,
|
||||||
|
TYPE_VXLAN
|
||||||
|
)
|
||||||
|
self._sync_vxlan_allocations()
|
||||||
|
|
||||||
|
def validate_provider_segment(self, segment):
|
||||||
|
physical_network = segment.get(api.PHYSICAL_NETWORK)
|
||||||
|
if physical_network:
|
||||||
|
msg = _("provider:physical_network specified for VXLAN "
|
||||||
|
"network")
|
||||||
|
raise exc.InvalidInput(error_message=msg)
|
||||||
|
|
||||||
|
segmentation_id = segment.get(api.SEGMENTATION_ID)
|
||||||
|
if segmentation_id is None:
|
||||||
|
msg = _("segmentation_id required for VXLAN provider network")
|
||||||
|
raise exc.InvalidInput(error_message=msg)
|
||||||
|
|
||||||
|
def reserve_provider_segment(self, session, segment):
|
||||||
|
segmentation_id = segment.get(api.SEGMENTATION_ID)
|
||||||
|
with session.begin(subtransactions=True):
|
||||||
|
try:
|
||||||
|
alloc = (session.query(VxlanAllocation).
|
||||||
|
filter_by(vxlan_vni=segmentation_id).
|
||||||
|
with_lockmode('update').
|
||||||
|
one())
|
||||||
|
if alloc.allocated:
|
||||||
|
raise exc.TunnelIdInUse(tunnel_id=segmentation_id)
|
||||||
|
LOG.debug(_("Reserving specific vxlan tunnel %s from pool"),
|
||||||
|
segmentation_id)
|
||||||
|
alloc.allocated = True
|
||||||
|
except sa_exc.NoResultFound:
|
||||||
|
LOG.debug(_("Reserving specific vxlan tunnel %s outside pool"),
|
||||||
|
segmentation_id)
|
||||||
|
alloc = VxlanAllocation(vxlan_vni=segmentation_id)
|
||||||
|
alloc.allocated = True
|
||||||
|
session.add(alloc)
|
||||||
|
|
||||||
|
def allocate_tenant_segment(self, session):
|
||||||
|
with session.begin(subtransactions=True):
|
||||||
|
alloc = (session.query(VxlanAllocation).
|
||||||
|
filter_by(allocated=False).
|
||||||
|
with_lockmode('update').
|
||||||
|
first())
|
||||||
|
if alloc:
|
||||||
|
LOG.debug(_("Allocating vxlan tunnel vni %(vxlan_vni)s"),
|
||||||
|
{'vxlan_vni': alloc.vxlan_vni})
|
||||||
|
alloc.allocated = True
|
||||||
|
return {api.NETWORK_TYPE: TYPE_VXLAN,
|
||||||
|
api.PHYSICAL_NETWORK: None,
|
||||||
|
api.SEGMENTATION_ID: alloc.vxlan_vni}
|
||||||
|
|
||||||
|
def release_segment(self, session, segment):
|
||||||
|
vxlan_vni = segment[api.SEGMENTATION_ID]
|
||||||
|
with session.begin(subtransactions=True):
|
||||||
|
try:
|
||||||
|
alloc = (session.query(VxlanAllocation).
|
||||||
|
filter_by(vxlan_vni=vxlan_vni).
|
||||||
|
with_lockmode('update').
|
||||||
|
one())
|
||||||
|
alloc.allocated = False
|
||||||
|
for low, high in self.vxlan_vni_ranges:
|
||||||
|
if low <= vxlan_vni <= high:
|
||||||
|
LOG.debug(_("Releasing vxlan tunnel %s to pool"),
|
||||||
|
vxlan_vni)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
session.delete(alloc)
|
||||||
|
LOG.debug(_("Releasing vxlan tunnel %s outside pool"),
|
||||||
|
vxlan_vni)
|
||||||
|
except sa_exc.NoResultFound:
|
||||||
|
LOG.warning(_("vxlan_vni %s not found"), vxlan_vni)
|
||||||
|
|
||||||
|
def _sync_vxlan_allocations(self):
|
||||||
|
"""
|
||||||
|
Synchronize vxlan_allocations table with configured tunnel ranges.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# determine current configured allocatable vnis
|
||||||
|
vxlan_vnis = set()
|
||||||
|
for tun_min, tun_max in self.vxlan_vni_ranges:
|
||||||
|
if tun_max + 1 - tun_min > MAX_VXLAN_VNI:
|
||||||
|
LOG.error(_("Skipping unreasonable VXLAN VNI range "
|
||||||
|
"%(tun_min)s:%(tun_max)s"),
|
||||||
|
{'tun_min': tun_min, 'tun_max': tun_max})
|
||||||
|
else:
|
||||||
|
vxlan_vnis |= set(xrange(tun_min, tun_max + 1))
|
||||||
|
|
||||||
|
session = db_api.get_session()
|
||||||
|
with session.begin(subtransactions=True):
|
||||||
|
# remove from table unallocated tunnels not currently allocatable
|
||||||
|
allocs = session.query(VxlanAllocation)
|
||||||
|
for alloc in allocs:
|
||||||
|
try:
|
||||||
|
# see if tunnel is allocatable
|
||||||
|
vxlan_vnis.remove(alloc.vxlan_vni)
|
||||||
|
except KeyError:
|
||||||
|
# it's not allocatable, so check if its allocated
|
||||||
|
if not alloc.allocated:
|
||||||
|
# it's not, so remove it from table
|
||||||
|
LOG.debug(_("Removing tunnel %s from pool"),
|
||||||
|
alloc.vxlan_vni)
|
||||||
|
session.delete(alloc)
|
||||||
|
|
||||||
|
# add missing allocatable tunnels to table
|
||||||
|
for vxlan_vni in sorted(vxlan_vnis):
|
||||||
|
alloc = VxlanAllocation(vxlan_vni=vxlan_vni)
|
||||||
|
session.add(alloc)
|
||||||
|
|
||||||
|
def get_vxlan_allocation(self, session, vxlan_vni):
|
||||||
|
with session.begin(subtransactions=True):
|
||||||
|
return session.query(VxlanAllocation).filter_by(
|
||||||
|
vxlan_vni=vxlan_vni).first()
|
||||||
|
|
||||||
|
def get_endpoints(self):
|
||||||
|
"""Get every vxlan endpoints from database."""
|
||||||
|
|
||||||
|
LOG.debug(_("get_vxlan_endpoints() called"))
|
||||||
|
session = db_api.get_session()
|
||||||
|
|
||||||
|
with session.begin(subtransactions=True):
|
||||||
|
vxlan_endpoints = session.query(VxlanEndpoints)
|
||||||
|
return [{'ip_address': vxlan_endpoint.ip_address,
|
||||||
|
'udp_port': vxlan_endpoint.udp_port}
|
||||||
|
for vxlan_endpoint in vxlan_endpoints]
|
||||||
|
|
||||||
|
def add_endpoint(self, ip, udp_port=VXLAN_UDP_PORT):
|
||||||
|
LOG.debug(_("add_vxlan_endpoint() called for ip %s"), ip)
|
||||||
|
session = db_api.get_session()
|
||||||
|
with session.begin(subtransactions=True):
|
||||||
|
try:
|
||||||
|
vxlan_endpoint = (session.query(VxlanEndpoints).
|
||||||
|
filter_by(ip_address=ip).
|
||||||
|
with_lockmode('update').one())
|
||||||
|
except sa_exc.NoResultFound:
|
||||||
|
vxlan_endpoint = VxlanEndpoints(ip_address=ip,
|
||||||
|
udp_port=udp_port)
|
||||||
|
session.add(vxlan_endpoint)
|
||||||
|
return vxlan_endpoint
|
196
neutron/tests/unit/ml2/test_type_vxlan.py
Normal file
196
neutron/tests/unit/ml2/test_type_vxlan.py
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
# Copyright (c) 2013 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 oslo.config import cfg
|
||||||
|
import testtools
|
||||||
|
from testtools import matchers
|
||||||
|
|
||||||
|
from neutron.common import exceptions as exc
|
||||||
|
from neutron.db import api as db
|
||||||
|
from neutron.plugins.ml2 import db as ml2_db
|
||||||
|
from neutron.plugins.ml2 import driver_api as api
|
||||||
|
from neutron.plugins.ml2.drivers import type_vxlan
|
||||||
|
from neutron.tests import base
|
||||||
|
|
||||||
|
|
||||||
|
TUNNEL_IP_ONE = "10.10.10.10"
|
||||||
|
TUNNEL_IP_TWO = "10.10.10.20"
|
||||||
|
TUN_MIN = 100
|
||||||
|
TUN_MAX = 109
|
||||||
|
TUNNEL_RANGES = [(TUN_MIN, TUN_MAX)]
|
||||||
|
UPDATED_TUNNEL_RANGES = [(TUN_MIN + 5, TUN_MAX + 5)]
|
||||||
|
INVALID_VXLAN_VNI = 7337
|
||||||
|
MULTICAST_GROUP = "239.1.1.1"
|
||||||
|
VXLAN_UDP_PORT_ONE = 9999
|
||||||
|
VXLAN_UDP_PORT_TWO = 8888
|
||||||
|
|
||||||
|
|
||||||
|
class VxlanTypeTest(base.BaseTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(VxlanTypeTest, self).setUp()
|
||||||
|
ml2_db.initialize()
|
||||||
|
cfg.CONF.set_override('vni_ranges', [TUNNEL_RANGES],
|
||||||
|
group='ml2_type_vxlan')
|
||||||
|
cfg.CONF.set_override('vxlan_group', MULTICAST_GROUP,
|
||||||
|
group='ml2_type_vxlan')
|
||||||
|
self.driver = type_vxlan.VxlanTypeDriver()
|
||||||
|
self.driver.vxlan_vni_ranges = TUNNEL_RANGES
|
||||||
|
self.driver._sync_vxlan_allocations()
|
||||||
|
self.session = db.get_session()
|
||||||
|
self.addCleanup(cfg.CONF.reset)
|
||||||
|
self.addCleanup(db.clear_db)
|
||||||
|
|
||||||
|
def test_vxlan_tunnel_type(self):
|
||||||
|
self.assertEqual(self.driver.get_type(), type_vxlan.TYPE_VXLAN)
|
||||||
|
|
||||||
|
def test_validate_provider_segment(self):
|
||||||
|
segment = {api.NETWORK_TYPE: 'vxlan',
|
||||||
|
api.PHYSICAL_NETWORK: 'phys_net',
|
||||||
|
api.SEGMENTATION_ID: None}
|
||||||
|
|
||||||
|
with testtools.ExpectedException(exc.InvalidInput):
|
||||||
|
self.driver.validate_provider_segment(segment)
|
||||||
|
|
||||||
|
segment[api.PHYSICAL_NETWORK] = None
|
||||||
|
with testtools.ExpectedException(exc.InvalidInput):
|
||||||
|
self.driver.validate_provider_segment(segment)
|
||||||
|
|
||||||
|
def test_sync_tunnel_allocations(self):
|
||||||
|
self.assertIsNone(
|
||||||
|
self.driver.get_vxlan_allocation(self.session,
|
||||||
|
(TUN_MIN - 1))
|
||||||
|
)
|
||||||
|
self.assertFalse(
|
||||||
|
self.driver.get_vxlan_allocation(self.session,
|
||||||
|
(TUN_MIN)).allocated
|
||||||
|
)
|
||||||
|
self.assertFalse(
|
||||||
|
self.driver.get_vxlan_allocation(self.session,
|
||||||
|
(TUN_MIN + 1)).allocated
|
||||||
|
)
|
||||||
|
self.assertFalse(
|
||||||
|
self.driver.get_vxlan_allocation(self.session,
|
||||||
|
(TUN_MAX - 1)).allocated
|
||||||
|
)
|
||||||
|
self.assertFalse(
|
||||||
|
self.driver.get_vxlan_allocation(self.session,
|
||||||
|
(TUN_MAX)).allocated
|
||||||
|
)
|
||||||
|
self.assertIsNone(
|
||||||
|
self.driver.get_vxlan_allocation(self.session,
|
||||||
|
(TUN_MAX + 1))
|
||||||
|
)
|
||||||
|
|
||||||
|
self.driver.vxlan_vni_ranges = UPDATED_TUNNEL_RANGES
|
||||||
|
self.driver._sync_vxlan_allocations()
|
||||||
|
|
||||||
|
self.assertIsNone(self.driver.
|
||||||
|
get_vxlan_allocation(self.session,
|
||||||
|
(TUN_MIN + 5 - 1)))
|
||||||
|
self.assertFalse(self.driver.
|
||||||
|
get_vxlan_allocation(self.session, (TUN_MIN + 5)).
|
||||||
|
allocated)
|
||||||
|
self.assertFalse(self.driver.
|
||||||
|
get_vxlan_allocation(self.session, (TUN_MIN + 5 + 1)).
|
||||||
|
allocated)
|
||||||
|
self.assertFalse(self.driver.
|
||||||
|
get_vxlan_allocation(self.session, (TUN_MAX + 5 - 1)).
|
||||||
|
allocated)
|
||||||
|
self.assertFalse(self.driver.
|
||||||
|
get_vxlan_allocation(self.session, (TUN_MAX + 5)).
|
||||||
|
allocated)
|
||||||
|
self.assertIsNone(self.driver.
|
||||||
|
get_vxlan_allocation(self.session,
|
||||||
|
(TUN_MAX + 5 + 1)))
|
||||||
|
|
||||||
|
def test_reserve_provider_segment(self):
|
||||||
|
segment = {api.NETWORK_TYPE: 'vxlan',
|
||||||
|
api.PHYSICAL_NETWORK: 'None',
|
||||||
|
api.SEGMENTATION_ID: 101}
|
||||||
|
self.driver.reserve_provider_segment(self.session, segment)
|
||||||
|
alloc = self.driver.get_vxlan_allocation(self.session,
|
||||||
|
segment[api.SEGMENTATION_ID])
|
||||||
|
self.assertTrue(alloc.allocated)
|
||||||
|
|
||||||
|
with testtools.ExpectedException(exc.TunnelIdInUse):
|
||||||
|
self.driver.reserve_provider_segment(self.session, segment)
|
||||||
|
|
||||||
|
self.driver.release_segment(self.session, segment)
|
||||||
|
alloc = self.driver.get_vxlan_allocation(self.session,
|
||||||
|
segment[api.SEGMENTATION_ID])
|
||||||
|
self.assertFalse(alloc.allocated)
|
||||||
|
|
||||||
|
segment[api.SEGMENTATION_ID] = 1000
|
||||||
|
self.driver.reserve_provider_segment(self.session, segment)
|
||||||
|
alloc = self.driver.get_vxlan_allocation(self.session,
|
||||||
|
segment[api.SEGMENTATION_ID])
|
||||||
|
self.assertTrue(alloc.allocated)
|
||||||
|
|
||||||
|
self.driver.release_segment(self.session, segment)
|
||||||
|
alloc = self.driver.get_vxlan_allocation(self.session,
|
||||||
|
segment[api.SEGMENTATION_ID])
|
||||||
|
self.assertIsNone(alloc)
|
||||||
|
|
||||||
|
def test_allocate_tenant_segment(self):
|
||||||
|
tunnel_ids = set()
|
||||||
|
for x in xrange(TUN_MIN, TUN_MAX + 1):
|
||||||
|
segment = self.driver.allocate_tenant_segment(self.session)
|
||||||
|
self.assertThat(segment[api.SEGMENTATION_ID],
|
||||||
|
matchers.GreaterThan(TUN_MIN - 1))
|
||||||
|
self.assertThat(segment[api.SEGMENTATION_ID],
|
||||||
|
matchers.LessThan(TUN_MAX + 1))
|
||||||
|
tunnel_ids.add(segment[api.SEGMENTATION_ID])
|
||||||
|
|
||||||
|
segment = self.driver.allocate_tenant_segment(self.session)
|
||||||
|
self.assertEqual(None, segment)
|
||||||
|
|
||||||
|
segment = {api.NETWORK_TYPE: 'vxlan',
|
||||||
|
api.PHYSICAL_NETWORK: 'None',
|
||||||
|
api.SEGMENTATION_ID: tunnel_ids.pop()}
|
||||||
|
self.driver.release_segment(self.session, segment)
|
||||||
|
segment = self.driver.allocate_tenant_segment(self.session)
|
||||||
|
self.assertThat(segment[api.SEGMENTATION_ID],
|
||||||
|
matchers.GreaterThan(TUN_MIN - 1))
|
||||||
|
self.assertThat(segment[api.SEGMENTATION_ID],
|
||||||
|
matchers.LessThan(TUN_MAX + 1))
|
||||||
|
tunnel_ids.add(segment[api.SEGMENTATION_ID])
|
||||||
|
|
||||||
|
for tunnel_id in tunnel_ids:
|
||||||
|
segment[api.SEGMENTATION_ID] = tunnel_id
|
||||||
|
self.driver.release_segment(self.session, segment)
|
||||||
|
|
||||||
|
def test_vxlan_endpoints(self):
|
||||||
|
"""Test VXLAN allocation/de-allocation."""
|
||||||
|
|
||||||
|
# Set first endpoint, verify it gets VXLAN VNI 1
|
||||||
|
vxlan1_endpoint = self.driver.add_endpoint(TUNNEL_IP_ONE,
|
||||||
|
VXLAN_UDP_PORT_ONE)
|
||||||
|
self.assertEqual(TUNNEL_IP_ONE, vxlan1_endpoint.ip_address)
|
||||||
|
self.assertEqual(VXLAN_UDP_PORT_ONE, vxlan1_endpoint.udp_port)
|
||||||
|
|
||||||
|
# Set second endpoint, verify it gets VXLAN VNI 2
|
||||||
|
vxlan2_endpoint = self.driver.add_endpoint(TUNNEL_IP_TWO,
|
||||||
|
VXLAN_UDP_PORT_TWO)
|
||||||
|
self.assertEqual(TUNNEL_IP_TWO, vxlan2_endpoint.ip_address)
|
||||||
|
self.assertEqual(VXLAN_UDP_PORT_TWO, vxlan2_endpoint.udp_port)
|
||||||
|
|
||||||
|
# Get all the endpoints
|
||||||
|
endpoints = self.driver.get_endpoints()
|
||||||
|
for endpoint in endpoints:
|
||||||
|
if endpoint['ip_address'] == TUNNEL_IP_ONE:
|
||||||
|
self.assertEqual(VXLAN_UDP_PORT_ONE, endpoint['udp_port'])
|
||||||
|
elif endpoint['ip_address'] == TUNNEL_IP_TWO:
|
||||||
|
self.assertEqual(VXLAN_UDP_PORT_TWO, endpoint['udp_port'])
|
@ -108,6 +108,7 @@ neutron.ml2.type_drivers =
|
|||||||
local = neutron.plugins.ml2.drivers.type_local:LocalTypeDriver
|
local = neutron.plugins.ml2.drivers.type_local:LocalTypeDriver
|
||||||
vlan = neutron.plugins.ml2.drivers.type_vlan:VlanTypeDriver
|
vlan = neutron.plugins.ml2.drivers.type_vlan:VlanTypeDriver
|
||||||
gre = neutron.plugins.ml2.drivers.type_gre:GreTypeDriver
|
gre = neutron.plugins.ml2.drivers.type_gre:GreTypeDriver
|
||||||
|
vxlan = neutron.plugins.ml2.drivers.type_vxlan:VxlanTypeDriver
|
||||||
neutron.ml2.mechanism_drivers =
|
neutron.ml2.mechanism_drivers =
|
||||||
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
|
||||||
|
Loading…
Reference in New Issue
Block a user