ofctl: Add ovs-ofctl style action string parser
This commit adds a new method called ofp_instruction_from_str, which takes an ovs-ofctl style action string and returns a list of JSON dictionaries. Currently only a few action strings are understood. Signed-off-by: IWAMOTO Toshihiro <iwamoto@valinux.co.jp> Signed-off-by: FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>
This commit is contained in:
parent
a5dca1a1e3
commit
a52c62a120
@ -52,6 +52,10 @@ class OFPTruncatedMessage(RyuException):
|
||||
super(OFPTruncatedMessage, self).__init__(msg, **kwargs)
|
||||
|
||||
|
||||
class OFPInvalidActionString(RyuException):
|
||||
message = 'unable to parse: %(action_str)s'
|
||||
|
||||
|
||||
class NetworkNotFound(RyuException):
|
||||
message = 'no such network id %(network_id)s'
|
||||
|
||||
|
324
ryu/lib/ofctl_string.py
Normal file
324
ryu/lib/ofctl_string.py
Normal file
@ -0,0 +1,324 @@
|
||||
# Copyright (C) 2017 Nippon Telegraph and Telephone Corporation.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# 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 License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import re
|
||||
|
||||
import ryu.exception
|
||||
from ryu.lib.ofctl_utils import str_to_int
|
||||
from ryu.ofproto import nicira_ext
|
||||
|
||||
|
||||
def ofp_instruction_from_str(ofproto, action_str):
|
||||
"""
|
||||
Parse an ovs-ofctl style action string and return a list of
|
||||
jsondict representations of OFPInstructionActions, which
|
||||
can then be passed to ofproto_parser.ofp_instruction_from_jsondict.
|
||||
|
||||
Please note that this is for making transition from ovs-ofctl
|
||||
easier. Please consider using OFPAction constructors when writing
|
||||
new codes.
|
||||
|
||||
This function takes the following arguments.
|
||||
|
||||
=========== =================================================
|
||||
Argument Description
|
||||
=========== =================================================
|
||||
ofproto An ofproto module.
|
||||
action_str An action string.
|
||||
=========== =================================================
|
||||
"""
|
||||
action_re = re.compile("([a-z_]+)(\([^)]*\)|[^a-z_,()][^,()]*)*")
|
||||
result = []
|
||||
while len(action_str):
|
||||
m = action_re.match(action_str)
|
||||
if not m:
|
||||
raise ryu.exception.OFPInvalidActionString(action_str=action_str)
|
||||
action_name = m.group(1)
|
||||
this_action = m.group(0)
|
||||
paren_level = this_action.count('(') - this_action.count(')')
|
||||
assert paren_level >= 0
|
||||
try:
|
||||
# Parens can be nested. Look for as many ')'s as '('s.
|
||||
if paren_level > 0:
|
||||
this_action, rest = _tokenize_paren_block(action_str, m.end(0))
|
||||
else:
|
||||
rest = action_str[m.end(0):]
|
||||
if len(rest):
|
||||
assert rest[0] == ','
|
||||
rest = rest[1:]
|
||||
except Exception:
|
||||
raise ryu.exception.OFPInvalidActionString(action_str=action_str)
|
||||
if action_name == 'drop':
|
||||
assert this_action == 'drop'
|
||||
assert len(result) == 0 and rest == ''
|
||||
return []
|
||||
converter = getattr(OfctlActionConverter, action_name, None)
|
||||
if converter is None or not callable(converter):
|
||||
raise ryu.exception.OFPInvalidActionString(action_str=action_name)
|
||||
result.append(converter(ofproto, this_action))
|
||||
action_str = rest
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _tokenize_paren_block(string, pos):
|
||||
paren_re = re.compile("[()]")
|
||||
paren_level = string[:pos].count('(') - string[:pos].count(')')
|
||||
while paren_level > 0:
|
||||
m = paren_re.search(string[pos:])
|
||||
if m.group(0) == '(':
|
||||
paren_level += 1
|
||||
else:
|
||||
paren_level -= 1
|
||||
pos += m.end(0)
|
||||
return string[:pos], string[pos:]
|
||||
|
||||
|
||||
def tokenize_ofp_instruction_arg(arg):
|
||||
"""
|
||||
Tokenize an argument portion of ovs-ofctl style action string.
|
||||
"""
|
||||
arg_re = re.compile("[^,()]*")
|
||||
try:
|
||||
rest = arg
|
||||
result = []
|
||||
while len(rest):
|
||||
m = arg_re.match(rest)
|
||||
if m.end(0) == len(rest):
|
||||
result.append(rest)
|
||||
return result
|
||||
if rest[m.end(0)] == '(':
|
||||
this_block, rest = _tokenize_paren_block(
|
||||
rest, m.end(0) + 1)
|
||||
result.append(this_block)
|
||||
elif rest[m.end(0)] == ',':
|
||||
result.append(m.group(0))
|
||||
rest = rest[m.end(0):]
|
||||
else: # is ')'
|
||||
raise Exception
|
||||
if len(rest):
|
||||
assert rest[0] == ','
|
||||
rest = rest[1:]
|
||||
return result
|
||||
except Exception:
|
||||
raise ryu.exception.OFPInvalidActionString(action_str=arg)
|
||||
|
||||
|
||||
_OXM_FIELD_OFCTL_ALIASES = {
|
||||
'tun_id': 'tunnel_id',
|
||||
'in_port': 'in_port_nxm',
|
||||
'in_port_oxm': 'in_port',
|
||||
'dl_src': 'eth_src',
|
||||
'dl_type': 'eth_type',
|
||||
'nw_src': 'ipv4_src',
|
||||
'ip_src': 'ipv4_src',
|
||||
'nw_proto': 'ip_proto',
|
||||
'nw_ecn': 'ip_ecn',
|
||||
'tp_src': 'tcp_src',
|
||||
'icmp_type': 'icmpv4_type',
|
||||
'icmp_code': 'icmpv4_code',
|
||||
'nd_target': 'ipv6_nd_target',
|
||||
'nd_sll': 'ipv6_nd_sll',
|
||||
'nd_tll': 'ipv6_nd_tll',
|
||||
# Nicira extension
|
||||
'tun_src': 'tun_ipv4_src'
|
||||
}
|
||||
|
||||
|
||||
def ofp_ofctl_field_name_to_ryu(field):
|
||||
"""Convert an ovs-ofctl field name to ryu equivalent."""
|
||||
mapped = _OXM_FIELD_OFCTL_ALIASES.get(field)
|
||||
if mapped:
|
||||
return mapped
|
||||
if field.endswith("_dst"):
|
||||
mapped = _OXM_FIELD_OFCTL_ALIASES.get(field[:-3] + "src")
|
||||
if mapped:
|
||||
return mapped[:-3] + "dst"
|
||||
return field
|
||||
|
||||
|
||||
_NXM_FIELD_MAP = dict([(key, key + '_nxm') for key in [
|
||||
'arp_sha', 'arp_tha', 'ipv6_src', 'ipv6_dst',
|
||||
'icmpv6_type', 'icmpv6_code', 'ip_ecn', 'tcp_flags']])
|
||||
_NXM_FIELD_MAP.update({
|
||||
'tun_id': 'tunnel_id_nxm', 'ip_ttl': 'nw_ttl'})
|
||||
|
||||
_NXM_OF_FIELD_MAP = dict([(key, key + '_nxm') for key in [
|
||||
'in_port', 'eth_dst', 'eth_src', 'eth_type', 'ip_proto',
|
||||
'tcp_src', 'tcp_dst', 'udp_src', 'udp_dst',
|
||||
'arp_op', 'arp_spa', 'arp_tpa']])
|
||||
_NXM_OF_FIELD_MAP.update({
|
||||
'ip_src': 'ipv4_src_nxm', 'ip_dst': 'ipv4_dst_nxm',
|
||||
'icmp_type': 'icmpv4_type_nxm', 'icmp_code': 'icmpv4_code_nxm'})
|
||||
|
||||
|
||||
def nxm_field_name_to_ryu(field):
|
||||
"""
|
||||
Convert an ovs-ofctl style NXM_/OXM_ field name to
|
||||
a ryu match field name.
|
||||
"""
|
||||
if field.endswith("_W"):
|
||||
field = field[:-2]
|
||||
prefix = field[:7]
|
||||
field = field[7:].lower()
|
||||
mapped_result = None
|
||||
|
||||
if prefix == 'NXM_NX_':
|
||||
mapped_result = _NXM_FIELD_MAP.get(field)
|
||||
elif prefix == "NXM_OF_":
|
||||
mapped_result = _NXM_OF_FIELD_MAP.get(field)
|
||||
elif prefix == "OXM_OF_":
|
||||
# no mapping needed
|
||||
pass
|
||||
else:
|
||||
raise ValueError
|
||||
if mapped_result is not None:
|
||||
return mapped_result
|
||||
return field
|
||||
|
||||
|
||||
class OfctlActionConverter(object):
|
||||
|
||||
@classmethod
|
||||
def goto_table(cls, ofproto, action_str):
|
||||
assert action_str.startswith('goto_table:')
|
||||
table_id = str_to_int(action_str[len('goto_table:'):])
|
||||
return dict(OFPInstructionGotoTable={'table_id': table_id})
|
||||
|
||||
@classmethod
|
||||
def normal(cls, ofproto, action_str):
|
||||
return cls.output(ofproto, action_str)
|
||||
|
||||
@classmethod
|
||||
def output(cls, ofproto, action_str):
|
||||
if action_str == 'normal':
|
||||
port = ofproto.OFPP_NORMAL
|
||||
else:
|
||||
assert action_str.startswith('output:')
|
||||
port = str_to_int(action_str[len('output:'):])
|
||||
return dict(OFPActionOutput={'port': port})
|
||||
|
||||
@classmethod
|
||||
def pop_vlan(cls, ofproto, action_str):
|
||||
return dict(OFPActionPopVlan={})
|
||||
|
||||
@classmethod
|
||||
def set_field(cls, ofproto, action_str):
|
||||
try:
|
||||
assert action_str.startswith("set_field:")
|
||||
value, key = action_str[len("set_field:"):].split("->", 1)
|
||||
fieldarg = dict(field=ofp_ofctl_field_name_to_ryu(key))
|
||||
m = value.find('/')
|
||||
if m >= 0:
|
||||
fieldarg['value'] = str_to_int(value[:m])
|
||||
fieldarg['mask'] = str_to_int(value[m + 1:])
|
||||
else:
|
||||
fieldarg['value'] = str_to_int(value)
|
||||
except Exception:
|
||||
raise ryu.exception.OFPInvalidActionString(action_str=action_str)
|
||||
return dict(OFPActionSetField={
|
||||
'field': {'OXMTlv': fieldarg}})
|
||||
|
||||
# NX actions
|
||||
@classmethod
|
||||
def resubmit(cls, ofproto, action_str):
|
||||
arg = action_str[len("resubmit"):]
|
||||
kwargs = {}
|
||||
try:
|
||||
if arg[0] == ':':
|
||||
kwargs['in_port'] = str_to_int(arg[1:])
|
||||
elif arg[0] == '(' and arg[-1] == ')':
|
||||
in_port, table_id = arg[1:-1].split(',')
|
||||
if in_port:
|
||||
kwargs['in_port'] = str_to_int(in_port)
|
||||
if table_id:
|
||||
kwargs['table_id'] = str_to_int(table_id)
|
||||
else:
|
||||
raise Exception
|
||||
return dict(NXActionResubmitTable=kwargs)
|
||||
except Exception:
|
||||
raise ryu.exception.OFPInvalidActionString(
|
||||
action_str=action_str)
|
||||
|
||||
@classmethod
|
||||
def conjunction(cls, ofproto, action_str):
|
||||
try:
|
||||
assert action_str.startswith('conjunction(')
|
||||
assert action_str[-1] == ')'
|
||||
args = action_str[len('conjunction('):-1].split(',')
|
||||
assert len(args) == 2
|
||||
id_ = str_to_int(args[0])
|
||||
clauses = list(map(str_to_int, args[1].split('/')))
|
||||
assert len(clauses) == 2
|
||||
return dict(NXActionConjunction={
|
||||
'clause': clauses[0] - 1,
|
||||
'n_clauses': clauses[1],
|
||||
'id': id_})
|
||||
except Exception:
|
||||
raise ryu.exception.OFPInvalidActionString(
|
||||
action_str=action_str)
|
||||
|
||||
@classmethod
|
||||
def ct(cls, ofproto, action_str):
|
||||
str_to_port = {'ftp': 21, 'tftp': 69}
|
||||
flags = 0
|
||||
zone_src = ""
|
||||
zone_ofs_nbits = 0
|
||||
recirc_table = nicira_ext.NX_CT_RECIRC_NONE
|
||||
alg = 0
|
||||
ct_actions = []
|
||||
|
||||
if len(action_str) > 2:
|
||||
if (not action_str.startswith('ct(') or
|
||||
action_str[-1] != ')'):
|
||||
raise ryu.exception.OFPInvalidActionString(
|
||||
action_str=action_str)
|
||||
rest = tokenize_ofp_instruction_arg(action_str[len('ct('):-1])
|
||||
else:
|
||||
rest = []
|
||||
for arg in rest:
|
||||
if arg == 'commit':
|
||||
flags |= nicira_ext.NX_CT_F_COMMIT
|
||||
rest = rest[len('commit'):]
|
||||
elif arg == 'force':
|
||||
flags |= nicira_ext.NX_CT_F_FORCE
|
||||
elif arg.startswith('exec('):
|
||||
ct_actions = ofp_instruction_from_str(
|
||||
ofproto, arg[len('exec('):-1])
|
||||
else:
|
||||
try:
|
||||
k, v = arg.split('=', 1)
|
||||
if k == 'table':
|
||||
recirc_table = str_to_int(v)
|
||||
elif k == 'zone':
|
||||
m = re.search('\[(\d*)\.\.(\d*)\]', v)
|
||||
if m:
|
||||
zone_ofs_nbits = nicira_ext.ofs_nbits(
|
||||
int(m.group(1)), int(m.group(2)))
|
||||
zone_src = nxm_field_name_to_ryu(
|
||||
v[:m.start(0)])
|
||||
else:
|
||||
zone_ofs_nbits = str_to_int(v)
|
||||
elif k == 'alg':
|
||||
alg = str_to_port[arg[len('alg='):]]
|
||||
except Exception:
|
||||
raise ryu.exception.OFPInvalidActionString(
|
||||
action_str=action_str)
|
||||
return dict(NXActionCT={'flags': flags,
|
||||
'zone_src': zone_src,
|
||||
'zone_ofs_nbits': zone_ofs_nbits,
|
||||
'recirc_table': recirc_table,
|
||||
'alg': alg,
|
||||
'actions': ct_actions})
|
@ -125,6 +125,58 @@ def ofp_msg_from_jsondict(dp, jsondict):
|
||||
return cls.from_jsondict(v, datapath=dp)
|
||||
|
||||
|
||||
def ofp_instruction_from_jsondict(dp, jsonlist, encap=True):
|
||||
"""
|
||||
This function is intended to be used with
|
||||
ryu.lib.ofctl_string.ofp_instruction_from_str.
|
||||
It is very similar to ofp_msg_from_jsondict, but works on
|
||||
a list of OFPInstructions/OFPActions. It also encapsulates
|
||||
OFPAction into OFPInstructionActions, as >OF1.0 OFPFlowMod
|
||||
requires that.
|
||||
|
||||
This function takes the following arguments.
|
||||
|
||||
======== ==================================================
|
||||
Argument Description
|
||||
======== ==================================================
|
||||
dp An instance of ryu.controller.Datapath.
|
||||
jsonlist A list of JSON style dictionaries.
|
||||
encap Encapsulate OFPAction into OFPInstructionActions.
|
||||
Must be false for OF10.
|
||||
======== ==================================================
|
||||
"""
|
||||
proto = dp.ofproto
|
||||
parser = dp.ofproto_parser
|
||||
result = []
|
||||
for jsondict in jsonlist:
|
||||
assert len(jsondict) == 1
|
||||
k, v = list(jsondict.items())[0]
|
||||
cls = getattr(parser, k)
|
||||
if not issubclass(cls, parser.OFPAction):
|
||||
ofpinst = getattr(parser, 'OFPInstruction', None)
|
||||
if not ofpinst or not issubclass(cls, ofpinst):
|
||||
raise ValueError("Supplied jsondict is of wrong type: %s",
|
||||
jsondict)
|
||||
result.append(cls.from_jsondict(v))
|
||||
|
||||
if not encap:
|
||||
return result
|
||||
insts = []
|
||||
actions = []
|
||||
result.append(None) # sentinel
|
||||
for act_or_inst in result:
|
||||
if isinstance(act_or_inst, parser.OFPAction):
|
||||
actions.append(act_or_inst)
|
||||
else:
|
||||
if actions:
|
||||
insts.append(parser.OFPInstructionActions(
|
||||
proto.OFPIT_APPLY_ACTIONS, actions))
|
||||
actions = []
|
||||
if act_or_inst is not None:
|
||||
insts.append(act_or_inst)
|
||||
return insts
|
||||
|
||||
|
||||
class StringifyMixin(stringify.StringifyMixin):
|
||||
_class_prefixes = ["OFP", "ONF", "MT", "NX"]
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user