summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorIWASE Yusuke <iwase.yusuke0@gmail.com>2015-10-26 17:30:54 +0900
committerFUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>2015-10-28 14:32:02 +0900
commit74d5de78031d113d8313bc57a5cc4be6b91ef63a (patch)
tree4c692ab88486f13908f4a6c98976effc3c8e658c
parent7c7fb56ad67990bc3674730d658b0e574177af64 (diff)
tester: Support to test OpenFlow1.0 switch
Signed-off-by: IWASE Yusuke <iwase.yusuke0@gmail.com> Signed-off-by: FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>
-rw-r--r--ryu/flags.py6
-rw-r--r--ryu/tests/switch/tester.py307
2 files changed, 140 insertions, 173 deletions
diff --git a/ryu/flags.py b/ryu/flags.py
index a77bebd0..e1fabf84 100644
--- a/ryu/flags.py
+++ b/ryu/flags.py
@@ -46,10 +46,12 @@ CONF.register_cli_opts([
cfg.StrOpt('dir', default='ryu/tests/switch/of13',
help='test files directory'),
cfg.StrOpt('target-version', default='openflow13',
- help='target sw OFP version [openflow13|openflow14] '
+ help='target sw OFP version '
+ '[openflow10|openflow13|openflow14] '
'(default: openflow13)'),
cfg.StrOpt('tester-version', default='openflow13',
- help='tester sw OFP version [openflow13|openflow14] '
+ help='tester sw OFP version '
+ '[openflow10|openflow13|openflow14] '
'(default: openflow13)'),
cfg.IntOpt('interval', default=0,
help='interval time in seconds of each test '
diff --git a/ryu/tests/switch/tester.py b/ryu/tests/switch/tester.py
index c01e8b93..0912a57c 100644
--- a/ryu/tests/switch/tester.py
+++ b/ryu/tests/switch/tester.py
@@ -48,9 +48,11 @@ from ryu.lib import dpid as dpid_lib
from ryu.lib import hub
from ryu.lib import stringify
from ryu.lib.packet import packet
+from ryu.ofproto import ofproto_parser
from ryu.ofproto import ofproto_protocol
+from ryu.ofproto import ofproto_v1_0
+from ryu.ofproto import ofproto_v1_2
from ryu.ofproto import ofproto_v1_3
-from ryu.ofproto import ofproto_v1_3_parser
from ryu.ofproto import ofproto_v1_4
from ryu.ofproto import ofproto_v1_5
@@ -289,6 +291,7 @@ class OfTester(app_manager.RyuApp):
def __get_version(opt):
vers = {
+ 'openflow10': ofproto_v1_0.OFP_VERSION,
'openflow13': ofproto_v1_3.OFP_VERSION,
'openflow14': ofproto_v1_4.OFP_VERSION,
'openflow15': ofproto_v1_5.OFP_VERSION
@@ -297,9 +300,8 @@ class OfTester(app_manager.RyuApp):
if ver is None:
self.logger.error(
'%s is not supported. '
- 'Supported versions are openflow13, '
- 'openflow14 and openflow15.',
- opt)
+ 'Supported versions are %s.',
+ opt, list(vers.keys()))
self._test_end()
return ver
@@ -364,6 +366,7 @@ class OfTester(app_manager.RyuApp):
def _register_sw(self, dp):
vers = {
+ ofproto_v1_0.OFP_VERSION: 'openflow10',
ofproto_v1_3.OFP_VERSION: 'openflow13',
ofproto_v1_4.OFP_VERSION: 'openflow14',
ofproto_v1_5.OFP_VERSION: 'openflow15'
@@ -381,9 +384,6 @@ class OfTester(app_manager.RyuApp):
vers[OfTester.tester_ver]
else:
self.tester_sw.dp = dp
- self.tester_sw.add_flow(
- in_port=self.tester_recv_port_1,
- out_port=dp.ofproto.OFPP_CONTROLLER)
msg = 'Join tester SW.'
else:
msg = 'Connect unknown SW.'
@@ -456,8 +456,8 @@ class OfTester(app_manager.RyuApp):
self._test(STATE_INIT_METER)
self._test(STATE_INIT_GROUP)
self._test(STATE_INIT_FLOW, self.target_sw)
- self._test(STATE_INIT_THROUGHPUT_FLOW, self.tester_sw,
- THROUGHPUT_COOKIE)
+ self._test(STATE_INIT_THROUGHPUT_FLOW, self.tester_sw)
+
# Install flows.
for flow in test.prerequisite:
if isinstance(
@@ -599,8 +599,17 @@ class OfTester(app_manager.RyuApp):
self.state = state
return test[state](*args)
- def _test_initialize_flow(self, datapath, cookie=0):
- xid = datapath.del_flows(cookie)
+ def _test_initialize_flow(self, datapath):
+ # Note: Because DELETE and DELETE_STRICT commands in OpenFlow 1.0
+ # can not be filtered by the cookie value, this tool deletes all
+ # flow entries of the tester switch temporarily and inserts default
+ # flow entry immediately.
+ xid = datapath.del_flows()
+ self.send_msg_xids.append(xid)
+
+ xid = datapath.add_flow(
+ in_port=self.tester_recv_port_1,
+ out_port=datapath.dp.ofproto.OFPP_CONTROLLER)
self.send_msg_xids.append(xid)
xid = datapath.send_barrier_request()
@@ -624,21 +633,24 @@ class OfTester(app_manager.RyuApp):
assert isinstance(msg, datapath.dp.ofproto_parser.OFPBarrierReply)
def _test_exist_check(self, method, message):
+ ofp = method.__self__.dp.ofproto
parser = method.__self__.dp.ofproto_parser
method_dict = {
OpenFlowSw.send_flow_stats.__name__: {
'reply': parser.OFPFlowStatsReply,
'compare': self._compare_flow
- },
- OpenFlowSw.send_meter_config_stats.__name__: {
- 'reply': parser.OFPMeterConfigStatsReply,
- 'compare': self._compare_meter
- },
- OpenFlowSw.send_group_desc_stats.__name__: {
+ }
+ }
+ if ofp.OFP_VERSION >= ofproto_v1_2.OFP_VERSION:
+ method_dict[OpenFlowSw.send_group_desc_stats.__name__] = {
'reply': parser.OFPGroupDescStatsReply,
'compare': self._compare_group
}
- }
+ if ofp.OFP_VERSION >= ofproto_v1_3.OFP_VERSION:
+ method_dict[OpenFlowSw.send_meter_config_stats.__name__] = {
+ 'reply': parser.OFPMeterConfigStatsReply,
+ 'compare': self._compare_meter
+ }
xid = method()
self.send_msg_xids.append(xid)
self._wait()
@@ -655,13 +667,18 @@ class OfTester(app_manager.RyuApp):
ng_stats.append(stats)
error_dict = {
- OpenFlowSw.send_flow_stats.__name__:
- {'flows': ', '.join(ng_stats)},
- OpenFlowSw.send_meter_config_stats.__name__:
- {'meters': ', '.join(ng_stats)},
- OpenFlowSw.send_group_desc_stats.__name__:
- {'groups': ', '.join(ng_stats)}
+ OpenFlowSw.send_flow_stats.__name__: {
+ 'flows': ', '.join(ng_stats)
+ }
}
+ if ofp.OFP_VERSION >= ofproto_v1_2.OFP_VERSION:
+ error_dict[OpenFlowSw.send_group_desc_stats.__name__] = {
+ 'groups': ', '.join(ng_stats)
+ }
+ if ofp.OFP_VERSION >= ofproto_v1_3.OFP_VERSION:
+ error_dict[OpenFlowSw.send_meter_config_stats.__name__] = {
+ 'meters': ', '.join(ng_stats)
+ }
raise TestFailure(self.state, **error_dict[method.__name__])
def _test_get_packet_count(self, is_target):
@@ -711,15 +728,17 @@ class OfTester(app_manager.RyuApp):
else pkt[KEY_PKT_IN])
if hasattr(msg.datapath.ofproto, "OFPR_NO_MATCH"):
- table_miss_value = msg.datapath.ofproto.OFPR_NO_MATCH
+ invalid_packet_in_reason = [msg.datapath.ofproto.OFPR_NO_MATCH]
else:
- table_miss_value = msg.datapath.ofproto.OFPR_TABLE_MISS
+ invalid_packet_in_reason = [msg.datapath.ofproto.OFPR_TABLE_MISS]
+ if hasattr(msg.datapath.ofproto, "OFPR_INVALID_TTL"):
+ invalid_packet_in_reason.append(
+ msg.datapath.ofproto.OFPR_INVALID_TTL)
if msg.datapath.id != pkt_in_src_model.dp.id:
pkt_type = 'packet-in'
err_msg = 'SW[dpid=%s]' % dpid_lib.dpid_to_str(msg.datapath.id)
- elif msg.reason == table_miss_value or \
- msg.reason == msg.datapath.ofproto.OFPR_INVALID_TTL:
+ elif msg.reason in invalid_packet_in_reason:
pkt_type = 'packet-in'
err_msg = 'OFPPacketIn[reason=%d]' % msg.reason
elif repr(msg.data) != repr(model_pkt):
@@ -878,45 +897,26 @@ class OfTester(app_manager.RyuApp):
def __reasm_match(match):
""" reassemble match_fields. """
- mask_lengths = {'vlan_vid': 12 + 1,
- 'ipv6_flabel': 20,
- 'ipv6_exthdr': 9}
- match_fields = list()
- for key, united_value in match.items():
- if isinstance(united_value, tuple):
- (value, mask) = united_value
- # look up oxm_fields.TypeDescr to get mask length.
- for ofb in stats2.datapath.ofproto.oxm_types:
- if ofb.name == key:
- # create all one bits mask
- mask_len = mask_lengths.get(
- key, ofb.type.size * 8)
- all_one_bits = 2 ** mask_len - 1
- # convert mask to integer
- mask_bytes = ofb.type.from_user(mask)
- oxm_mask = int(binascii.hexlify(mask_bytes), 16)
- # when mask is all one bits, remove mask
- if oxm_mask & all_one_bits == all_one_bits:
- united_value = value
- # when mask is all zero bits, remove field.
- elif oxm_mask & all_one_bits == 0:
- united_value = None
- break
- if united_value is not None:
- match_fields.append((key, united_value))
+ match_fields = match.to_jsondict()
+ # For only OpenFlow1.0
+ match_fields['OFPMatch'].pop('wildcards', None)
return match_fields
attr_list = ['cookie', 'priority', 'hard_timeout', 'idle_timeout',
- 'table_id', 'instructions', 'match']
+ 'match']
+ if self.target_sw.dp.ofproto.OFP_VERSION == ofproto_v1_0.OFP_VERSION:
+ attr_list += ['actions']
+ else:
+ attr_list += ['table_id', 'instructions']
for attr in attr_list:
value1 = getattr(stats1, attr)
value2 = getattr(stats2, attr)
- if attr == 'instructions':
+ if attr in ['actions', 'instructions']:
value1 = sorted(value1, key=lambda x: x.type)
value2 = sorted(value2, key=lambda x: x.type)
elif attr == 'match':
- value1 = sorted(__reasm_match(value1))
- value2 = sorted(__reasm_match(value2))
+ value1 = __reasm_match(value1)
+ value2 = __reasm_match(value2)
if str(value1) != str(value2):
flow_stats = []
for attr in attr_list:
@@ -1084,27 +1084,31 @@ class OfTester(app_manager.RyuApp):
def stats_reply_handler(self, ev):
# keys: stats reply event classes
# values: states in which the events should be processed
+ ofp = ev.msg.datapath.ofproto
event_states = {
ofp_event.EventOFPFlowStatsReply:
[STATE_FLOW_EXIST_CHK,
STATE_THROUGHPUT_FLOW_EXIST_CHK,
STATE_GET_THROUGHPUT],
- ofp_event.EventOFPMeterConfigStatsReply:
- [STATE_METER_EXIST_CHK],
ofp_event.EventOFPTableStatsReply:
[STATE_GET_MATCH_COUNT,
STATE_FLOW_UNMATCH_CHK],
ofp_event.EventOFPPortStatsReply:
[STATE_TARGET_PKT_COUNT,
STATE_TESTER_PKT_COUNT],
- ofp_event.EventOFPGroupDescStatsReply:
- [STATE_GROUP_EXIST_CHK]
}
+ if ofp.OFP_VERSION >= ofproto_v1_2.OFP_VERSION:
+ event_states[ofp_event.EventOFPGroupDescStatsReply] = [
+ STATE_GROUP_EXIST_CHK
+ ]
+ if ofp.OFP_VERSION >= ofproto_v1_3.OFP_VERSION:
+ event_states[ofp_event.EventOFPMeterConfigStatsReply] = [
+ STATE_METER_EXIST_CHK
+ ]
if self.state in event_states[ev.__class__]:
if self.waiter and ev.msg.xid in self.send_msg_xids:
self.rcv_msgs.append(ev.msg)
- if not ev.msg.flags & \
- ev.msg.datapath.ofproto.OFPMPF_REPLY_MORE:
+ if not ev.msg.flags:
self.waiter.set()
hub.sleep(0)
@@ -1165,38 +1169,48 @@ class OpenFlowSw(object):
""" Add flow. """
ofp = self.dp.ofproto
parser = self.dp.ofproto_parser
-
match = parser.OFPMatch(in_port=in_port)
- max_len = (0 if out_port != ofp.OFPP_CONTROLLER
- else ofp.OFPCML_MAX)
- actions = [parser.OFPActionOutput(out_port, max_len)]
- inst = [parser.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS,
- actions)]
- mod = parser.OFPFlowMod(self.dp, cookie=0,
- command=ofp.OFPFC_ADD,
- match=match, instructions=inst)
+ actions = [parser.OFPActionOutput(out_port)]
+ if ofp.OFP_VERSION == ofproto_v1_0.OFP_VERSION:
+ mod = parser.OFPFlowMod(
+ self.dp, match=match, cookie=0, command=ofp.OFPFC_ADD,
+ actions=actions)
+ else:
+ inst = [parser.OFPInstructionActions(
+ ofp.OFPIT_APPLY_ACTIONS, actions)]
+ mod = parser.OFPFlowMod(
+ self.dp, cookie=0, command=ofp.OFPFC_ADD, match=match,
+ instructions=inst)
return self.send_msg(mod)
def del_flows(self, cookie=0):
- """ Delete all flow except default flow. """
+ """
+ Delete all flow except default flow by using the cookie value.
+
+ Note: In OpenFlow 1.0, DELETE and DELETE_STRICT commands can
+ not be filtered by the cookie value and this value is ignored.
+ """
ofp = self.dp.ofproto
parser = self.dp.ofproto_parser
cookie_mask = 0
if cookie:
cookie_mask = 0xffffffffffffffff
- mod = parser.OFPFlowMod(self.dp,
- cookie=cookie,
- cookie_mask=cookie_mask,
- table_id=ofp.OFPTT_ALL,
- command=ofp.OFPFC_DELETE,
- out_port=ofp.OFPP_ANY,
- out_group=ofp.OFPG_ANY)
+ if ofp.OFP_VERSION == ofproto_v1_0.OFP_VERSION:
+ match = parser.OFPMatch()
+ mod = parser.OFPFlowMod(self.dp, match, cookie, ofp.OFPFC_DELETE)
+ else:
+ mod = parser.OFPFlowMod(
+ self.dp, cookie=cookie, cookie_mask=cookie_mask,
+ table_id=ofp.OFPTT_ALL, command=ofp.OFPFC_DELETE,
+ out_port=ofp.OFPP_ANY, out_group=ofp.OFPG_ANY)
return self.send_msg(mod)
def del_meters(self):
""" Delete all meter entries. """
ofp = self.dp.ofproto
parser = self.dp.ofproto_parser
+ if ofp.OFP_VERSION < ofproto_v1_3.OFP_VERSION:
+ return None
mod = parser.OFPMeterMod(self.dp,
command=ofp.OFPMC_DELETE,
flags=0,
@@ -1204,8 +1218,11 @@ class OpenFlowSw(object):
return self.send_msg(mod)
def del_groups(self):
+ """ Delete all group entries. """
ofp = self.dp.ofproto
parser = self.dp.ofproto_parser
+ if ofp.OFP_VERSION < ofproto_v1_2.OFP_VERSION:
+ return None
mod = parser.OFPGroupMod(self.dp,
command=ofp.OFPGC_DELETE,
type_=0,
@@ -1223,26 +1240,41 @@ class OpenFlowSw(object):
ofp = self.dp.ofproto
parser = self.dp.ofproto_parser
flags = 0
- req = parser.OFPPortStatsRequest(self.dp, flags, ofp.OFPP_ANY)
+ if ofp.OFP_VERSION == ofproto_v1_0.OFP_VERSION:
+ port = ofp.OFPP_NONE
+ else:
+ port = ofp.OFPP_ANY
+ req = parser.OFPPortStatsRequest(self.dp, flags, port)
return self.send_msg(req)
def send_flow_stats(self):
""" Get all flow. """
ofp = self.dp.ofproto
parser = self.dp.ofproto_parser
- req = parser.OFPFlowStatsRequest(self.dp, 0, ofp.OFPTT_ALL,
- ofp.OFPP_ANY, ofp.OFPG_ANY,
- 0, 0, parser.OFPMatch())
+ if ofp.OFP_VERSION == ofproto_v1_0.OFP_VERSION:
+ req = parser.OFPFlowStatsRequest(
+ self.dp, 0, parser.OFPMatch(), 0xff, ofp.OFPP_NONE)
+ else:
+ req = parser.OFPFlowStatsRequest(
+ self.dp, 0, ofp.OFPTT_ALL, ofp.OFPP_ANY, ofp.OFPG_ANY,
+ 0, 0, parser.OFPMatch())
return self.send_msg(req)
def send_meter_config_stats(self):
""" Get all meter. """
+ ofp = self.dp.ofproto
parser = self.dp.ofproto_parser
+ if ofp.OFP_VERSION < ofproto_v1_3.OFP_VERSION:
+ return None
stats = parser.OFPMeterConfigStatsRequest(self.dp)
return self.send_msg(stats)
def send_group_desc_stats(self):
+ """ Get all group. """
+ ofp = self.dp.ofproto
parser = self.dp.ofproto_parser
+ if ofp.OFP_VERSION < ofproto_v1_2.OFP_VERSION:
+ return None
stats = parser.OFPGroupDescStatsRequest(self.dp)
return self.send_msg(stats)
@@ -1350,36 +1382,9 @@ class Test(stringify.StringifyMixin):
data.serialize()
return six.binary_type(data.data)
- def __normalize_match(ofproto, match):
- match_json = match.to_jsondict()
- oxm_fields = match_json['OFPMatch']['oxm_fields']
- fields = []
- for field in oxm_fields:
- field_obj = ofproto.oxm_from_jsondict(field)
- field_obj = ofproto.oxm_normalize_user(*field_obj)
- fields.append(field_obj)
- return match.__class__(_ordered_fields=fields)
-
- def __normalize_action(ofproto, action):
- action_json = action.to_jsondict()
- field = action_json['OFPActionSetField']['field']
- field_obj = ofproto.oxm_from_jsondict(field)
- field_obj = ofproto.oxm_normalize_user(*field_obj)
- kwargs = {}
- kwargs[field_obj[0]] = field_obj[1]
- return action.__class__(**kwargs)
-
- # get ofproto modules using user-specified versions
- (target_ofproto, target_parser) = ofproto_protocol._versions[
- OfTester.target_ver]
- (tester_ofproto, tester_parser) = ofproto_protocol._versions[
- OfTester.tester_ver]
- target_dp = DummyDatapath()
- target_dp.ofproto = target_ofproto
- target_dp.ofproto_parser = target_parser
- tester_dp = DummyDatapath()
- tester_dp.ofproto = tester_ofproto
- tester_dp.ofproto_parser = tester_parser
+ # create Datapath instance using user-specified versions
+ target_dp = DummyDatapath(OfTester.target_ver)
+ tester_dp = DummyDatapath(OfTester.tester_ver)
# parse 'description'
description = buf.get(KEY_DESC)
@@ -1388,51 +1393,9 @@ class Test(stringify.StringifyMixin):
prerequisite = []
if KEY_PREREQ not in buf:
raise ValueError('a test requires a "%s" block' % KEY_PREREQ)
- allowed_mod = [KEY_FLOW, KEY_METER, KEY_GROUP]
for flow in buf[KEY_PREREQ]:
- key, value = flow.popitem()
- if key not in allowed_mod:
- raise ValueError(
- '"%s" block allows only the followings: %s' % (
- KEY_PREREQ, allowed_mod))
- cls = getattr(target_parser, key)
- msg = cls.from_jsondict(value, datapath=target_dp)
- msg.version = target_ofproto.OFP_VERSION
- msg.msg_type = msg.cls_msg_type
- msg.xid = 0
- if isinstance(msg, target_parser.OFPFlowMod):
- # normalize OFPMatch
- msg.match = __normalize_match(target_ofproto, msg.match)
- # normalize OFPActionSetField
- insts = []
- for inst in msg.instructions:
- if isinstance(inst, target_parser.OFPInstructionActions):
- acts = []
- for act in inst.actions:
- if isinstance(
- act, target_parser.OFPActionSetField):
- act = __normalize_action(target_ofproto, act)
- acts.append(act)
- inst = target_parser.OFPInstructionActions(
- inst.type, actions=acts)
- insts.append(inst)
- msg.instructions = insts
- elif isinstance(msg, target_parser.OFPGroupMod):
- # normalize OFPActionSetField
- buckets = []
- for bucket in msg.buckets:
- acts = []
- for act in bucket.actions:
- if isinstance(act, target_parser.OFPActionSetField):
- act = __normalize_action(target_ofproto, act)
- acts.append(act)
- bucket = target_parser.OFPBucket(
- weight=bucket.weight,
- watch_port=bucket.watch_port,
- watch_group=bucket.watch_group,
- actions=acts)
- buckets.append(bucket)
- msg.buckets = buckets
+ msg = ofproto_parser.ofp_msg_from_jsondict(
+ target_dp, flow)
msg.serialize()
prerequisite.append(msg)
@@ -1476,14 +1439,17 @@ class Test(stringify.StringifyMixin):
throughputs = []
for throughput in test[KEY_EGRESS][KEY_THROUGHPUT]:
one = {}
- mod = {'match': {'OFPMatch': throughput[KEY_MATCH]}}
- cls = getattr(tester_parser, KEY_FLOW)
- msg = cls.from_jsondict(
- mod, datapath=tester_dp,
- cookie=THROUGHPUT_COOKIE,
- priority=THROUGHPUT_PRIORITY)
- msg.match = __normalize_match(
- tester_ofproto, msg.match)
+ mod = {
+ "OFPFlowMod": {
+ 'cookie': THROUGHPUT_COOKIE,
+ 'priority': THROUGHPUT_PRIORITY,
+ 'match': {
+ 'OFPMatch': throughput[KEY_MATCH]
+ }
+ }
+ }
+ msg = ofproto_parser.ofp_msg_from_jsondict(
+ tester_dp, mod)
one[KEY_FLOW] = msg
one[KEY_KBPS] = throughput.get(KEY_KBPS)
one[KEY_PKTPS] = throughput.get(KEY_PKTPS)
@@ -1505,10 +1471,9 @@ class Test(stringify.StringifyMixin):
return (description, prerequisite, tests)
-class DummyDatapath(object):
- def __init__(self):
- self.ofproto = ofproto_v1_3
- self.ofproto_parser = ofproto_v1_3_parser
+class DummyDatapath(ofproto_protocol.ProtocolDesc):
+ def __init__(self, version=None):
+ super(DummyDatapath, self).__init__(version)
def set_xid(self, _):
pass