Merge pull request #27 from InCNTRE/master
message_types
diff --git a/oft b/oft
index 9e92a99..1f28784 100755
--- a/oft
+++ b/oft
@@ -153,11 +153,13 @@
"param" : None,
"platform" : "local",
"platform_args" : None,
- "controller_host" : "0.0.0.0",
+ "switch_ip" : None, # If not none, actively connect to switch
+ "controller_host" : "0.0.0.0", # For passive bind
"controller_port" : 6633,
"relax" : False,
"test_spec" : "all",
"log_file" : "oft.log",
+ "log_append" : False,
"list" : False,
"list_test_names" : False,
"debug" : _debug_default,
@@ -225,6 +227,8 @@
parser.add_option("-P", "--platform", help=plat_help)
parser.add_option("-H", "--host", dest="controller_host",
help="The IP/name of the test controller host")
+ parser.add_option("-S", "--switch-ip", dest="switch_ip",
+ help="If set, actively connect to this switch by IP")
parser.add_option("-p", "--port", dest="controller_port",
type="int", help="Port number of the test controller")
test_list_help = """Indicate tests to run. Valid entries are "all" (the
@@ -236,6 +240,8 @@
parser.add_option("-T", "--test-spec", "--test-list", help=test_list_help)
parser.add_option("--log-file",
help="Name of log file, empty string to log to console")
+ parser.add_option("--log-append", action="store_true",
+ help="Do not delete log file if specified")
parser.add_option("--debug",
help="Debug lvl: debug, info, warning, error, critical")
parser.add_option("--port-count", type="int",
@@ -312,7 +318,9 @@
"""
_format = "%(asctime)s %(name)-10s: %(levelname)-8s: %(message)s"
_datefmt = "%H:%M:%S"
+ _mode = config["log_append"] and "a" or "w"
logging.basicConfig(filename=config["log_file"],
+ filemode=_mode,
level=config["dbg_level"],
format=_format, datefmt=_datefmt)
@@ -433,6 +441,9 @@
if test.__name__ in profile_mod.skip_test_list:
logging.info("Skipping test %s due to profile" % test.__name__)
return TEST_PRIO_SKIP
+ if test.__name__ in profile_mod.run_test_list:
+ logging.info("Add test %s due to profile" % test.__name__)
+ return TEST_PRIO_DEFAULT
return getattr(test, "priority", TEST_PRIO_DEFAULT)
#
diff --git a/platforms/ovs-dummy.py b/platforms/ovs-dummy.py
new file mode 100644
index 0000000..2dad1a5
--- /dev/null
+++ b/platforms/ovs-dummy.py
@@ -0,0 +1,213 @@
+"""
+Dummy platform
+
+This platform uses Open vSwitch dummy interfaces.
+"""
+
+import logging
+import os
+import select
+import socket
+import struct
+import sys
+import time
+from threading import Thread
+from threading import Lock
+
+RCV_TIMEOUT = 10000
+RUN_DIR = os.environ.get("OVS_RUNDIR", "/var/run/openvswitch")
+
+class DataPlanePortOVSDummy(Thread):
+ """
+ Class defining a port monitoring object that uses Unix domain
+ sockets for ports, intended for connecting to Open vSwitch "dummy"
+ netdevs.
+ """
+
+ def __init__(self, interface_name, port_number, parent, max_pkts=1024):
+ """
+ Set up a port monitor object
+ @param interface_name The name of the physical interface like eth1
+ @param port_number The port number associated with this port
+ @param parent The controlling dataplane object; for pkt wait CV
+ @param max_pkts Maximum number of pkts to keep in queue
+ """
+ Thread.__init__(self)
+ self.interface_name = interface_name
+ self.max_pkts = max_pkts
+ self.packets_total = 0
+ self.packets = []
+ self.packets_discarded = 0
+ self.port_number = port_number
+ self.txq = []
+ self.txq_lock = Lock()
+ logname = "dp-" + interface_name
+ self.logger = logging.getLogger(logname)
+ try:
+ self.socket = DataPlanePortOVSDummy.interface_open(interface_name)
+ except:
+ self.logger.info("Could not open socket")
+ raise
+ self.logger.info("Opened port monitor (class %s)", type(self).__name__)
+ self.parent = parent
+
+ @staticmethod
+ def interface_open(interface_name):
+ """
+ Open a Unix domain socket interface.
+ @param interface_name port name as a string such as 'eth1'
+ @retval s socket
+ """
+ s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ s.settimeout(RCV_TIMEOUT)
+ s.setblocking(0)
+ s.connect("%s/%s" % (RUN_DIR, interface_name))
+ return s
+
+ def run(self):
+ """
+ Activity function for class
+ """
+ self.running = True
+ rxbuf = ""
+ while self.running:
+ try:
+ self.txq_lock.acquire()
+ if self.txq:
+ wlist = [self.socket]
+ else:
+ wlist = []
+ self.txq_lock.release()
+
+ rout, wout, eout = select.select([self.socket], wlist, [], 1)
+ except:
+ print sys.exc_info()
+ self.logger.error("Select error, exiting")
+ break
+
+ if not self.running:
+ break
+
+ if wout:
+ self.txq_lock.acquire()
+ if self.txq:
+ retval = self.socket.send(self.txq[0])
+ if retval > 0:
+ self.txq[0] = self.txq[0][retval:]
+ if len(self.txq[0]) == 0:
+ self.txq = self.txq[1:]
+ self.txq_lock.release()
+
+ if rout:
+ if len(rxbuf) < 2:
+ n = 2 - len(rxbuf)
+ else:
+ frame_len = struct.unpack('>h', rxbuf[:2])[0]
+ n = (2 + frame_len) - len(rxbuf)
+
+ data = self.socket.recv(n)
+ rxbuf += data
+ if len(data) == n and len(rxbuf) > 2:
+ rcvtime = time.time()
+ self.logger.debug("Pkt len " + str(len(rxbuf)) +
+ " in at " + str(rcvtime) + " on port " +
+ str(self.port_number))
+
+ # Enqueue packet
+ with self.parent.pkt_sync:
+ if len(self.packets) >= self.max_pkts:
+ # Queue full, throw away oldest
+ self.packets.pop(0)
+ self.packets_discarded += 1
+ self.logger.debug("Discarding oldest packet to make room")
+ self.packets.append((rxbuf[2:], rcvtime))
+ self.packets_total += 1
+ self.parent.pkt_sync.notify_all()
+
+ rxbuf = ''
+
+ self.logger.info("Thread exit")
+
+ def kill(self):
+ """
+ Terminate the running thread
+ """
+ self.logger.debug("Port monitor kill")
+ self.running = False
+ try:
+ self.socket.close()
+ except:
+ self.logger.info("Ignoring dataplane soc shutdown error")
+
+ def timestamp_head(self):
+ """
+ Return the timestamp of the head of queue or None if empty
+ """
+ rv = None
+ try:
+ rv = self.packets[0][1]
+ except:
+ rv = None
+ return rv
+
+ def flush(self):
+ """
+ Clear the packet queue
+ """
+ with self.parent.pkt_sync:
+ self.packets_discarded += len(self.packets)
+ self.packets = []
+
+ def send(self, packet):
+ """
+ Send a packet to the dataplane port
+ @param packet The packet data to send to the port
+ @retval The number of bytes sent
+ """
+
+ self.txq_lock.acquire()
+ if len(self.txq) < self.max_pkts:
+ self.txq.append(struct.pack('>h', len(packet)) + packet)
+ retval = len(packet)
+ else:
+ retval = 0
+ self.txq_lock.release()
+
+ return retval
+
+ def register(self, handler):
+ """
+ Register a callback function to receive packets from this
+ port. The callback will be passed the packet, the
+ interface name and the port number (if set) on which the
+ packet was received.
+
+ To be implemented
+ """
+ pass
+
+ def show(self, prefix=''):
+ print prefix + "Name: " + self.interface_name
+ print prefix + "Pkts pending: " + str(len(self.packets))
+ print prefix + "Pkts total: " + str(self.packets_total)
+ print prefix + "socket: " + str(self.socket)
+
+# Update this dictionary to suit your environment.
+dummy_port_map = {
+ 1 : "p1",
+ 2 : "p2",
+ 3 : "p3",
+ 4 : "p4"
+}
+
+def platform_config_update(config):
+ """
+ Update configuration for the dummy platform
+
+ @param config The configuration dictionary to use/update
+ """
+ global dummy_port_map
+ config["port_map"] = dummy_port_map.copy()
+ config["caps_table_idx"] = 0
+ config["dataplane"] = {"portclass": DataPlanePortOVSDummy}
+ config["allow_user"] = True
diff --git a/profiles/default.py b/profiles/default.py
index 8313eb7..592797e 100644
--- a/profiles/default.py
+++ b/profiles/default.py
@@ -7,3 +7,7 @@
#@var skip_test_list The list of tests to skip for this run
skip_test_list = [
]
+
+#@var run_test_list List of tests to run which would normally be skipped
+run_test_list = [
+]
diff --git a/profiles/example.py b/profiles/example.py
index 6e23b92..0928119 100644
--- a/profiles/example.py
+++ b/profiles/example.py
@@ -19,15 +19,17 @@
#@var skip_test_list The list of tests to skip for this run
skip_test_list = []
-# TO BE IMPLEMENTED
-# A list of test cases with parameters(?)
-# TBD
+# A list of test cases
#@var run_test_list List of tests to run which would normally be skipped
-run_test_list = dict(
- # Example
- # SomeTestCase = [dict(<params1>), dict(<params2>),...],
-)
+run_test_list = []
+#
+# Ideally, we should allow parameters to be specified
+#
+# run_test_list = (
+# testcase = [dict(param1), dict(param2), ...],
+# )
+#
# for test_dict in profile.run_test_list:
# for test_name, test_params in test_dict.items():
# ...
diff --git a/src/python/oftest/base_tests.py b/src/python/oftest/base_tests.py
index 3664d9f..734e213 100644
--- a/src/python/oftest/base_tests.py
+++ b/src/python/oftest/base_tests.py
@@ -25,6 +25,7 @@
def setUp(self):
logging.info("** START TEST CASE " + str(self))
self.controller = controller.Controller(
+ switch=config["switch_ip"],
host=config["controller_host"],
port=config["controller_port"])
# clean_shutdown should be set to False to force quit app
diff --git a/src/python/oftest/controller.py b/src/python/oftest/controller.py
index cfe2eda..b4c3ac5 100644
--- a/src/python/oftest/controller.py
+++ b/src/python/oftest/controller.py
@@ -86,6 +86,7 @@
echo replies
@var initial_hello If true, will send a hello message immediately
upon connecting to the switch
+ @var switch If not None, do an active connection to the switch
@var host The host to use for connect
@var port The port to connect on
@var packets_total Total number of packets received
@@ -94,7 +95,7 @@
@var dbg_state Debug indication of state
"""
- def __init__(self, host='127.0.0.1', port=6633, max_pkts=1024):
+ def __init__(self, switch=None, host='127.0.0.1', port=6633, max_pkts=1024):
Thread.__init__(self)
# Socket related
self.rcv_size = RCV_SIZE_DEFAULT
@@ -127,7 +128,8 @@
# Settings
self.max_pkts = max_pkts
- self.passive = True
+ self.switch = switch
+ self.passive = not self.switch
self.host = host
self.port = port
self.dbg_state = "init"
@@ -282,7 +284,7 @@
@returns 0 on success, -1 on error
"""
- if s and s == self.listen_socket:
+ if self.passive and s and s == self.listen_socket:
if self.switch_socket:
self.logger.warning("Ignoring incoming connection; already connected to switch")
(sock, addr) = self.listen_socket.accept()
@@ -295,7 +297,7 @@
self.logger.warning("Error on listen socket accept")
return -1
self.socs.append(sock)
- self.logger.info("Incoming connection from %s" % str(addr))
+ self.logger.info(self.host+":"+str(self.port)+": Incoming connection from "+str(addr))
with self.connect_cv:
(self.switch_socket, self.switch_addr) = (sock, addr)
@@ -330,6 +332,23 @@
return 0
+ def active_connect(self):
+ """
+ Actively connect to a switch IP addr
+ """
+ try:
+ self.logger.info("Trying active connection to %s" % self.switch)
+ soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ soc.connect((self.switch, self.port))
+ self.logger.info("Connected to " + self.switch + " on " +
+ str(self.port))
+ self.switch_addr = (self.switch, self.port)
+ return soc
+ except (StandardError, socket.error), e:
+ self.logger.error("Could not connect to %s at %d:: %s" %
+ (self.switch, self.port, str(e)))
+ return None
+
def run(self):
"""
Activity function for class
@@ -348,18 +367,19 @@
self.dbg_state = "starting"
# Create listen socket
- self.logger.info("Create/listen at " + self.host + ":" +
- str(self.port))
- self.listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- self.listen_socket.setsockopt(socket.SOL_SOCKET,
- socket.SO_REUSEADDR, 1)
- self.listen_socket.bind((self.host, self.port))
- self.dbg_state = "listening"
- self.listen_socket.listen(LISTEN_QUEUE_SIZE)
+ if self.passive:
+ self.logger.info("Create/listen at " + self.host + ":" +
+ str(self.port))
+ self.listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.listen_socket.setsockopt(socket.SOL_SOCKET,
+ socket.SO_REUSEADDR, 1)
+ self.listen_socket.bind((self.host, self.port))
+ self.dbg_state = "listening"
+ self.listen_socket.listen(LISTEN_QUEUE_SIZE)
- self.logger.info("Waiting for switch connection")
- self.socs = [self.listen_socket]
- self.dbg_state = "running"
+ self.logger.info("Listening for switch connection")
+ self.socs = [self.listen_socket]
+ self.dbg_state = "running"
while self.active:
try:
@@ -391,8 +411,28 @@
@return Boolean, True if connected
"""
- with self.connect_cv:
- timed_wait(self.connect_cv, lambda: self.switch_socket, timeout=timeout)
+ if not self.passive: # Do active connection now
+ self.logger.info("Attempting to connect to %s on port %s" %
+ (self.switch, str(self.port)))
+ soc = self.active_connect()
+ if soc:
+ self.logger.info("Connected to %s", self.switch)
+ self.socs = [soc]
+ self.dbg_state = "running"
+ self.switch_socket = soc
+ with self.connect_cv:
+ if self.initial_hello:
+ self.message_send(hello())
+ self.connect_cv.notify() # Notify anyone waiting
+ else:
+ self.logger.error("Could not actively connect to switch %s",
+ self.switch)
+ self.active = False
+ else:
+ with self.connect_cv:
+ timed_wait(self.connect_cv, lambda: self.switch_socket,
+ timeout=timeout)
+
return self.switch_socket is not None
def disconnect(self, timeout=-1):
@@ -496,7 +536,7 @@
If an error occurs, (None, None) is returned
"""
- if exp_msg:
+ if exp_msg is not None:
self.logger.debug("Poll for %s" % ofp_type_map[exp_msg])
else:
self.logger.debug("Poll for any OF message")
@@ -504,7 +544,7 @@
# Take the packet from the queue
def grab():
if len(self.packets) > 0:
- if not exp_msg:
+ if exp_msg is None:
self.logger.debug("Looking for any packet")
(msg, pkt) = self.packets.pop(0)
return (msg, pkt)
@@ -620,6 +660,7 @@
string += " parse errors " + str(self.parse_errors) + "\n"
string += " sock errrors " + str(self.socket_errors) + "\n"
string += " max pkts " + str(self.max_pkts) + "\n"
+ string += " target switch " + str(self.switch) + "\n"
string += " host " + str(self.host) + "\n"
string += " port " + str(self.port) + "\n"
string += " keep_alive " + str(self.keep_alive) + "\n"
diff --git a/src/python/oftest/dataplane.py b/src/python/oftest/dataplane.py
index 2124421..717a85f 100644
--- a/src/python/oftest/dataplane.py
+++ b/src/python/oftest/dataplane.py
@@ -141,7 +141,7 @@
self.kill()
break
- rcvtime = time.clock()
+ rcvtime = time.time()
self.logger.debug("Pkt len " + str(len(rcvmsg)) +
" in at " + str(rcvtime) + " on port " +
str(self.port_number))
@@ -286,9 +286,8 @@
# Enqueue packet
with self.parent.pkt_sync:
for (timestamp, rcvmsg) in self.pcap.readpkts():
- rcvtime = time.clock()
self.logger.debug("Pkt len " + str(len(rcvmsg)) +
- " in at " + str(rcvtime) + " on port " +
+ " in at " + str(timestamp) + " on port " +
str(self.port_number))
if len(self.packets) >= self.max_pkts:
@@ -296,7 +295,7 @@
self.packets.pop(0)
self.packets_discarded += 1
self.logger.debug("Discarding oldest packet to make room")
- self.packets.append((rcvmsg, rcvtime))
+ self.packets.append((rcvmsg, timestamp))
self.packets_total += 1
self.parent.pkt_sync.notify_all()
diff --git a/src/python/oftest/testutils.py b/src/python/oftest/testutils.py
index 53c18fe..50316b7 100644
--- a/src/python/oftest/testutils.py
+++ b/src/python/oftest/testutils.py
@@ -56,6 +56,8 @@
def simple_tcp_packet(pktlen=100,
dl_dst='00:01:02:03:04:05',
dl_src='00:06:07:08:09:0a',
+ dl_qinq_enable=False,
+ dl_vlan_outer=20,
dl_vlan_enable=False,
dl_vlan=0,
dl_vlan_pcp=0,
@@ -75,6 +77,8 @@
@param len Length of packet in bytes w/o CRC
@param dl_dst Destinatino MAC
@param dl_src Source MAC
+ @param dl_qinq_enable True if the packet is double vlan tags
+ @param dl_vlan_outer Outer VLAN ID
@param dl_vlan_enable True if the packet is with vlan, False otherwise
@param dl_vlan VLAN ID
@param dl_vlan_pcp VLAN priority
@@ -93,7 +97,13 @@
pktlen = MINSIZE
# Note Dot1Q.id is really CFI
- if (dl_vlan_enable):
+ if (dl_qinq_enable):
+ pkt = scapy.Ether(dst=dl_dst, src=dl_src)/ \
+ scapy.Dot1Q(prio=0, id=0, vlan=dl_vlan_outer)/ \
+ scapy.Dot1Q(prio=dl_vlan_pcp, id=dl_vlan_cfi, vlan=dl_vlan)/ \
+ scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ihl=ip_ihl)/ \
+ scapy.TCP(sport=tcp_sport, dport=tcp_dport)
+ elif (dl_vlan_enable):
pkt = scapy.Ether(dst=dl_dst, src=dl_src)/ \
scapy.Dot1Q(prio=dl_vlan_pcp, id=dl_vlan_cfi, vlan=dl_vlan)/ \
scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ihl=ip_ihl)/ \
@@ -109,9 +119,64 @@
scapy.TCP(sport=tcp_sport, dport=tcp_dport)
pkt = pkt/("D" * (pktlen - len(pkt)))
-
- #print pkt.show()
- #print scapy.Ether(str(pkt)).show()
+
+ return pkt
+
+def simple_udp_packet(pktlen=100,
+ dl_dst='00:01:02:03:04:05',
+ dl_src='00:06:07:08:09:0a',
+ dl_vlan_enable=False,
+ dl_vlan=0,
+ dl_vlan_pcp=0,
+ dl_vlan_cfi=0,
+ ip_src='192.168.0.1',
+ ip_dst='192.168.0.2',
+ ip_tos=0,
+ udp_sport=1234,
+ udp_dport=80,
+ ip_ihl=None,
+ ip_options=False
+ ):
+ """
+ Return a simple dataplane UDP packet
+
+ Supports a few parameters:
+ @param len Length of packet in bytes w/o CRC
+ @param dl_dst Destination MAC
+ @param dl_src Source MAC
+ @param dl_vlan_enable True if the packet is with vlan, False otherwise
+ @param dl_vlan VLAN ID
+ @param dl_vlan_pcp VLAN priority
+ @param ip_src IP source
+ @param ip_dst IP destination
+ @param ip_tos IP ToS
+ @param udp_dport UDP destination port
+ @param udp_sport UDP source port
+
+ Generates a simple UDP packet. Users shouldn't assume anything about
+ this packet other than that it is a valid ethernet/IP/UDP frame.
+ """
+
+ if MINSIZE > pktlen:
+ pktlen = MINSIZE
+
+ # Note Dot1Q.id is really CFI
+ if (dl_vlan_enable):
+ pkt = scapy.Ether(dst=dl_dst, src=dl_src)/ \
+ scapy.Dot1Q(prio=dl_vlan_pcp, id=dl_vlan_cfi, vlan=dl_vlan)/ \
+ scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ihl=ip_ihl)/ \
+ scapy.UDP(sport=udp_sport, dport=udp_dport)
+ else:
+ if not ip_options:
+ pkt = scapy.Ether(dst=dl_dst, src=dl_src)/ \
+ scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ihl=ip_ihl)/ \
+ scapy.UDP(sport=udp_sport, dport=udp_dport)
+ else:
+ pkt = scapy.Ether(dst=dl_dst, src=dl_src)/ \
+ scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ihl=ip_ihl, options=ip_options)/ \
+ scapy.UDP(sport=udp_sport, dport=udp_dport)
+
+ pkt = pkt/("D" * (pktlen - len(pkt)))
return pkt
@@ -179,13 +244,13 @@
return pkt
-def do_barrier(ctrl):
+def do_barrier(ctrl, timeout=-1):
"""
Do a barrier command
Return 0 on success, -1 on error
"""
b = message.barrier_request()
- (resp, pkt) = ctrl.transact(b)
+ (resp, pkt) = ctrl.transact(b, timeout=timeout)
# We'll trust the transaction processing in the controller that xid matched
if not resp:
return -1
@@ -251,6 +316,12 @@
@param no_ports Set or list of ports that should not receive packet
@param assert_if Object that implements assertXXX
"""
+
+ # Wait this long for packets that we don't expect to receive.
+ # 100ms is (rarely) too short for positive tests on slow
+ # switches but is definitely not too short for a negative test.
+ negative_timeout = 0.1
+
exp_pkt_arg = None
if config["relax"]:
exp_pkt_arg = pkt
@@ -268,11 +339,11 @@
"Response packet does not match send packet " +
"on port " + str(ofport))
if len(no_ports) > 0:
- time.sleep(1)
+ time.sleep(negative_timeout)
for ofport in no_ports:
logging.debug("Negative check for pkt on port " + str(ofport))
(rcv_port, rcv_pkt, pkt_time) = dp.poll(
- port_number=ofport, timeout=1, exp_pkt=exp_pkt_arg)
+ port_number=ofport, timeout=0, exp_pkt=exp_pkt_arg)
assert_if.assertTrue(rcv_pkt is None,
"Unexpected pkt on port " + str(ofport))
@@ -316,6 +387,8 @@
+ str(exp_pkt).encode('hex'))
logging.debug("Received len " + str(len(rcv_pkt)) + ": "
+ str(rcv_pkt).encode('hex'))
+ logging.debug("Expected packet: " + inspect_packet(scapy.Ether(str(exp_pkt))))
+ logging.debug("Received packet: " + inspect_packet(scapy.Ether(str(rcv_pkt))))
parent.assertEqual(str(exp_pkt), str(rcv_pkt),
"Packet match error on port " + str(check_port))
@@ -499,6 +572,43 @@
exp_pkt = pkt
receive_pkt_verify(parent, egr_ports, exp_pkt, ing_port)
+def flow_match_test_pktout(parent, ing_port, egr_ports,
+ dl_vlan=-1, pkt=None, exp_pkt=None,
+ action_list=None):
+ """
+ Packet-out test on single TCP packet
+ @param egr_ports A single port or list of ports
+
+ Run test sending packet-out to egr_ports. The goal is to test the actions
+ taken on the packet, not the matching which is of course irrelevant.
+ See flow_match_test for parameter descriptions
+ """
+
+ if pkt is None:
+ pkt = simple_tcp_packet(dl_vlan_enable=(dl_vlan >= 0), dl_vlan=dl_vlan)
+
+ msg = message.packet_out()
+ msg.in_port = ing_port
+ msg.data = str(pkt)
+ if action_list is not None:
+ for act in action_list:
+ assert(msg.actions.add(act))
+
+ # Set up output action
+ if egr_ports is not None:
+ for egr_port in egr_ports:
+ act = action.action_output()
+ act.port = egr_port
+ assert(msg.actions.add(act))
+
+ logging.debug(msg.show())
+ rv = parent.controller.message_send(msg)
+ parent.assertTrue(rv == 0, "Error sending out message")
+
+ if exp_pkt is None:
+ exp_pkt = pkt
+ receive_pkt_verify(parent, egr_ports, exp_pkt, ing_port)
+
def get_egr_list(parent, of_ports, how_many, exclude_list=[]):
"""
Generate a list of ports avoiding those in the exclude list
@@ -527,7 +637,7 @@
exp_pkt=None, action_list=None,
max_test=0, egr_count=1, ing_port=False):
"""
- Run flow_match_test_port_pair on all port pairs
+ Run flow_match_test_port_pair on all port pairs and packet-out
@param max_test If > 0 no more than this number of tests are executed.
@param parent Must implement controller, dataplane, assertTrue, assertEqual
@@ -565,7 +675,18 @@
test_count += 1
if (max_test > 0) and (test_count > max_test):
logging.info("Ran " + str(test_count) + " tests; exiting")
- return
+ break
+
+
+ ingress_port = of_ports[0]
+ egr_ports = get_egr_list(parent, of_ports, egr_count,
+ exclude_list=[ingress_port])
+ if ing_port:
+ egr_ports.append(ofp.OFPP_IN_PORT)
+ flow_match_test_pktout(parent, ingress_port, egr_ports,
+ dl_vlan=dl_vlan,
+ pkt=pkt, exp_pkt=exp_pkt,
+ action_list=action_list)
def test_param_get(key, default=None):
"""
@@ -639,13 +760,19 @@
elif field_to_mod == 'tcp_dport':
act = action.action_set_tp_dst()
act.tp_port = mod_field_vals['tcp_dport']
+ elif field_to_mod == 'udp_sport':
+ act = action.action_set_tp_src()
+ act.tp_port = mod_field_vals['udp_sport']
+ elif field_to_mod == 'udp_dport':
+ act = action.action_set_tp_dst()
+ act.tp_port = mod_field_vals['udp_dport']
else:
parent.assertTrue(0, "Unknown field to modify: " + str(field_to_mod))
return act
def pkt_action_setup(parent, start_field_vals={}, mod_field_vals={},
- mod_fields=[], check_test_params=False):
+ mod_fields=[], tp="tcp", check_test_params=False):
"""
Set up the ingress and expected packet and action list for a test
@@ -671,8 +798,12 @@
base_pkt_params['ip_src'] = '192.168.0.1'
base_pkt_params['ip_dst'] = '192.168.0.2'
base_pkt_params['ip_tos'] = 0
- base_pkt_params['tcp_sport'] = 1234
- base_pkt_params['tcp_dport'] = 80
+ if tp == "tcp":
+ base_pkt_params['tcp_sport'] = 1234
+ base_pkt_params['tcp_dport'] = 80
+ elif tp == "udp":
+ base_pkt_params['udp_sport'] = 1234
+ base_pkt_params['udp_dport'] = 80
for keyname in start_field_vals.keys():
base_pkt_params[keyname] = start_field_vals[keyname]
@@ -686,8 +817,12 @@
mod_pkt_params['ip_src'] = '10.20.30.40'
mod_pkt_params['ip_dst'] = '50.60.70.80'
mod_pkt_params['ip_tos'] = 0xf0
- mod_pkt_params['tcp_sport'] = 4321
- mod_pkt_params['tcp_dport'] = 8765
+ if tp == "tcp":
+ mod_pkt_params['tcp_sport'] = 4321
+ mod_pkt_params['tcp_dport'] = 8765
+ elif tp == "udp":
+ mod_pkt_params['udp_sport'] = 4321
+ mod_pkt_params['udp_dport'] = 8765
for keyname in mod_field_vals.keys():
mod_pkt_params[keyname] = mod_field_vals[keyname]
@@ -722,8 +857,15 @@
mod_fields.append('dl_vlan_enable')
mod_fields.append('pktlen')
+ if tp == "tcp":
+ packet_builder = simple_tcp_packet
+ elif tp == "udp":
+ packet_builder = simple_udp_packet
+ else:
+ raise NotImplementedError("unknown transport protocol %s" % tp)
+
# Build the ingress packet
- ingress_pkt = simple_tcp_packet(**base_pkt_params)
+ ingress_pkt = packet_builder(**base_pkt_params)
# Build the expected packet, modifying the indicated fields
for item in mod_fields:
@@ -732,7 +874,7 @@
if act:
new_actions.append(act)
- expected_pkt = simple_tcp_packet(**base_pkt_params)
+ expected_pkt = packet_builder(**base_pkt_params)
return (ingress_pkt, expected_pkt, new_actions)
@@ -823,3 +965,20 @@
def format_packet(pkt):
return "Packet length %d \n%s" % (len(str(pkt)),
hex_dump_buffer(str(pkt)))
+
+def inspect_packet(pkt):
+ """
+ Wrapper around scapy's show() method.
+ @returns A string showing the dissected packet.
+ """
+ from cStringIO import StringIO
+ out = None
+ backup = sys.stdout
+ try:
+ sys.stdout = StringIO()
+ pkt.show2()
+ out = sys.stdout.getvalue()
+ sys.stdout.close()
+ finally:
+ sys.stdout = backup
+ return out
diff --git a/tests/FuncUtils.py b/tests/FuncUtils.py
index a252d37..f70fb7e 100644
--- a/tests/FuncUtils.py
+++ b/tests/FuncUtils.py
@@ -285,7 +285,7 @@
#Generate Match_Vlan_Priority
#Create a simple tcp packet and generate match on ethernet dst address flow
- pkt_matchvlanpcp = simple_tcp_packet(dl_vlan_enable=True,dl_vlan=1,dl_vlan_pcp=10)
+ pkt_matchvlanpcp = simple_tcp_packet(dl_vlan_enable=True,dl_vlan=1,dl_vlan_pcp=5)
match = parse.packet_to_flow_match(pkt_matchvlanpcp)
self.assertTrue(match is not None, "Could not generate flow match from pkt")
@@ -367,7 +367,7 @@
#Generate a Match on IP Type of service flow
#Create a simple tcp packet and generate match on Type of service
- pkt_iptos = simple_tcp_packet(ip_tos=30)
+ pkt_iptos = simple_tcp_packet(ip_tos=28)
match = parse.packet_to_flow_match(pkt_iptos)
self.assertTrue(match is not None, "Could not generate flow match from pkt")
diff --git a/tests/basic.py b/tests/basic.py
index e4b933e..862b6df 100644
--- a/tests/basic.py
+++ b/tests/basic.py
@@ -208,6 +208,7 @@
logging.info("PKT OUT test with %s, port %s" % (opt, dp_port))
msg = message.packet_out()
+ msg.in_port = ofp.OFPP_NONE
msg.data = str(outpkt)
act = action.action_output()
act.port = dp_port
@@ -264,6 +265,7 @@
logging.info("PKT OUT test with " + opt +
", ports " + str(dp_ports))
msg = message.packet_out()
+ msg.in_port = ofp.OFPP_NONE
msg.data = str(outpkt)
act = action.action_output()
for i in range(0,num_ports):
diff --git a/tests/caps.py b/tests/caps.py
index fe90310..6c48840 100644
--- a/tests/caps.py
+++ b/tests/caps.py
@@ -60,7 +60,7 @@
logging.info("Inserting initial flow")
rv = obj.controller.message_send(request)
obj.assertTrue(rv != -1, "Error installing flow mod")
- obj.assertEqual(do_barrier(obj.controller), 0, "Barrier failed")
+ obj.assertEqual(do_barrier(obj.controller, timeout=10), 0, "Barrier failed")
flow_count = 1
logging.info("Table idx: " + str(table_idx))
@@ -71,7 +71,7 @@
rv = obj.controller.message_send(request)
flow_count += 1
if flow_count % count_check == 0:
- obj.assertEqual(do_barrier(obj.controller), 0, "Barrier failed")
+ obj.assertEqual(do_barrier(obj.controller, timeout=10), 0, "Barrier failed")
response, pkt = obj.controller.transact(tstats)
obj.assertTrue(response is not None, "Get tab stats failed")
logging.info(response.show())
@@ -87,6 +87,11 @@
logging.error("RESULT: " + str(flow_count) + " flows inserted")
logging.error("RESULT: " + str(active_flows) + " flows reported")
+ # clean up and wait a bit in case the table is really big
+ rv = delete_all_flows(obj.controller)
+ time.sleep(flow_count / 100)
+
+
class FillTableExact(base_tests.SimpleProtocol):
"""
diff --git a/tests/cxn.py b/tests/cxn.py
index 5524434..e9059cc 100644
--- a/tests/cxn.py
+++ b/tests/cxn.py
@@ -112,55 +112,84 @@
self.num_controllers = test_param_get('num_controllers', default=1)
self.controller_timeout = test_param_get('controller_timeout',
default=-1)
+ self.hello_timeout = test_param_get('hello_timeout',
+ default=5)
+ self.features_req_timeout = test_param_get('features_req_timeout',
+ default=5)
for i in range(self.num_controllers):
self.controllerSetup(config["controller_host"],
config["controller_port"]+i)
for i in range(self.num_controllers):
- self.controllers[i].handshake_done = False
+ self.controllers[i].cstate = 0
+ self.controllers[i].keep_alive = True
+ tick = 0.1 # time period in seconds at which controllers are handled
- # try to maintain switch connections for specified timeout
- # -1 means forever
while True:
for con in self.controllers:
- if con.switch_socket and con.handshake_done:
- if (self.controller_timeout < 0 or
- con.count < self.controller_timeout):
- logging.info(con.host + ":" + str(con.port) +
- ": maintaining connection to " +
- str(con.switch_addr))
- con.count = con.count + 1
- else:
- logging.info(con.host + ":" + str(con.port) +
- ": disconnecting from " +
- str(con.switch_addr))
- con.disconnect()
- con.handshake_done = False
- con.count = 0
- time.sleep(1)
- else:
- #@todo Add an option to wait for a pkt transaction to
- # ensure version compatibilty?
- con.connect(self.default_timeout)
- if not con.switch_socket:
- logging.info("Did not connect to switch")
- continue
- logging.info("TCP Connected " + str(con.switch_addr))
- logging.info("Sending hello")
- con.message_send(message.hello())
- request = message.features_request()
- reply, pkt = con.transact(request,
- timeout=self.default_timeout)
- if reply:
- logging.info("Handshake complete with " +
- str(con.switch_addr))
- con.handshake_done = True
- con.keep_alive = True
- con.count = 0
- else:
- logging.info("Did not complete features_request " +
- "for handshake")
- con.disconnect()
- con.handshake_done = False
- con.count = 0
+ condesc = con.host + ":" + str(con.port) + ": "
+ logging.debug("Checking " + condesc)
+ if con.switch_socket:
+ if con.cstate == 0:
+ logging.info(condesc + "Sending hello to " +
+ str(con.switch_addr))
+ con.message_send(message.hello())
+ con.cstate = 1
+ con.count = 0
+ elif con.cstate == 1:
+ reply, pkt = con.poll(exp_msg=ofp.OFPT_HELLO,
+ timeout=0)
+ if reply is not None:
+ logging.info(condesc +
+ "Hello received from " +
+ str(con.switch_addr))
+ con.cstate = 2
+ else:
+ con.count = con.count + 1
+ # fall back to previous state on timeout
+ if con.count >= self.hello_timeout/tick:
+ logging.info(condesc +
+ "Timeout hello from " +
+ str(con.switch_addr))
+ con.cstate = 0
+ elif con.cstate == 2:
+ logging.info(condesc + "Sending features request to " +
+ str(con.switch_addr))
+ con.message_send(message.features_request())
+ con.cstate = 3
+ con.count = 0
+ elif con.cstate == 3:
+ reply, pkt = con.poll(exp_msg=ofp.OFPT_FEATURES_REPLY,
+ timeout=0)
+ if reply is not None:
+ logging.info(condesc +
+ "Features request received from " +
+ str(con.switch_addr))
+ con.cstate = 4
+ con.count = 0
+ else:
+ con.count = con.count + 1
+ # fall back to previous state on timeout
+ if con.count >= self.features_req_timeout/tick:
+ logging.info(condesc +
+ "Timeout features request from " +
+ str(con.switch_addr))
+ con.cstate = 2
+ elif con.cstate == 4:
+ if (self.controller_timeout < 0 or
+ con.count < self.controller_timeout/tick):
+ logging.debug(condesc +
+ "Maintaining connection to " +
+ str(con.switch_addr))
+ con.count = con.count + 1
+ else:
+ logging.info(condesc +
+ "Disconnecting from " +
+ str(con.switch_addr))
+ con.disconnect()
+ con.cstate = 0
+ else:
+ con.cstate = 0
+
+ time.sleep(tick)
diff --git a/tests/flow_matches.py b/tests/flow_matches.py
index 9edd4c0..4b12a95 100644
--- a/tests/flow_matches.py
+++ b/tests/flow_matches.py
@@ -414,7 +414,7 @@
receive_pkt_check(self.dataplane,pkt,[yes_ports],no_ports,self)
#Create a non-matching packet , verify packet_in get generated
- pkt2 = simple_tcp_packet(ip_tos=2);
+ pkt2 = simple_tcp_packet(ip_tos=4);
self.dataplane.send(of_ports[0], str(pkt2))
(response, raw) = self.controller.poll(ofp.OFPT_PACKET_IN,timeout=4)
self.assertTrue(response is not None, "PacketIn not received for non matching packet")
diff --git a/tests/flow_stats.py b/tests/flow_stats.py
index 2a42b94..040fec8 100644
--- a/tests/flow_stats.py
+++ b/tests/flow_stats.py
@@ -458,3 +458,78 @@
self.assertEquals(response.stats[0].flow_count, 0)
self.assertEquals(response.stats[0].packet_count, 0)
self.assertEquals(response.stats[0].byte_count, 0)
+
+class DeletedFlowStats(base_tests.SimpleDataPlane):
+ """
+ Verify flow stats are properly returned when a flow is deleted.
+
+ Generate a packet
+ Generate and install a matching flow
+ Send the packet
+ Delete the flow
+ Verify that the flow_removed message has the correct stats
+ """
+
+ def runTest(self):
+ # TODO: set from command-line parameter
+ test_timeout = 60
+
+ of_ports = config["port_map"].keys()
+ of_ports.sort()
+ self.assertTrue(len(of_ports) > 1, "Not enough ports for test")
+
+ rc = delete_all_flows(self.controller)
+ self.assertEqual(rc, 0, "Failed to delete all flows")
+
+ # build packet
+ pkt = simple_tcp_packet()
+ match = packet_to_flow_match(self, pkt)
+ match.wildcards &= ~ofp.OFPFW_IN_PORT
+ self.assertTrue(match is not None,
+ "Could not generate flow match from pkt")
+ act = action.action_output()
+
+ # build flow
+ ingress_port = of_ports[0];
+ egress_port = of_ports[1];
+ logging.info("Ingress " + str(ingress_port) +
+ " to egress " + str(egress_port))
+ match.in_port = ingress_port
+ flow_mod_msg = message.flow_mod()
+ flow_mod_msg.match = copy.deepcopy(match)
+ flow_mod_msg.cookie = random.randint(0,9007199254740992)
+ flow_mod_msg.buffer_id = 0xffffffff
+ flow_mod_msg.idle_timeout = 0
+ flow_mod_msg.hard_timeout = 0
+ flow_mod_msg.priority = 100
+ flow_mod_msg.flags = ofp.OFPFF_SEND_FLOW_REM
+ act.port = egress_port
+ self.assertTrue(flow_mod_msg.actions.add(act), "Could not add action")
+
+ # send flow
+ logging.info("Inserting flow")
+ rv = self.controller.message_send(flow_mod_msg)
+ self.assertTrue(rv != -1, "Error installing flow mod")
+ self.assertEqual(do_barrier(self.controller), 0, "Barrier failed")
+
+ # send packet N times
+ num_sends = random.randint(10,20)
+ logging.info("Sending " + str(num_sends) + " test packets")
+ for i in range(0,num_sends):
+ sendPacket(self, pkt, ingress_port, egress_port,
+ test_timeout)
+
+ # delete flow
+ logging.info("Deleting flow")
+ rc = delete_all_flows(self.controller)
+ self.assertEqual(rc, 0, "Failed to delete all flows")
+
+ # wait for flow_removed message
+ flow_removed, _ = self.controller.poll(
+ exp_msg=ofp.OFPT_FLOW_REMOVED, timeout=test_timeout)
+
+ self.assertTrue(flow_removed != None, "Did not receive flow_removed message")
+ self.assertEqual(flow_removed.cookie, flow_mod_msg.cookie)
+ self.assertEqual(flow_removed.reason, ofp.OFPRR_DELETE)
+ self.assertEqual(flow_removed.packet_count, num_sends)
+ self.assertEqual(flow_removed.byte_count, num_sends * len(str(pkt)))
diff --git a/tests/load.py b/tests/load.py
index 0f74d56..dcd873b 100644
--- a/tests/load.py
+++ b/tests/load.py
@@ -82,6 +82,7 @@
# Create packet out and send to port lb_port + 1
msg = message.packet_out()
+ msg.in_port = lb_port
msg.data = str(pkt)
act = action.action_output()
act.port = lb_port + 1
@@ -176,6 +177,7 @@
logging.info("PKT OUT test with %s, port %s" % (opt, dp_port))
msg = message.packet_out()
+ msg.in_port = ofp.OFPP_NONE
msg.data = str(outpkt)
act = action.action_output()
act.port = dp_port
diff --git a/tests/pktact.py b/tests/pktact.py
index 34654d1..ff6927f 100644
--- a/tests/pktact.py
+++ b/tests/pktact.py
@@ -870,6 +870,12 @@
"Could not generate flow match from pkt")
act = action.action_output()
+ # Clear OFPPC_NO_FLOOD on each port
+ for of_port in of_ports:
+ rv = port_config_set(self.controller, of_port,
+ 0, ofp.OFPPC_NO_FLOOD)
+ self.assertEqual(rv, 0, "Failed to set port config")
+
for idx in range(len(of_ports)):
rv = delete_all_flows(self.controller)
self.assertEqual(rv, 0, "Failed to delete all flows")
@@ -907,6 +913,14 @@
rv = port_config_set(self.controller, no_flood_port,
0, ofp.OFPPC_NO_FLOOD)
self.assertEqual(rv, 0, "Failed to reset port config")
+ self.assertEqual(do_barrier(self.controller), 0, "Barrier failed")
+
+ # Check that packets are now flooded to no_flood_port
+ logging.info("Sending packet to dp port " + str(ingress_port))
+ self.dataplane.send(ingress_port, str(pkt))
+ no_ports = set([ingress_port])
+ yes_ports = set(of_ports).difference(no_ports)
+ receive_pkt_check(self.dataplane, pkt, yes_ports, no_ports, self)
#@todo Should check no other packets received
@@ -1095,6 +1109,8 @@
on port specified by this flow.
3. Add wildcard flow with even higher priority, verify packet received
on port specified by this flow.
+ 4. Add wildcard flow with lower priority, verify packet received
+ on port specified by the highest priority flow.
"""
def runTest(self):
@@ -1117,6 +1133,11 @@
# priority
self.installFlow(1001, of_ports[0], of_ports[3])
self.verifyFlow(of_ports[0], of_ports[3])
+ # Install a flow with wildcards for our packet with lower
+ # priority
+ self.installFlow(999, of_ports[0], of_ports[1],
+ wildcards=ofp.OFPFW_DL_SRC)
+ self.verifyFlow(of_ports[0], of_ports[3])
class WildcardPriorityWithDelete(SingleWildcardMatchPriority):
@@ -1546,6 +1567,21 @@
flow_match_test(self, config["port_map"], pkt=pkt, exp_pkt=exp_pkt,
action_list=acts, max_test=2)
+class ModifyL4SrcUdp(BaseMatchCase):
+ """
+ Modify the source UDP port of a UDP packet
+ """
+ def runTest(self):
+ sup_acts = self.supported_actions
+ if not (sup_acts & 1 << ofp.OFPAT_SET_TP_DST):
+ skip_message_emit(self, "ModifyL4SrcUdp test")
+ return
+
+ (pkt, exp_pkt, acts) = pkt_action_setup(self, mod_fields=['udp_sport'],
+ check_test_params=True, tp="udp")
+ flow_match_test(self, config["port_map"], pkt=pkt, exp_pkt=exp_pkt,
+ action_list=acts, max_test=2)
+
class ModifyL4Dst(BaseMatchCase):
"""
Modify the dest TCP port of a TCP packet (TP1)
@@ -1561,6 +1597,21 @@
flow_match_test(self, config["port_map"], pkt=pkt, exp_pkt=exp_pkt,
action_list=acts, max_test=2)
+class ModifyL4DstUdp(BaseMatchCase):
+ """
+ Modify the dest UDP port of a UDP packet
+ """
+ def runTest(self):
+ sup_acts = self.supported_actions
+ if not (sup_acts & 1 << ofp.OFPAT_SET_TP_DST):
+ skip_message_emit(self, "ModifyL4DstUdp test")
+ return
+
+ (pkt, exp_pkt, acts) = pkt_action_setup(self, mod_fields=['udp_dport'],
+ check_test_params=True, tp="udp")
+ flow_match_test(self, config["port_map"], pkt=pkt, exp_pkt=exp_pkt,
+ action_list=acts, max_test=2)
+
class ModifyTOS(BaseMatchCase):
"""
Modify the IP type of service of an IP packet (TP1)
@@ -1673,6 +1724,40 @@
flow_match_test(self, config["port_map"], pkt=pkt, exp_pkt=exp_pkt,
action_list=acts, max_test=2, egr_count=-1)
+class ModifyAll(BaseMatchCase):
+ """
+ Modify all supported fields and output to a port
+ """
+ def runTest(self):
+ sup_acts = self.supported_actions
+
+ sup_map = {
+ "dl_dst" : ofp.OFPAT_SET_DL_DST,
+ "dl_src" : ofp.OFPAT_SET_DL_SRC,
+ "dl_vlan_enable" : ofp.OFPAT_SET_VLAN_VID,
+ "dl_vlan" : ofp.OFPAT_SET_VLAN_VID,
+ "dl_vlan_pcp" : ofp.OFPAT_SET_VLAN_PCP,
+ "ip_src" : ofp.OFPAT_SET_NW_SRC,
+ "ip_dst" : ofp.OFPAT_SET_NW_DST,
+ "ip_tos" : ofp.OFPAT_SET_NW_TOS,
+ "tcp_sport" : ofp.OFPAT_SET_TP_SRC,
+ "tcp_dport" : ofp.OFPAT_SET_TP_DST,
+ }
+
+ mod_fields = [field for (field, bit) in sup_map.items() if (sup_acts & 1 << bit)]
+ random.shuffle(mod_fields)
+ start_field_vals = { "dl_vlan_enable" : True }
+ mod_field_vals = { "dl_vlan_enable" : True }
+ logging.info("modifying fields: %s" % repr(mod_fields))
+
+ (pkt, exp_pkt, acts) = pkt_action_setup(self,
+ mod_fields=mod_fields,
+ start_field_vals=start_field_vals,
+ mod_field_vals=mod_field_vals,
+ check_test_params=True)
+ flow_match_test(self, config["port_map"], pkt=pkt, exp_pkt=exp_pkt,
+ action_list=acts, max_test=2)
+
class FlowToggle(BaseMatchCase):
"""
Add flows to the table and modify them repeatedly
@@ -1961,16 +2046,6 @@
# - VLAN?
# - action
- def pktToStr(self, pkt):
- from cStringIO import StringIO
- backup = sys.stdout
- sys.stdout = StringIO()
- pkt.show2()
- out = sys.stdout.getvalue()
- sys.stdout.close()
- sys.stdout = backup
- return out
-
def createMatch(self, **kwargs):
match = ofp.ofp_match()
match.wildcards = ofp.OFPFW_ALL
@@ -2014,7 +2089,7 @@
logging.info("Ingress %s to egress %s" %
(str(ingress_port), str(egress_port)))
logging.info("Packet:")
- logging.info(self.pktToStr(pkt))
+ logging.info(inspect_packet(pkt))
match.in_port = ingress_port
@@ -2078,7 +2153,7 @@
if str_pkt != str_rcv_pkt:
logging.error("Response packet does not match send packet")
logging.info("Response:")
- logging.info(self.pktToStr(scapy.Ether(rcv_pkt)))
+ logging.info(inspect_packet(scapy.Ether(rcv_pkt)))
self.assertEqual(str_pkt, str_rcv_pkt,
'Response packet does not match send packet')
elif expected_result == self.RESULT_NOMATCH:
diff --git a/tests/port_stats.py b/tests/port_stats.py
index 670e31f..89b2cf6 100644
--- a/tests/port_stats.py
+++ b/tests/port_stats.py
@@ -78,6 +78,21 @@
logging.info("Port %d stats count: tx %d rx %d" % (port, packet_sent, packet_recv))
return packet_sent, packet_recv
+def getAllStats(obj):
+ stat_req = message.port_stats_request()
+ stat_req.port_no = ofp.OFPP_NONE
+
+ logging.info("Sending all port stats request")
+ response, pkt = obj.controller.transact(stat_req, timeout=2)
+ obj.assertTrue(response is not None,
+ "No response to stats request")
+ obj.assertTrue(len(response.stats) >= 3,
+ "Did not receive all port stats reply")
+ stats = {}
+ for item in response.stats:
+ stats[ item.port_no ] = ( item.tx_packets, item.rx_packets )
+ return stats
+
def verifyStats(obj, port, test_timeout, packet_sent, packet_recv):
stat_req = message.port_stats_request()
stat_req.port_no = port
@@ -179,7 +194,6 @@
verifyStats(self, ingress_port, test_timeout, initTxInPort, initRxInPort + num_sends)
verifyStats(self, egress_port, test_timeout, initTxOutPort + num_sends, initRxOutPort)
-
class MultiFlowStats(base_tests.SimpleDataPlane):
"""
Verify flow stats are properly retrieved.
@@ -260,3 +274,80 @@
initTxOutPort1 + num_pkt1s, initRxOutPort1)
verifyStats(self, egress_port2, test_timeout,
initTxOutPort2 + num_pkt2s, initRxOutPort2)
+
+class AllPortStats(base_tests.SimpleDataPlane):
+ """
+ Verify all port stats are properly retrieved.
+
+ First, get stats from each port. Then get all port stats, verify
+ consistency with single port stats.
+ """
+
+ # TODO: This is copied from MultiFlowStats. Need to combine.
+ def buildFlowModMsg(self, pkt, ingress_port, egress_port):
+ match = packet_to_flow_match(self, pkt)
+ match.wildcards &= ~ofp.OFPFW_IN_PORT
+ self.assertTrue(match is not None,
+ "Could not generate flow match from pkt")
+ match.in_port = ingress_port
+
+ flow_mod_msg = message.flow_mod()
+ flow_mod_msg.match = match
+ flow_mod_msg.cookie = random.randint(0,9007199254740992)
+ flow_mod_msg.buffer_id = 0xffffffff
+ flow_mod_msg.idle_timeout = 0
+ flow_mod_msg.hard_timeout = 0
+ act = action.action_output()
+ act.port = egress_port
+ self.assertTrue(flow_mod_msg.actions.add(act), "Could not add action")
+
+ logging.info("Ingress " + str(ingress_port) +
+ " to egress " + str(egress_port))
+
+ return flow_mod_msg
+
+ def runTest(self):
+ # TODO: set from command-line parameter
+ test_timeout = 60
+
+ of_ports = config["port_map"].keys()
+ of_ports.sort()
+ self.assertTrue(len(of_ports) >= 3, "Not enough ports for test")
+ port0 = of_ports[0];
+ port1 = of_ports[1];
+ port2 = of_ports[2];
+
+ # construct some packets and flows, send to switch
+ pkt1 = simple_tcp_packet()
+ flow_mod_msg1 = self.buildFlowModMsg(pkt1, port0, port1)
+
+ pkt2 = simple_tcp_packet(dl_src='0:7:7:7:7:7')
+ flow_mod_msg2 = self.buildFlowModMsg(pkt2, port0, port2)
+
+ logging.info("Inserting flow1")
+ rv = self.controller.message_send(flow_mod_msg1)
+ self.assertTrue(rv != -1, "Error installing flow mod")
+ logging.info("Inserting flow2")
+ rv = self.controller.message_send(flow_mod_msg2)
+ self.assertTrue(rv != -1, "Error installing flow mod")
+ self.assertEqual(do_barrier(self.controller), 0, "Barrier failed")
+
+ num_pkt1s = random.randint(5,10)
+ logging.info("Sending " + str(num_pkt1s) + " pkt1s")
+ num_pkt2s = random.randint(10,15)
+ logging.info("Sending " + str(num_pkt2s) + " pkt2s")
+ for i in range(0,num_pkt1s):
+ sendPacket(self, pkt1, port0, port1, test_timeout)
+ for i in range(0,num_pkt2s):
+ sendPacket(self, pkt2, port0, port2, test_timeout)
+
+ # get individual port stats count
+ port_stats = {}
+ port_stats[ port0 ] = getStats(self, port0)
+ port_stats[ port1 ] = getStats(self, port1)
+ port_stats[ port2 ] = getStats(self, port2)
+
+ all_stats = getAllStats(self)
+ self.assertEqual(port_stats[ port0 ], all_stats[ port0 ])
+ self.assertEqual(port_stats[ port1 ], all_stats[ port1 ])
+ self.assertEqual(port_stats[ port2 ], all_stats[ port2 ])