blob: 3a9c3d85fe71e0ce01e8ae8c4aadcff77abd3753 [file] [log] [blame]
Rich Lane629393f2013-01-10 15:37:33 -08001"""
2Basic protocol and dataplane test cases
3
4It is recommended that these definitions be kept in their own
5namespace as different groups of tests will likely define
6similar identifiers.
7"""
8
9import sys
10import logging
11
12import unittest
13
14import oftest.controller as controller
15import oftest.dataplane as dataplane
16import oftest.base_tests as base_tests
17
18from oftest import config
19import oftest.controller as controller
20import oftest.dataplane as dataplane
21import oftest.base_tests as base_tests
22import of12.cstruct as ofp
23import of12.message as message
24import of12.action as action
25
26import oftest.oft12.testutils as testutils
27import ipaddr
28
29class Echo(base_tests.SimpleProtocol):
30 """
31 Test echo response with no data
32 """
33 def runTest(self):
34 testutils.do_echo_request_reply_test(self, self.controller)
35
36class EchoWithData(base_tests.SimpleProtocol):
37 """
38 Test echo response with short string data
39 """
40 def runTest(self):
41 request = message.echo_request()
42 request.data = 'OpenFlow Will Rule The World'
43 response, _ = self.controller.transact(request)
44 self.assertEqual(response.header.type, ofp.OFPT_ECHO_REPLY,
45 'response is not echo_reply')
46 self.assertEqual(request.header.xid, response.header.xid,
47 'response xid != request xid')
48 self.assertEqual(request.data, response.data,
49 'response data does not match request')
50
51class FeaturesRequest(base_tests.SimpleProtocol):
52 """
53 Test features_request to make sure we get a response
54
55 Does NOT test the contents; just that we get a response
56 """
57 def runTest(self):
58 request = message.features_request()
59 response,_ = self.controller.transact(request)
60 self.assertTrue(response,"Got no features_reply to features_request")
61 self.assertEqual(response.header.type, ofp.OFPT_FEATURES_REPLY,
62 'response is not echo_reply')
63 self.assertTrue(len(response) >= 32, "features_reply too short: %d < 32 " % len(response))
64
65class PacketIn(base_tests.SimpleDataPlane):
66 """
67 Test packet in function
68
69 Send a packet to each dataplane port and verify that a packet
70 in message is received from the controller for each
71 """
72 def runTest(self):
73 # Construct packet to send to dataplane
74 # Send packet to dataplane, once to each port
75 # Poll controller with expect message type packet in
76
77 rc = testutils.delete_all_flows(self.controller, logging)
78 self.assertEqual(rc, 0, "Failed to delete all flows")
79
80 for of_port in config["port_map"].keys():
81 logging.info("PKT IN test, port " + str(of_port))
82 pkt = testutils.simple_tcp_packet()
83 self.dataplane.send(of_port, str(pkt))
84 #@todo Check for unexpected messages?
85 (response, _) = self.controller.poll(ofp.OFPT_PACKET_IN, 2)
86
87 self.assertTrue(response is not None,
88 'Packet in message not received on port ' +
89 str(of_port))
90 if str(pkt) != response.data:
91 logging.debug("pkt len " + str(len(str(pkt))) +
92 ": " + str(pkt))
93 logging.debug("resp len " +
94 str(len(str(response.data))) +
95 ": " + str(response.data))
96
97 self.assertEqual(str(pkt), response.data,
98 'Response packet does not match send packet' +
99 ' for port ' + str(of_port))
100
101class PacketOut(base_tests.SimpleDataPlane):
102 """
103 Test packet out function
104
105 Send packet out message to controller for each dataplane port and
106 verify the packet appears on the appropriate dataplane port
107 """
108 def runTest(self):
109 # Construct packet to send to dataplane
110 # Send packet to dataplane
111 # Poll controller with expect message type packet in
112
113 rc = testutils.delete_all_flows(self.controller, logging)
114 self.assertEqual(rc, 0, "Failed to delete all flows")
115
116 # These will get put into function
117 outpkt = testutils.simple_tcp_packet()
118 of_ports = config["port_map"].keys()
119 of_ports.sort()
120 for dp_port in of_ports:
121 msg = message.packet_out()
122 msg.in_port = ofp.OFPP_CONTROLLER
123 msg.data = str(outpkt)
124 act = action.action_output()
125 act.port = dp_port
126 self.assertTrue(msg.actions.add(act), 'Could not add action to msg')
127
128 logging.info("PacketOut to: " + str(dp_port))
129 rv = self.controller.message_send(msg)
130 self.assertTrue(rv == 0, "Error sending out message")
131
132 (of_port, pkt, _) = self.dataplane.poll(timeout=1)
133
134 self.assertTrue(pkt is not None, 'Packet not received')
135 logging.info("PacketOut: got pkt from " + str(of_port))
136 if of_port is not None:
137 self.assertEqual(of_port, dp_port, "Unexpected receive port")
138 self.assertEqual(str(outpkt), str(pkt),
139 'Response packet does not match send packet')
140
141class FlowRemoveAll(base_tests.SimpleProtocol):
142 """
143 Remove all flows; required for almost all tests
144
145 Add a bunch of flows, remove them, and then make sure there are no flows left
146 This is an intentionally naive test to see if the baseline functionality works
147 and should be a precondition to any more complicated deletion test (e.g.,
148 delete_strict vs. delete)
149 """
150 def runTest(self):
151 logging.info("Running StatsGet")
152 logging.info("Inserting trial flow")
153 request = message.flow_mod()
154 request.buffer_id = 0xffffffff
155 for i in range(1,5):
156 request.priority = i*1000
157 logging.debug("Adding flow %d" % i)
158 rv = self.controller.message_send(request)
159 self.assertTrue(rv != -1, "Failed to insert test flow %d" % i)
160 logging.info("Removing all flows")
161 testutils.delete_all_flows(self.controller, logging)
162 logging.info("Sending flow request")
163 request = message.flow_stats_request()
164 request.out_port = ofp.OFPP_ANY
165 request.out_group = ofp.OFPG_ANY
166 request.table_id = 0xff
167 response, _ = self.controller.transact(request, timeout=2)
168 self.assertTrue(response is not None, "Did not get response")
169 self.assertTrue(isinstance(response,message.flow_stats_reply),"Not a flow_stats_reply")
170 self.assertEqual(len(response.stats),0)
171 logging.debug(response.show())
172
173
174
175class FlowStatsGet(base_tests.SimpleProtocol):
176 """
177 Get stats
178
179 Simply verify stats get transaction
180 """
181 def runTest(self):
182 logging.info("Running StatsGet")
183 logging.info("Inserting trial flow")
184 request = message.flow_mod()
185 request.buffer_id = 0xffffffff
186 rv = self.controller.message_send(request)
187 self.assertTrue(rv != -1, "Failed to insert test flow")
188
189 logging.info("Sending flow request")
190 response = testutils.flow_stats_get(self)
191 logging.debug(response.show())
192
193class TableStatsGet(base_tests.SimpleProtocol):
194 """
195 Get table stats
196
197 Naively verify that we get a reply
198 do better sanity check of data in stats.TableStats test
199 """
200 def runTest(self):
201 logging.info("Running TableStatsGet")
202 logging.info("Sending table stats request")
203 request = message.table_stats_request()
204 response, _ = self.controller.transact(request, timeout=2)
205 self.assertTrue(response is not None, "Did not get response")
206 logging.debug(response.show())
207
208class FlowMod(base_tests.SimpleProtocol):
209 """
210 Insert a flow
211
212 Simple verification of a flow mod transaction
213 """
214
215 def runTest(self):
216 logging.info("Running " + str(self))
217 request = message.flow_mod()
218 request.buffer_id = 0xffffffff
219 rv = self.controller.message_send(request)
220 self.assertTrue(rv != -1, "Error installing flow mod")
221
222class PortConfigMod(base_tests.SimpleProtocol):
223 """
224 Modify a bit in port config and verify changed
225
226 Get the switch configuration, modify the port configuration
227 and write it back; get the config again and verify changed.
228 Then set it back to the way it was.
229 """
230
231 def runTest(self):
232 logging.info("Running " + str(self))
233 for of_port, _ in config["port_map"].items(): # Grab first port
234 break
235
236 (_, port_config, _) = \
237 testutils.port_config_get(self.controller, of_port, logging)
238 self.assertTrue(port_config is not None, "Did not get port config")
239
240 logging.debug("No flood bit port " + str(of_port) + " is now " +
241 str(port_config & ofp.OFPPC_NO_PACKET_IN))
242
243 rv = testutils.port_config_set(self.controller, of_port,
244 port_config ^ ofp.OFPPC_NO_PACKET_IN, ofp.OFPPC_NO_PACKET_IN,
245 logging)
246 self.assertTrue(rv != -1, "Error sending port mod")
247
248 # Verify change took place with same feature request
249 (_, port_config2, _) = \
250 testutils.port_config_get(self.controller, of_port, logging)
251 logging.debug("No packet_in bit port " + str(of_port) + " is now " +
252 str(port_config2 & ofp.OFPPC_NO_PACKET_IN))
253 self.assertTrue(port_config2 is not None, "Did not get port config2")
254 self.assertTrue(port_config2 & ofp.OFPPC_NO_PACKET_IN !=
255 port_config & ofp.OFPPC_NO_PACKET_IN,
256 "Bit change did not take")
257 # Set it back
258 rv = testutils.port_config_set(self.controller, of_port, port_config,
259 ofp.OFPPC_NO_PACKET_IN, logging)
260 self.assertTrue(rv != -1, "Error sending port mod")
261
262class TableModConfig(base_tests.SimpleProtocol):
263 """ Simple table modification
264
265 Mostly to make sure the switch correctly responds to these messages.
266 More complicated tests in the multi-tables.py tests
267 """
268 def runTest(self):
269 logging.info("Running " + str(self))
270 table_mod = message.table_mod()
271 table_mod.table_id = 0 # first table should always exist
272 table_mod.config = ofp.OFPTC_TABLE_MISS_CONTROLLER
273
274 rv = self.controller.message_send(table_mod)
275 self.assertTrue(rv != -1, "Error sending table_mod")
276 testutils.do_echo_request_reply_test(self, self.controller)
277
278
279if __name__ == "__main__":
280 print "Please run through oft script: ./oft --test_spec=basic"