Merge "OVS: Add support for IPv6 addresses as tunnel endpoints" into stable/mitaka

This commit is contained in:
Jenkins 2016-07-12 19:58:25 +00:00 committed by Gerrit Code Review
commit 9b301abecf
8 changed files with 165 additions and 40 deletions

View File

@ -44,8 +44,9 @@ ovs_opts = [
cfg.StrOpt('tun_peer_patch_port', default='patch-int',
help=_("Peer patch port in tunnel bridge for integration "
"bridge.")),
cfg.IPOpt('local_ip', version=4,
help=_("Local IP address of tunnel endpoint.")),
cfg.IPOpt('local_ip',
help=_("Local IP address of tunnel endpoint. Can be either "
"an IPv4 or IPv6 address.")),
cfg.ListOpt('bridge_mappings',
default=DEFAULT_BRIDGE_MAPPINGS,
help=_("Comma-separated list of <physical_network>:<bridge> "

View File

@ -13,8 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
import base64
import collections
import functools
import hashlib
import signal
import sys
import time
@ -1409,6 +1411,18 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
return port_needs_binding
def _setup_tunnel_port(self, br, port_name, remote_ip, tunnel_type):
try:
if (netaddr.IPAddress(self.local_ip).version !=
netaddr.IPAddress(remote_ip).version):
LOG.error(_LE("IP version mismatch, cannot create tunnel: "
"local_ip=%(lip)s remote_ip=%(rip)s"),
{'lip': self.local_ip, 'rip': remote_ip})
return 0
except Exception:
LOG.error(_LE("Invalid local or remote IP, cannot create tunnel: "
"local_ip=%(lip)s remote_ip=%(rip)s"),
{'lip': self.local_ip, 'rip': remote_ip})
return 0
ofport = br.add_tunnel_port(port_name,
remote_ip,
self.local_ip,
@ -1667,9 +1681,18 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
return failed_devices
@classmethod
def get_ip_in_hex(cls, ip_address):
def get_tunnel_hash(cls, ip_address, hashlen):
try:
return '%08x' % netaddr.IPAddress(ip_address, version=4)
addr = netaddr.IPAddress(ip_address)
if addr.version == n_const.IP_VERSION_4:
# We cannot change this from 8, since it could break
# backwards-compatibility
return '%08x' % addr
else:
# Create 32-bit Base32 encoded hash
sha1 = hashlib.sha1(ip_address.encode())
iphash = base64.b32encode(sha1.digest())
return iphash[:hashlen].decode().lower()
except Exception:
LOG.warning(_LW("Invalid remote IP: %s"), ip_address)
return
@ -1704,10 +1727,18 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
@classmethod
def get_tunnel_name(cls, network_type, local_ip, remote_ip):
remote_ip_hex = cls.get_ip_in_hex(remote_ip)
if not remote_ip_hex:
# This string is used to build port and interface names in OVS.
# Port and interface names can be max 16 characters long,
# including NULL, and must be unique per table per host.
# We make the name as long as possible given the network_type,
# for example, 'vxlan-012345678' or 'geneve-01234567'.
# Remove length of network type and dash
hashlen = n_const.DEVICE_NAME_MAX_LEN - len(network_type) - 1
remote_tunnel_hash = cls.get_tunnel_hash(remote_ip, hashlen)
if not remote_tunnel_hash:
return None
return '%s-%s' % (network_type, remote_ip_hex)
return '%s-%s' % (network_type, remote_tunnel_hash)
def _agent_has_updates(self, polling_manager):
return (polling_manager.is_polling_required or

View File

@ -15,6 +15,7 @@
import hashlib
import sys
from oslo_utils import encodeutils
from neutron.cmd.eventlet.plugins.ovs_neutron_agent import main as _main
from neutron.common import constants as n_const
@ -24,16 +25,20 @@ from neutron.plugins.ml2.drivers.openvswitch.agent.ovs_neutron_agent \
def get_tunnel_name_full(cls, network_type, local_ip, remote_ip):
network_type = network_type[:3]
remote_ip_hex = cls.get_ip_in_hex(remote_ip)
if not remote_ip_hex:
return None
# Remove length of network_type and two dashes
hashlen = (n_const.DEVICE_NAME_MAX_LEN - len(network_type) - 2) // 2
remote_ip_hex = remote_ip_hex.encode('utf-8')
remote_ip_hash = hashlib.sha1(remote_ip_hex).hexdigest()[0:hashlen]
local_ip_hex = cls.get_ip_in_hex(local_ip).encode('utf-8')
source_ip_hash = hashlib.sha1(local_ip_hex).hexdigest()[0:hashlen]
remote_tunnel_hash = cls.get_tunnel_hash(remote_ip, hashlen)
if not remote_tunnel_hash:
return None
remote_tunnel_hash = encodeutils.to_utf8(remote_tunnel_hash)
remote_ip_hash = hashlib.sha1(remote_tunnel_hash).hexdigest()[:hashlen]
local_tunnel_hash = cls.get_tunnel_hash(local_ip, hashlen)
local_tunnel_hash = encodeutils.to_utf8(local_tunnel_hash)
source_ip_hash = hashlib.sha1(local_tunnel_hash).hexdigest()[:hashlen]
return '%s-%s-%s' % (network_type, source_ip_hash, remote_ip_hash)
OVSNeutronAgent.get_tunnel_name = get_tunnel_name_full

View File

@ -99,7 +99,8 @@ class OVSAgentTestFramework(base.BaseOVSLinuxTestCase):
'br_tun': br_tun.OVSTunnelBridge
}
def create_agent(self, create_tunnels=True, ancillary_bridge=None):
def create_agent(self, create_tunnels=True, ancillary_bridge=None,
local_ip='192.168.10.1'):
if create_tunnels:
tunnel_types = [p_const.TYPE_VXLAN]
else:
@ -108,7 +109,7 @@ class OVSAgentTestFramework(base.BaseOVSLinuxTestCase):
self.config.set_override('tunnel_types', tunnel_types, "AGENT")
self.config.set_override('polling_interval', 1, "AGENT")
self.config.set_override('prevent_arp_spoofing', False, "AGENT")
self.config.set_override('local_ip', '192.168.10.1', "OVS")
self.config.set_override('local_ip', local_ip, "OVS")
self.config.set_override('bridge_mappings', bridge_mappings, "OVS")
# Physical bridges should be created prior to running
self._bridge_classes()['br_phys'](self.br_phys).create()

View File

@ -158,13 +158,19 @@ class TestOVSAgent(base.OVSAgentTestFramework):
self.wait_until_ports_state(self.ports, up=True)
self.assert_vlan_tags(self.ports, self.agent)
def test_assert_bridges_ports_vxlan(self):
agent = self.create_agent()
def _test_assert_bridges_ports_vxlan(self, local_ip=None):
agent = self.create_agent(local_ip=local_ip)
self.assertTrue(self.ovs.bridge_exists(self.br_int))
self.assertTrue(self.ovs.bridge_exists(self.br_tun))
self.assert_bridge_ports()
self.assert_patch_ports(agent)
def test_assert_bridges_ports_vxlan_ipv4(self):
self._test_assert_bridges_ports_vxlan()
def test_assert_bridges_ports_vxlan_ipv6(self):
self._test_assert_bridges_ports_vxlan(local_ip='2001:db8:100::1')
def test_assert_bridges_ports_no_tunnel(self):
self.create_agent(create_tunnels=False)
self.assertTrue(self.ovs.bridge_exists(self.br_int))

View File

@ -147,19 +147,29 @@ class OVSBridgeTestCase(OVSBridgeTestBase):
self.br.br_name, 'datapath_id', dpid)
self.assertIn(dpid, self.br.get_datapath_id())
def test_add_tunnel_port(self):
def _test_add_tunnel_port(self, attrs):
port_name = tests_base.get_rand_device_name(net_helpers.PORT_PREFIX)
self.br.add_tunnel_port(port_name, attrs['remote_ip'],
attrs['local_ip'])
self.assertEqual('gre',
self.ovs.db_get_val('Interface', port_name, 'type'))
options = self.ovs.db_get_val('Interface', port_name, 'options')
for attr, val in attrs.items():
self.assertEqual(val, options[attr])
def test_add_tunnel_port_ipv4(self):
attrs = {
'remote_ip': '192.0.2.1', # RFC 5737 TEST-NET-1
'local_ip': '198.51.100.1', # RFC 5737 TEST-NET-2
}
port_name = tests_base.get_rand_device_name(net_helpers.PORT_PREFIX)
self.br.add_tunnel_port(port_name, attrs['remote_ip'],
attrs['local_ip'])
self.assertEqual(self.ovs.db_get_val('Interface', port_name, 'type'),
'gre')
options = self.ovs.db_get_val('Interface', port_name, 'options')
for attr, val in attrs.items():
self.assertEqual(val, options[attr])
self._test_add_tunnel_port(attrs)
def test_add_tunnel_port_ipv6(self):
attrs = {
'remote_ip': '2001:db8:200::1',
'local_ip': '2001:db8:100::1',
}
self._test_add_tunnel_port(attrs)
def test_add_patch_port(self):
local = tests_base.get_rand_device_name(net_helpers.PORT_PREFIX)

View File

@ -43,6 +43,7 @@ OVS_LINUX_KERN_VERS_WITHOUT_VXLAN = "3.12.0"
FAKE_MAC = '00:11:22:33:44:55'
FAKE_IP1 = '10.0.0.1'
FAKE_IP2 = '10.0.0.2'
FAKE_IP6 = '2001:db8:42:42::10'
TEST_PORT_ID1 = 'port-id-1'
TEST_PORT_ID2 = 'port-id-2'
@ -1377,6 +1378,7 @@ class TestOvsNeutronAgent(object):
self.agent.l2_pop = False
self.agent.udp_vxlan_port = 8472
self.agent.tun_br_ofports['vxlan'] = {}
self.agent.local_ip = '2.3.4.5'
with mock.patch.object(self.agent.tun_br,
"add_tunnel_port",
return_value='6') as add_tun_port_fn,\
@ -1594,23 +1596,50 @@ class TestOvsNeutronAgent(object):
mock_loop.assert_called_once_with(polling_manager=mock.ANY)
def test_setup_tunnel_port_invalid_ofport(self):
remote_ip = '1.2.3.4'
with mock.patch.object(
self.agent.tun_br,
'add_tunnel_port',
return_value=ovs_lib.INVALID_OFPORT) as add_tunnel_port_fn,\
mock.patch.object(self.mod_agent.LOG, 'error') as log_error_fn:
self.agent.local_ip = '1.2.3.4'
ofport = self.agent._setup_tunnel_port(
self.agent.tun_br, 'gre-1', 'remote_ip', p_const.TYPE_GRE)
self.agent.tun_br, 'gre-1', remote_ip, p_const.TYPE_GRE)
add_tunnel_port_fn.assert_called_once_with(
'gre-1', 'remote_ip', self.agent.local_ip, p_const.TYPE_GRE,
'gre-1', remote_ip, self.agent.local_ip, p_const.TYPE_GRE,
self.agent.vxlan_udp_port, self.agent.dont_fragment,
self.agent.tunnel_csum)
log_error_fn.assert_called_once_with(
_("Failed to set-up %(type)s tunnel port to %(ip)s"),
{'type': p_const.TYPE_GRE, 'ip': 'remote_ip'})
{'type': p_const.TYPE_GRE, 'ip': remote_ip})
self.assertEqual(0, ofport)
def test_setup_tunnel_port_invalid_address_mismatch(self):
remote_ip = '2001:db8::2'
with mock.patch.object(self.mod_agent.LOG, 'error') as log_error_fn:
self.agent.local_ip = '1.2.3.4'
ofport = self.agent._setup_tunnel_port(
self.agent.tun_br, 'gre-1', remote_ip, p_const.TYPE_GRE)
log_error_fn.assert_called_once_with(
_("IP version mismatch, cannot create tunnel: "
"local_ip=%(lip)s remote_ip=%(rip)s"),
{'lip': self.agent.local_ip, 'rip': remote_ip})
self.assertEqual(0, ofport)
def test_setup_tunnel_port_invalid_netaddr_exception(self):
remote_ip = '2001:db8::2'
with mock.patch.object(self.mod_agent.LOG, 'error') as log_error_fn:
self.agent.local_ip = '1.2.3.4.5'
ofport = self.agent._setup_tunnel_port(
self.agent.tun_br, 'gre-1', remote_ip, p_const.TYPE_GRE)
log_error_fn.assert_called_once_with(
_("Invalid local or remote IP, cannot create tunnel: "
"local_ip=%(lip)s remote_ip=%(rip)s"),
{'lip': self.agent.local_ip, 'rip': remote_ip})
self.assertEqual(0, ofport)
def test_setup_tunnel_port_error_negative_df_disabled(self):
remote_ip = '1.2.3.4'
with mock.patch.object(
self.agent.tun_br,
'add_tunnel_port',
@ -1618,18 +1647,20 @@ class TestOvsNeutronAgent(object):
mock.patch.object(self.mod_agent.LOG, 'error') as log_error_fn:
self.agent.dont_fragment = False
self.agent.tunnel_csum = False
self.agent.local_ip = '2.3.4.5'
ofport = self.agent._setup_tunnel_port(
self.agent.tun_br, 'gre-1', 'remote_ip', p_const.TYPE_GRE)
self.agent.tun_br, 'gre-1', remote_ip, p_const.TYPE_GRE)
add_tunnel_port_fn.assert_called_once_with(
'gre-1', 'remote_ip', self.agent.local_ip, p_const.TYPE_GRE,
'gre-1', remote_ip, self.agent.local_ip, p_const.TYPE_GRE,
self.agent.vxlan_udp_port, self.agent.dont_fragment,
self.agent.tunnel_csum)
log_error_fn.assert_called_once_with(
_("Failed to set-up %(type)s tunnel port to %(ip)s"),
{'type': p_const.TYPE_GRE, 'ip': 'remote_ip'})
{'type': p_const.TYPE_GRE, 'ip': remote_ip})
self.assertEqual(0, ofport)
def test_setup_tunnel_port_error_negative_tunnel_csum(self):
remote_ip = '1.2.3.4'
with mock.patch.object(
self.agent.tun_br,
'add_tunnel_port',
@ -1637,15 +1668,16 @@ class TestOvsNeutronAgent(object):
mock.patch.object(self.mod_agent.LOG, 'error') as log_error_fn:
self.agent.dont_fragment = True
self.agent.tunnel_csum = True
self.agent.local_ip = '2.3.4.5'
ofport = self.agent._setup_tunnel_port(
self.agent.tun_br, 'gre-1', 'remote_ip', p_const.TYPE_GRE)
self.agent.tun_br, 'gre-1', remote_ip, p_const.TYPE_GRE)
add_tunnel_port_fn.assert_called_once_with(
'gre-1', 'remote_ip', self.agent.local_ip, p_const.TYPE_GRE,
'gre-1', remote_ip, self.agent.local_ip, p_const.TYPE_GRE,
self.agent.vxlan_udp_port, self.agent.dont_fragment,
self.agent.tunnel_csum)
log_error_fn.assert_called_once_with(
_("Failed to set-up %(type)s tunnel port to %(ip)s"),
{'type': p_const.TYPE_GRE, 'ip': 'remote_ip'})
{'type': p_const.TYPE_GRE, 'ip': remote_ip})
self.assertEqual(0, ofport)
def test_tunnel_sync_with_ml2_plugin(self):
@ -1977,8 +2009,10 @@ class TestOvsNeutronAgent(object):
self.agent.l2_pop = False
self.agent.local_vlan_map = {
'foo': self.mod_agent.LocalVLANMapping(4, tunnel_type, 2, 1)}
self.agent.local_ip = '2.3.4.5'
bridge.install_flood_to_tun.side_effect = add_new_vlan_mapping
self.agent._setup_tunnel_port(bridge, 1, 2, tunnel_type=tunnel_type)
self.agent._setup_tunnel_port(bridge, 1, '1.2.3.4',
tunnel_type=tunnel_type)
self.assertIn('bar', self.agent.local_vlan_map)
def test_setup_entry_for_arp_reply_ignores_ipv6_addresses(self):
@ -3126,6 +3160,12 @@ class TestValidateTunnelLocalIP(base.BaseTestCase):
ovs_agent.validate_local_ip(FAKE_IP1)
mock_get_device_by_ip.assert_called_once_with(FAKE_IP1)
def test_validate_local_ip_with_valid_ipv6(self):
mock_get_device_by_ip = mock.patch.object(
ip_lib.IPWrapper, 'get_device_by_ip').start()
ovs_agent.validate_local_ip(FAKE_IP6)
mock_get_device_by_ip.assert_called_once_with(FAKE_IP6)
def test_validate_local_ip_with_none_ip(self):
with testtools.ExpectedException(SystemExit):
ovs_agent.validate_local_ip(None)
@ -3138,11 +3178,20 @@ class TestValidateTunnelLocalIP(base.BaseTestCase):
ovs_agent.validate_local_ip(FAKE_IP1)
mock_get_device_by_ip.assert_called_once_with(FAKE_IP1)
def test_validate_local_ip_with_invalid_ipv6(self):
mock_get_device_by_ip = mock.patch.object(
ip_lib.IPWrapper, 'get_device_by_ip').start()
mock_get_device_by_ip.return_value = None
with testtools.ExpectedException(SystemExit):
ovs_agent.validate_local_ip(FAKE_IP6)
mock_get_device_by_ip.assert_called_once_with(FAKE_IP6)
class TestOvsAgentTunnelName(base.BaseTestCase):
def test_get_ip_in_hex_invalid_address(self):
def test_get_tunnel_hash_invalid_address(self):
hashlen = n_const.DEVICE_NAME_MAX_LEN
self.assertIsNone(
ovs_agent.OVSNeutronAgent.get_ip_in_hex('a.b.c.d'))
ovs_agent.OVSNeutronAgent.get_tunnel_hash('a.b.c.d', hashlen))
def test_get_tunnel_name_vxlan(self):
self.assertEqual(
@ -3155,3 +3204,15 @@ class TestOvsAgentTunnelName(base.BaseTestCase):
'gre-7f000002',
ovs_agent.OVSNeutronAgent.get_tunnel_name(
'gre', '127.0.0.1', '127.0.0.2'))
def test_get_tunnel_name_vxlan_ipv6(self):
self.assertEqual(
'vxlan-pehtjzksi',
ovs_agent.OVSNeutronAgent.get_tunnel_name(
'vxlan', '2001:db8::1', '2001:db8::2'))
def test_get_tunnel_name_gre_ipv6(self):
self.assertEqual(
'gre-pehtjzksiqr',
ovs_agent.OVSNeutronAgent.get_tunnel_name(
'gre', '2001:db8::1', '2001:db8::2'))

View File

@ -0,0 +1,10 @@
---
prelude: >
Support for IPv6 addresses as tunnel endpoints in OVS.
features:
- The local_ip value in ml2_conf.ini can now be set to
an IPv6 address configured on the system.
other:
- Requires OVS 2.5+ version or higher with linux kernel
4.3 or higher. More info at
`OVS github page <https://github.com/openvswitch/ovs/blob/master/FAQ.md>`_.