Add Geneve type driver support to ML2

More information about Geneve protocol can be found here:
  https://tools.ietf.org/pdf/draft-gross-geneve-02.pdf

Following configuration variables were added:
[ml2_type_geneve]
  vni_ranges - Comma-separated list of <vni_min>:<vni_max> tuples
               enumerating ranges of Geneve VNI IDs that are
               available for tenant network allocation
  max_header_size - Geneve encapsulation header size is dynamic, this
                    value is used to calculate the maximum MTU for the driver
                    this is the sum of the sizes of the outer
                    ETH + IP + UDP + GENEVE header sizes

DocImpact

Change-Id: I8c29a1c1a7c79e02c26ac9e2ad2645d30dfbeefc
Closes-Bug: #1461069
changes/45/187945/21
Aaron Rosen 2015-05-05 14:35:08 -07:00 committed by Gal Sagie
parent 9ed4be7559
commit 7b7c15ba4e
17 changed files with 295 additions and 14 deletions

View File

@ -26,7 +26,6 @@ GRE Tunneling is documented in depth in the `Networking in too much
detail <http://openstack.redhat.com/Networking_in_too_much_detail>`_
by RedHat.
VXLAN Tunnels
-------------
@ -35,6 +34,16 @@ at layer 2 into a UDP header.
More information can be found in `The VXLAN wiki page.
<http://en.wikipedia.org/wiki/Virtual_Extensible_LAN>`_
Geneve Tunnels
--------------
Geneve uses UDP as its transport protocol and is dynamic
in size using extensible option headers.
It is important to note that currently it is only supported in
newer kernels. (kernel >= 3.18, OVS version >=2.4)
More information can be found in the `Geneve RFC document.
<https://tools.ietf.org/html/draft-ietf-nvo3-geneve-00>`_
Bridge Management
-----------------
@ -71,6 +80,7 @@ future to support existing VLAN-tagged traffic (coming from NFV VMs
for instance) and/or to deal with potential QinQ support natively
available in the Open vSwitch.
Further Reading
---------------

View File

@ -2,15 +2,16 @@
# (ListOpt) List of network type driver entrypoints to be loaded from
# the neutron.ml2.type_drivers namespace.
#
# type_drivers = local,flat,vlan,gre,vxlan
# Example: type_drivers = flat,vlan,gre,vxlan
# type_drivers = local,flat,vlan,gre,vxlan,geneve
# Example: type_drivers = flat,vlan,gre,vxlan,geneve
# (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,vxlan
# Example: tenant_network_types = vlan,gre,vxlan,geneve
# (ListOpt) Ordered list of networking mechanism driver entrypoints
# to be loaded from the neutron.ml2.mechanism_drivers namespace.
@ -93,6 +94,22 @@
# vxlan_group =
# Example: vxlan_group = 239.1.1.1
[ml2_type_geneve]
# (ListOpt) Comma-separated list of <vni_min>:<vni_max> tuples enumerating
# ranges of Geneve VNI IDs that are available for tenant network allocation.
#
# vni_ranges =
# (IntOpt) Geneve encapsulation header size is dynamic, this
# value is used to calculate the maximum MTU for the driver.
# this is the sum of the sizes of the outer ETH+IP+UDP+GENEVE
# header sizes.
# The default size for this field is 50, which is the size of the
# Geneve header without any additional option headers
#
# max_header_size =
# Example: max_header_size = 50 (Geneve headers with no additional options)
[securitygroup]
# Controls if neutron security group is enabled or not.
# It should be false when you use nova security group.

View File

@ -52,6 +52,13 @@ def ovs_vxlan_supported(from_ip='192.0.2.1', to_ip='192.0.2.2'):
return port != ovs_lib.INVALID_OFPORT
def ovs_geneve_supported(from_ip='192.0.2.3', to_ip='192.0.2.4'):
name = "genevetest-" + utils.get_random_string(6)
with ovs_lib.OVSBridge(name) as br:
port = br.add_tunnel_port(from_ip, to_ip, const.TYPE_GENEVE)
return port != ovs_lib.INVALID_OFPORT
def iproute2_vxlan_supported():
ip = ip_lib.IPWrapper()
name = "vxlantest-" + utils.get_random_string(4)

View File

@ -56,6 +56,15 @@ def check_ovs_vxlan():
return result
def check_ovs_geneve():
result = checks.ovs_geneve_supported()
if not result:
LOG.error(_LE('Check for Open vSwitch Geneve support failed. '
'Please ensure that the version of openvswitch '
'and kernel being used has Geneve support.'))
return result
def check_iproute2_vxlan():
result = checks.iproute2_vxlan_supported()
if not result:
@ -181,6 +190,8 @@ def check_ebtables():
OPTS = [
BoolOptCallback('ovs_vxlan', check_ovs_vxlan, default=False,
help=_('Check for OVS vxlan support')),
BoolOptCallback('ovs_geneve', check_ovs_geneve, default=False,
help=_('Check for OVS Geneve support')),
BoolOptCallback('iproute2_vxlan', check_iproute2_vxlan, default=False,
help=_('Check for iproute2 vxlan support')),
BoolOptCallback('ovs_patch', check_ovs_patch, default=False,
@ -216,6 +227,8 @@ def enable_tests_from_config():
if 'vxlan' in cfg.CONF.AGENT.tunnel_types:
cfg.CONF.set_override('ovs_vxlan', True)
if 'geneve' in cfg.CONF.AGENT.tunnel_types:
cfg.CONF.set_override('ovs_geneve', True)
if ('vxlan' in cfg.CONF.ml2.type_drivers or
cfg.CONF.VXLAN.enable_vxlan):
cfg.CONF.set_override('iproute2_vxlan', True)

View File

@ -1,2 +1,2 @@
2e5352a0ad4d
11926bcfe72d
34af2b5c5a59

View File

@ -0,0 +1,49 @@
# Copyright 2015 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.
#
"""add geneve ml2 type driver
Revision ID: 11926bcfe72d
Revises: 2e5352a0ad4d
Create Date: 2015-08-27 19:56:16.356522
"""
# revision identifiers, used by Alembic.
revision = '11926bcfe72d'
down_revision = '2e5352a0ad4d'
from alembic import op
import sqlalchemy as sa
def upgrade():
op.create_table(
'ml2_geneve_allocations',
sa.Column('geneve_vni', sa.Integer(),
autoincrement=False, nullable=False),
sa.Column('allocated', sa.Boolean(),
server_default=sa.sql.false(), nullable=False),
sa.PrimaryKeyConstraint('geneve_vni'),
)
op.create_index(op.f('ix_ml2_geneve_allocations_allocated'),
'ml2_geneve_allocations', ['allocated'], unique=False)
op.create_table(
'ml2_geneve_endpoints',
sa.Column('ip_address', sa.String(length=64), nullable=False),
sa.Column('host', sa.String(length=255), nullable=True),
sa.PrimaryKeyConstraint('ip_address'),
sa.UniqueConstraint('host', name='unique_ml2_geneve_endpoints0host'),
)

View File

@ -56,6 +56,7 @@ from neutron.plugins.cisco.db import network_models_v2 # noqa
from neutron.plugins.ml2.drivers.brocade.db import ( # noqa
models as ml2_brocade_models)
from neutron.plugins.ml2.drivers import type_flat # noqa
from neutron.plugins.ml2.drivers import type_geneve # noqa
from neutron.plugins.ml2.drivers import type_gre # noqa
from neutron.plugins.ml2.drivers import type_vlan # noqa
from neutron.plugins.ml2.drivers import type_vxlan # noqa

View File

@ -56,6 +56,7 @@ ACTIVE_PENDING_STATUSES = (
# Network Type constants
TYPE_FLAT = 'flat'
TYPE_GENEVE = 'geneve'
TYPE_GRE = 'gre'
TYPE_LOCAL = 'local'
TYPE_VXLAN = 'vxlan'
@ -68,6 +69,10 @@ TYPE_NONE = 'none'
MIN_VLAN_TAG = 1
MAX_VLAN_TAG = 4094
# For Geneve Tunnel
MIN_GENEVE_VNI = 1
MAX_GENEVE_VNI = 2 ** 24 - 1
# For GRE Tunnel
MIN_GRE_ID = 1
MAX_GRE_ID = 2 ** 32 - 1
@ -78,5 +83,6 @@ MAX_VXLAN_VNI = 2 ** 24 - 1
VXLAN_UDP_PORT = 4789
# Network Type MTU overhead
GENEVE_ENCAP_MIN_OVERHEAD = 50
GRE_ENCAP_OVERHEAD = 42
VXLAN_ENCAP_OVERHEAD = 50

View File

@ -35,10 +35,15 @@ def is_valid_vxlan_vni(vni):
return p_const.MIN_VXLAN_VNI <= vni <= p_const.MAX_VXLAN_VNI
def is_valid_geneve_vni(vni):
return p_const.MIN_GENEVE_VNI <= vni <= p_const.MAX_GENEVE_VNI
def verify_tunnel_range(tunnel_range, tunnel_type):
"""Raise an exception for invalid tunnel range or malformed range."""
mappings = {p_const.TYPE_GRE: is_valid_gre_id,
p_const.TYPE_VXLAN: is_valid_vxlan_vni}
p_const.TYPE_VXLAN: is_valid_vxlan_vni,
p_const.TYPE_GENEVE: is_valid_geneve_vni}
if tunnel_type in mappings:
for ident in tunnel_range:
if not mappings[tunnel_type](ident):

View File

@ -18,7 +18,7 @@ from oslo_config import cfg
ml2_opts = [
cfg.ListOpt('type_drivers',
default=['local', 'flat', 'vlan', 'gre', 'vxlan'],
default=['local', 'flat', 'vlan', 'gre', 'vxlan', 'geneve'],
help=_("List of network type driver entrypoints to be loaded "
"from the neutron.ml2.type_drivers namespace.")),
cfg.ListOpt('tenant_network_types',

View File

@ -32,7 +32,9 @@ PEER_PHYSICAL_PREFIX = 'phy-'
NONEXISTENT_PEER = 'nonexistent-peer'
# The different types of tunnels
TUNNEL_NETWORK_TYPES = [p_const.TYPE_GRE, p_const.TYPE_VXLAN]
TUNNEL_NETWORK_TYPES = [p_const.TYPE_GRE, p_const.TYPE_VXLAN,
p_const.TYPE_GENEVE]
# Various tables for DVR use of integration bridge flows
LOCAL_SWITCHING = 0
@ -44,6 +46,8 @@ DVR_PROCESS = 1
PATCH_LV_TO_TUN = 2
GRE_TUN_TO_LV = 3
VXLAN_TUN_TO_LV = 4
GENEVE_TUN_TO_LV = 6
DVR_NOT_LEARN = 9
LEARN_FROM_TUN = 10
UCAST_TO_TUN = 20
@ -67,7 +71,9 @@ ARP_REPLY = '0x2'
# Map tunnel types to tables number
TUN_TABLE = {p_const.TYPE_GRE: GRE_TUN_TO_LV,
p_const.TYPE_VXLAN: VXLAN_TUN_TO_LV}
p_const.TYPE_VXLAN: VXLAN_TUN_TO_LV,
p_const.TYPE_GENEVE: GENEVE_TUN_TO_LV}
# The default respawn interval for the ovsdb monitor
DEFAULT_OVSDBMON_RESPAWN = 30

View File

@ -239,7 +239,9 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
self.bridge_mappings = bridge_mappings
self.setup_physical_bridges(self.bridge_mappings)
self.local_vlan_map = {}
self.tun_br_ofports = {p_const.TYPE_GRE: {},
self.tun_br_ofports = {p_const.TYPE_GENEVE: {},
p_const.TYPE_GRE: {},
p_const.TYPE_VXLAN: {}}
self.polling_interval = polling_interval
@ -584,7 +586,7 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
:param net_uuid: the uuid of the network associated with this vlan.
:param network_type: the network type ('gre', 'vxlan', 'vlan', 'flat',
'local')
'local', 'geneve')
:param physical_network: the physical network for 'vlan' or 'flat'
:param segmentation_id: the VID for 'vlan' or tunnel ID for 'tunnel'
'''
@ -1738,9 +1740,10 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
def _check_agent_configurations(self):
if (self.enable_distributed_routing and self.enable_tunneling
and not self.l2_pop):
raise ValueError(_("DVR deployments for VXLAN/GRE underlays "
"require L2-pop to be enabled, in both the "
"Agent and Server side."))
raise ValueError(_("DVR deployments for VXLAN/GRE/Geneve "
"underlays require L2-pop to be enabled, "
"in both the Agent and Server side."))
def create_agent_config_map(config):

View File

@ -0,0 +1,103 @@
# Copyright (c) 2015 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.
from oslo_config import cfg
from oslo_log import log
import sqlalchemy as sa
from sqlalchemy import sql
from neutron.common import exceptions as n_exc
from neutron.db import model_base
from neutron.i18n import _LE
from neutron.plugins.common import constants as p_const
from neutron.plugins.ml2.drivers import type_tunnel
LOG = log.getLogger(__name__)
geneve_opts = [
cfg.ListOpt('vni_ranges',
default=[],
help=_("Comma-separated list of <vni_min>:<vni_max> tuples "
"enumerating ranges of Geneve VNI IDs that are "
"available for tenant network allocation")),
cfg.IntOpt('max_header_size',
default=p_const.GENEVE_ENCAP_MIN_OVERHEAD,
help=_("Geneve encapsulation header size is dynamic, this "
"value is used to calculate the maximum MTU "
"for the driver."
"this is the sum of the sizes of the outer "
"ETH + IP + UDP + GENEVE header sizes")),
]
cfg.CONF.register_opts(geneve_opts, "ml2_type_geneve")
class GeneveAllocation(model_base.BASEV2):
__tablename__ = 'ml2_geneve_allocations'
geneve_vni = sa.Column(sa.Integer, nullable=False, primary_key=True,
autoincrement=False)
allocated = sa.Column(sa.Boolean, nullable=False, default=False,
server_default=sql.false(), index=True)
class GeneveEndpoints(model_base.BASEV2):
"""Represents tunnel endpoint in RPC mode."""
__tablename__ = 'ml2_geneve_endpoints'
__table_args__ = (
sa.UniqueConstraint('host',
name='unique_ml2_geneve_endpoints0host'),
model_base.BASEV2.__table_args__
)
ip_address = sa.Column(sa.String(64), primary_key=True)
host = sa.Column(sa.String(255), nullable=True)
def __repr__(self):
return "<GeneveTunnelEndpoint(%s)>" % self.ip_address
class GeneveTypeDriver(type_tunnel.EndpointTunnelTypeDriver):
def __init__(self):
super(GeneveTypeDriver, self).__init__(GeneveAllocation,
GeneveEndpoints)
self.max_encap_size = cfg.CONF.ml2_type_geneve.max_header_size
def get_type(self):
return p_const.TYPE_GENEVE
def initialize(self):
try:
self._initialize(cfg.CONF.ml2_type_geneve.vni_ranges)
except n_exc.NetworkTunnelRangeError:
LOG.error(_LE("Failed to parse vni_ranges. "
"Service terminated!"))
raise SystemExit()
def get_endpoints(self):
"""Get every geneve endpoints from database."""
geneve_endpoints = self._get_endpoints()
return [{'ip_address': geneve_endpoint.ip_address,
'host': geneve_endpoint.host}
for geneve_endpoint in geneve_endpoints]
def add_endpoint(self, ip, host):
return self._add_endpoint(ip, host)
def get_mtu(self, physical_network=None):
mtu = super(GeneveTypeDriver, self).get_mtu()
return mtu - self.max_encap_size if mtu else 0

View File

@ -50,6 +50,9 @@ class SanityTestCaseRoot(functional_base.BaseSudoTestCase):
def test_ovs_vxlan_support_runs(self):
checks.ovs_vxlan_supported()
def test_ovs_geneve_support_runs(self):
checks.ovs_geneve_supported()
def test_iproute2_vxlan_support_runs(self):
checks.iproute2_vxlan_supported()

View File

@ -53,6 +53,7 @@ class OVSTunnelBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase,
'actions': 'resubmit(,22)'},
{'priority': 0, 'table': 3, 'actions': 'drop'},
{'priority': 0, 'table': 4, 'actions': 'drop'},
{'priority': 0, 'table': 6, 'actions': 'drop'},
{'priority': 1, 'table': 10,
'actions': 'learn(cookie=0x0,table=20,priority=1,'
'hard_timeout=300,NXM_OF_VLAN_TCI[0..11],'
@ -87,6 +88,7 @@ class OVSTunnelBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase,
'actions': 'resubmit(,22)'},
{'priority': 0, 'table': 3, 'actions': 'drop'},
{'priority': 0, 'table': 4, 'actions': 'drop'},
{'priority': 0, 'table': 6, 'actions': 'drop'},
{'priority': 1, 'table': 10,
'actions': 'learn(cookie=0x0,table=20,priority=1,'
'hard_timeout=300,NXM_OF_VLAN_TCI[0..11],'

View File

@ -0,0 +1,55 @@
# Copyright (c) 2015 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.
from neutron.plugins.common import constants as p_const
from neutron.plugins.ml2.drivers import type_geneve
from neutron.tests.unit.plugins.ml2.drivers import base_type_tunnel
from neutron.tests.unit.plugins.ml2 import test_rpc
from neutron.tests.unit import testlib_api
TUNNEL_IP_ONE = "10.10.10.77"
TUNNEL_IP_TWO = "10.10.10.78"
HOST_ONE = 'fake_host_one1'
HOST_TWO = 'fake_host_two2'
class GeneveTypeTest(base_type_tunnel.TunnelTypeTestMixin,
testlib_api.SqlTestCase):
DRIVER_CLASS = type_geneve.GeneveTypeDriver
TYPE = p_const.TYPE_GENEVE
def test_get_endpoints(self):
self.driver.add_endpoint(TUNNEL_IP_ONE, HOST_ONE)
self.driver.add_endpoint(TUNNEL_IP_TWO, HOST_TWO)
endpoints = self.driver.get_endpoints()
for endpoint in endpoints:
if endpoint['ip_address'] == TUNNEL_IP_ONE:
self.assertEqual(HOST_ONE, endpoint['host'])
elif endpoint['ip_address'] == TUNNEL_IP_TWO:
self.assertEqual(HOST_TWO, endpoint['host'])
class GeneveTypeMultiRangeTest(base_type_tunnel.TunnelTypeMultiRangeTestMixin,
testlib_api.SqlTestCase):
DRIVER_CLASS = type_geneve.GeneveTypeDriver
class GeneveTypeRpcCallbackTest(base_type_tunnel.TunnelRpcCallbackTestMixin,
test_rpc.RpcCallbacksTestCase,
testlib_api.SqlTestCase):
DRIVER_CLASS = type_geneve.GeneveTypeDriver
TYPE = p_const.TYPE_GENEVE

View File

@ -151,6 +151,7 @@ neutron.ml2.type_drivers =
flat = neutron.plugins.ml2.drivers.type_flat:FlatTypeDriver
local = neutron.plugins.ml2.drivers.type_local:LocalTypeDriver
vlan = neutron.plugins.ml2.drivers.type_vlan:VlanTypeDriver
geneve = neutron.plugins.ml2.drivers.type_geneve:GeneveTypeDriver
gre = neutron.plugins.ml2.drivers.type_gre:GreTypeDriver
vxlan = neutron.plugins.ml2.drivers.type_vxlan:VxlanTypeDriver
neutron.ml2.mechanism_drivers =