blob: 86f9b2bf74a788a969e49a4091c03af0cae71168 [file] [log] [blame]
Rich Lanea06d0c32013-03-25 08:52:03 -07001#!/usr/bin/env python
2# Copyright 2013, Big Switch Networks, Inc.
3#
4# LoxiGen is licensed under the Eclipse Public License, version 1.0 (EPL), with
5# the following special exception:
6#
7# LOXI Exception
8#
9# As a special exception to the terms of the EPL, you may distribute libraries
10# generated by LoxiGen (LoxiGen Libraries) under the terms of your choice, provided
11# that copyright and licensing notices generated by LoxiGen are not altered or removed
12# from the LoxiGen Libraries and the notice provided below is (i) included in
13# the LoxiGen Libraries, if distributed in source code form and (ii) included in any
14# documentation for the LoxiGen Libraries, if distributed in binary form.
15#
16# Notice: "Copyright 2013, Big Switch Networks, Inc. This library was generated by the LoxiGen Compiler."
17#
18# You may not use this file except in compliance with the EPL or LOXI Exception. You may obtain
19# a copy of the EPL at:
20#
21# http://www.eclipse.org/legal/epl-v10.html
22#
23# Unless required by applicable law or agreed to in writing, software
24# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
25# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
26# EPL for the specific language governing permissions and limitations
27# under the EPL.
28import unittest
29
30try:
Rich Lanefb7de0e2013-03-29 16:50:29 -070031 import loxi.of10 as ofp
Rich Lanea06d0c32013-03-25 08:52:03 -070032except ImportError:
33 exit("loxi package not found. Try setting PYTHONPATH.")
34
35class TestImports(unittest.TestCase):
36 def test_toplevel(self):
37 import loxi
38 self.assertTrue(hasattr(loxi, "ProtocolError"))
39 ofp = loxi.protocol(1)
40 self.assertEquals(ofp.OFP_VERSION, 1)
41 self.assertTrue(hasattr(ofp, "action"))
42 self.assertTrue(hasattr(ofp, "common"))
43 self.assertTrue(hasattr(ofp, "const"))
44 self.assertTrue(hasattr(ofp, "message"))
45
46 def test_version(self):
Rich Lanea94fb082013-04-04 17:04:17 -070047 import loxi
Rich Lanea06d0c32013-03-25 08:52:03 -070048 self.assertTrue(hasattr(loxi.of10, "ProtocolError"))
49 self.assertTrue(hasattr(loxi.of10, "OFP_VERSION"))
50 self.assertEquals(loxi.of10.OFP_VERSION, 1)
51 self.assertTrue(hasattr(loxi.of10, "action"))
52 self.assertTrue(hasattr(loxi.of10, "common"))
53 self.assertTrue(hasattr(loxi.of10, "const"))
54 self.assertTrue(hasattr(loxi.of10, "message"))
55
56class TestActions(unittest.TestCase):
57 def test_output_pack(self):
Rich Lanea06d0c32013-03-25 08:52:03 -070058 expected = "\x00\x00\x00\x08\xff\xf8\xff\xff"
59 action = ofp.action.output(port=ofp.OFPP_IN_PORT, max_len=0xffff)
60 self.assertEquals(expected, action.pack())
61
62 def test_output_unpack(self):
Rich Lanea06d0c32013-03-25 08:52:03 -070063 # Normal case
64 buf = "\x00\x00\x00\x08\xff\xf8\xff\xff"
65 action = ofp.action.output.unpack(buf)
66 self.assertEqual(action.port, ofp.OFPP_IN_PORT)
67 self.assertEqual(action.max_len, 0xffff)
68
69 # Invalid length
70 buf = "\x00\x00\x00\x09\xff\xf8\xff\xff\x00"
71 with self.assertRaises(ofp.ProtocolError):
72 ofp.action.output.unpack(buf)
73
74 def test_output_equality(self):
Rich Lanea06d0c32013-03-25 08:52:03 -070075 action = ofp.action.output(port=1, max_len=0x1234)
76 action2 = ofp.action.output(port=1, max_len=0x1234)
77 self.assertEquals(action, action2)
78
79 action2.port = 2
80 self.assertNotEquals(action, action2)
81 action2.port = 1
82
83 action2.max_len = 0xffff
84 self.assertNotEquals(action, action2)
85 action2.max_len = 0x1234
86
87 def test_output_show(self):
Rich Lanea06d0c32013-03-25 08:52:03 -070088 action = ofp.action.output(port=1, max_len=0x1234)
89 expected = "output { port = 1, max_len = 0x1234 }"
90 self.assertEquals(expected, action.show())
91
92 def test_bsn_set_tunnel_dst_pack(self):
Rich Lanea06d0c32013-03-25 08:52:03 -070093 expected = ''.join([
94 "\xff\xff", "\x00\x10", # type/length
95 "\x00\x5c\x16\xc7", # experimenter
96 "\x00\x00\x00\x02", # subtype
97 "\x12\x34\x56\x78" # dst
98 ])
99 action = ofp.action.bsn_set_tunnel_dst(dst=0x12345678)
100 self.assertEquals(expected, action.pack())
101
102 def test_bsn_set_tunnel_dst_unpack(self):
Rich Lanea06d0c32013-03-25 08:52:03 -0700103 buf = ''.join([
104 "\xff\xff", "\x00\x10", # type/length
105 "\x00\x5c\x16\xc7", # experimenter
106 "\x00\x00\x00\x02", # subtype
107 "\x12\x34\x56\x78" # dst
108 ])
109 action = ofp.action.bsn_set_tunnel_dst.unpack(buf)
110 self.assertEqual(action.subtype, 2)
111 self.assertEqual(action.dst, 0x12345678)
112
113# Assumes action serialization/deserialization works
114class TestActionList(unittest.TestCase):
115 def test_normal(self):
Rich Lanea06d0c32013-03-25 08:52:03 -0700116 expected = []
117 bufs = []
118
119 def add(action):
120 expected.append(action)
121 bufs.append(action.pack())
122
123 add(ofp.action.output(port=1, max_len=0xffff))
124 add(ofp.action.output(port=2, max_len=0xffff))
125 add(ofp.action.output(port=ofp.OFPP_IN_PORT, max_len=0xffff))
126 add(ofp.action.bsn_set_tunnel_dst(dst=0x12345678))
127 add(ofp.action.nicira_dec_ttl())
128
129 actions = ofp.action.unpack_list(''.join(bufs))
130 self.assertEquals(actions, expected)
131
132 def test_empty_list(self):
Rich Lanea06d0c32013-03-25 08:52:03 -0700133 self.assertEquals(ofp.action.unpack_list(''), [])
134
135 def test_invalid_list_length(self):
Rich Lanea06d0c32013-03-25 08:52:03 -0700136 buf = '\x00' * 9
137 with self.assertRaisesRegexp(ofp.ProtocolError, 'not a multiple of 8'):
138 ofp.action.unpack_list(buf)
139
140 def test_invalid_action_length(self):
Rich Lanea06d0c32013-03-25 08:52:03 -0700141 buf = '\x00' * 8
Rich Laned4e08692013-02-20 17:40:33 -0800142 with self.assertRaisesRegexp(ofp.ProtocolError, 'is less than the header length'):
Rich Lanea06d0c32013-03-25 08:52:03 -0700143 ofp.action.unpack_list(buf)
144
145 buf = '\x00\x00\x00\x04'
146 with self.assertRaisesRegexp(ofp.ProtocolError, 'not a multiple of 8'):
147 ofp.action.unpack_list(buf)
148
149 buf = '\x00\x00\x00\x10\x00\x00\x00\x00'
150 with self.assertRaisesRegexp(ofp.ProtocolError, 'overrun'):
151 ofp.action.unpack_list(buf)
152
153 def test_invalid_action_type(self):
Rich Lanea06d0c32013-03-25 08:52:03 -0700154 buf = '\xff\xfe\x00\x08\x00\x00\x00\x00'
155 with self.assertRaisesRegexp(ofp.ProtocolError, 'unknown action type'):
156 ofp.action.unpack_list(buf)
157
158class TestConstants(unittest.TestCase):
159 def test_ports(self):
Rich Lanea06d0c32013-03-25 08:52:03 -0700160 self.assertEquals(0xffff, ofp.OFPP_NONE)
161
162 def test_wildcards(self):
Rich Lanea06d0c32013-03-25 08:52:03 -0700163 self.assertEquals(0xfc000, ofp.OFPFW_NW_DST_MASK)
164
165class TestCommon(unittest.TestCase):
166 def test_port_desc_pack(self):
Rich Lanea06d0c32013-03-25 08:52:03 -0700167 obj = ofp.port_desc(port_no=ofp.OFPP_CONTROLLER,
168 hw_addr=[1,2,3,4,5,6],
169 name="foo",
170 config=ofp.OFPPC_NO_FLOOD,
171 state=ofp.OFPPS_STP_FORWARD,
172 curr=ofp.OFPPF_10MB_HD,
173 advertised=ofp.OFPPF_1GB_FD,
174 supported=ofp.OFPPF_AUTONEG,
175 peer=ofp.OFPPF_PAUSE_ASYM)
176 expected = ''.join([
177 '\xff\xfd', # port_no
178 '\x01\x02\x03\x04\x05\x06', # hw_addr
179 'foo'.ljust(16, '\x00'), # name
180 '\x00\x00\x00\x10', # config
181 '\x00\x00\x02\x00', # state
182 '\x00\x00\x00\x01', # curr
183 '\x00\x00\x00\x20', # advertised
184 '\x00\x00\x02\x00', # supported
185 '\x00\x00\x08\x00', # peer
186 ])
187 self.assertEquals(expected, obj.pack())
188
189 def test_port_desc_unpack(self):
Rich Lanea06d0c32013-03-25 08:52:03 -0700190 buf = ''.join([
191 '\xff\xfd', # port_no
192 '\x01\x02\x03\x04\x05\x06', # hw_addr
193 'foo'.ljust(16, '\x00'), # name
194 '\x00\x00\x00\x10', # config
195 '\x00\x00\x02\x00', # state
196 '\x00\x00\x00\x01', # curr
197 '\x00\x00\x00\x20', # advertised
198 '\x00\x00\x02\x00', # supported
199 '\x00\x00\x08\x00', # peer
200 ])
201 obj = ofp.port_desc.unpack(buf)
202 self.assertEquals(ofp.OFPP_CONTROLLER, obj.port_no)
203 self.assertEquals('foo', obj.name)
204 self.assertEquals(ofp.OFPPF_PAUSE_ASYM, obj.peer)
205
206 def test_table_stats_entry_pack(self):
Rich Lanea06d0c32013-03-25 08:52:03 -0700207 obj = ofp.table_stats_entry(table_id=3,
208 name="foo",
209 wildcards=ofp.OFPFW_ALL,
210 max_entries=5,
211 active_count=2,
212 lookup_count=1099511627775,
213 matched_count=9300233470495232273L)
214 expected = ''.join([
215 '\x03', # table_id
216 '\x00\x00\x00', # pad
217 'foo'.ljust(32, '\x00'), # name
218 '\x00\x3f\xFF\xFF', # wildcards
219 '\x00\x00\x00\x05', # max_entries
220 '\x00\x00\x00\x02', # active_count
221 '\x00\x00\x00\xff\xff\xff\xff\xff', # lookup_count
222 '\x81\x11\x11\x11\x11\x11\x11\x11', # matched_count
223 ])
224 self.assertEquals(expected, obj.pack())
225
226 def test_table_stats_entry_unpack(self):
Rich Lanea06d0c32013-03-25 08:52:03 -0700227 buf = ''.join([
228 '\x03', # table_id
229 '\x00\x00\x00', # pad
230 'foo'.ljust(32, '\x00'), # name
231 '\x00\x3f\xFF\xFF', # wildcards
232 '\x00\x00\x00\x05', # max_entries
233 '\x00\x00\x00\x02', # active_count
234 '\x00\x00\x00\xff\xff\xff\xff\xff', # lookup_count
235 '\x81\x11\x11\x11\x11\x11\x11\x11', # matched_count
236 ])
237 obj = ofp.table_stats_entry.unpack(buf)
238 self.assertEquals(3, obj.table_id)
239 self.assertEquals('foo', obj.name)
240 self.assertEquals(9300233470495232273L, obj.matched_count)
241
242 def test_flow_stats_entry_pack(self):
Rich Lanea06d0c32013-03-25 08:52:03 -0700243 obj = ofp.flow_stats_entry(table_id=3,
244 match=ofp.match(),
245 duration_sec=1,
246 duration_nsec=2,
247 priority=100,
248 idle_timeout=5,
249 hard_timeout=10,
250 cookie=0x0123456789abcdef,
251 packet_count=10,
252 byte_count=1000,
253 actions=[ofp.action.output(port=1),
254 ofp.action.output(port=2)])
255 expected = ''.join([
256 '\x00\x68', # length
257 '\x03', # table_id
258 '\x00', # pad
259 '\x00\x3f\xff\xff', # match.wildcards
260 '\x00' * 36, # remaining match fields
261 '\x00\x00\x00\x01', # duration_sec
262 '\x00\x00\x00\x02', # duration_nsec
263 '\x00\x64', # priority
264 '\x00\x05', # idle_timeout
265 '\x00\x0a', # hard_timeout
266 '\x00' * 6, # pad2
267 '\x01\x23\x45\x67\x89\xab\xcd\xef', # cookie
268 '\x00\x00\x00\x00\x00\x00\x00\x0a', # packet_count
269 '\x00\x00\x00\x00\x00\x00\x03\xe8', # byte_count
270 '\x00\x00', # actions[0].type
271 '\x00\x08', # actions[0].len
272 '\x00\x01', # actions[0].port
273 '\x00\x00', # actions[0].max_len
274 '\x00\x00', # actions[1].type
275 '\x00\x08', # actions[1].len
276 '\x00\x02', # actions[1].port
277 '\x00\x00', # actions[1].max_len
278 ])
279 self.assertEquals(expected, obj.pack())
280
281 def test_flow_stats_entry_unpack(self):
Rich Lanea06d0c32013-03-25 08:52:03 -0700282 buf = ''.join([
283 '\x00\x68', # length
284 '\x03', # table_id
285 '\x00', # pad
286 '\x00\x3f\xff\xff', # match.wildcards
287 '\x00' * 36, # remaining match fields
288 '\x00\x00\x00\x01', # duration_sec
289 '\x00\x00\x00\x02', # duration_nsec
290 '\x00\x64', # priority
291 '\x00\x05', # idle_timeout
292 '\x00\x0a', # hard_timeout
293 '\x00' * 6, # pad2
294 '\x01\x23\x45\x67\x89\xab\xcd\xef', # cookie
295 '\x00\x00\x00\x00\x00\x00\x00\x0a', # packet_count
296 '\x00\x00\x00\x00\x00\x00\x03\xe8', # byte_count
297 '\x00\x00', # actions[0].type
298 '\x00\x08', # actions[0].len
299 '\x00\x01', # actions[0].port
300 '\x00\x00', # actions[0].max_len
301 '\x00\x00', # actions[1].type
302 '\x00\x08', # actions[1].len
303 '\x00\x02', # actions[1].port
304 '\x00\x00', # actions[1].max_len
305 ])
306 obj = ofp.flow_stats_entry.unpack(buf)
307 self.assertEquals(3, obj.table_id)
308 self.assertEquals(ofp.OFPFW_ALL, obj.match.wildcards)
309 self.assertEquals(2, len(obj.actions))
310 self.assertEquals(1, obj.actions[0].port)
311 self.assertEquals(2, obj.actions[1].port)
312
313 def test_match(self):
Rich Lanea06d0c32013-03-25 08:52:03 -0700314 match = ofp.match()
315 self.assertEquals(match.wildcards, ofp.OFPFW_ALL)
316 self.assertEquals(match.tcp_src, 0)
317 buf = match.pack()
318 match2 = ofp.match.unpack(buf)
319 self.assertEquals(match, match2)
320
321class TestMessages(unittest.TestCase):
322 def test_hello_construction(self):
Rich Lanea06d0c32013-03-25 08:52:03 -0700323 msg = ofp.message.hello()
324 self.assertEquals(msg.version, ofp.OFP_VERSION)
325 self.assertEquals(msg.type, ofp.OFPT_HELLO)
326 self.assertEquals(msg.xid, None)
327
328 msg = ofp.message.hello(xid=123)
329 self.assertEquals(msg.xid, 123)
330
331 # 0 is a valid xid distinct from None
332 msg = ofp.message.hello(xid=0)
333 self.assertEquals(msg.xid, 0)
334
335 def test_hello_unpack(self):
Rich Lanea06d0c32013-03-25 08:52:03 -0700336 # Normal case
337 buf = "\x01\x00\x00\x08\x12\x34\x56\x78"
338 msg = ofp.message.hello(xid=0x12345678)
339 self.assertEquals(buf, msg.pack())
340
341 # Invalid length
342 buf = "\x01\x00\x00\x09\x12\x34\x56\x78\x9a"
343 with self.assertRaisesRegexp(ofp.ProtocolError, "should be 8"):
344 ofp.message.hello.unpack(buf)
345
346 def test_echo_request_construction(self):
Rich Lanea06d0c32013-03-25 08:52:03 -0700347 msg = ofp.message.echo_request(data="abc")
348 self.assertEquals(msg.data, "abc")
349
350 def test_echo_request_pack(self):
Rich Lanea06d0c32013-03-25 08:52:03 -0700351 msg = ofp.message.echo_request(xid=0x12345678, data="abc")
352 buf = msg.pack()
353 self.assertEquals(buf, "\x01\x02\x00\x0b\x12\x34\x56\x78\x61\x62\x63")
354
355 msg2 = ofp.message.echo_request.unpack(buf)
356 self.assertEquals(msg, msg2)
357
358 def test_echo_request_unpack(self):
Rich Lanea06d0c32013-03-25 08:52:03 -0700359 # Normal case
360 buf = "\x01\x02\x00\x0b\x12\x34\x56\x78\x61\x62\x63"
361 msg = ofp.message.echo_request(xid=0x12345678, data="abc")
362 self.assertEquals(buf, msg.pack())
363
364 # Invalid length
365 buf = "\x01\x02\x00\x07\x12\x34\x56"
366 with self.assertRaisesRegexp(ofp.ProtocolError, "buffer too short"):
367 ofp.message.echo_request.unpack(buf)
368
369 def test_echo_request_equality(self):
Rich Lanea06d0c32013-03-25 08:52:03 -0700370 msg = ofp.message.echo_request(xid=0x12345678, data="abc")
371 msg2 = ofp.message.echo_request(xid=0x12345678, data="abc")
372 #msg2 = ofp.message.echo_request.unpack(msg.pack())
373 self.assertEquals(msg, msg2)
374
375 msg2.xid = 1
376 self.assertNotEquals(msg, msg2)
377 msg2.xid = msg.xid
378
379 msg2.data = "a"
380 self.assertNotEquals(msg, msg2)
381 msg2.data = msg.data
382
383 def test_echo_request_show(self):
Rich Lanea06d0c32013-03-25 08:52:03 -0700384 expected = "echo_request { xid = 0x12345678, data = 'ab\\x01' }"
385 msg = ofp.message.echo_request(xid=0x12345678, data="ab\x01")
386 self.assertEquals(msg.show(), expected)
387
388 def test_flow_add(self):
Rich Lanea06d0c32013-03-25 08:52:03 -0700389 match = ofp.match()
390 msg = ofp.message.flow_add(xid=1,
391 match=match,
392 cookie=1,
393 idle_timeout=5,
394 flags=ofp.OFPFF_CHECK_OVERLAP,
395 actions=[
396 ofp.action.output(port=1),
397 ofp.action.output(port=2),
398 ofp.action.output(port=ofp.OFPP_CONTROLLER,
399 max_len=1024)])
400 buf = msg.pack()
401 msg2 = ofp.message.flow_add.unpack(buf)
402 self.assertEquals(msg, msg2)
403
404 def test_port_mod_pack(self):
Rich Lanea06d0c32013-03-25 08:52:03 -0700405 msg = ofp.message.port_mod(xid=2,
406 port_no=ofp.OFPP_CONTROLLER,
407 hw_addr=[1,2,3,4,5,6],
408 config=0x90ABCDEF,
409 mask=0xFF11FF11,
410 advertise=0xCAFE6789)
411 expected = "\x01\x0f\x00\x20\x00\x00\x00\x02\xff\xfd\x01\x02\x03\x04\x05\x06\x90\xab\xcd\xef\xff\x11\xff\x11\xca\xfe\x67\x89\x00\x00\x00\x00"
412 self.assertEquals(expected, msg.pack())
413
414 def test_desc_stats_reply_pack(self):
Rich Lanea06d0c32013-03-25 08:52:03 -0700415 msg = ofp.message.desc_stats_reply(xid=3,
416 flags=ofp.OFPSF_REPLY_MORE,
417 mfr_desc="The Indigo-2 Community",
418 hw_desc="Unknown server",
419 sw_desc="Indigo-2 LRI pre-release",
420 serial_num="11235813213455",
421 dp_desc="Indigo-2 LRI forwarding module")
422 expected = ''.join([
423 '\x01', '\x11', # version/type
424 '\x04\x2c', # length
425 '\x00\x00\x00\x03', # xid
426 '\x00\x00', # stats_type
427 '\x00\x01', # flags
428 'The Indigo-2 Community'.ljust(256, '\x00'), # mfr_desc
429 'Unknown server'.ljust(256, '\x00'), # hw_desc
430 'Indigo-2 LRI pre-release'.ljust(256, '\x00'), # sw_desc
431 '11235813213455'.ljust(32, '\x00'), # serial_num
432 'Indigo-2 LRI forwarding module'.ljust(256, '\x00'), # dp_desc
433 ])
434 self.assertEquals(expected, msg.pack())
435
436 def test_desc_stats_reply_unpack(self):
Rich Lanea06d0c32013-03-25 08:52:03 -0700437 buf = ''.join([
438 '\x01', '\x11', # version/type
439 '\x04\x2c', # length
440 '\x00\x00\x00\x03', # xid
441 '\x00\x00', # stats_type
442 '\x00\x01', # flags
443 'The Indigo-2 Community'.ljust(256, '\x00'), # mfr_desc
444 'Unknown server'.ljust(256, '\x00'), # hw_desc
445 'Indigo-2 LRI pre-release'.ljust(256, '\x00'), # sw_desc
446 '11235813213455'.ljust(32, '\x00'), # serial_num
447 'Indigo-2 LRI forwarding module'.ljust(256, '\x00'), # dp_desc
448 ])
449 msg = ofp.message.desc_stats_reply.unpack(buf)
450 self.assertEquals('Indigo-2 LRI forwarding module', msg.dp_desc)
451 self.assertEquals('11235813213455', msg.serial_num)
452 self.assertEquals(ofp.OFPST_DESC, msg.stats_type)
453 self.assertEquals(ofp.OFPSF_REPLY_MORE, msg.flags)
454
455 def test_port_status_pack(self):
Rich Lanea06d0c32013-03-25 08:52:03 -0700456 desc = ofp.port_desc(port_no=ofp.OFPP_CONTROLLER,
457 hw_addr=[1,2,3,4,5,6],
458 name="foo",
459 config=ofp.OFPPC_NO_FLOOD,
460 state=ofp.OFPPS_STP_FORWARD,
461 curr=ofp.OFPPF_10MB_HD,
462 advertised=ofp.OFPPF_1GB_FD,
463 supported=ofp.OFPPF_AUTONEG,
464 peer=ofp.OFPPF_PAUSE_ASYM)
465
466 msg = ofp.message.port_status(xid=4,
467 reason=ofp.OFPPR_DELETE,
468 desc=desc)
469 expected = ''.join([
470 '\x01', '\x0c', # version/type
471 '\x00\x40', # length
472 '\x00\x00\x00\x04', # xid
473 '\x01', # reason
474 '\x00\x00\x00\x00\x00\x00\x00' # pad
475 '\xff\xfd', # desc.port_no
476 '\x01\x02\x03\x04\x05\x06', # desc.hw_addr
477 'foo'.ljust(16, '\x00'), # desc.name
478 '\x00\x00\x00\x10', # desc.config
479 '\x00\x00\x02\x00', # desc.state
480 '\x00\x00\x00\x01', # desc.curr
481 '\x00\x00\x00\x20', # desc.advertised
482 '\x00\x00\x02\x00', # desc.supported
483 '\x00\x00\x08\x00', # desc.peer
484 ])
485 self.assertEquals(expected, msg.pack())
486
487 def test_port_status_unpack(self):
Rich Lanea06d0c32013-03-25 08:52:03 -0700488 buf = ''.join([
489 '\x01', '\x0c', # version/type
490 '\x00\x40', # length
491 '\x00\x00\x00\x04', # xid
492 '\x01', # reason
493 '\x00\x00\x00\x00\x00\x00\x00' # pad
494 '\xff\xfd', # desc.port_no
495 '\x01\x02\x03\x04\x05\x06', # desc.hw_addr
496 'foo'.ljust(16, '\x00'), # desc.name
497 '\x00\x00\x00\x10', # desc.config
498 '\x00\x00\x02\x00', # desc.state
499 '\x00\x00\x00\x01', # desc.curr
500 '\x00\x00\x00\x20', # desc.advertised
501 '\x00\x00\x02\x00', # desc.supported
502 '\x00\x00\x08\x00', # desc.peer
503 ])
504 msg = ofp.message.port_status.unpack(buf)
505 self.assertEquals('foo', msg.desc.name)
506 self.assertEquals(ofp.OFPPF_PAUSE_ASYM, msg.desc.peer)
507
508 def test_port_stats_reply_pack(self):
Rich Lanea06d0c32013-03-25 08:52:03 -0700509 msg = ofp.message.port_stats_reply(xid=5, flags=0, entries=[
510 ofp.port_stats_entry(port_no=1, rx_packets=56, collisions=5),
511 ofp.port_stats_entry(port_no=ofp.OFPP_LOCAL, rx_packets=1, collisions=1)])
512 expected = ''.join([
513 '\x01', '\x11', # version/type
514 '\x00\xdc', # length
515 '\x00\x00\x00\x05', # xid
516 '\x00\x04', # stats_type
517 '\x00\x00', # flags
518 '\x00\x01', # entries[0].port_no
519 '\x00\x00\x00\x00\x00\x00' # entries[0].pad
520 '\x00\x00\x00\x00\x00\x00\x00\x38', # entries[0].rx_packets
521 '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[0].tx_packets
522 '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[0].rx_bytes
523 '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[0].tx_bytes
524 '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[0].rx_dropped
525 '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[0].tx_dropped
526 '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[0].rx_errors
527 '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[0].tx_errors
528 '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[0].rx_frame_err
529 '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[0].rx_over_err
530 '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[0].rx_crc_err
531 '\x00\x00\x00\x00\x00\x00\x00\x05', # entries[0].collisions
532 '\xff\xfe', # entries[1].port_no
533 '\x00\x00\x00\x00\x00\x00' # entries[1].pad
534 '\x00\x00\x00\x00\x00\x00\x00\x01', # entries[1].rx_packets
535 '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[1].tx_packets
536 '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[1].rx_bytes
537 '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[1].tx_bytes
538 '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[1].rx_dropped
539 '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[1].tx_dropped
540 '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[1].rx_errors
541 '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[1].tx_errors
542 '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[1].rx_frame_err
543 '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[1].rx_over_err
544 '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[1].rx_crc_err
545 '\x00\x00\x00\x00\x00\x00\x00\x01', # entries[1].collisions
546 ])
547 self.assertEquals(expected, msg.pack())
548
549 def test_port_stats_reply_unpack(self):
Rich Lanea06d0c32013-03-25 08:52:03 -0700550 buf = ''.join([
551 '\x01', '\x11', # version/type
552 '\x00\xdc', # length
553 '\x00\x00\x00\x05', # xid
554 '\x00\x04', # stats_type
555 '\x00\x00', # flags
556 '\x00\x01', # entries[0].port_no
557 '\x00\x00\x00\x00\x00\x00' # entries[0].pad
558 '\x00\x00\x00\x00\x00\x00\x00\x38', # entries[0].rx_packets
559 '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[0].tx_packets
560 '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[0].rx_bytes
561 '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[0].tx_bytes
562 '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[0].rx_dropped
563 '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[0].tx_dropped
564 '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[0].rx_errors
565 '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[0].tx_errors
566 '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[0].rx_frame_err
567 '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[0].rx_over_err
568 '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[0].rx_crc_err
569 '\x00\x00\x00\x00\x00\x00\x00\x05', # entries[0].collisions
570 '\xff\xfe', # entries[1].port_no
571 '\x00\x00\x00\x00\x00\x00' # entries[1].pad
572 '\x00\x00\x00\x00\x00\x00\x00\x01', # entries[1].rx_packets
573 '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[1].tx_packets
574 '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[1].rx_bytes
575 '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[1].tx_bytes
576 '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[1].rx_dropped
577 '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[1].tx_dropped
578 '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[1].rx_errors
579 '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[1].tx_errors
580 '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[1].rx_frame_err
581 '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[1].rx_over_err
582 '\x00\x00\x00\x00\x00\x00\x00\x00', # entries[1].rx_crc_err
583 '\x00\x00\x00\x00\x00\x00\x00\x01', # entries[1].collisions
584 ])
585 msg = ofp.message.port_stats_reply.unpack(buf)
586 self.assertEquals(ofp.OFPST_PORT, msg.stats_type)
587 self.assertEquals(2, len(msg.entries))
588
589 sample_flow_stats_reply_buf = ''.join([
590 '\x01', '\x11', # version/type
591 '\x00\xe4', # length
592 '\x00\x00\x00\x06', # xid
593 '\x00\x01', # stats_type
594 '\x00\x00', # flags
595 '\x00\x68', # entries[0].length
596 '\x03', # entries[0].table_id
597 '\x00', # entries[0].pad
598 '\x00\x3f\xff\xff', # entries[0].match.wildcards
599 '\x00' * 36, # remaining match fields
600 '\x00\x00\x00\x01', # entries[0].duration_sec
601 '\x00\x00\x00\x02', # entries[0].duration_nsec
602 '\x00\x64', # entries[0].priority
603 '\x00\x05', # entries[0].idle_timeout
604 '\x00\x0a', # entries[0].hard_timeout
605 '\x00' * 6, # entries[0].pad2
606 '\x01\x23\x45\x67\x89\xab\xcd\xef', # entries[0].cookie
607 '\x00\x00\x00\x00\x00\x00\x00\x0a', # entries[0].packet_count
608 '\x00\x00\x00\x00\x00\x00\x03\xe8', # entries[0].byte_count
609 '\x00\x00', # entries[0].actions[0].type
610 '\x00\x08', # entries[0].actions[0].len
611 '\x00\x01', # entries[0].actions[0].port
612 '\x00\x00', # entries[0].actions[0].max_len
613 '\x00\x00', # entries[0].actions[1].type
614 '\x00\x08', # entries[0].actions[1].len
615 '\x00\x02', # entries[0].actions[1].port
616 '\x00\x00', # entries[0].actions[1].max_len
617 '\x00\x70', # entries[1].length
618 '\x04', # entries[1].table_id
619 '\x00', # entries[1].pad
620 '\x00\x3f\xff\xff', # entries[1].match.wildcards
621 '\x00' * 36, # remaining match fields
622 '\x00\x00\x00\x01', # entries[1].duration_sec
623 '\x00\x00\x00\x02', # entries[1].duration_nsec
624 '\x00\x64', # entries[1].priority
625 '\x00\x05', # entries[1].idle_timeout
626 '\x00\x0a', # entries[1].hard_timeout
627 '\x00' * 6, # entries[1].pad2
628 '\x01\x23\x45\x67\x89\xab\xcd\xef', # entries[1].cookie
629 '\x00\x00\x00\x00\x00\x00\x00\x0a', # entries[1].packet_count
630 '\x00\x00\x00\x00\x00\x00\x03\xe8', # entries[1].byte_count
631 '\x00\x00', # entries[1].actions[0].type
632 '\x00\x08', # entries[1].actions[0].len
633 '\x00\x01', # entries[1].actions[0].port
634 '\x00\x00', # entries[1].actions[0].max_len
635 '\x00\x00', # entries[1].actions[1].type
636 '\x00\x08', # entries[1].actions[1].len
637 '\x00\x02', # entries[1].actions[1].port
638 '\x00\x00', # entries[1].actions[1].max_len
639 '\x00\x00', # entries[1].actions[2].type
640 '\x00\x08', # entries[1].actions[2].len
641 '\x00\x03', # entries[1].actions[2].port
642 '\x00\x00', # entries[1].actions[2].max_len
643 ])
644
645 def test_flow_stats_reply_pack(self):
Rich Lanea06d0c32013-03-25 08:52:03 -0700646 msg = ofp.message.flow_stats_reply(xid=6, flags=0, entries=[
647 ofp.flow_stats_entry(table_id=3,
648 match=ofp.match(),
649 duration_sec=1,
650 duration_nsec=2,
651 priority=100,
652 idle_timeout=5,
653 hard_timeout=10,
654 cookie=0x0123456789abcdef,
655 packet_count=10,
656 byte_count=1000,
657 actions=[ofp.action.output(port=1),
658 ofp.action.output(port=2)]),
659 ofp.flow_stats_entry(table_id=4,
660 match=ofp.match(),
661 duration_sec=1,
662 duration_nsec=2,
663 priority=100,
664 idle_timeout=5,
665 hard_timeout=10,
666 cookie=0x0123456789abcdef,
667 packet_count=10,
668 byte_count=1000,
669 actions=[ofp.action.output(port=1),
670 ofp.action.output(port=2),
671 ofp.action.output(port=3)])])
672 self.assertEquals(self.sample_flow_stats_reply_buf, msg.pack())
673
674 def test_flow_stats_reply_unpack(self):
Rich Lanea06d0c32013-03-25 08:52:03 -0700675 msg = ofp.message.flow_stats_reply.unpack(self.sample_flow_stats_reply_buf)
676 self.assertEquals(ofp.OFPST_FLOW, msg.stats_type)
677 self.assertEquals(2, len(msg.entries))
678 self.assertEquals(2, len(msg.entries[0].actions))
679 self.assertEquals(3, len(msg.entries[1].actions))
680
681 def test_flow_add_show(self):
Rich Lanea06d0c32013-03-25 08:52:03 -0700682 expected = """\
683flow_add {
684 xid = None,
685 match = match_v1 {
686 wildcards = OFPFW_DL_SRC|OFPFW_DL_DST,
687 in_port = 3,
688 eth_src = 01:23:45:67:89:ab,
689 eth_dst = cd:ef:01:23:45:67,
690 vlan_vid = 0x0,
691 vlan_pcp = 0x0,
Rich Lanec58a2322013-03-15 23:28:52 -0700692 pad = 0x0,
Rich Lanea06d0c32013-03-25 08:52:03 -0700693 eth_type = 0x0,
694 ip_dscp = 0x0,
695 ip_proto = 0x0,
Rich Lanec58a2322013-03-15 23:28:52 -0700696 pad1 = [ 0, 0 ],
Rich Lanea06d0c32013-03-25 08:52:03 -0700697 ipv4_src = 192.168.3.127,
698 ipv4_dst = 255.255.255.255,
699 tcp_src = 0x0,
700 tcp_dst = 0x0
701 },
702 cookie = 0x0,
703 idle_timeout = 0x0,
704 hard_timeout = 0x0,
705 priority = 0x0,
706 buffer_id = 0x0,
707 out_port = 0,
708 flags = 0x0,
709 actions = [
710 output { port = OFPP_FLOOD, max_len = 0x0 },
Rich Lanec58a2322013-03-15 23:28:52 -0700711 nicira_dec_ttl { pad = 0x0, pad1 = 0x0 },
Rich Lanea06d0c32013-03-25 08:52:03 -0700712 bsn_set_tunnel_dst { dst = 0x0 }
713 ]
714}"""
715 msg = ofp.message.flow_add(
716 match=ofp.match(
717 wildcards=ofp.OFPFW_DL_SRC|ofp.OFPFW_DL_DST,
718 in_port=3,
719 ipv4_src=0xc0a8037f,
720 ipv4_dst=0xffffffff,
721 eth_src=[0x01, 0x23, 0x45, 0x67, 0x89, 0xab],
722 eth_dst=[0xcd, 0xef, 0x01, 0x23, 0x45, 0x67]),
723 actions=[
724 ofp.action.output(port=ofp.OFPP_FLOOD),
725 ofp.action.nicira_dec_ttl(),
726 ofp.action.bsn_set_tunnel_dst()])
727 self.assertEquals(msg.show(), expected)
728
729 sample_packet_out_buf = ''.join([
730 '\x01', '\x0d', # version/type
731 '\x00\x23', # length
732 '\x12\x34\x56\x78', # xid
733 '\xab\xcd\xef\x01', # buffer_id
734 '\xff\xfe', # in_port
735 '\x00\x10', # actions_len
736 '\x00\x00', # actions[0].type
737 '\x00\x08', # actions[0].len
738 '\x00\x01', # actions[0].port
739 '\x00\x00', # actions[0].max_len
740 '\x00\x00', # actions[1].type
741 '\x00\x08', # actions[1].len
742 '\x00\x02', # actions[1].port
743 '\x00\x00', # actions[1].max_len
744 'abc' # data
745 ])
746
747 def test_packet_out_pack(self):
Rich Lanea06d0c32013-03-25 08:52:03 -0700748 msg = ofp.message.packet_out(
749 xid=0x12345678,
750 buffer_id=0xabcdef01,
751 in_port=ofp.OFPP_LOCAL,
752 actions=[
753 ofp.action.output(port=1),
754 ofp.action.output(port=2)],
755 data='abc')
756 self.assertEquals(self.sample_packet_out_buf, msg.pack())
757
758 def test_packet_out_unpack(self):
Rich Lanea06d0c32013-03-25 08:52:03 -0700759 msg = ofp.message.packet_out.unpack(self.sample_packet_out_buf)
760 self.assertEquals(0x12345678, msg.xid)
761 self.assertEquals(0xabcdef01, msg.buffer_id)
762 self.assertEquals(ofp.OFPP_LOCAL, msg.in_port)
763 self.assertEquals(2, len(msg.actions))
764 self.assertEquals(1, msg.actions[0].port)
765 self.assertEquals(2, msg.actions[1].port)
766 self.assertEquals('abc', msg.data)
767
768 sample_packet_in_buf = ''.join([
769 '\x01', '\x0a', # version/type
770 '\x00\x15', # length
771 '\x12\x34\x56\x78', # xid
772 '\xab\xcd\xef\x01', # buffer_id
773 '\x00\x09', # total_len
774 '\xff\xfe', # in_port
775 '\x01', # reason
776 '\x00', # pad
777 'abc', # data
778 ])
779
780 def test_packet_in_pack(self):
Rich Lanea06d0c32013-03-25 08:52:03 -0700781 msg = ofp.message.packet_in(
782 xid=0x12345678,
783 buffer_id=0xabcdef01,
784 total_len=9,
785 in_port=ofp.OFPP_LOCAL,
786 reason=ofp.OFPR_ACTION,
787 data='abc')
788 self.assertEquals(self.sample_packet_in_buf, msg.pack())
789
790 def test_packet_in_unpack(self):
Rich Lanea06d0c32013-03-25 08:52:03 -0700791 msg = ofp.message.packet_in.unpack(self.sample_packet_in_buf)
792 self.assertEquals(0x12345678, msg.xid)
793 self.assertEquals(0xabcdef01, msg.buffer_id)
794 self.assertEquals(9, msg.total_len)
795 self.assertEquals(ofp.OFPP_LOCAL, msg.in_port)
796 self.assertEquals(ofp.OFPR_ACTION, msg.reason)
797 self.assertEquals('abc', msg.data)
798
799 sample_queue_get_config_reply_buf = ''.join([
800 '\x01', '\x15', # version/type
801 '\x00\x50', # length
802 '\x12\x34\x56\x78', # xid
803 '\xff\xfe', # port
804 '\x00\x00\x00\x00\x00\x00', # pad
805 '\x00\x00\x00\x01', # queues[0].queue_id
806 '\x00\x18', # queues[0].len
807 '\x00\x00', # queues[0].pad
808 '\x00\x01', # queues[0].properties[0].type
809 '\x00\x10', # queues[0].properties[0].length
810 '\x00\x00\x00\x00', # queues[0].properties[0].pad
811 '\x00\x05', # queues[0].properties[0].rate
812 '\x00\x00\x00\x00\x00\x00', # queues[0].properties[0].pad2
813 '\x00\x00\x00\x02', # queues[1].queue_id
814 '\x00\x28', # queues[1].len
815 '\x00\x00', # queues[1].pad
816 '\x00\x01', # queues[1].properties[0].type
817 '\x00\x10', # queues[1].properties[0].length
818 '\x00\x00\x00\x00', # queues[1].properties[0].pad
819 '\x00\x06', # queues[1].properties[0].rate
820 '\x00\x00\x00\x00\x00\x00', # queues[1].properties[0].pad2
821 '\x00\x01', # queues[1].properties[1].type
822 '\x00\x10', # queues[1].properties[1].length
823 '\x00\x00\x00\x00', # queues[1].properties[1].pad
824 '\x00\x07', # queues[1].properties[1].rate
825 '\x00\x00\x00\x00\x00\x00', # queues[1].properties[1].pad2
826 ])
827
828 def test_queue_get_config_reply_pack(self):
Rich Lanea06d0c32013-03-25 08:52:03 -0700829 msg = ofp.message.queue_get_config_reply(
830 xid=0x12345678,
831 port=ofp.OFPP_LOCAL,
832 queues=[
833 ofp.packet_queue(queue_id=1, properties=[
834 ofp.queue_prop_min_rate(rate=5)]),
835 ofp.packet_queue(queue_id=2, properties=[
836 ofp.queue_prop_min_rate(rate=6),
837 ofp.queue_prop_min_rate(rate=7)])])
838 self.assertEquals(self.sample_queue_get_config_reply_buf, msg.pack())
839
840 def test_queue_get_config_reply_unpack(self):
Rich Lanea06d0c32013-03-25 08:52:03 -0700841 msg = ofp.message.queue_get_config_reply.unpack(self.sample_queue_get_config_reply_buf)
842 self.assertEquals(ofp.OFPP_LOCAL, msg.port)
843 self.assertEquals(msg.queues[0].queue_id, 1)
844 self.assertEquals(msg.queues[0].properties[0].rate, 5)
845 self.assertEquals(msg.queues[1].queue_id, 2)
846 self.assertEquals(msg.queues[1].properties[0].rate, 6)
847 self.assertEquals(msg.queues[1].properties[1].rate, 7)
848
849class TestParse(unittest.TestCase):
850 def test_parse_header(self):
851 import loxi
Rich Lanea06d0c32013-03-25 08:52:03 -0700852
853 msg_ver, msg_type, msg_len, msg_xid = ofp.message.parse_header("\x01\x04\xAF\xE8\x12\x34\x56\x78")
854 self.assertEquals(1, msg_ver)
855 self.assertEquals(4, msg_type)
856 self.assertEquals(45032, msg_len)
857 self.assertEquals(0x12345678, msg_xid)
858
859 with self.assertRaisesRegexp(loxi.ProtocolError, "too short"):
860 ofp.message.parse_header("\x01\x04\xAF\xE8\x12\x34\x56")
861
862 def test_parse_message(self):
863 import loxi
864 import loxi.of10 as ofp
865
866 buf = "\x01\x00\x00\x08\x12\x34\x56\x78"
867 msg = ofp.message.parse_message(buf)
868 assert(msg.xid == 0x12345678)
869
870 # Get a list of all message classes
871 test_klasses = [x for x in ofp.message.__dict__.values()
872 if type(x) == type
873 and issubclass(x, ofp.message.Message)
874 and x != ofp.message.Message]
875
876 for klass in test_klasses:
877 self.assertIsInstance(ofp.message.parse_message(klass(xid=1).pack()), klass)
878
879class TestUtils(unittest.TestCase):
880 def test_unpack_array(self):
881 import loxi
882 import loxi.of10.util as util
883
884 a = util.unpack_array(str, 3, "abcdefghi")
885 self.assertEquals(['abc', 'def', 'ghi'], a)
886
887 with self.assertRaisesRegexp(loxi.ProtocolError, "invalid array length"):
888 util.unpack_array(str, 3, "abcdefgh")
889
890 def test_pretty_wildcards(self):
Rich Lanea06d0c32013-03-25 08:52:03 -0700891 self.assertEquals("OFPFW_ALL", ofp.util.pretty_wildcards(ofp.OFPFW_ALL))
892 self.assertEquals("0", ofp.util.pretty_wildcards(0))
893 self.assertEquals("OFPFW_DL_SRC|OFPFW_DL_DST",
894 ofp.util.pretty_wildcards(ofp.OFPFW_DL_SRC|ofp.OFPFW_DL_DST))
895 self.assertEquals("OFPFW_NW_SRC_MASK&0x2000",
896 ofp.util.pretty_wildcards(ofp.OFPFW_NW_SRC_ALL))
897 self.assertEquals("OFPFW_NW_SRC_MASK&0x1a00",
898 ofp.util.pretty_wildcards(0x00001a00))
899 self.assertEquals("OFPFW_IN_PORT|0x80000000",
900 ofp.util.pretty_wildcards(ofp.OFPFW_IN_PORT|0x80000000))
901
902class TestAll(unittest.TestCase):
903 """
904 Round-trips every class through serialization/deserialization.
905 Not a replacement for handcoded tests because it only uses the
906 default member values.
907 """
908
909 def setUp(self):
Rich Lanea06d0c32013-03-25 08:52:03 -0700910 mods = [ofp.action,ofp.message,ofp.common]
911 self.klasses = [klass for mod in mods
912 for klass in mod.__dict__.values()
913 if hasattr(klass, 'show')]
914 self.klasses.sort(key=lambda x: str(x))
915
916 def test_serialization(self):
Rich Lanea06d0c32013-03-25 08:52:03 -0700917 expected_failures = []
918 for klass in self.klasses:
919 def fn():
920 obj = klass()
921 if hasattr(obj, "xid"): obj.xid = 42
922 buf = obj.pack()
923 obj2 = klass.unpack(buf)
924 self.assertEquals(obj, obj2)
925 if klass in expected_failures:
926 self.assertRaises(Exception, fn)
927 else:
928 fn()
929
930 def test_show(self):
Rich Lanea06d0c32013-03-25 08:52:03 -0700931 expected_failures = []
932 for klass in self.klasses:
933 def fn():
934 obj = klass()
935 if hasattr(obj, "xid"): obj.xid = 42
936 obj.show()
937 if klass in expected_failures:
938 self.assertRaises(Exception, fn)
939 else:
940 fn()
941
942if __name__ == '__main__':
943 unittest.main()