Uniquely identify tunnel interfaces for fullstack tests

OVS agent tunnel interfaces are named via:
'%s-%s' % (tunnel_type, destination_ip)

This means that the tunnel interface name is not unique if
two OVS agents on the same machine try to form a tunnel with a
third agent. This happens during full stack tests that start
multiple copies of the OVS agent on the test machine.

Thus, for full stack tests, to make sure that the tunnel
interface names created by ovs agents are globally unique, they
will have the following format :
'%s-%s-%s' % (tunnel_type, hash of source IP, hash of dest IP)

Since this patch centralizes the formation of the tunnel interface
name in a dedicated method that is monkey patched by the full stack
framework, a unit test has been added for this method.

Co-Authored-By: Mathieu Rohon <mathieu.rohon@gmail.com>
Closes-Bug: #1467633
Change-Id: I991af6a5f982746cc297f0248454f803dfbb2daf
This commit is contained in:
Assaf Muller 2015-06-19 21:20:58 -04:00
parent f40538e629
commit 1a1ccb8c94
5 changed files with 92 additions and 17 deletions

View File

@ -451,9 +451,6 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
if not self.enable_tunneling:
return
tunnel_ip = kwargs.get('tunnel_ip')
tunnel_ip_hex = self.get_ip_in_hex(tunnel_ip)
if not tunnel_ip_hex:
return
tunnel_type = kwargs.get('tunnel_type')
if not tunnel_type:
LOG.error(_LE("No tunnel_type specified, cannot create tunnels"))
@ -464,7 +461,9 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
return
if tunnel_ip == self.local_ip:
return
tun_name = '%s-%s' % (tunnel_type, tunnel_ip_hex)
tun_name = self.get_tunnel_name(tunnel_type, self.local_ip, tunnel_ip)
if tun_name is None:
return
if not self.l2_pop:
self._setup_tunnel_port(self.tun_br, tun_name, tunnel_ip,
tunnel_type)
@ -1388,10 +1387,10 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
return ofport
def setup_tunnel_port(self, br, remote_ip, network_type):
remote_ip_hex = self.get_ip_in_hex(remote_ip)
if not remote_ip_hex:
port_name = self.get_tunnel_name(
network_type, self.local_ip, remote_ip)
if port_name is None:
return 0
port_name = '%s-%s' % (network_type, remote_ip_hex)
ofport = self._setup_tunnel_port(br,
port_name,
remote_ip,
@ -1408,8 +1407,8 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
items = list(self.tun_br_ofports[tunnel_type].items())
for remote_ip, ofport in items:
if ofport == tun_ofport:
port_name = '%s-%s' % (tunnel_type,
self.get_ip_in_hex(remote_ip))
port_name = self.get_tunnel_name(
tunnel_type, self.local_ip, remote_ip)
br.delete_port(port_name)
br.cleanup_tunnel_port(ofport)
self.tun_br_ofports[tunnel_type].pop(remote_ip, None)
@ -1613,7 +1612,8 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
'elapsed': time.time() - start})
return failed_devices
def get_ip_in_hex(self, ip_address):
@classmethod
def get_ip_in_hex(cls, ip_address):
try:
return '%08x' % netaddr.IPAddress(ip_address, version=4)
except Exception:
@ -1632,10 +1632,10 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
for tunnel in tunnels:
if self.local_ip != tunnel['ip_address']:
remote_ip = tunnel['ip_address']
remote_ip_hex = self.get_ip_in_hex(remote_ip)
if not remote_ip_hex:
tun_name = self.get_tunnel_name(
tunnel_type, self.local_ip, remote_ip)
if tun_name is None:
continue
tun_name = '%s-%s' % (tunnel_type, remote_ip_hex)
self._setup_tunnel_port(self.tun_br,
tun_name,
tunnel['ip_address'],
@ -1646,6 +1646,13 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
return True
return False
@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:
return None
return '%s-%s' % (network_type, remote_ip_hex)
def _agent_has_updates(self, polling_manager):
return (polling_manager.is_polling_required or
self.updated_ports or

View File

@ -0,0 +1,46 @@
#!/usr/bin/env python
#
# 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.
import hashlib
import sys
from neutron.cmd.eventlet.plugins.ovs_neutron_agent import main as _main
from neutron.common import constants as n_const
from neutron.plugins.ml2.drivers.openvswitch.agent.ovs_neutron_agent \
import OVSNeutronAgent
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]
return '%s-%s-%s' % (network_type, source_ip_hash, remote_ip_hash)
OVSNeutronAgent.get_tunnel_name = get_tunnel_name_full
def main():
_main()
if __name__ == "__main__":
sys.exit(main())

View File

@ -151,7 +151,9 @@ class OVSAgentFixture(fixtures.Fixture):
self.process_fixture = self.useFixture(ProcessFixture(
test_name=self.test_name,
process_name=self.NEUTRON_OVS_AGENT,
exec_name=self.NEUTRON_OVS_AGENT,
exec_name=spawn.find_executable(
'ovs_agent.py',
path=os.path.join(base.ROOTDIR, 'common', 'agents')),
config_filenames=config_filenames))
@ -173,7 +175,6 @@ class L3AgentFixture(fixtures.Fixture):
config_filenames = [self.neutron_cfg_fixture.filename,
self.l3_agent_cfg_fixture.filename]
self.process_fixture = self.useFixture(ProcessFixture(
test_name=self.test_name,
process_name=self.NEUTRON_L3_AGENT,

View File

@ -45,7 +45,7 @@ class TestConnectivitySameNetwork(base.BaseFullStackTestCase):
# agent types present on machines.
environment.HostDescription(
l3_agent=self.l2_pop,
of_interface=self.of_interface) for _ in range(2)]
of_interface=self.of_interface) for _ in range(3)]
env = environment.Environment(
environment.EnvironmentDescription(
network_type=self.network_type,
@ -67,9 +67,11 @@ class TestConnectivitySameNetwork(base.BaseFullStackTestCase):
network['id'],
tenant_uuid,
self.safe_client))
for i in range(2)]
for i in range(3)]
for vm in vms:
vm.block_until_boot()
vms[0].block_until_ping(vms[1].ip)
vms[0].block_until_ping(vms[2].ip)
vms[1].block_until_ping(vms[2].ip)

View File

@ -107,6 +107,7 @@ class TestOvsNeutronAgent(object):
group='SECURITYGROUP')
cfg.CONF.set_default('quitting_rpc_timeout', 10, 'AGENT')
cfg.CONF.set_default('prevent_arp_spoofing', False, 'AGENT')
cfg.CONF.set_default('local_ip', '127.0.0.1', 'OVS')
mock.patch(
'neutron.agent.common.ovs_lib.OVSBridge.get_ports_attributes',
return_value=[]).start()
@ -2910,3 +2911,21 @@ class TestValidateTunnelLocalIP(base.BaseTestCase):
with testtools.ExpectedException(SystemExit):
ovs_agent.validate_local_ip(FAKE_IP1)
mock_get_device_by_ip.assert_called_once_with(FAKE_IP1)
class TestOvsAgentTunnelName(base.BaseTestCase):
def test_get_ip_in_hex_invalid_address(self):
self.assertIsNone(
ovs_agent.OVSNeutronAgent.get_ip_in_hex('a.b.c.d'))
def test_get_tunnel_name_vxlan(self):
self.assertEqual(
'vxlan-7f000002',
ovs_agent.OVSNeutronAgent.get_tunnel_name(
'vxlan', '127.0.0.1', '127.0.0.2'))
def test_get_tunnel_name_gre(self):
self.assertEqual(
'gre-7f000002',
ovs_agent.OVSNeutronAgent.get_tunnel_name(
'gre', '127.0.0.1', '127.0.0.2'))