blob: 3398aaf3a84758af8da036a200d822e8221621f4 [file] [log] [blame]
# Copyright 2017-present Open Networking 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.
"""
Flow stats test case.
Similar to Flow stats test case in the perl test harness.
"""
import logging
import unittest
import random
import copy
from oftest import config
import oftest.controller as controller
import ofp
import oftest.dataplane as dataplane
import oftest.parse as parse
import oftest.base_tests as base_tests
from oftest.testutils import *
from time import sleep
# TODO: ovs has problems with VLAN id?
WILDCARD_VALUES = [ofp.OFPFW_IN_PORT,
# (ofp.OFPFW_DL_VLAN | ofp.OFPFW_DL_VLAN_PCP),
ofp.OFPFW_DL_SRC,
ofp.OFPFW_DL_DST,
(ofp.OFPFW_DL_TYPE | ofp.OFPFW_NW_SRC_ALL |
ofp.OFPFW_NW_DST_ALL | ofp.OFPFW_NW_TOS | ofp.OFPFW_NW_PROTO |
ofp.OFPFW_TP_SRC | ofp.OFPFW_TP_DST),
(ofp.OFPFW_NW_PROTO | ofp.OFPFW_TP_SRC | ofp.OFPFW_TP_DST),
ofp.OFPFW_TP_SRC,
ofp.OFPFW_TP_DST,
ofp.OFPFW_NW_SRC_MASK,
ofp.OFPFW_NW_DST_MASK,
ofp.OFPFW_DL_VLAN_PCP,
ofp.OFPFW_NW_TOS]
def sendPacket(obj, pkt, ingress_port, egress_port, test_timeout):
logging.info("Sending packet to dp port " + str(ingress_port) +
", expecting output on " + str(egress_port))
obj.dataplane.send(ingress_port, str(pkt))
exp_pkt_arg = None
exp_port = None
if config["relax"]:
exp_pkt_arg = pkt
exp_port = egress_port
(rcv_port, rcv_pkt, pkt_time) = obj.dataplane.poll(port_number=exp_port,
exp_pkt=exp_pkt_arg)
obj.assertTrue(rcv_pkt is not None,
"Packet not received on port " + str(egress_port))
logging.debug("Packet len " + str(len(rcv_pkt)) + " in on " +
str(rcv_port))
obj.assertEqual(rcv_port, egress_port,
"Packet received on port " + str(rcv_port) +
", expected port " + str(egress_port))
obj.assertEqual(str(pkt), str(rcv_pkt),
'Response packet does not match send packet')
class SingleFlowStats(base_tests.SimpleDataPlane):
"""
Verify flow stats are properly retrieved.
Generate a packet
Generate and install a matching flow
Send the packet
Send a flow stats request to match the flow and retrieve stats
Verify that the packet counter has incremented
"""
def verifyStats(self, flow_mod_msg, match, out_port, test_timeout, packet_count):
stat_req = ofp.message.flow_stats_request()
stat_req.match = match
stat_req.table_id = 0xff
stat_req.out_port = out_port
all_packets_received = 0
for i in range(0,test_timeout):
logging.info("Sending stats request")
response, pkt = self.controller.transact(stat_req,
timeout=test_timeout)
self.assertTrue(response is not None,
"No response to stats request")
self.assertTrue(len(response.entries) == 1,
"Did not receive flow stats reply")
for obj in response.entries:
self.assertEqual(flow_mod_msg.match, obj.match,
"Matches do not match")
self.assertEqual(obj.cookie, flow_mod_msg.cookie)
self.assertEqual(obj.priority, flow_mod_msg.priority)
self.assertEqual(obj.idle_timeout, flow_mod_msg.idle_timeout)
self.assertEqual(obj.hard_timeout, flow_mod_msg.hard_timeout)
self.assertEqual(obj.actions, flow_mod_msg.actions)
logging.info("Received " + str(obj.packet_count) + " packets")
if obj.packet_count == packet_count:
all_packets_received = 1
if all_packets_received:
break
sleep(1)
self.assertTrue(all_packets_received,
"Packet count does not match number sent")
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")
delete_all_flows(self.controller)
# 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 = ofp.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 = ofp.message.flow_add()
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 = 60000
flow_mod_msg.hard_timeout = 65000
flow_mod_msg.priority = 100
act.port = egress_port
flow_mod_msg.actions.append(act)
# send flow
logging.info("Inserting flow")
self.controller.message_send(flow_mod_msg)
do_barrier(self.controller)
# no packets sent, so zero packet count
self.verifyStats(flow_mod_msg, match, ofp.OFPP_NONE, test_timeout, 0)
# 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)
self.verifyStats(flow_mod_msg, match, ofp.OFPP_NONE, test_timeout, num_sends)
self.verifyStats(flow_mod_msg, match, egress_port, test_timeout, num_sends)
for wc in WILDCARD_VALUES:
match.wildcards = required_wildcards(self) | wc
self.verifyStats(flow_mod_msg, match, egress_port, test_timeout, num_sends)
class TwoFlowStats(base_tests.SimpleDataPlane):
"""
Verify flow stats are properly retrieved.
Generate two packets and install two matching flows
Send some number of packets
Send a flow stats request to match the flows and retrieve stats
Verify that the packet counter has incremented
TODO: add a third flow, and then configure the match to exclude
that flow?
"""
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 = ofp.message.flow_add()
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 = ofp.action.output()
act.port = egress_port
flow_mod_msg.actions.append(act)
logging.info("Ingress " + str(ingress_port) +
" to egress " + str(egress_port))
return flow_mod_msg
def sumStatsReplyCounts(self, response):
total_packets = 0
for obj in response.entries:
#self.assertEqual(match, obj.match,
# "Matches do not match")
logging.info("Received " + str(obj.packet_count)
+ " packets")
total_packets += obj.packet_count
return total_packets
def verifyStats(self, match, out_port, test_timeout, packet_count):
stat_req = ofp.message.flow_stats_request()
stat_req.match = match
stat_req.table_id = 0xff
stat_req.out_port = out_port
all_packets_received = 0
for i in range(0,test_timeout):
logging.info("Sending stats request")
# TODO: move REPLY_MORE handling to controller.transact?
response, pkt = self.controller.transact(stat_req,
timeout=test_timeout)
self.assertTrue(response is not None,
"No response to stats request")
total_packets = self.sumStatsReplyCounts(response)
while response.flags == ofp.OFPSF_REPLY_MORE:
response, pkt = self.controller.poll(exp_msg=
ofp.OFPT_STATS_REPLY,
timeout=test_timeout)
total_packets += self.sumStatsReplyCounts(response)
if total_packets == packet_count:
all_packets_received = 1
break
sleep(1)
self.assertTrue(all_packets_received,
"Total stats packet count " + str(total_packets) +
" does not match number sent " + str(packet_count))
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")
ingress_port = of_ports[0];
egress_port1 = of_ports[1];
egress_port2 = of_ports[2];
delete_all_flows(self.controller)
pkt1 = simple_tcp_packet()
flow_mod_msg1 = self.buildFlowModMsg(pkt1, ingress_port, egress_port1)
pkt2 = simple_tcp_packet(eth_src='0:7:7:7:7:7')
flow_mod_msg2 = self.buildFlowModMsg(pkt2, ingress_port, egress_port2)
logging.info("Inserting flow1")
self.controller.message_send(flow_mod_msg1)
logging.info("Inserting flow2")
self.controller.message_send(flow_mod_msg2)
do_barrier(self.controller)
num_pkt1s = random.randint(10,30)
logging.info("Sending " + str(num_pkt1s) + " pkt1s")
num_pkt2s = random.randint(10,30)
logging.info("Sending " + str(num_pkt2s) + " pkt2s")
for i in range(0,num_pkt1s):
sendPacket(self, pkt1, ingress_port, egress_port1, test_timeout)
for i in range(0,num_pkt2s):
sendPacket(self, pkt2, ingress_port, egress_port2, test_timeout)
match1 = packet_to_flow_match(self, pkt1)
logging.info("Verifying flow1's " + str(num_pkt1s) + " packets")
self.verifyStats(match1, ofp.OFPP_NONE, test_timeout, num_pkt1s)
match2 = packet_to_flow_match(self, pkt2)
logging.info("Verifying flow2's " + str(num_pkt2s) + " packets")
self.verifyStats(match2, ofp.OFPP_NONE, test_timeout, num_pkt2s)
match1.wildcards |= ofp.OFPFW_DL_SRC
logging.info("Verifying combined " + str(num_pkt1s+num_pkt2s) + " packets")
self.verifyStats(match1, ofp.OFPP_NONE, test_timeout,
num_pkt1s+num_pkt2s)
# TODO: sweep through the wildcards to verify matching?
class AggregateStats(base_tests.SimpleDataPlane):
"""
Verify aggregate flow stats are properly retrieved.
Generate two packets
Generate and install two matching flows
Send an aggregate stats request
Verify that aggregate stats are correct
Also verify out_port filtering
"""
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 = ofp.message.flow_add()
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 = ofp.action.output()
act.port = egress_port
flow_mod_msg.actions.append(act)
logging.info("Ingress " + str(ingress_port) +
" to egress " + str(egress_port))
return flow_mod_msg
def verifyAggFlowStats(self, match, out_port, test_timeout,
flow_count, packet_count):
stat_req = ofp.message.aggregate_stats_request()
stat_req.match = match
stat_req.table_id = 0xff
stat_req.out_port = out_port
all_packets_received = 0
for i in range(0,test_timeout):
logging.info("Sending stats request")
response, pkt = self.controller.transact(stat_req,
timeout=test_timeout)
self.assertTrue(response is not None,
"No response to stats request")
self.assertTrue(response.flow_count == flow_count,
"Flow count " + str(response.flow_count) +
" does not match expected " + str(flow_count))
logging.info("Received " + str(response.packet_count) + " packets")
if response.packet_count == packet_count:
all_packets_received = 1
if all_packets_received:
break
sleep(1)
self.assertTrue(all_packets_received,
"Packet count does not match number sent")
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")
ingress_port = of_ports[0];
egress_port1 = of_ports[1];
egress_port2 = of_ports[2];
delete_all_flows(self.controller)
pkt1 = simple_tcp_packet()
flow_mod_msg1 = self.buildFlowModMsg(pkt1, ingress_port, egress_port1)
pkt2 = simple_tcp_packet(eth_src='0:7:7:7:7:7')
flow_mod_msg2 = self.buildFlowModMsg(pkt2, ingress_port, egress_port2)
logging.info("Inserting flow1")
self.controller.message_send(flow_mod_msg1)
logging.info("Inserting flow2")
self.controller.message_send(flow_mod_msg2)
do_barrier(self.controller)
num_pkt1s = random.randint(10,30)
logging.info("Sending " + str(num_pkt1s) + " pkt1s")
num_pkt2s = random.randint(10,30)
logging.info("Sending " + str(num_pkt2s) + " pkt2s")
for i in range(0,num_pkt1s):
sendPacket(self, pkt1, ingress_port, egress_port1, test_timeout)
for i in range(0,num_pkt2s):
sendPacket(self, pkt2, ingress_port, egress_port2, test_timeout)
# loop on flow stats request until timeout
match = packet_to_flow_match(self, pkt1)
match.wildcards |= ofp.OFPFW_DL_SRC
self.verifyAggFlowStats(match, ofp.OFPP_NONE, test_timeout,
2, num_pkt1s+num_pkt2s)
# out_port filter for egress_port1
self.verifyAggFlowStats(match, egress_port1, test_timeout,
1, num_pkt1s)
# out_port filter for egress_port1
self.verifyAggFlowStats(match, egress_port2, test_timeout,
1, num_pkt2s)
class EmptyFlowStats(base_tests.SimpleDataPlane):
"""
Verify the switch replies to a flow stats request when
the query doesn't match any flows.
"""
def runTest(self):
delete_all_flows(self.controller)
match = ofp.match()
match.wildcards = 0
stat_req = ofp.message.flow_stats_request()
stat_req.match = match
stat_req.table_id = 0xff
stat_req.out_port = ofp.OFPP_NONE
response, pkt = self.controller.transact(stat_req)
self.assertTrue(response is not None,
"No response to stats request")
self.assertEquals(len(response.entries), 0)
self.assertEquals(response.flags, 0)
class EmptyAggregateStats(base_tests.SimpleDataPlane):
"""
Verify aggregate flow stats are properly retrieved when
the query doesn't match any flows.
"""
def runTest(self):
delete_all_flows(self.controller)
match = ofp.match()
match.wildcards = 0
stat_req = ofp.message.aggregate_stats_request()
stat_req.match = match
stat_req.table_id = 0xff
stat_req.out_port = ofp.OFPP_NONE
response, pkt = self.controller.transact(stat_req)
self.assertTrue(response is not None,
"No response to stats request")
self.assertEquals(response.flow_count, 0)
self.assertEquals(response.packet_count, 0)
self.assertEquals(response.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")
delete_all_flows(self.controller)
# 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 = ofp.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 = ofp.message.flow_add()
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
flow_mod_msg.actions.append(act)
# send flow
logging.info("Inserting flow")
self.controller.message_send(flow_mod_msg)
do_barrier(self.controller)
# 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)
# wait some time for flow stats to be propagated
# FIXME timeout handling should be unified
sleep(test_param_get('default_timeout', default=2))
# delete flow
logging.info("Deleting flow")
delete_all_flows(self.controller)
# 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,
"Received cookie " + str(flow_removed.cookie) +
" does not match expected " + str(flow_mod_msg.cookie))
self.assertEqual(flow_removed.reason, ofp.OFPRR_DELETE,
"Received reason " + str(flow_removed.reason) +
" does not match expected " + str(ofp.OFPRR_DELETE))
self.assertEqual(flow_removed.packet_count, num_sends,
"Received packet count " + str(flow_removed.packet_count) +
" does not match expected " + str(num_sends))
tolerance = 5 # percent
self.assertTrue(flow_removed.byte_count >=
(1-tolerance/100.0) * num_sends * len(str(pkt)) and
flow_removed.byte_count <=
(1+tolerance/100.0) * num_sends * len(str(pkt)),
"Received byte count " + str(flow_removed.byte_count) +
" is not within " + str(tolerance) + "% of expected " +
str(num_sends*len(str(pkt))))