]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Add VXLAN tunneling support for the ML2 plugin
authorKyle Mestery <kmestery@cisco.com>
Wed, 26 Jun 2013 07:13:32 +0000 (07:13 +0000)
committerKyle Mestery <kmestery@cisco.com>
Tue, 16 Jul 2013 13:34:46 +0000 (13:34 +0000)
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

etc/neutron/plugins/ml2/ml2_conf.ini
neutron/db/migration/alembic_migrations/versions/477a4488d3f4_ml2_vxlan_type_driver.py [new file with mode: 0644]
neutron/plugins/ml2/config.py
neutron/plugins/ml2/drivers/type_gre.py
neutron/plugins/ml2/drivers/type_tunnel.py
neutron/plugins/ml2/drivers/type_vxlan.py [new file with mode: 0644]
neutron/tests/unit/ml2/test_type_vxlan.py [new file with mode: 0644]
setup.cfg

index 3e58206086a8c116ee7970a06f3f468d13aec7b6..4ad62e3eef5f0eb3f09a8b4fb1c236f2f1509883 100644 (file)
@@ -3,15 +3,22 @@
 # (ListOpt) List of network type driver entrypoints to be loaded from
 # the quantum.ml2.type_drivers namespace.
 #
-# type_drivers = local,flat,vlan,gre
-# Example: type_drivers = flat,vlan,gre
+# type_drivers = local,flat,vlan,gre,vxlan
+# Example: type_drivers = flat,vlan,gre,vxlan
 
 # (ListOpt) Ordered list of network_types to allocate as tenant
 # networks. The default value 'local' is useful for single-box testing
 # but provides no connectivity between hosts.
 #
 # 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
 # to be loaded from the neutron.ml2.mechanism_drivers namespace.
diff --git a/neutron/db/migration/alembic_migrations/versions/477a4488d3f4_ml2_vxlan_type_driver.py b/neutron/db/migration/alembic_migrations/versions/477a4488d3f4_ml2_vxlan_type_driver.py
new file mode 100644 (file)
index 0000000..ecb8a5d
--- /dev/null
@@ -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')
index a24152523b92fe2aa90b7f07dae20642c1395d4d..bbec2ab1d0f86409bee4fb107d513f42eeaa4e19 100644 (file)
@@ -20,7 +20,7 @@ from neutron import scheduler
 
 ml2_opts = [
     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 "
                        "from the neutron.ml2.type_drivers namespace.")),
     cfg.ListOpt('tenant_network_types',
index ba565ee9ad58855313aa62fd077d467633f53d37..906d61f867d2fe057e653ff7efca99e650aa780a 100644 (file)
@@ -26,6 +26,8 @@ from neutron.plugins.ml2.drivers import type_tunnel
 
 LOG = log.getLogger(__name__)
 
+TYPE_GRE = 'gre'
+
 gre_opts = [
     cfg.ListOpt('tunnel_id_ranges',
                 default=[],
@@ -60,11 +62,15 @@ class GreTypeDriver(api.TypeDriver,
                     type_tunnel.TunnelTypeDriver):
 
     def get_type(self):
-        return type_tunnel.TYPE_GRE
+        return TYPE_GRE
 
     def initialize(self):
         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()
 
     def validate_provider_segment(self, segment):
@@ -109,7 +115,7 @@ class GreTypeDriver(api.TypeDriver,
                 LOG.debug(_("Allocating gre tunnel id  %(gre_id)s"),
                           {'gre_id': alloc.gre_id})
                 alloc.allocated = True
-                return {api.NETWORK_TYPE: type_tunnel.TYPE_GRE,
+                return {api.NETWORK_TYPE: TYPE_GRE,
                         api.PHYSICAL_NETWORK: None,
                         api.SEGMENTATION_ID: alloc.gre_id}
 
@@ -134,21 +140,6 @@ class GreTypeDriver(api.TypeDriver,
             except sa_exc.NoResultFound:
                 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):
         """Synchronize gre_allocations table with configured tunnel ranges."""
 
index 4038176aa8d8443224567df39e2ebfee38c86671..b447f5582bc5cf4b35c6a23a31724e94dbd109a2 100644 (file)
@@ -22,8 +22,6 @@ LOG = log.getLogger(__name__)
 
 TUNNEL = 'tunnel'
 
-TYPE_GRE = 'gre'
-
 
 class TunnelTypeDriver(object):
     """Define stable abstract interface for ML2 type drivers.
@@ -50,6 +48,21 @@ class TunnelTypeDriver(object):
         """
         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):
 
diff --git a/neutron/plugins/ml2/drivers/type_vxlan.py b/neutron/plugins/ml2/drivers/type_vxlan.py
new file mode 100644 (file)
index 0000000..949a11b
--- /dev/null
@@ -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
diff --git a/neutron/tests/unit/ml2/test_type_vxlan.py b/neutron/tests/unit/ml2/test_type_vxlan.py
new file mode 100644 (file)
index 0000000..7f4fd8b
--- /dev/null
@@ -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'])
index 0da8260c31d539f7f778de427830bfb279d633e3..8e2167b9e6923b5830f93ea7dc0bb5e4c8bbf778 100644 (file)
--- a/setup.cfg
+++ b/setup.cfg
@@ -108,6 +108,7 @@ neutron.ml2.type_drivers =
     local = neutron.plugins.ml2.drivers.type_local:LocalTypeDriver
     vlan = neutron.plugins.ml2.drivers.type_vlan:VlanTypeDriver
     gre = neutron.plugins.ml2.drivers.type_gre:GreTypeDriver
+    vxlan = neutron.plugins.ml2.drivers.type_vxlan:VxlanTypeDriver
 neutron.ml2.mechanism_drivers =
     logger = neutron.tests.unit.ml2.drivers.mechanism_logger:LoggerMechanismDriver
     test = neutron.tests.unit.ml2.drivers.mechanism_test:TestMechanismDriver