Merge pull request #56 from rlane/of13
OpenFlow 1.3 basic test suite
diff --git a/src/python/loxi/of10/common.py b/src/python/loxi/of10/common.py
index 616cf75..28b079a 100644
--- a/src/python/loxi/of10/common.py
+++ b/src/python/loxi/of10/common.py
@@ -385,7 +385,7 @@
class match_v1(object):
- def __init__(self, wildcards=None, in_port=None, eth_src=None, eth_dst=None, vlan_vid=None, vlan_pcp=None, eth_type=None, ip_dscp=None, ip_proto=None, src_meta_id=None, dst_meta_id=None, ipv4_src=None, ipv4_dst=None, tcp_src=None, tcp_dst=None):
+ def __init__(self, wildcards=None, in_port=None, eth_src=None, eth_dst=None, vlan_vid=None, vlan_pcp=None, eth_type=None, ip_dscp=None, ip_proto=None, ipv4_src=None, ipv4_dst=None, tcp_src=None, tcp_dst=None):
if wildcards != None:
self.wildcards = wildcards
else:
@@ -422,14 +422,6 @@
self.ip_proto = ip_proto
else:
self.ip_proto = 0
- if src_meta_id != None:
- self.src_meta_id = src_meta_id
- else:
- self.src_meta_id = 0
- if dst_meta_id != None:
- self.dst_meta_id = dst_meta_id
- else:
- self.dst_meta_id = 0
if ipv4_src != None:
self.ipv4_src = ipv4_src
else:
@@ -460,8 +452,7 @@
packed.append(struct.pack("!H", self.eth_type))
packed.append(struct.pack("!B", self.ip_dscp))
packed.append(struct.pack("!B", self.ip_proto))
- packed.append(struct.pack("!B", self.src_meta_id))
- packed.append(struct.pack("!B", self.dst_meta_id))
+ packed.append('\x00' * 2)
packed.append(struct.pack("!L", self.ipv4_src))
packed.append(struct.pack("!L", self.ipv4_dst))
packed.append(struct.pack("!H", self.tcp_src))
@@ -485,8 +476,7 @@
obj.eth_type = reader.read("!H")[0]
obj.ip_dscp = reader.read("!B")[0]
obj.ip_proto = reader.read("!B")[0]
- obj.src_meta_id = reader.read("!B")[0]
- obj.dst_meta_id = reader.read("!B")[0]
+ reader.skip(2)
obj.ipv4_src = reader.read("!L")[0]
obj.ipv4_dst = reader.read("!L")[0]
obj.tcp_src = reader.read("!H")[0]
@@ -504,8 +494,6 @@
if self.eth_type != other.eth_type: return False
if self.ip_dscp != other.ip_dscp: return False
if self.ip_proto != other.ip_proto: return False
- if self.src_meta_id != other.src_meta_id: return False
- if self.dst_meta_id != other.dst_meta_id: return False
if self.ipv4_src != other.ipv4_src: return False
if self.ipv4_dst != other.ipv4_dst: return False
if self.tcp_src != other.tcp_src: return False
@@ -551,12 +539,6 @@
q.text("ip_proto = ");
q.text("%#x" % self.ip_proto)
q.text(","); q.breakable()
- q.text("src_meta_id = ");
- q.text("%#x" % self.src_meta_id)
- q.text(","); q.breakable()
- q.text("dst_meta_id = ");
- q.text("%#x" % self.dst_meta_id)
- q.text(","); q.breakable()
q.text("ipv4_src = ");
q.text(util.pretty_ipv4(self.ipv4_src))
q.text(","); q.breakable()
diff --git a/src/python/loxi/of10/const.py b/src/python/loxi/of10/const.py
index 0273c7a..1d29975 100644
--- a/src/python/loxi/of10/const.py
+++ b/src/python/loxi/of10/const.py
@@ -235,8 +235,6 @@
OFPFW_NW_DST_MASK = 1032192
OFPFW_DL_VLAN_PCP = 1048576
OFPFW_NW_TOS = 2097152
-OFPFW_SRC_META_ID = 4194304
-OFPFW_DST_META_ID = 8388608
OFPFW_ALL = 4194303
ofp_flow_wildcards_map = {
@@ -250,8 +248,6 @@
128: 'OFPFW_TP_DST',
1048576: 'OFPFW_DL_VLAN_PCP',
2097152: 'OFPFW_NW_TOS',
- 4194304: 'OFPFW_SRC_META_ID',
- 8388608: 'OFPFW_DST_META_ID',
}
# Identifiers from group ofp_hello_failed_code
diff --git a/src/python/loxi/of10/message.py b/src/python/loxi/of10/message.py
index e01c342..6e6649e 100644
--- a/src/python/loxi/of10/message.py
+++ b/src/python/loxi/of10/message.py
@@ -6281,8 +6281,8 @@
def parse_message(buf):
msg_ver, msg_type, msg_len, msg_xid = parse_header(buf)
- if msg_ver != const.OFP_VERSION and msg_type != ofp.OFPT_HELLO:
- raise loxi.ProtocolError("wrong OpenFlow version")
+ if msg_ver != const.OFP_VERSION and msg_type != const.OFPT_HELLO:
+ raise loxi.ProtocolError("wrong OpenFlow version (expected %d, got %d)" % (const.OFP_VERSION, msg_ver))
if len(buf) != msg_len:
raise loxi.ProtocolError("incorrect message size")
if msg_type in parsers:
@@ -6291,9 +6291,10 @@
raise loxi.ProtocolError("unexpected message type")
def parse_flow_mod(buf):
- if len(buf) < 56 + 2:
+ if len(buf) < 57 + 1:
raise loxi.ProtocolError("message too short")
- cmd, = struct.unpack_from("!H", buf, 56)
+ # Technically uint16_t for OF 1.0
+ cmd, = struct.unpack_from("!B", buf, 57)
if cmd in flow_mod_parsers:
return flow_mod_parsers[cmd](buf)
else:
diff --git a/src/python/loxi/of11/message.py b/src/python/loxi/of11/message.py
index 12aa4a1..091e70c 100644
--- a/src/python/loxi/of11/message.py
+++ b/src/python/loxi/of11/message.py
@@ -5973,8 +5973,8 @@
def parse_message(buf):
msg_ver, msg_type, msg_len, msg_xid = parse_header(buf)
- if msg_ver != const.OFP_VERSION and msg_type != ofp.OFPT_HELLO:
- raise loxi.ProtocolError("wrong OpenFlow version")
+ if msg_ver != const.OFP_VERSION and msg_type != const.OFPT_HELLO:
+ raise loxi.ProtocolError("wrong OpenFlow version (expected %d, got %d)" % (const.OFP_VERSION, msg_ver))
if len(buf) != msg_len:
raise loxi.ProtocolError("incorrect message size")
if msg_type in parsers:
@@ -5983,9 +5983,10 @@
raise loxi.ProtocolError("unexpected message type")
def parse_flow_mod(buf):
- if len(buf) < 56 + 2:
+ if len(buf) < 25 + 1:
raise loxi.ProtocolError("message too short")
- cmd, = struct.unpack_from("!H", buf, 56)
+ # Technically uint16_t for OF 1.0
+ cmd, = struct.unpack_from("!B", buf, 25)
if cmd in flow_mod_parsers:
return flow_mod_parsers[cmd](buf)
else:
diff --git a/src/python/loxi/of12/message.py b/src/python/loxi/of12/message.py
index 76424fe..ed1f04f 100644
--- a/src/python/loxi/of12/message.py
+++ b/src/python/loxi/of12/message.py
@@ -6405,8 +6405,8 @@
def parse_message(buf):
msg_ver, msg_type, msg_len, msg_xid = parse_header(buf)
- if msg_ver != const.OFP_VERSION and msg_type != ofp.OFPT_HELLO:
- raise loxi.ProtocolError("wrong OpenFlow version")
+ if msg_ver != const.OFP_VERSION and msg_type != const.OFPT_HELLO:
+ raise loxi.ProtocolError("wrong OpenFlow version (expected %d, got %d)" % (const.OFP_VERSION, msg_ver))
if len(buf) != msg_len:
raise loxi.ProtocolError("incorrect message size")
if msg_type in parsers:
@@ -6415,9 +6415,10 @@
raise loxi.ProtocolError("unexpected message type")
def parse_flow_mod(buf):
- if len(buf) < 56 + 2:
+ if len(buf) < 25 + 1:
raise loxi.ProtocolError("message too short")
- cmd, = struct.unpack_from("!H", buf, 56)
+ # Technically uint16_t for OF 1.0
+ cmd, = struct.unpack_from("!B", buf, 25)
if cmd in flow_mod_parsers:
return flow_mod_parsers[cmd](buf)
else:
diff --git a/src/python/loxi/of12/oxm.py b/src/python/loxi/of12/oxm.py
index fb1291d..f17151e 100644
--- a/src/python/loxi/of12/oxm.py
+++ b/src/python/loxi/of12/oxm.py
@@ -566,114 +566,6 @@
q.breakable()
q.text('}')
-class dst_meta_id(OXM):
- type_len = 258561
-
- def __init__(self, value=None):
- if value != None:
- self.value = value
- else:
- self.value = 0
-
- def pack(self):
- packed = []
- packed.append(struct.pack("!L", self.type_len))
- packed.append(struct.pack("!B", self.value))
- return ''.join(packed)
-
- @staticmethod
- def unpack(buf):
- obj = dst_meta_id()
- if type(buf) == loxi.generic_util.OFReader:
- reader = buf
- else:
- reader = loxi.generic_util.OFReader(buf)
- _type_len = reader.read("!L")[0]
- assert(_type_len == 258561)
- obj.value = reader.read("!B")[0]
- return obj
-
- def __eq__(self, other):
- if type(self) != type(other): return False
- if self.value != other.value: return False
- return True
-
- def __ne__(self, other):
- return not self.__eq__(other)
-
- def show(self):
- import loxi.pp
- return loxi.pp.pp(self)
-
- def pretty_print(self, q):
- q.text("dst_meta_id {")
- with q.group():
- with q.indent(2):
- q.breakable()
- q.text("value = ");
- q.text("%#x" % self.value)
- q.breakable()
- q.text('}')
-
-class dst_meta_id_masked(OXM):
- type_len = 258818
-
- def __init__(self, value=None, value_mask=None):
- if value != None:
- self.value = value
- else:
- self.value = 0
- if value_mask != None:
- self.value_mask = value_mask
- else:
- self.value_mask = 0
-
- def pack(self):
- packed = []
- packed.append(struct.pack("!L", self.type_len))
- packed.append(struct.pack("!B", self.value))
- packed.append(struct.pack("!B", self.value_mask))
- return ''.join(packed)
-
- @staticmethod
- def unpack(buf):
- obj = dst_meta_id_masked()
- if type(buf) == loxi.generic_util.OFReader:
- reader = buf
- else:
- reader = loxi.generic_util.OFReader(buf)
- _type_len = reader.read("!L")[0]
- assert(_type_len == 258818)
- obj.value = reader.read("!B")[0]
- obj.value_mask = reader.read("!B")[0]
- return obj
-
- def __eq__(self, other):
- if type(self) != type(other): return False
- if self.value != other.value: return False
- if self.value_mask != other.value_mask: return False
- return True
-
- def __ne__(self, other):
- return not self.__eq__(other)
-
- def show(self):
- import loxi.pp
- return loxi.pp.pp(self)
-
- def pretty_print(self, q):
- q.text("dst_meta_id_masked {")
- with q.group():
- with q.indent(2):
- q.breakable()
- q.text("value = ");
- q.text("%#x" % self.value)
- q.text(","); q.breakable()
- q.text("value_mask = ");
- q.text("%#x" % self.value_mask)
- q.breakable()
- q.text('}')
-
class eth_dst(OXM):
type_len = 2147485190
@@ -3374,114 +3266,6 @@
q.breakable()
q.text('}')
-class src_meta_id(OXM):
- type_len = 258049
-
- def __init__(self, value=None):
- if value != None:
- self.value = value
- else:
- self.value = 0
-
- def pack(self):
- packed = []
- packed.append(struct.pack("!L", self.type_len))
- packed.append(struct.pack("!B", self.value))
- return ''.join(packed)
-
- @staticmethod
- def unpack(buf):
- obj = src_meta_id()
- if type(buf) == loxi.generic_util.OFReader:
- reader = buf
- else:
- reader = loxi.generic_util.OFReader(buf)
- _type_len = reader.read("!L")[0]
- assert(_type_len == 258049)
- obj.value = reader.read("!B")[0]
- return obj
-
- def __eq__(self, other):
- if type(self) != type(other): return False
- if self.value != other.value: return False
- return True
-
- def __ne__(self, other):
- return not self.__eq__(other)
-
- def show(self):
- import loxi.pp
- return loxi.pp.pp(self)
-
- def pretty_print(self, q):
- q.text("src_meta_id {")
- with q.group():
- with q.indent(2):
- q.breakable()
- q.text("value = ");
- q.text("%#x" % self.value)
- q.breakable()
- q.text('}')
-
-class src_meta_id_masked(OXM):
- type_len = 258306
-
- def __init__(self, value=None, value_mask=None):
- if value != None:
- self.value = value
- else:
- self.value = 0
- if value_mask != None:
- self.value_mask = value_mask
- else:
- self.value_mask = 0
-
- def pack(self):
- packed = []
- packed.append(struct.pack("!L", self.type_len))
- packed.append(struct.pack("!B", self.value))
- packed.append(struct.pack("!B", self.value_mask))
- return ''.join(packed)
-
- @staticmethod
- def unpack(buf):
- obj = src_meta_id_masked()
- if type(buf) == loxi.generic_util.OFReader:
- reader = buf
- else:
- reader = loxi.generic_util.OFReader(buf)
- _type_len = reader.read("!L")[0]
- assert(_type_len == 258306)
- obj.value = reader.read("!B")[0]
- obj.value_mask = reader.read("!B")[0]
- return obj
-
- def __eq__(self, other):
- if type(self) != type(other): return False
- if self.value != other.value: return False
- if self.value_mask != other.value_mask: return False
- return True
-
- def __ne__(self, other):
- return not self.__eq__(other)
-
- def show(self):
- import loxi.pp
- return loxi.pp.pp(self)
-
- def pretty_print(self, q):
- q.text("src_meta_id_masked {")
- with q.group():
- with q.indent(2):
- q.breakable()
- q.text("value = ");
- q.text("%#x" % self.value)
- q.text(","); q.breakable()
- q.text("value_mask = ");
- q.text("%#x" % self.value_mask)
- q.breakable()
- q.text('}')
-
class tcp_dst(OXM):
type_len = 2147490818
@@ -4132,10 +3916,6 @@
parsers = {
- 258049 : src_meta_id.unpack,
- 258306 : src_meta_id_masked.unpack,
- 258561 : dst_meta_id.unpack,
- 258818 : dst_meta_id_masked.unpack,
2147483652 : in_port.unpack,
2147483912 : in_port_masked.unpack,
2147484164 : in_phy_port.unpack,
diff --git a/src/python/loxi/of13/message.py b/src/python/loxi/of13/message.py
index 543442b..4c22e45 100644
--- a/src/python/loxi/of13/message.py
+++ b/src/python/loxi/of13/message.py
@@ -7554,8 +7554,8 @@
def parse_message(buf):
msg_ver, msg_type, msg_len, msg_xid = parse_header(buf)
- if msg_ver != const.OFP_VERSION and msg_type != ofp.OFPT_HELLO:
- raise loxi.ProtocolError("wrong OpenFlow version")
+ if msg_ver != const.OFP_VERSION and msg_type != const.OFPT_HELLO:
+ raise loxi.ProtocolError("wrong OpenFlow version (expected %d, got %d)" % (const.OFP_VERSION, msg_ver))
if len(buf) != msg_len:
raise loxi.ProtocolError("incorrect message size")
if msg_type in parsers:
@@ -7564,9 +7564,10 @@
raise loxi.ProtocolError("unexpected message type")
def parse_flow_mod(buf):
- if len(buf) < 56 + 2:
+ if len(buf) < 25 + 1:
raise loxi.ProtocolError("message too short")
- cmd, = struct.unpack_from("!H", buf, 56)
+ # Technically uint16_t for OF 1.0
+ cmd, = struct.unpack_from("!B", buf, 25)
if cmd in flow_mod_parsers:
return flow_mod_parsers[cmd](buf)
else:
@@ -7648,7 +7649,39 @@
const.OFPFC_DELETE_STRICT : flow_delete_strict.unpack,
}
-# TODO OF 1.3 multipart messages
+multipart_reply_parsers = {
+ const.OFPMP_DESC : desc_stats_reply.unpack,
+ const.OFPMP_FLOW : flow_stats_reply.unpack,
+ const.OFPMP_AGGREGATE : aggregate_stats_reply.unpack,
+ const.OFPMP_TABLE : table_stats_reply.unpack,
+ const.OFPMP_PORT_STATS : port_stats_reply.unpack,
+ const.OFPMP_QUEUE : queue_stats_reply.unpack,
+ const.OFPMP_GROUP : group_stats_reply.unpack,
+ const.OFPMP_GROUP_DESC : group_desc_stats_reply.unpack,
+ const.OFPMP_GROUP_FEATURES : group_features_stats_reply.unpack,
+ const.OFPMP_METER : meter_stats_reply.unpack,
+ const.OFPMP_METER_CONFIG : meter_config_stats_reply.unpack,
+ const.OFPMP_METER_FEATURES : meter_features_stats_reply.unpack,
+ const.OFPMP_TABLE_FEATURES : table_features_stats_reply.unpack,
+ const.OFPMP_PORT_DESC : port_desc_stats_reply.unpack,
+}
+
+multipart_request_parsers = {
+ const.OFPMP_DESC : desc_stats_request.unpack,
+ const.OFPMP_FLOW : flow_stats_request.unpack,
+ const.OFPMP_AGGREGATE : aggregate_stats_request.unpack,
+ const.OFPMP_TABLE : table_stats_request.unpack,
+ const.OFPMP_PORT_STATS : port_stats_request.unpack,
+ const.OFPMP_QUEUE : queue_stats_request.unpack,
+ const.OFPMP_GROUP : group_stats_request.unpack,
+ const.OFPMP_GROUP_DESC : group_desc_stats_request.unpack,
+ const.OFPMP_GROUP_FEATURES : group_features_stats_request.unpack,
+ const.OFPMP_METER : meter_stats_request.unpack,
+ const.OFPMP_METER_CONFIG : meter_config_stats_request.unpack,
+ const.OFPMP_METER_FEATURES : meter_features_stats_request.unpack,
+ const.OFPMP_TABLE_FEATURES : table_features_stats_request.unpack,
+ const.OFPMP_PORT_DESC : port_desc_stats_request.unpack,
+}
experimenter_parsers = {
6035143 : {
diff --git a/src/python/loxi/of13/oxm.py b/src/python/loxi/of13/oxm.py
index fb1291d..f17151e 100644
--- a/src/python/loxi/of13/oxm.py
+++ b/src/python/loxi/of13/oxm.py
@@ -566,114 +566,6 @@
q.breakable()
q.text('}')
-class dst_meta_id(OXM):
- type_len = 258561
-
- def __init__(self, value=None):
- if value != None:
- self.value = value
- else:
- self.value = 0
-
- def pack(self):
- packed = []
- packed.append(struct.pack("!L", self.type_len))
- packed.append(struct.pack("!B", self.value))
- return ''.join(packed)
-
- @staticmethod
- def unpack(buf):
- obj = dst_meta_id()
- if type(buf) == loxi.generic_util.OFReader:
- reader = buf
- else:
- reader = loxi.generic_util.OFReader(buf)
- _type_len = reader.read("!L")[0]
- assert(_type_len == 258561)
- obj.value = reader.read("!B")[0]
- return obj
-
- def __eq__(self, other):
- if type(self) != type(other): return False
- if self.value != other.value: return False
- return True
-
- def __ne__(self, other):
- return not self.__eq__(other)
-
- def show(self):
- import loxi.pp
- return loxi.pp.pp(self)
-
- def pretty_print(self, q):
- q.text("dst_meta_id {")
- with q.group():
- with q.indent(2):
- q.breakable()
- q.text("value = ");
- q.text("%#x" % self.value)
- q.breakable()
- q.text('}')
-
-class dst_meta_id_masked(OXM):
- type_len = 258818
-
- def __init__(self, value=None, value_mask=None):
- if value != None:
- self.value = value
- else:
- self.value = 0
- if value_mask != None:
- self.value_mask = value_mask
- else:
- self.value_mask = 0
-
- def pack(self):
- packed = []
- packed.append(struct.pack("!L", self.type_len))
- packed.append(struct.pack("!B", self.value))
- packed.append(struct.pack("!B", self.value_mask))
- return ''.join(packed)
-
- @staticmethod
- def unpack(buf):
- obj = dst_meta_id_masked()
- if type(buf) == loxi.generic_util.OFReader:
- reader = buf
- else:
- reader = loxi.generic_util.OFReader(buf)
- _type_len = reader.read("!L")[0]
- assert(_type_len == 258818)
- obj.value = reader.read("!B")[0]
- obj.value_mask = reader.read("!B")[0]
- return obj
-
- def __eq__(self, other):
- if type(self) != type(other): return False
- if self.value != other.value: return False
- if self.value_mask != other.value_mask: return False
- return True
-
- def __ne__(self, other):
- return not self.__eq__(other)
-
- def show(self):
- import loxi.pp
- return loxi.pp.pp(self)
-
- def pretty_print(self, q):
- q.text("dst_meta_id_masked {")
- with q.group():
- with q.indent(2):
- q.breakable()
- q.text("value = ");
- q.text("%#x" % self.value)
- q.text(","); q.breakable()
- q.text("value_mask = ");
- q.text("%#x" % self.value_mask)
- q.breakable()
- q.text('}')
-
class eth_dst(OXM):
type_len = 2147485190
@@ -3374,114 +3266,6 @@
q.breakable()
q.text('}')
-class src_meta_id(OXM):
- type_len = 258049
-
- def __init__(self, value=None):
- if value != None:
- self.value = value
- else:
- self.value = 0
-
- def pack(self):
- packed = []
- packed.append(struct.pack("!L", self.type_len))
- packed.append(struct.pack("!B", self.value))
- return ''.join(packed)
-
- @staticmethod
- def unpack(buf):
- obj = src_meta_id()
- if type(buf) == loxi.generic_util.OFReader:
- reader = buf
- else:
- reader = loxi.generic_util.OFReader(buf)
- _type_len = reader.read("!L")[0]
- assert(_type_len == 258049)
- obj.value = reader.read("!B")[0]
- return obj
-
- def __eq__(self, other):
- if type(self) != type(other): return False
- if self.value != other.value: return False
- return True
-
- def __ne__(self, other):
- return not self.__eq__(other)
-
- def show(self):
- import loxi.pp
- return loxi.pp.pp(self)
-
- def pretty_print(self, q):
- q.text("src_meta_id {")
- with q.group():
- with q.indent(2):
- q.breakable()
- q.text("value = ");
- q.text("%#x" % self.value)
- q.breakable()
- q.text('}')
-
-class src_meta_id_masked(OXM):
- type_len = 258306
-
- def __init__(self, value=None, value_mask=None):
- if value != None:
- self.value = value
- else:
- self.value = 0
- if value_mask != None:
- self.value_mask = value_mask
- else:
- self.value_mask = 0
-
- def pack(self):
- packed = []
- packed.append(struct.pack("!L", self.type_len))
- packed.append(struct.pack("!B", self.value))
- packed.append(struct.pack("!B", self.value_mask))
- return ''.join(packed)
-
- @staticmethod
- def unpack(buf):
- obj = src_meta_id_masked()
- if type(buf) == loxi.generic_util.OFReader:
- reader = buf
- else:
- reader = loxi.generic_util.OFReader(buf)
- _type_len = reader.read("!L")[0]
- assert(_type_len == 258306)
- obj.value = reader.read("!B")[0]
- obj.value_mask = reader.read("!B")[0]
- return obj
-
- def __eq__(self, other):
- if type(self) != type(other): return False
- if self.value != other.value: return False
- if self.value_mask != other.value_mask: return False
- return True
-
- def __ne__(self, other):
- return not self.__eq__(other)
-
- def show(self):
- import loxi.pp
- return loxi.pp.pp(self)
-
- def pretty_print(self, q):
- q.text("src_meta_id_masked {")
- with q.group():
- with q.indent(2):
- q.breakable()
- q.text("value = ");
- q.text("%#x" % self.value)
- q.text(","); q.breakable()
- q.text("value_mask = ");
- q.text("%#x" % self.value_mask)
- q.breakable()
- q.text('}')
-
class tcp_dst(OXM):
type_len = 2147490818
@@ -4132,10 +3916,6 @@
parsers = {
- 258049 : src_meta_id.unpack,
- 258306 : src_meta_id_masked.unpack,
- 258561 : dst_meta_id.unpack,
- 258818 : dst_meta_id_masked.unpack,
2147483652 : in_port.unpack,
2147483912 : in_port_masked.unpack,
2147484164 : in_phy_port.unpack,
diff --git a/src/python/oftest/testutils.py b/src/python/oftest/testutils.py
index 821f962..7e1b2c8 100644
--- a/src/python/oftest/testutils.py
+++ b/src/python/oftest/testutils.py
@@ -40,9 +40,15 @@
logging.info("Deleting all flows")
msg = ofp.message.flow_delete()
- msg.match.wildcards = ofp.OFPFW_ALL
- msg.out_port = ofp.OFPP_NONE
- msg.buffer_id = 0xffffffff
+ if ofp.OFP_VERSION in [1, 2]:
+ msg.match.wildcards = ofp.OFPFW_ALL
+ msg.out_port = ofp.OFPP_NONE
+ msg.buffer_id = 0xffffffff
+ elif ofp.OFP_VERSION >= 3:
+ msg.table_id = ofp.OFPTT_ALL
+ msg.buffer_id = ofp.OFP_NO_BUFFER
+ msg.out_port = ofp.OFPP_ANY
+ msg.out_group = ofp.OFPG_ANY
ctrl.message_send(msg)
do_barrier(ctrl)
return 0 # for backwards compatibility
@@ -357,16 +363,28 @@
@returns (hwaddr, config, advert) The hwaddress, configuration and
advertised values
"""
- request = ofp.message.features_request()
- reply, pkt = controller.transact(request)
- logging.debug(reply.show())
- if reply is None:
- logging.warn("Get feature request failed")
- return None, None, None
- for idx in range(len(reply.ports)):
- if reply.ports[idx].port_no == port_no:
- return (reply.ports[idx].hw_addr, reply.ports[idx].config,
- reply.ports[idx].advertised)
+
+ if ofp.OFP_VERSION <= 3:
+ request = ofp.message.features_request()
+ reply, _ = controller.transact(request)
+ if reply is None:
+ logging.warn("Get feature request failed")
+ return None, None, None
+ logging.debug(reply.show())
+ ports = reply.ports
+ else:
+ request = ofp.message.port_desc_stats_request()
+ # TODO do multipart correctly
+ reply, _ = controller.transact(request)
+ if reply is None:
+ logging.warn("Port desc stats request failed")
+ return None, None, None
+ logging.debug(reply.show())
+ ports = reply.entries
+
+ for port in ports:
+ if port.port_no == port_no:
+ return (port.hw_addr, port.config, port.advertised)
logging.warn("Did not find port number for port config")
return None, None, None
@@ -379,24 +397,16 @@
configuration value according to config and mask
"""
logging.info("Setting port " + str(port_no) + " to config " + str(config))
- request = ofp.message.features_request()
- reply, pkt = controller.transact(request)
- if reply is None:
- return -1
- logging.debug(reply.show())
- p = None
- for idx in range(len(reply.ports)):
- if reply.ports[idx].port_no == port_no:
- p = reply.ports[idx]
- break
+
+ hw_addr, _, _ = port_config_get(controller, port_no)
+
mod = ofp.message.port_mod()
mod.port_no = port_no
- if p:
- mod.hw_addr = p.hw_addr
+ if hw_addr != None:
+ mod.hw_addr = hw_addr
mod.config = config
mod.mask = mask
- if p:
- mod.advertise = p.advertised
+ mod.advertise = 0 # No change
controller.message_send(mod)
return 0
@@ -545,7 +555,11 @@
def packet_to_flow_match(parent, packet):
match = oftest.parse.packet_to_flow_match(packet)
- match.wildcards |= required_wildcards(parent)
+ if ofp.OFP_VERSION in [1, 2]:
+ match.wildcards |= required_wildcards(parent)
+ else:
+ # TODO remove incompatible OXM entries
+ pass
return match
def flow_msg_create(parent, pkt, ing_port=None, action_list=None, wildcards=None,
@@ -1144,25 +1158,51 @@
"""
Retrieve a list of stats entries. Handles OFPSF_REPLY_MORE.
"""
+ if ofp.OFP_VERSION <= 3:
+ more_flag = ofp.OFPSF_REPLY_MORE
+ else:
+ more_flag = ofp.OFPMPF_REPLY_MORE
stats = []
reply, _ = test.controller.transact(req)
test.assertTrue(reply is not None, "No response to stats request")
stats.extend(reply.entries)
- while reply.flags & ofp.OFPSF_REPLY_MORE != 0:
+ while reply.flags & more_flag != 0:
reply, pkt = self.controller.poll(exp_msg=ofp.OFPT_STATS_REPLY)
test.assertTrue(reply is not None, "No response to stats request")
stats.extend(reply.entries)
return stats
-def get_flow_stats(test, match, table_id=0xff, out_port=None):
+def get_flow_stats(test, match, table_id=None,
+ out_port=None, out_group=None,
+ cookie=0, cookie_mask=0):
"""
Retrieve a list of flow stats entries.
"""
+
+ if table_id == None:
+ if ofp.OFP_VERSION <= 2:
+ table_id = 0xff
+ else:
+ table_id = ofp.OFPTT_ALL
+
if out_port == None:
- out_port = ofp.OFPP_NONE
+ if ofp.OFP_VERSION == 1:
+ out_port = ofp.OFPP_NONE
+ else:
+ out_port = ofp.OFPP_ANY
+
+ if out_group == None:
+ if ofp.OFP_VERSION > 1:
+ out_group = ofp.OFPP_ANY
+
req = ofp.message.flow_stats_request(match=match,
- table_id=table_id,
- out_port=out_port)
+ table_id=table_id,
+ out_port=out_port)
+ if ofp.OFP_VERSION > 1:
+ req.out_group = out_group
+ req.cookie = cookie
+ req.cookie_mask = cookie_mask
+
return get_stats(test, req)
def get_port_stats(test, port_no):
@@ -1323,11 +1363,21 @@
@param reason Expected packet_in reason, or None
"""
- if in_port and in_port != msg.in_port:
+ if ofp.OFP_VERSION <= 2:
+ pkt_in_port = msg.in_port
+ else:
+ oxms = { type(oxm): oxm for oxm in msg.match.oxm_list }
+ if ofp.oxm.in_port in oxms:
+ pkt_in_port = oxms[ofp.oxm.in_port].value
+ else:
+ logging.warn("Missing in_port in packet-in message")
+ pkt_in_port = None
+
+ if in_port != None and in_port != pkt_in_port:
logging.debug("Incorrect packet_in in_port (expected %d, received %d)", in_port, msg.in_port)
return False
- if reason and reason != msg.reason:
+ if reason != None and reason != msg.reason:
logging.debug("Incorrect packet_in reason (expected %d, received %d)", reason, msg.reason)
return False
diff --git a/tests-1.3/basic.py b/tests-1.3/basic.py
index f096940..c8e6758 100644
--- a/tests-1.3/basic.py
+++ b/tests-1.3/basic.py
@@ -1,9 +1,16 @@
+# Distributed under the OpenFlow Software License (see LICENSE)
+# Copyright (c) 2010 The Board of Trustees of The Leland Stanford Junior University
+# Copyright (c) 2012, 2013 Big Switch Networks, Inc.
+# Copyright (c) 2012, 2013 CPqD
+# Copyright (c) 2012, 2013 Ericsson
"""
Basic test cases
Test cases in other modules depend on this functionality.
"""
+import logging
+
from oftest import config
import oftest.base_tests as base_tests
import ofp
@@ -25,3 +32,446 @@
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):
+ data = 'OpenFlow Will Rule The World'
+ request = ofp.message.echo_request(data=data)
+ response, _ = 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(request.data, response.data,
+ 'response data != request data')
+
+class FeaturesRequest(base_tests.SimpleProtocol):
+ """
+ Test features_request to make sure we get a response
+
+ Does NOT test the contents; just that we get a response
+ """
+ def runTest(self):
+ request = ofp.message.features_request()
+ response,_ = self.controller.transact(request)
+ self.assertTrue(response is not None,
+ 'Did not get features reply')
+
+class OutputExact(base_tests.SimpleDataPlane):
+ """
+ Test output function for an exact-match flow
+
+ For each port A, adds a flow directing matching packets to that port.
+ Then, for all other ports B != A, verifies that sending a matching packet
+ to B results in an output to A.
+ """
+ def runTest(self):
+ ports = sorted(config["port_map"].keys())
+
+ delete_all_flows(self.controller)
+
+ parsed_pkt = simple_tcp_packet()
+ pkt = str(parsed_pkt)
+ match = packet_to_flow_match(self, parsed_pkt)
+
+ for out_port in ports:
+ request = ofp.message.flow_add(
+ table_id=0,
+ cookie=42,
+ match=match,
+ instructions=[
+ ofp.instruction.apply_actions(
+ actions=[
+ ofp.action.output(
+ port=out_port,
+ max_len=ofp.OFPCML_NO_BUFFER)])],
+ buffer_id=ofp.OFP_NO_BUFFER,
+ priority=1000)
+
+ logging.info("Inserting flow sending matching packets to port %d", out_port)
+ self.controller.message_send(request)
+ do_barrier(self.controller)
+
+ for in_port in ports:
+ if in_port == out_port:
+ continue
+ logging.info("OutputExact test, ports %d to %d", in_port, out_port)
+ self.dataplane.send(in_port, pkt)
+ receive_pkt_verify(self, [out_port], pkt, in_port)
+
+class PacketInExact(base_tests.SimpleDataPlane):
+ """
+ Test packet in function for an exact-match flow
+
+ Send a packet to each dataplane port and verify that a packet
+ in message is received from the controller for each
+ """
+ def runTest(self):
+ delete_all_flows(self.controller)
+
+ parsed_pkt = simple_tcp_packet()
+ pkt = str(parsed_pkt)
+ match = packet_to_flow_match(self, parsed_pkt)
+
+ request = ofp.message.flow_add(
+ table_id=0,
+ cookie=42,
+ match=match,
+ instructions=[
+ ofp.instruction.apply_actions(
+ actions=[
+ ofp.action.output(
+ port=ofp.OFPP_CONTROLLER,
+ max_len=ofp.OFPCML_NO_BUFFER)])],
+ buffer_id=ofp.OFP_NO_BUFFER,
+ priority=1000)
+
+ logging.info("Inserting flow sending matching packets to controller")
+ self.controller.message_send(request)
+ do_barrier(self.controller)
+
+ for of_port in config["port_map"].keys():
+ logging.info("PacketInExact test, port %d", of_port)
+ self.dataplane.send(of_port, pkt)
+ verify_packet_in(self, pkt, of_port, ofp.OFPR_ACTION)
+
+class PacketInMiss(base_tests.SimpleDataPlane):
+ """
+ Test packet in function for a table-miss flow
+
+ Send a packet to each dataplane port and verify that a packet
+ in message is received from the controller for each
+ """
+ def runTest(self):
+ delete_all_flows(self.controller)
+
+ parsed_pkt = simple_tcp_packet()
+ pkt = str(parsed_pkt)
+
+ request = ofp.message.flow_add(
+ table_id=0,
+ cookie=42,
+ instructions=[
+ ofp.instruction.apply_actions(
+ actions=[
+ ofp.action.output(
+ port=ofp.OFPP_CONTROLLER,
+ max_len=ofp.OFPCML_NO_BUFFER)])],
+ buffer_id=ofp.OFP_NO_BUFFER,
+ priority=0)
+
+ logging.info("Inserting table-miss flow sending all packets to controller")
+ self.controller.message_send(request)
+ do_barrier(self.controller)
+
+ for of_port in config["port_map"].keys():
+ logging.info("PacketInMiss test, port %d", of_port)
+ self.dataplane.send(of_port, pkt)
+ verify_packet_in(self, pkt, of_port, ofp.OFPR_NO_MATCH)
+
+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):
+ pkt = str(simple_tcp_packet())
+
+ for of_port in config["port_map"].keys():
+ msg = ofp.message.packet_out(
+ in_port=ofp.OFPP_CONTROLLER,
+ actions=[ofp.action.output(port=of_port)],
+ buffer_id=ofp.OFP_NO_BUFFER,
+ data=pkt)
+
+ logging.info("PacketOut test, port %d", of_port)
+ self.controller.message_send(msg)
+ receive_pkt_verify(self, [of_port], pkt, ofp.OFPP_CONTROLLER)
+
+class FlowRemoveAll(base_tests.SimpleProtocol):
+ """
+ Remove all flows; required for almost all tests
+
+ Add a bunch of flows, remove them, and then make sure there are no flows left
+ This is an intentionally naive test to see if the baseline functionality works
+ and should be a precondition to any more complicated deletion test (e.g.,
+ delete_strict vs. delete)
+ """
+ def runTest(self):
+ for i in range(1,5):
+ logging.debug("Adding flow %d", i)
+ request = ofp.message.flow_add(
+ buffer_id=ofp.OFP_NO_BUFFER,
+ priority=i*1000)
+ self.controller.message_send(request)
+ do_barrier(self.controller)
+
+ delete_all_flows(self.controller)
+
+ logging.info("Sending flow stats request")
+ stats = get_flow_stats(self, ofp.match())
+ self.assertEqual(len(stats), 0, "Expected empty flow stats reply")
+
+
+## Multipart messages
+
+class DescStats(base_tests.SimpleProtocol):
+ """
+ Switch description multipart transaction
+
+ Only verifies we get a single reply.
+ """
+ def runTest(self):
+ request = ofp.message.desc_stats_request()
+ logging.info("Sending desc stats request")
+ response, _ = self.controller.transact(request)
+ self.assertTrue(response != None, "No response to desc stats request")
+ logging.info(response.show())
+ self.assertEquals(response.flags, 0, "Unexpected bit set in desc stats reply flags")
+
+class FlowStats(base_tests.SimpleProtocol):
+ """
+ Flow stats multipart transaction
+
+ Only verifies we get a reply.
+ """
+ def runTest(self):
+ logging.info("Sending flow stats request")
+ stats = get_flow_stats(self, ofp.match())
+ logging.info("Received %d flow stats entries", len(stats))
+ for entry in stats:
+ logging.info(entry.show())
+
+class AggregateStats(base_tests.SimpleProtocol):
+ """
+ Aggregate flow stats multipart transaction
+
+ Only verifies we get a single reply.
+ """
+ def runTest(self):
+ request = ofp.message.aggregate_stats_request(
+ table_id=ofp.OFPTT_ALL,
+ out_port=ofp.OFPP_ANY,
+ out_group=ofp.OFPG_ANY,
+ cookie=0,
+ cookie_mask=0)
+ logging.info("Sending aggregate flow stats request")
+ response, _ = self.controller.transact(request)
+ self.assertTrue(response != None, "No response to aggregate stats request")
+ logging.info(response.show())
+ self.assertEquals(response.flags, 0, "Unexpected bit set in aggregate stats reply flags")
+
+class TableStats(base_tests.SimpleProtocol):
+ """
+ Table stats multipart transaction
+
+ Only verifies we get a reply.
+ """
+ def runTest(self):
+ logging.info("Sending table stats request")
+ stats = get_stats(self, ofp.message.table_stats_request())
+ logging.info("Received %d table stats entries", len(stats))
+ for entry in stats:
+ logging.info(entry.show())
+
+class PortStats(base_tests.SimpleProtocol):
+ """
+ Port stats multipart transaction
+
+ Only verifies we get a reply.
+ """
+ def runTest(self):
+ request = ofp.message.port_stats_request(port_no=ofp.OFPP_ANY)
+ logging.info("Sending port stats request")
+ stats = get_stats(self, request)
+ logging.info("Received %d port stats entries", len(stats))
+ for entry in stats:
+ logging.info(entry.show())
+
+class QueueStats(base_tests.SimpleProtocol):
+ """
+ Queue stats multipart transaction
+
+ Only verifies we get a reply.
+ """
+ def runTest(self):
+ request = ofp.message.queue_stats_request(port_no=ofp.OFPP_ANY,
+ queue_id=ofp.OFPQ_ALL)
+ logging.info("Sending queue stats request")
+ stats = get_stats(self, request)
+ logging.info("Received %d queue stats entries", len(stats))
+ for entry in stats:
+ logging.info(entry.show())
+
+class GroupStats(base_tests.SimpleProtocol):
+ """
+ Group stats multipart transaction
+
+ Only verifies we get a reply.
+ """
+ def runTest(self):
+ request = ofp.message.group_stats_request(group_id=ofp.OFPG_ALL)
+ logging.info("Sending group stats request")
+ stats = get_stats(self, request)
+ logging.info("Received %d group stats entries", len(stats))
+ for entry in stats:
+ logging.info(entry.show())
+
+class GroupDescStats(base_tests.SimpleProtocol):
+ """
+ Group description multipart transaction
+
+ Only verifies we get a reply.
+ """
+ def runTest(self):
+ request = ofp.message.group_desc_stats_request()
+ logging.info("Sending group desc stats request")
+ stats = get_stats(self, request)
+ logging.info("Received %d group desc stats entries", len(stats))
+ for entry in stats:
+ logging.info(entry.show())
+
+class GroupFeaturesStats(base_tests.SimpleProtocol):
+ """
+ Group features multipart transaction
+
+ Only verifies we get a single reply.
+ """
+ def runTest(self):
+ request = ofp.message.group_features_stats_request()
+ logging.info("Sending group features stats request")
+ response, _ = self.controller.transact(request)
+ self.assertTrue(response != None, "No response to group features stats request")
+ logging.info(response.show())
+ self.assertEquals(response.flags, 0, "Unexpected bit set in group features stats reply flags")
+
+class MeterStats(base_tests.SimpleProtocol):
+ """
+ Meter stats multipart transaction
+
+ Only verifies we get a reply.
+ """
+ def runTest(self):
+ request = ofp.message.meter_stats_request(meter_id=ofp.OFPM_ALL)
+ logging.info("Sending meter stats request")
+ stats = get_stats(self, request)
+ logging.info("Received %d meter stats entries", len(stats))
+ for entry in stats:
+ logging.info(entry.show())
+
+class MeterConfigStats(base_tests.SimpleProtocol):
+ """
+ Meter config multipart transaction
+
+ Only verifies we get a reply.
+ """
+ def runTest(self):
+ request = ofp.message.meter_config_stats_request(meter_id=ofp.OFPM_ALL)
+ logging.info("Sending meter config stats request")
+ stats = get_stats(self, request)
+ logging.info("Received %d meter config stats entries", len(stats))
+ for entry in stats:
+ logging.info(entry.show())
+
+class MeterFeaturesStats(base_tests.SimpleProtocol):
+ """
+ Meter features multipart transaction
+
+ Only verifies we get a single reply.
+ """
+ def runTest(self):
+ request = ofp.message.meter_features_stats_request()
+ logging.info("Sending meter features stats request")
+ response, _ = self.controller.transact(request)
+ self.assertTrue(response != None, "No response to meter features stats request")
+ logging.info(response.show())
+ self.assertEquals(response.flags, 0, "Unexpected bit set in meter features stats reply flags")
+
+@disabled # pyloxi does not yet support table features
+class TableFeaturesStats(base_tests.SimpleProtocol):
+ """
+ Table features multipart transaction
+
+ Only verifies we get a reply.
+ """
+ def runTest(self):
+ logging.info("Sending table features stats request")
+ stats = get_stats(self, ofp.message.table_features_stats_request())
+ logging.info("Received %d table features stats entries", len(stats))
+ for entry in stats:
+ logging.info(entry.show())
+
+class PortDescStats(base_tests.SimpleProtocol):
+ """
+ Port description multipart transaction
+
+ Only verifies we get a reply.
+ """
+ def runTest(self):
+ logging.info("Sending port desc stats request")
+ stats = get_stats(self, ofp.message.port_desc_stats_request())
+ logging.info("Received %d port desc stats entries", len(stats))
+ for entry in stats:
+ logging.info(entry.show())
+
+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, _ in config["port_map"].items(): # Grab first port
+ break
+
+ (_, config1, _) = \
+ port_config_get(self.controller, of_port)
+ self.assertTrue(config is not None, "Did not get port config")
+
+ logging.debug("OFPPC_NO_PACKET_IN bit port " + str(of_port) + " is now " +
+ str(config1 & ofp.OFPPC_NO_PACKET_IN))
+
+ rv = port_config_set(self.controller, of_port,
+ config1 ^ ofp.OFPPC_NO_PACKET_IN,
+ ofp.OFPPC_NO_PACKET_IN)
+ self.assertTrue(rv != -1, "Error sending port mod")
+
+ # Verify change took place with same feature request
+ (_, config2, _) = port_config_get(self.controller, of_port)
+ self.assertTrue(config2 is not None, "Did not get port config2")
+ logging.debug("OFPPC_NO_PACKET_IN bit port " + str(of_port) + " is now " +
+ str(config2 & ofp.OFPPC_NO_PACKET_IN))
+ self.assertTrue(config2 & ofp.OFPPC_NO_PACKET_IN !=
+ config1 & ofp.OFPPC_NO_PACKET_IN,
+ "Bit change did not take")
+ # Set it back
+ rv = port_config_set(self.controller, of_port, config1,
+ ofp.OFPPC_NO_PACKET_IN)
+ self.assertTrue(rv != -1, "Error sending port mod")
+
+class AsyncConfigGet(base_tests.SimpleProtocol):
+ """
+ Verify initial async config
+
+ Other tests rely on connections starting with these values.
+ """
+
+ def runTest(self):
+ logging.info("Sending get async config request")
+ response, _ = self.controller.transact(ofp.message.async_get_request())
+ self.assertTrue(response != None, "No response to get async config request")
+ logging.info(response.show())
+ self.assertEquals(response.packet_in_mask_equal_master & 0x07, 0x07)
+ self.assertEquals(response.port_status_mask_equal_master & 0x07, 0x07)
+ self.assertEquals(response.flow_removed_mask_equal_master & 0x0f, 0x0f)