Merge pull request #322 from rlane/of14-pyloxi
[of14] Add OF 1.4 support to the python backend
diff --git a/Makefile b/Makefile
index 018db0b..5f3d8e5 100644
--- a/Makefile
+++ b/Makefile
@@ -131,6 +131,7 @@
PYTHONPATH=${LOXI_OUTPUT_DIR}/pyloxi:. python py_gen/tests/of11.py
PYTHONPATH=${LOXI_OUTPUT_DIR}/pyloxi:. python py_gen/tests/of12.py
PYTHONPATH=${LOXI_OUTPUT_DIR}/pyloxi:. python py_gen/tests/of13.py
+ PYTHONPATH=${LOXI_OUTPUT_DIR}/pyloxi:. python py_gen/tests/of14.py
check-c: c
make -j4 -C ${LOXI_OUTPUT_DIR}/locitest
diff --git a/cmdline.py b/cmdline.py
index 6395fff..de7070a 100644
--- a/cmdline.py
+++ b/cmdline.py
@@ -33,7 +33,7 @@
# The default configuration dictionary for LOXI code generation
options_default = {
"lang" : "c",
- "version-list" : "1.0 1.1 1.2 1.3",
+ "version-list" : "1.0 1.1 1.2 1.3 1.4",
"install-dir" : "loxi_output",
}
diff --git a/openflow_input/standard-1.4 b/openflow_input/standard-1.4
index d7e38eb..918078c 100644
--- a/openflow_input/standard-1.4
+++ b/openflow_input/standard-1.4
@@ -772,6 +772,18 @@
uint16_t length; /* Length in bytes of this property. */
};
+struct of_port_desc_prop_ethernet : of_port_desc_prop {
+ uint16_t type == 0;
+ uint16_t length;
+ pad(4);
+ uint32_t curr;
+ uint32_t advertised;
+ uint32_t supported;
+ uint32_t peer;
+ uint32_t curr_speed;
+ uint32_t max_speed;
+};
+
struct of_port_desc {
of_port_no_t port_no;
uint16_t length;
@@ -834,8 +846,7 @@
list(of_port_mod_prop_t) properties;
};
-// FIXME Does this need to be v4?
-struct of_match_v4(align=8, length_includes_align=False) {
+struct of_match_v3(align=8, length_includes_align=False) {
uint16_t type == 1;
uint16_t length;
list(of_oxm_t) oxm_list;
@@ -979,7 +990,8 @@
struct of_instruction_goto_table : of_instruction {
uint16_t type == 1;
uint16_t len;
- of_octets_t exp_data;
+ uint8_t table_id;
+ pad(3);
};
struct of_instruction_write_metadata : of_instruction {
@@ -1534,6 +1546,8 @@
uint16_t length;
pad(2);
of_port_no_t port_no;
+ uint32_t duration_sec;
+ uint32_t duration_nsec;
uint64_t rx_packets;
uint64_t tx_packets;
uint64_t rx_bytes;
@@ -1542,8 +1556,6 @@
uint64_t tx_dropped;
uint64_t rx_errors;
uint64_t tx_errors;
- uint32_t duration_sec;
- uint32_t duration_nsec;
list(of_port_stats_prop_t) properties;
};
diff --git a/py_gen/codegen.py b/py_gen/codegen.py
index 3b0988e..a664d87 100644
--- a/py_gen/codegen.py
+++ b/py_gen/codegen.py
@@ -43,6 +43,12 @@
'of_instruction_id': 'instruction_id',
'of_meter_band': 'meter_band',
'of_bsn_tlv': 'bsn_tlv',
+ 'of_port_desc_prop': 'port_desc_prop',
+ 'of_port_stats_prop': 'port_stats_prop',
+ 'of_port_mod_prop': 'port_mod_prop',
+ 'of_table_mod_prop': 'table_mod_prop',
+ 'of_queue_desc_prop': 'queue_desc_prop',
+ 'of_queue_stats_prop': 'queue_stats_prop',
}
# Return the module and class names for the generated Python class
diff --git a/py_gen/templates/_common_extra.py b/py_gen/templates/_common_extra.py
index 8639dc4..f801965 100644
--- a/py_gen/templates/_common_extra.py
+++ b/py_gen/templates/_common_extra.py
@@ -35,4 +35,7 @@
:: elif version == OFVersions.VERSION_1_3:
:: # HACK
match = match_v3
+:: elif version == OFVersions.VERSION_1_4:
+:: # HACK
+match = match_v3
:: #endif
diff --git a/py_gen/templates/toplevel_init.py b/py_gen/templates/toplevel_init.py
index e5493a5..0393169 100644
--- a/py_gen/templates/toplevel_init.py
+++ b/py_gen/templates/toplevel_init.py
@@ -39,20 +39,13 @@
"""
Import and return the protocol module for the given wire version.
"""
- if ver == 1:
- import of10
- return of10
- elif ver == 2:
- import of11
- return of11
- elif ver == 3:
- import of12
- return of12
- elif ver == 4:
- import of13
- return of13
- else:
- raise ValueError
+:: for v in loxi_globals.OFVersions.all_supported:
+ if ver == ${v.wire_version}:
+ import of${v.version.replace('.', '')}
+ return of${v.version.replace('.', '')}
+
+:: #endfor
+ raise ValueError
class ProtocolError(Exception):
"""
diff --git a/py_gen/tests/of14.py b/py_gen/tests/of14.py
new file mode 100644
index 0000000..3fdfb8e
--- /dev/null
+++ b/py_gen/tests/of14.py
@@ -0,0 +1,130 @@
+#!/usr/bin/env python
+# Copyright 2014, Big Switch Networks, Inc.
+#
+# LoxiGen is licensed under the Eclipse Public License, version 1.0 (EPL), with
+# the following special exception:
+#
+# LOXI Exception
+#
+# As a special exception to the terms of the EPL, you may distribute libraries
+# generated by LoxiGen (LoxiGen Libraries) under the terms of your choice, provided
+# that copyright and licensing notices generated by LoxiGen are not altered or removed
+# from the LoxiGen Libraries and the notice provided below is (i) included in
+# the LoxiGen Libraries, if distributed in source code form and (ii) included in any
+# documentation for the LoxiGen Libraries, if distributed in binary form.
+#
+# Notice: "Copyright 2014, Big Switch Networks, Inc. This library was generated by the LoxiGen Compiler."
+#
+# You may not use this file except in compliance with the EPL or LOXI Exception. You may obtain
+# a copy of the EPL at:
+#
+# http://www.eclipse.org/legal/epl-v10.html
+#
+# 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
+# EPL for the specific language governing permissions and limitations
+# under the EPL.
+import unittest
+from testutil import test_serialization
+from testutil import add_datafiles_tests
+
+try:
+ import loxi
+ import loxi.of14 as ofp
+ from loxi.generic_util import OFReader
+except ImportError:
+ exit("loxi package not found. Try setting PYTHONPATH.")
+
+class TestImports(unittest.TestCase):
+ def test_toplevel(self):
+ import loxi
+ self.assertTrue(hasattr(loxi, "ProtocolError"))
+ self.assertEquals(loxi.version_names[5], "1.4")
+ ofp = loxi.protocol(5)
+ self.assertEquals(ofp.OFP_VERSION, 5)
+ self.assertTrue(hasattr(ofp, "action"))
+ self.assertTrue(hasattr(ofp, "common"))
+ self.assertTrue(hasattr(ofp, "const"))
+ self.assertTrue(hasattr(ofp, "message"))
+ self.assertTrue(hasattr(ofp, "oxm"))
+
+ def test_version(self):
+ import loxi
+ self.assertTrue(hasattr(loxi.of14, "ProtocolError"))
+ self.assertTrue(hasattr(loxi.of14, "OFP_VERSION"))
+ self.assertEquals(loxi.of14.OFP_VERSION, 5)
+ self.assertTrue(hasattr(loxi.of14, "action"))
+ self.assertTrue(hasattr(loxi.of14, "common"))
+ self.assertTrue(hasattr(loxi.of14, "const"))
+ self.assertTrue(hasattr(loxi.of14, "message"))
+ self.assertTrue(hasattr(loxi.of14, "oxm"))
+
+# The majority of the serialization tests are created here using the files in
+# the test_data directory.
+class TestDataFiles(unittest.TestCase):
+ pass
+add_datafiles_tests(TestDataFiles, 'of14/', ofp)
+
+class TestAllof14(unittest.TestCase):
+ """
+ Round-trips every class through serialization/deserialization.
+ Not a replacement for handcoded tests because it only uses the
+ default member values.
+ """
+
+ def setUp(self):
+ mods = [ofp.action,ofp.message,ofp.common,ofp.oxm]
+ self.klasses = [klass for mod in mods
+ for klass in mod.__dict__.values()
+ if isinstance(klass, type) and
+ issubclass(klass, loxi.OFObject) and
+ not hasattr(klass, 'subtypes')]
+ self.klasses.sort(key=lambda x: str(x))
+
+ def test_serialization(self):
+ expected_failures = [
+ ofp.action.set_field, # field defaults to None
+ ]
+ for klass in self.klasses:
+ def fn():
+ obj = klass()
+ if hasattr(obj, "xid"): obj.xid = 42
+ buf = obj.pack()
+ obj2 = klass.unpack(OFReader(buf))
+ self.assertEquals(obj, obj2)
+ if klass in expected_failures:
+ self.assertRaises(Exception, fn)
+ else:
+ fn()
+
+ def test_parse_message(self):
+ expected_failures = [
+ ]
+ for klass in self.klasses:
+ if not issubclass(klass, ofp.message.message):
+ continue
+ def fn():
+ obj = klass(xid=42)
+ buf = obj.pack()
+ obj2 = ofp.message.parse_message(buf)
+ self.assertEquals(obj, obj2)
+ if klass in expected_failures:
+ self.assertRaises(Exception, fn)
+ else:
+ fn()
+
+ def test_show(self):
+ expected_failures = []
+ for klass in self.klasses:
+ def fn():
+ obj = klass()
+ if hasattr(obj, "xid"): obj.xid = 42
+ obj.show()
+ if klass in expected_failures:
+ self.assertRaises(Exception, fn)
+ else:
+ fn()
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/test_data/of14/flow_add.data b/test_data/of14/flow_add.data
new file mode 100644
index 0000000..88cf939
--- /dev/null
+++ b/test_data/of14/flow_add.data
@@ -0,0 +1,104 @@
+-- binary
+05 0e # version, type
+00 80 # length
+12 34 56 78 # xid
+
+fe dc ba 98 76 54 32 10 # cookie
+
+ff 00 ff 00 ff 00 ff 00 # cookie_mask
+
+03 # table_id
+00 # _command
+00 05 # idle_timeout
+00 0a # hard_timeout
+17 70 # priority
+
+00 00 00 32 # buffer_id
+00 00 00 06 # out_port
+
+00 00 00 08 # out_group
+00 00 # flags
+aa bb # importance
+
+00 01 # match.type
+00 3F # match.length # 59 bytes OXMs + 4 bytes match header
+
+80 00 01 08 # match.oxm_list[0].type_len - IN_PORT
+00 00 00 04 # match.oxm_list[0].value
+00 00 00 05 # match.oxm_list[0].mask
+
+80 00 0A 02 # match.oxm_list[1].type_len - ETH_TYPE
+86 DD # match.oxm_list[1].value - ETH_TYPE = IPv6
+
+80 00 14 01 # match.oxm_list[2].type_len - IP Proto
+06 # match.oxm_list[2].value = IP_PROTO = TCP
+
+80 00 35 20 # match.oxm_list[3].type_len - IPV6_SRC
+1C CA FE 1C B1 10 1C 00 00 28 00 00 00 00 00 00 # match.oxm_list[3].value
+FF FF FF FF FF F0 FF FF 1C 2C 3C 00 00 00 00 00 # match.oxm_list[3].mask
+
+00 # match.pad
+
+00 01 # instructions[0].type
+00 08 # instructions[0].length
+04 # instructions[0].table_id
+00 00 00 # pad
+
+00 01 # instructions[1].type
+00 08 # instructions[1].length
+07 # instructions[1].table_id
+00 00 00 # pad
+-- python
+ofp.message.flow_add(
+ xid=0x12345678,
+ cookie=0xFEDCBA9876543210,
+ cookie_mask=0xFF00FF00FF00FF00,
+ table_id=3,
+ idle_timeout=5,
+ hard_timeout=10,
+ priority=6000,
+ buffer_id=50,
+ out_port=6,
+ out_group=8,
+ flags=0,
+ importance=0xaabb,
+ match=ofp.match(oxm_list=[
+ ofp.oxm.in_port_masked(value=4, value_mask=5),
+ ofp.oxm.eth_type(value=0x86dd),
+ ofp.oxm.ip_proto(value=6),
+ ofp.oxm.ipv6_src_masked(
+ value ='\x1C\xCA\xFE\x1C\xB1\x10\x1C\x00\x00\x28\x00\x00\x00\x00\x00\x00',
+ value_mask='\xFF\xFF\xFF\xFF\xFF\xF0\xFF\xFF\x1C\x2C\x3C\x00\x00\x00\x00\x00')
+ ]),
+ instructions=[
+ ofp.instruction.goto_table(table_id=4),
+ ofp.instruction.goto_table(table_id=7)])
+-- java
+builder.setXid(0x12345678)
+ .setCookie(U64.parseHex("FEDCBA9876543210"))
+ .setCookieMask(U64.parseHex("FF00FF00FF00FF00"))
+ .setTableId(TableId.of(3))
+ .setIdleTimeout(5)
+ .setHardTimeout(10)
+ .setPriority(6000)
+ .setBufferId(OFBufferId.of(50))
+ .setOutPort(OFPort.of(6))
+ .setOutGroup(OFGroup.of(8))
+ .setFlags(ImmutableSet.<OFFlowModFlags>of())
+ .setImportance(0xaabb)
+ .setMatch(
+ factory.buildMatch()
+ .setMasked(MatchField.IN_PORT, OFPort.of(4), OFPort.of(5))
+ .setExact(MatchField.ETH_TYPE, EthType.IPv6)
+ .setExact(MatchField.IP_PROTO, IpProtocol.TCP)
+ .setMasked(MatchField.IPV6_SRC,
+ IPv6Address.of(0x1CCAFE1CB1101C00l, 0x0028000000000000l),
+ IPv6Address.of(0xFFFFFFFFFFF0FFFFl, 0x1C2C3C0000000000l))
+ .build()
+ )
+ .setInstructions(
+ ImmutableList.<OFInstruction>of(
+ factory.instructions().gotoTable(TableId.of(4)),
+ factory.instructions().gotoTable(TableId.of(7))
+ )
+ );
diff --git a/test_data/of14/port_desc_stats_reply.data b/test_data/of14/port_desc_stats_reply.data
new file mode 100644
index 0000000..b969b4a
--- /dev/null
+++ b/test_data/of14/port_desc_stats_reply.data
@@ -0,0 +1,38 @@
+-- binary
+05 13 # version/type
+00 58 # length
+00 00 00 05 # xid
+00 0d # stats_type
+00 00 # flags
+00 00 00 00 # pad
+00 00 00 01 # entries[0].port_no
+00 48 # entries[0].length
+00 00 # pad
+01 02 03 04 05 06 # entries[0].hw_addr
+00 00 # pad
+69 6e 74 65 72 66 61 63 65 31 32 33 34 35 36 37 # entries[0].name
+00 00 00 50 # entries[0].config
+00 00 00 60 # entries[0].state
+00 00 # entries[0].properties[0].type (ethernet)
+00 20 # entries[0].properties[0].length
+00 00 00 00 # pad
+00 00 00 70 # entries[0].properties[0].curr
+00 00 00 00 # entries[0].properties[0].advertised
+00 00 00 00 # entries[0].properties[0].supported
+00 00 00 00 # entries[0].properties[0].peer
+00 00 00 00 # entries[0].properties[0].curr_speed
+00 00 00 80 # entries[0].properties[0].max_speed
+-- python
+ofp.message.port_desc_stats_reply(
+ xid=5, flags=0, entries=[
+ ofp.port_desc(
+ port_no=1,
+ hw_addr=[1,2,3,4,5,6],
+ name="interface1234567",
+ config=0x50,
+ state=0x60,
+ properties=[
+ ofp.port_desc_prop.ethernet(
+ curr=0x70,
+ max_speed=0x80)])])
+
diff --git a/test_data/of14/port_stats_reply.data b/test_data/of14/port_stats_reply.data
new file mode 100644
index 0000000..43dc471
--- /dev/null
+++ b/test_data/of14/port_stats_reply.data
@@ -0,0 +1,88 @@
+-- binary
+05 13 # version/type
+00 d8 # length
+00 00 00 05 # xid
+00 04 # stats_type
+00 00 # flags
+00 00 00 00 # pad
+00 78 # entries[0].length
+00 00 # pad
+00 00 00 01 # entries[0].port_no
+00 00 00 00 # entries[0].duration_sec
+00 00 00 00 # entries[0].duration_nsec
+00 00 00 00 00 00 00 01 # entries[0].rx_packets
+00 00 00 00 00 00 00 00 # entries[0].tx_packets
+00 00 00 00 00 00 00 00 # entries[0].rx_bytes
+00 00 00 00 00 00 00 00 # entries[0].tx_bytes
+00 00 00 00 00 00 00 00 # entries[0].rx_dropped
+00 00 00 00 00 00 00 00 # entries[0].tx_dropped
+00 00 00 00 00 00 00 00 # entries[0].rx_errors
+00 00 00 00 00 00 00 02 # entries[0].tx_errors
+00 00 # entries[0].properties[0].type (ethernet)
+00 28 # entries[0].properties[0].length
+00 00 00 00 # pad
+00 00 00 00 00 00 00 01 # entries[0].properties[0].rx_frame_err
+00 00 00 00 00 00 00 02 # entries[0].properties[0].rx_over_err
+00 00 00 00 00 00 00 03 # entries[0].properties[0].rx_crc_err
+00 00 00 00 00 00 00 04 # entries[0].properties[0].collisions
+00 50 # entries[1].length
+00 00 # pad
+ff ff ff fe # entries[1].port_no
+00 00 00 00 # entries[1].duration_sec
+00 00 00 00 # entries[1].duration_nsec
+00 00 00 00 00 00 00 03 # entries[1].rx_packets
+00 00 00 00 00 00 00 00 # entries[1].tx_packets
+00 00 00 00 00 00 00 00 # entries[1].rx_bytes
+00 00 00 00 00 00 00 00 # entries[1].tx_bytes
+00 00 00 00 00 00 00 00 # entries[1].rx_dropped
+00 00 00 00 00 00 00 00 # entries[1].tx_dropped
+00 00 00 00 00 00 00 00 # entries[1].rx_errors
+00 00 00 00 00 00 00 04 # entries[1].tx_errors
+-- python
+ofp.message.port_stats_reply(
+ xid=5, flags=0, entries=[
+ ofp.port_stats_entry(port_no=1, rx_packets=1, tx_errors=2,
+ properties=[
+ ofp.port_stats_prop.ethernet(
+ rx_frame_err=1,
+ rx_over_err=2,
+ rx_crc_err=3,
+ collisions=4)]),
+ ofp.port_stats_entry(port_no=ofp.OFPP_LOCAL, rx_packets=3, tx_errors=4)])
+-- c
+obj = of_port_stats_reply_new(OF_VERSION_1_4);
+{
+ of_object_t list;
+ of_port_stats_reply_entries_bind(obj, &list);
+ {
+ of_object_t *obj = of_port_stats_entry_new(OF_VERSION_1_4);
+ of_port_stats_entry_port_no_set(obj, 1);
+ of_port_stats_entry_rx_packets_set(obj, 1);
+ of_port_stats_entry_tx_packets_set(obj, 0);
+ of_port_stats_entry_rx_bytes_set(obj, 0);
+ of_port_stats_entry_tx_bytes_set(obj, 0);
+ of_port_stats_entry_rx_dropped_set(obj, 0);
+ of_port_stats_entry_tx_dropped_set(obj, 0);
+ of_port_stats_entry_rx_errors_set(obj, 0);
+ of_port_stats_entry_tx_errors_set(obj, 2);
+ /* TODO append property */
+ of_list_append(&list, obj);
+ of_object_delete(obj);
+ }
+ {
+ of_object_t *obj = of_port_stats_entry_new(OF_VERSION_1_4);
+ of_port_stats_entry_port_no_set(obj, OF_PORT_DEST_LOCAL);
+ of_port_stats_entry_rx_packets_set(obj, 3);
+ of_port_stats_entry_tx_packets_set(obj, 0);
+ of_port_stats_entry_rx_bytes_set(obj, 0);
+ of_port_stats_entry_tx_bytes_set(obj, 0);
+ of_port_stats_entry_rx_dropped_set(obj, 0);
+ of_port_stats_entry_tx_dropped_set(obj, 0);
+ of_port_stats_entry_rx_errors_set(obj, 0);
+ of_port_stats_entry_tx_errors_set(obj, 4);
+ of_list_append(&list, obj);
+ of_object_delete(obj);
+ }
+}
+of_port_stats_reply_flags_set(obj, 0);
+of_port_stats_reply_xid_set(obj, 5);