aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBob Lantz <rlantz@cs.stanford.edu>2019-08-14 15:38:24 -0700
committerBob Lantz <rlantz@cs.stanford.edu>2019-08-23 02:04:09 -0700
commit30e1a04ab9ed22215fa75f6667bbe18cee887c78 (patch)
tree5579d33f90946a82a87116e74d3b547302e7b975
parent2eea40ada1fdc1f3cb08492d0fca2b63a6a19b47 (diff)
downloadfaucet-30e1a04ab9ed22215fa75f6667bbe18cee887c78.tar.gz
faucet-30e1a04ab9ed22215fa75f6667bbe18cee887c78.tar.bz2
faucet-30e1a04ab9ed22215fa75f6667bbe18cee887c78.zip
Tests for up to 48 ports
We extend FaucetUntaggedTest to allow it to work with more than 4 ports, and add Faucet32PortTest and Faucet48PortTest. The 48p test works locally but seems to be slow/unreliable on travis so we just run the 32p test for now. Clarify comment in _attach_physical_switch
-rwxr-xr-xclib/clib_mininet_test_main.py4
-rw-r--r--clib/mininet_test_base.py73
-rw-r--r--clib/mininet_test_topo.py31
-rw-r--r--tests/integration/mininet_tests.py58
4 files changed, 130 insertions, 36 deletions
diff --git a/clib/clib_mininet_test_main.py b/clib/clib_mininet_test_main.py
index 0a0e667b..73518c38 100755
--- a/clib/clib_mininet_test_main.py
+++ b/clib/clib_mininet_test_main.py
@@ -152,8 +152,8 @@ def import_hw_config():
valid_types, config_file_name))
sys.exit(-1)
dp_ports = config['dp_ports']
- if len(dp_ports) != REQUIRED_TEST_PORTS:
- print('Exactly %u dataplane ports are required, '
+ if len(dp_ports) < REQUIRED_TEST_PORTS:
+ print('At least %u dataplane ports are required, '
'%d are provided in %s.' %
(REQUIRED_TEST_PORTS, len(dp_ports), config_file_name))
sys.exit(-1)
diff --git a/clib/mininet_test_base.py b/clib/mininet_test_base.py
index d842b52a..2c175b69 100644
--- a/clib/mininet_test_base.py
+++ b/clib/mininet_test_base.py
@@ -26,9 +26,10 @@ import requests
from ryu.ofproto import ofproto_v1_3 as ofp
-from mininet.log import error, output # pylint: disable=import-error
-from mininet.net import Mininet # pylint: disable=import-error
-from mininet.util import dumpNodeConnections, pmonitor # pylint: disable=import-error
+from mininet.link import Intf as HWIntf # pylint: disable=import-error
+from mininet.log import error, output # pylint: disable=import-error
+from mininet.net import Mininet # pylint: disable=import-error
+from mininet.util import dumpNodeConnections, pmonitor # pylint: disable=import-error
from clib import mininet_test_util
from clib import mininet_test_topo
@@ -429,31 +430,49 @@ class FaucetTestBase(unittest.TestCase):
_cmd(cmd)
def _attach_physical_switch(self):
- """Bridge a physical switch into test topology."""
+ """Bridge a physical switch into test topology.
+
+ We do this for now to enable us to reconnect
+ virtual ethernet interfaces which may already
+ exist on emulated hosts and other OVS instances.
+
+ (One alternative would be to create a Link() class
+ that uses the hardware interfaces directly.)
+
+ We repurpose the first OvS switch in the topology
+ as a patch panel that transparently connects the
+ hardware interfaces to the host/switch veth links."""
switch = self.first_switch()
- phys_macs = set()
- mapped_base = len(self.switch_map)
- for port_i, test_host_port in enumerate(sorted(self.switch_map), start=1):
- mapped_port_i = mapped_base + port_i
- phys_port = FaucetIntf(self.switch_map[test_host_port], node=switch)
- phys_mac = self.get_mac_of_intf(phys_port.name)
- self.assertFalse(phys_mac in phys_macs, 'duplicate physical MAC %s' % phys_mac)
- phys_macs.add(phys_mac)
- switch.cmd(
- ('ovs-vsctl add-port %s %s -- '
- 'set Interface %s ofport_request=%u') % (
- switch.name,
- phys_port.name,
- phys_port.name,
- mapped_port_i))
- switch.cmd('%s add-flow %s in_port=%u,eth_src=%s,priority=2,actions=drop' % (
- self.OFCTL, switch.name, mapped_port_i, phys_mac))
- switch.cmd('%s add-flow %s in_port=%u,eth_dst=%s,priority=2,actions=drop' % (
- self.OFCTL, switch.name, port_i, phys_mac))
- for port_pair in ((port_i, mapped_port_i), (mapped_port_i, port_i)):
- in_port, out_port = port_pair
- switch.cmd('%s add-flow %s in_port=%u,priority=1,actions=output:%u' % (
- self.OFCTL, switch.name, in_port, out_port))
+ # hw_names are the names of the server hardware interfaces
+ # that are cabled to the device under test, sorted by OF port number
+ hw_names = [self.switch_map[port] for port in sorted(self.switch_map)]
+ hw_macs = set()
+ # ovs_ports are the (sorted) OF port numbers of the OvS interfaces
+ # that are already attached to the emulated network.
+ # The actual tests reorder them according to port_map
+ ovs_ports = sorted(self.topo.switch_ports[switch.name])
+ # Patch hardware interfaces through to to OvS interfaces
+ for hw_name, ovs_port in zip(hw_names, ovs_ports):
+ # Note we've already removed any Linux IP addresses from hw_name
+ # and blocked traffic to/from its meaningless MAC
+ hw_mac = self.get_mac_of_intf(hw_name)
+ self.assertFalse(hw_mac in hw_macs,
+ 'duplicate hardware MAC %s' % hw_mac)
+ hw_macs.add(hw_mac)
+ # Create mininet Intf and attach it to the switch
+ hw_intf = HWIntf(hw_name, node=switch)
+ switch.attach(hw_intf)
+ hw_port = switch.ports[hw_intf]
+ # Connect hw_port <-> ovs_port
+ src, dst = hw_port, ovs_port
+ for flow in (
+ # Drop anything to or from the meaningless hw_mac
+ 'eth_src=%s,priority=2,actions=drop' % hw_mac,
+ 'eth_dst=%s,priority=2,actions=drop' % hw_mac,
+ # Forward traffic bidirectionally src <-> dst
+ 'in_port=%u,priority=1,actions=output:%u' % (src, dst),
+ 'in_port=%u,priority=1,actions=output:%u' % (dst, src)):
+ switch.cmd(self.OFCTL, 'add-flow', switch, flow)
def create_port_map(self, dpid):
"""Return a port map {'port_1': port...} for a dpid in self.topo"""
diff --git a/clib/mininet_test_topo.py b/clib/mininet_test_topo.py
index 7be2098a..7fd4508a 100644
--- a/clib/mininet_test_topo.py
+++ b/clib/mininet_test_topo.py
@@ -12,7 +12,7 @@ import netifaces
# pylint: disable=too-many-arguments
-from mininet.log import output
+from mininet.log import output, warn
from mininet.topo import Topo
from mininet.node import Controller
from mininet.node import CPULimitedHost
@@ -24,7 +24,6 @@ from clib import mininet_test_util
# TODO: this should be configurable (e.g for randomization)
SWITCH_START_PORT = 5
-HW_OFFSET = 16 # Must be > max(dp_ports)
class FaucetIntf(TCIntf):
@@ -69,18 +68,40 @@ class FaucetSwitch(OVSSwitch):
super().__init__(
name=name, reconnectms=8000, **params)
+ @staticmethod
+ def _workaround(args):
+ """Workarounds/hacks for errors resulting from
+ cmd() calls within Mininet"""
+ # Workaround: ignore ethtool errors on tap interfaces
+ # This allows us to use tap tunnels as cables to switch ports,
+ # for example to test against OvS in a VM.
+ if (len(args) > 1 and args[0] == 'ethtool -K' and
+ getattr(args[1], 'name', '').startswith('tap')):
+ return True
+ return False
+
def cmd(self, *args, success=0, **kwargs):
- """Switch commands should always succeed,
+ """Commands typically must succeed for proper switch operation,
so we check the exit code of the last command in *args.
success: desired exit code (or None to skip check)"""
# pylint: disable=arguments-differ
cmd_output = super().cmd(*args, **kwargs)
exit_code = int(super().cmd('echo $?'))
if success is not None and exit_code != success:
- raise RuntimeError(
- "%s exited with (%d):'%s'" % (args, exit_code, cmd_output))
+ msg = "%s exited with (%d):'%s'" % (args, exit_code, cmd_output)
+ if self._workaround(args):
+ warn('Ignoring:', msg, '\n')
+ else:
+ raise RuntimeError(msg)
return cmd_output
+ def attach(self, intf):
+ "Attach an interface and set its port"
+ super().attach(intf)
+ # This should be done in Mininet, but we do it for now
+ port = self.ports[intf]
+ self.cmd('ovs-vsctl set Interface', intf, 'ofport_request=%s' % port)
+
def start(self, controllers):
# Transcluded from Mininet source, since need to insert
# controller parameters at switch creation time.
diff --git a/tests/integration/mininet_tests.py b/tests/integration/mininet_tests.py
index ed833e44..0052118c 100644
--- a/tests/integration/mininet_tests.py
+++ b/tests/integration/mininet_tests.py
@@ -2,6 +2,7 @@
"""Mininet tests for FAUCET."""
+# pylint: disable=too-many-lines
# pylint: disable=missing-docstring
# pylint: disable=too-many-arguments
# pylint: disable=unbalanced-tuple-unpacking
@@ -122,9 +123,12 @@ vlans:
description: "untagged"
"""
+ # pylint: disable=invalid-name
CONFIG = CONFIG_BOILER_UNTAGGED
- def setUp(self): # pylint: disable=invalid-name
+ EVENT_LOGGER_TIMEOUT = 120 # Timeout for event logger process
+
+ def setUp(self): # pylint: disable=invalid-name
super(FaucetUntaggedTest, self).setUp()
self.topo = self.topo_class(
self.OVS_TYPE, self.ports_sock, self._test_name(), [self.dpid],
@@ -157,8 +161,11 @@ vlans:
event_log = os.path.join(self.tmpdir, 'event.log')
controller = self._get_controller()
sock = self.env['faucet']['FAUCET_EVENT_SOCK']
+ # Relying on a timeout seems a bit brittle;
+ # as an alternative we might possibly use something like
+ # `with popen(cmd...) as proc` to clean up on exceptions
controller.cmd(mininet_test_util.timeout_cmd(
- 'nc -U %s > %s &' % (sock, event_log), 120))
+ 'nc -U %s > %s &' % (sock, event_log), self.EVENT_LOGGER_TIMEOUT))
self.ping_all_when_learned()
self.flap_all_switch_ports()
self.verify_traveling_dhcp_mac()
@@ -8164,3 +8171,50 @@ class FaucetBadFlowModTest(FaucetUntaggedTest):
error('sending bad flow_mod', flow_mod, '\n')
self.send_flow_mod(flow_mod)
self.ping_all_when_learned()
+
+
+class FaucetUntaggedMorePortsBase(FaucetUntaggedTest):
+ """Base class for untagged test with more ports"""
+
+ # pylint: disable=invalid-name
+ N_UNTAGGED = 16 # Maximum number of ports to test
+ EVENT_LOGGER_TIMEOUT = 180 # Timeout for event logger process
+
+ # Config lines for additional ports
+ CONFIG_EXTRA_PORT = """
+ {port}:
+ native_vlan: 100""" + "\n"
+
+ def _init_faucet_config(self): # pylint: disable=invalid-name
+ """Extend config with more ports if needed"""
+ self.assertTrue(self.CONFIG.endswith(CONFIG_BOILER_UNTAGGED))
+ # We know how to extend the config for more ports
+ base_port_count = len(re.findall('port', CONFIG_BOILER_UNTAGGED))
+ ports = self.topo.dpid_ports(self.dpid)
+ for port in ports[base_port_count:]:
+ self.CONFIG += self.CONFIG_EXTRA_PORT.format(port=port)
+ super()._init_faucet_config()
+
+ def setUp(self):
+ """Make sure N_UNTAGGED doesn't exceed hw port count"""
+ if self.config and self.config.get('hw_switch', False):
+ self.N_UNTAGGED = min(len(self.config['dp_ports']),
+ self.N_UNTAGGED)
+ error('(%d ports) ' % self.N_UNTAGGED)
+ super().setUp()
+
+
+class FaucetUntagged32PortTest(FaucetUntaggedMorePortsBase):
+ """Untagged test with up to 32 ports"""
+
+ # pylint: disable=invalid-name
+ N_UNTAGGED = 32 # Maximum number of ports to test
+
+
+@unittest.skip('slow and potentially unreliable on travis')
+class FaucetUntagged48PortTest(FaucetUntaggedMorePortsBase):
+ """Untagged test with up to 48 ports"""
+
+ # pylint: disable=invalid-name
+ N_UNTAGGED = 48 # Maximum number of ports to test
+ EVENT_LOGGER_TIMEOUT = 360 # Timeout for event logger process