blob: 544c2c3d0de7dfb013700aaa11071da209096438 [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.
"""
Basic protocol and dataplane test cases
It is recommended that these definitions be kept in their own
namespace as different groups of tests will likely define
similar identifiers.
Current Assumptions:
The switch is actively attempting to contact the controller at the address
indicated in oftest.config.
"""
import time
import sys
import logging
import unittest
import random
from oftest import config
import oftest.controller as controller
import oftest.dataplane as dataplane
import oftest.base_tests as base_tests
import ofp
import oftest.illegal_message as illegal_message
from oftest.testutils import *
TEST_VID_DEFAULT = 2
@group('smoke')
class Echo(base_tests.SimpleProtocol):
"""
Test echo response with no data
"""
def runTest(self):
request = ofp.message.echo_request()
response, pkt = self.controller.transact(request)
self.assertTrue(response is not None,
"Did not get echo reply")
self.assertEqual(response.type, ofp.OFPT_ECHO_REPLY,
'response is not echo_reply')
self.assertEqual(request.xid, response.xid,
'response xid != request xid')
self.assertEqual(len(response.data), 0, 'response data non-empty')
class EchoWithData(base_tests.SimpleProtocol):
"""
Test echo response with short string data
"""
def runTest(self):
request = ofp.message.echo_request(data='OpenFlow Will Rule The World')
response, pkt = self.controller.transact(request)
self.assertTrue(response is not None,
"Did not get echo reply (with data)")
self.assertEqual(response.type, ofp.OFPT_ECHO_REPLY,
'response is not echo_reply')
self.assertEqual(request.xid, response.xid,
'response xid != request xid')
self.assertEqual(request.data, response.data,
'response data does not match request')
@group('smoke')
class PacketIn(base_tests.SimpleDataPlane):
"""
Test packet in function
Send a packet to each dataplane port and verify that a packet
in message is received from the controller for each
"""
def runTest(self):
# Construct packet to send to dataplane
# Send packet to dataplane, once to each port
# Poll controller with expect message type packet in
delete_all_flows(self.controller)
do_barrier(self.controller)
vid = test_param_get('vid', default=TEST_VID_DEFAULT)
for of_port in config["port_map"].keys():
for pkt, pt in [
(simple_tcp_packet(), "simple TCP packet"),
(simple_tcp_packet(dl_vlan_enable=True,vlan_vid=vid,pktlen=108),
"simple tagged TCP packet"),
(simple_eth_packet(), "simple Ethernet packet"),
(simple_eth_packet(pktlen=40), "tiny Ethernet packet")]:
logging.info("PKT IN test with %s, port %s" % (pt, of_port))
self.dataplane.send(of_port, str(pkt))
verify_packet_in(self, str(pkt), of_port, ofp.OFPR_NO_MATCH)
class PacketInBroadcastCheck(base_tests.SimpleDataPlane):
"""
Check if bcast pkts leak when no flows are present
Clear the flow table
Send in a broadcast pkt
Look for the packet on other dataplane ports.
"""
def runTest(self):
# Need at least two ports
self.assertTrue(len(config["port_map"]) > 1, "Too few ports for test")
delete_all_flows(self.controller)
do_barrier(self.controller)
of_ports = config["port_map"].keys()
d_port = of_ports[0]
pkt = simple_eth_packet(eth_dst='ff:ff:ff:ff:ff:ff')
logging.info("BCast Leak Test, send to port %s" % d_port)
self.dataplane.send(d_port, str(pkt))
(of_port, pkt_in, pkt_time) = self.dataplane.poll(exp_pkt=pkt)
self.assertTrue(pkt_in is None,
'BCast packet received on port ' + str(of_port))
@group('smoke')
class PacketOut(base_tests.SimpleDataPlane):
"""
Test packet out function
Send packet out message to controller for each dataplane port and
verify the packet appears on the appropriate dataplane port
"""
def runTest(self):
# Construct packet to send to dataplane
# Send packet to dataplane
# Poll controller with expect message type packet in
delete_all_flows(self.controller)
# These will get put into function
of_ports = config["port_map"].keys()
of_ports.sort()
for dp_port in of_ports:
for outpkt, opt in [
(simple_tcp_packet(), "simple TCP packet"),
(simple_eth_packet(), "simple Ethernet packet"),
(simple_eth_packet(pktlen=40), "tiny Ethernet packet")]:
logging.info("PKT OUT test with %s, port %s" % (opt, dp_port))
msg = ofp.message.packet_out(in_port=ofp.OFPP_NONE,
data=str(outpkt),
actions=[ofp.action.output(port=dp_port)],
buffer_id=0xffffffff)
logging.info("PacketOut to: " + str(dp_port))
self.controller.message_send(msg)
verify_packets(self, outpkt, [dp_port])
class PacketOutMC(base_tests.SimpleDataPlane):
"""
Test packet out to multiple output ports
Send packet out message to controller for 1 to N dataplane ports and
verify the packet appears on the appropriate ports
"""
def runTest(self):
# Construct packet to send to dataplane
# Send packet to dataplane
# Poll controller with expect message type packet in
delete_all_flows(self.controller)
# These will get put into function
of_ports = config["port_map"].keys()
random.shuffle(of_ports)
for num_ports in range(1,len(of_ports)+1):
for outpkt, opt in [
(simple_tcp_packet(), "simple TCP packet"),
(simple_eth_packet(), "simple Ethernet packet"),
(simple_eth_packet(pktlen=40), "tiny Ethernet packet")]:
dp_ports = of_ports[0:num_ports]
logging.info("PKT OUT test with " + opt +
", ports " + str(dp_ports))
actions = [ofp.action.output(port=port) for port in dp_ports]
msg = ofp.message.packet_out(in_port=ofp.OFPP_NONE,
data=str(outpkt),
actions=actions,
buffer_id=0xffffffff)
logging.info("PacketOut to: " + str(dp_ports))
self.controller.message_send(msg)
verify_packets(self, outpkt, dp_ports)
class FlowStatsGet(base_tests.SimpleProtocol):
"""
Get stats
Simply verify stats get transaction
"""
def runTest(self):
logging.info("Running StatsGet")
logging.info("Inserting trial flow")
request = flow_mod_gen(config["port_map"], True)
self.controller.message_send(request)
logging.info("Sending flow request")
request = ofp.message.flow_stats_request(out_port=ofp.OFPP_NONE,
table_id=0xff)
request.match.wildcards = 0 # ofp.OFPFW_ALL
response, pkt = self.controller.transact(request)
self.assertTrue(response is not None,
"Did not get response for flow stats")
logging.debug(response.show())
class TableStatsGet(base_tests.SimpleProtocol):
"""
Get table stats
Simply verify table stats get transaction
"""
def runTest(self):
logging.info("Running TableStatsGet")
logging.info("Inserting trial flow")
request = flow_mod_gen(config["port_map"], True)
self.controller.message_send(request)
logging.info("Sending table stats request")
request = ofp.message.table_stats_request()
response, pkt = self.controller.transact(request)
self.assertTrue(response is not None,
"Did not get reply for table stats")
logging.debug(response.show())
class DescStatsGet(base_tests.SimpleProtocol):
"""
Get stats
Simply verify stats get transaction
"""
def runTest(self):
logging.info("Running DescStatsGet")
logging.info("Sending stats request")
request = ofp.message.desc_stats_request()
response, pkt = self.controller.transact(request)
self.assertTrue(response is not None,
"Did not get reply for desc stats")
logging.debug(response.show())
class FlowMod(base_tests.SimpleProtocol):
"""
Insert a flow
Simple verification of a flow mod transaction
"""
def runTest(self):
logging.info("Running " + str(self))
request = flow_mod_gen(config["port_map"], True)
self.controller.message_send(request)
@group('smoke')
class PortConfigMod(base_tests.SimpleProtocol):
"""
Modify a bit in port config and verify changed
Get the switch configuration, modify the port configuration
and write it back; get the config again and verify changed.
Then set it back to the way it was.
"""
def runTest(self):
logging.info("Running " + str(self))
for of_port, ifname in config["port_map"].items(): # Grab first port
break
(hw_addr, port_config, advert) = \
port_config_get(self.controller, of_port)
self.assertTrue(port_config is not None, "Did not get port config")
logging.debug("No flood bit port " + str(of_port) + " is now " +
str(port_config & ofp.OFPPC_NO_FLOOD))
rv = port_config_set(self.controller, of_port,
port_config ^ ofp.OFPPC_NO_FLOOD, ofp.OFPPC_NO_FLOOD)
self.assertTrue(rv != -1, "Error sending port mod")
do_barrier(self.controller)
# Verify change took place with same feature request
(hw_addr, port_config2, advert) = \
port_config_get(self.controller, of_port)
logging.debug("No flood bit port " + str(of_port) + " is now " +
str(port_config2 & ofp.OFPPC_NO_FLOOD))
self.assertTrue(port_config2 is not None, "Did not get port config2")
self.assertTrue(port_config2 & ofp.OFPPC_NO_FLOOD !=
port_config & ofp.OFPPC_NO_FLOOD,
"Bit change did not take")
# Set it back
rv = port_config_set(self.controller, of_port, port_config,
ofp.OFPPC_NO_FLOOD)
self.assertTrue(rv != -1, "Error sending port mod")
do_barrier(self.controller)
class PortConfigModErr(base_tests.SimpleProtocol):
"""
Modify a bit in port config on an invalid port and verify
error message is received.
"""
def runTest(self):
logging.info("Running " + str(self))
# pick a random bad port number
bad_port = random.randint(1, ofp.OFPP_MAX)
count = 0
while (count < 50) and (bad_port in config["port_map"].keys()):
bad_port = random.randint(1, ofp.OFPP_MAX)
count = count + 1
self.assertTrue(count < 50, "Error selecting bad port")
logging.info("Select " + str(bad_port) + " as invalid port")
rv = port_config_set(self.controller, bad_port,
ofp.OFPPC_NO_FLOOD, ofp.OFPPC_NO_FLOOD)
self.assertTrue(rv != -1, "Error sending port mod")
# poll for error message
while True:
(response, raw) = self.controller.poll(ofp.OFPT_ERROR)
if not response: # Timeout
break
if response.code == ofp.OFPPMFC_BAD_PORT:
logging.info("Received error message with OFPPMFC_BAD_PORT code")
break
if not config["relax"]: # Only one attempt to match
break
count += 1
if count > 10: # Too many tries
break
self.assertTrue(response is not None, 'Did not receive error message')
@group('smoke')
class BadMessage(base_tests.SimpleProtocol):
"""
Send a message with a bad type and verify an error is returned
"""
def runTest(self):
logging.info("Running " + str(self))
request = illegal_message.illegal_message_type()
reply, pkt = self.controller.transact(request)
logging.info(repr(pkt))
self.assertTrue(reply is not None, "Did not get response to bad req")
self.assertTrue(reply.type == ofp.OFPT_ERROR,
"reply not an error message")
logging.info(reply.err_type)
self.assertTrue(reply.err_type == ofp.OFPET_BAD_REQUEST,
"reply error type is not bad request")
self.assertTrue(reply.code == ofp.OFPBRC_BAD_TYPE,
"reply error code is not bad type")
@group('smoke')
@version('1.1+')
class TableModConfig(base_tests.SimpleProtocol):
"""
Simple table modification
Mostly to make sure the switch correctly responds to these messages.
More complicated tests in the multi-tables.py tests
"""
def runTest(self):
# First table should always exist
table_id = 0
def get_table_config():
request = ofp.message.table_stats_request()
response, _ = self.controller.transact(request)
try:
table_stats = [x for x in response.entries if x.table_id == table_id][0]
except IndexError:
raise AssertionError("table id %d not found" % table_id)
return table_stats.config
# Get current configuration
orig_table_config = get_table_config()
# Change the configuration
if orig_table_config == ofp.OFPTC_TABLE_MISS_CONTROLLER:
new_table_config = ofp.OFPTC_TABLE_MISS_DROP
else:
new_table_config = ofp.OFPTC_TABLE_MISS_CONTROLLER
request = ofp.message.table_mod(table_id=table_id, config=new_table_config)
self.controller.message_send(request)
self.controller.transact(ofp.message.barrier_request())
# Check the configuration took
self.assertEqual(get_table_config(), new_table_config)
# Change the configuration back
request = ofp.message.table_mod(table_id=table_id, config=orig_table_config)
self.controller.message_send(request)
self.controller.transact(ofp.message.barrier_request())
if __name__ == "__main__":
print "Please run through oft script: ./oft --test_spec=basic"