From ebed2d0182856074635fe35ce419b9a540ce1204 Mon Sep 17 00:00:00 2001 From: Taoyu Li Date: Fri, 7 Apr 2017 01:10:05 -0700 Subject: [PATCH] [config/acl] Support everflow config translation (#468) --- sonic-slave/Dockerfile | 1 + src/sonic-config-engine/MANIFEST.in | 2 +- src/sonic-config-engine/minigraph.py | 11 +- .../sample_output/rules_for_dataacl.json | 29 ++ .../sample_output/rules_for_everflow.json | 14 + .../tests/sample_output/table_dataacl.json | 10 + .../tests/sample_output/table_everflow.json | 10 + .../tests/t0-sample-acl.json | 97 ++++++ .../tests/t0-sample-graph-everflow.xml | 320 ++++++++++++++++++ src/sonic-config-engine/tests/test_acl.py | 58 ++++ src/sonic-config-engine/tests/test_cfggen.py | 2 +- src/sonic-config-engine/translate_acl | 31 +- 12 files changed, 567 insertions(+), 18 deletions(-) create mode 100644 src/sonic-config-engine/tests/sample_output/rules_for_dataacl.json create mode 100644 src/sonic-config-engine/tests/sample_output/rules_for_everflow.json create mode 100644 src/sonic-config-engine/tests/sample_output/table_dataacl.json create mode 100644 src/sonic-config-engine/tests/sample_output/table_everflow.json create mode 100644 src/sonic-config-engine/tests/t0-sample-acl.json create mode 100644 src/sonic-config-engine/tests/t0-sample-graph-everflow.xml create mode 100644 src/sonic-config-engine/tests/test_acl.py diff --git a/sonic-slave/Dockerfile b/sonic-slave/Dockerfile index bec18bfbae..6ca6e93c5f 100644 --- a/sonic-slave/Dockerfile +++ b/sonic-slave/Dockerfile @@ -91,6 +91,7 @@ RUN apt-get update && apt-get install -y docutils-common libjs-sphinxdoc libjs-u # For sonic config engine testing RUN apt-get install -y python-lxml python-jinja2 python-netaddr python-ipaddr python-yaml +RUN pip install pyangbind # For templating RUN pip install j2cli diff --git a/src/sonic-config-engine/MANIFEST.in b/src/sonic-config-engine/MANIFEST.in index a7038d546e..8861dd6603 100644 --- a/src/sonic-config-engine/MANIFEST.in +++ b/src/sonic-config-engine/MANIFEST.in @@ -1 +1 @@ -recursive-include tests *.j2 *.yml *.xml +recursive-include tests *.j2 *.yml *.xml *.json diff --git a/src/sonic-config-engine/minigraph.py b/src/sonic-config-engine/minigraph.py index 2b02045647..709a7a2846 100644 --- a/src/sonic-config-engine/minigraph.py +++ b/src/sonic-config-engine/minigraph.py @@ -218,9 +218,10 @@ def parse_dpg(dpg, hname): aclintfs = child.find(str(QName(ns, "AclInterfaces"))) acls = {} for aclintf in aclintfs.findall(str(QName(ns, "AclInterface"))): - aclname = aclintf.find(str(QName(ns, "InAcl"))).text + aclname = aclintf.find(str(QName(ns, "InAcl"))).text.lower().replace(" ", "_").replace("-", "_") aclattach = aclintf.find(str(QName(ns, "AttachTo"))).text.split(';') acl_intfs = [] + is_mirror = False for member in aclattach: member = member.strip() if pcs.has_key(member): @@ -229,9 +230,13 @@ def parse_dpg(dpg, hname): print >> sys.stderr, "Warning: ACL " + aclname + " is attached to a Vlan interface, which is currently not supported" elif port_alias_map.has_key(member): acl_intfs.append(port_alias_map[member]) + elif member.lower() == 'erspan': + is_mirror = True; + # Erspan session will be attached to all front panel ports + acl_intfs = port_alias_map.values() + break; if acl_intfs: - acls[aclname] = acl_intfs - + acls[aclname] = { 'AttachTo': acl_intfs, 'IsMirror': is_mirror } return intfs, lo_intfs, mgmt_intf, vlans, pcs, acls return None, None, None, None, None, None diff --git a/src/sonic-config-engine/tests/sample_output/rules_for_dataacl.json b/src/sonic-config-engine/tests/sample_output/rules_for_dataacl.json new file mode 100644 index 0000000000..9fd0199c4c --- /dev/null +++ b/src/sonic-config-engine/tests/sample_output/rules_for_dataacl.json @@ -0,0 +1,29 @@ +[ + { + "ACL_RULE_TABLE:dataacl:Rule_1":{ + "IP_PROTOCOL":17, + "PACKET_ACTION":"FORWARD", + "SRC_IP":"10.0.0.0/8", + "priority":9999 + }, + "OP":"SET" + }, + { + "ACL_RULE_TABLE:dataacl:Rule_3":{ + "IP_PROTOCOL":17, + "PACKET_ACTION":"FORWARD", + "SRC_IP":"25.0.0.0/8", + "priority":9997 + }, + "OP":"SET" + }, + { + "ACL_RULE_TABLE:dataacl:Rule_2":{ + "IP_PROTOCOL":17, + "PACKET_ACTION":"FORWARD", + "SRC_IP":"100.64.0.0/10", + "priority":9998 + }, + "OP":"SET" + } +] \ No newline at end of file diff --git a/src/sonic-config-engine/tests/sample_output/rules_for_everflow.json b/src/sonic-config-engine/tests/sample_output/rules_for_everflow.json new file mode 100644 index 0000000000..c31965e31e --- /dev/null +++ b/src/sonic-config-engine/tests/sample_output/rules_for_everflow.json @@ -0,0 +1,14 @@ +[ + { + "ACL_RULE_TABLE:everflow:Rule_1":{ + "DST_IP":"127.0.0.1/32", + "IP_PROTOCOL":6, + "L4_DST_PORT":0, + "L4_SRC_PORT":0, + "MIRROR_ACTION":"everflow", + "SRC_IP":"127.0.0.1/32", + "priority":9999 + }, + "OP":"SET" + } +] \ No newline at end of file diff --git a/src/sonic-config-engine/tests/sample_output/table_dataacl.json b/src/sonic-config-engine/tests/sample_output/table_dataacl.json new file mode 100644 index 0000000000..ed10f31956 --- /dev/null +++ b/src/sonic-config-engine/tests/sample_output/table_dataacl.json @@ -0,0 +1,10 @@ +[ + { + "ACL_TABLE:dataacl":{ + "policy_desc":"dataacl", + "ports":"Ethernet112,Ethernet116,Ethernet120,Ethernet124", + "type":"L3" + }, + "OP":"SET" + } +] \ No newline at end of file diff --git a/src/sonic-config-engine/tests/sample_output/table_everflow.json b/src/sonic-config-engine/tests/sample_output/table_everflow.json new file mode 100644 index 0000000000..4f3411ed88 --- /dev/null +++ b/src/sonic-config-engine/tests/sample_output/table_everflow.json @@ -0,0 +1,10 @@ +[ + { + "ACL_TABLE:everflow":{ + "policy_desc":"everflow", + "ports":"Ethernet24,Ethernet40,Ethernet20,Ethernet44,Ethernet48,Ethernet28,Ethernet96,Ethernet92,Ethernet76,Ethernet116,Ethernet72,Ethernet112,Ethernet52,Ethernet108,Ethernet56,Ethernet32,Ethernet16,Ethernet36,Ethernet12,Ethernet120,Ethernet8,Ethernet4,Ethernet0,Ethernet124,Ethernet68,Ethernet84,Ethernet100,Ethernet80,Ethernet60,Ethernet104,Ethernet64,Ethernet88", + "type":"mirror" + }, + "OP":"SET" + } +] \ No newline at end of file diff --git a/src/sonic-config-engine/tests/t0-sample-acl.json b/src/sonic-config-engine/tests/t0-sample-acl.json new file mode 100644 index 0000000000..e04971e5f2 --- /dev/null +++ b/src/sonic-config-engine/tests/t0-sample-acl.json @@ -0,0 +1,97 @@ +{ + "acl": { + "acl-sets": { + "acl-set": { + "dataacl": { + "acl-entries": { + "acl-entry": { + "1": { + "actions": { + "config": { + "forwarding-action": "ACCEPT" + } + }, + "config": { + "sequence-id": 1 + }, + "ip": { + "config": { + "protocol": "IP_UDP", + "source-ip-address": "10.0.0.0/8" + } + } + }, + "2": { + "actions": { + "config": { + "forwarding-action": "ACCEPT" + } + }, + "config": { + "sequence-id": 2 + }, + "ip": { + "config": { + "protocol": "IP_UDP", + "source-ip-address": "100.64.0.0/10" + } + } + }, + "3": { + "actions": { + "config": { + "forwarding-action": "ACCEPT" + } + }, + "config": { + "sequence-id": 3 + }, + "ip": { + "config": { + "protocol": "IP_UDP", + "source-ip-address": "25.0.0.0/8" + } + } + } + } + }, + "config": { + "name": "dataacl" + } + }, + "everflow": { + "acl-entries": { + "acl-entry": { + "1": { + "actions": { + "config": { + "forwarding-action": "ACCEPT" + } + }, + "config": { + "sequence-id": 1 + }, + "ip": { + "config": { + "destination-ip-address": "127.0.0.1/32", + "protocol": "IP_TCP", + "source-ip-address": "127.0.0.1/32" + } + }, + "transport": { + "config": { + "destination-port": "0", + "source-port": "0" + } + } + } + } + }, + "config": { + "name": "everflow" + } + } + } + } + } +} diff --git a/src/sonic-config-engine/tests/t0-sample-graph-everflow.xml b/src/sonic-config-engine/tests/t0-sample-graph-everflow.xml new file mode 100644 index 0000000000..355f01122f --- /dev/null +++ b/src/sonic-config-engine/tests/t0-sample-graph-everflow.xml @@ -0,0 +1,320 @@ + + + + + + false + switch-t0 + 10.0.0.56 + ARISTA01T1 + 10.0.0.57 + 1 + 180 + 60 + + + switch-t0 + FC00::71 + ARISTA01T1 + FC00::72 + 1 + 180 + 60 + + + false + switch-t0 + 10.0.0.58 + ARISTA02T1 + 10.0.0.59 + 1 + 180 + 60 + + + switch-t0 + FC00::75 + ARISTA02T1 + FC00::76 + 1 + 180 + 60 + + + false + switch-t0 + 10.0.0.60 + ARISTA03T1 + 10.0.0.61 + 1 + 180 + 60 + + + switch-t0 + FC00::79 + ARISTA03T1 + FC00::7A + 1 + 180 + 60 + + + false + switch-t0 + 10.0.0.62 + ARISTA04T1 + 10.0.0.63 + 1 + 180 + 60 + + + switch-t0 + FC00::7D + ARISTA04T1 + FC00::7E + 1 + 180 + 60 + + + + + 65100 + switch-t0 + + +
10.0.0.57
+ + + +
+ +
10.0.0.59
+ + + +
+ +
10.0.0.61
+ + + +
+ +
10.0.0.63
+ + + +
+
+ +
+ + 64600 + ARISTA01T1 + + + + 64600 + ARISTA02T1 + + + + 64600 + ARISTA03T1 + + + + 64600 + ARISTA04T1 + + +
+
+ + + + + + HostIP + Loopback0 + + 10.1.0.32/32 + + 10.1.0.32/32 + + + HostIP1 + Loopback0 + + FC00:1::32/128 + + FC00:1::32/128 + + + + + HostIP + eth0 + + 10.0.0.100/24 + + 10.0.0.100/24 + + + + + + + switch-t0 + + + PortChannel01 + fortyGigE0/112 + + + + PortChannel02 + fortyGigE0/116 + + + + PortChannel03 + fortyGigE0/120 + + + + PortChannel04 + fortyGigE0/124 + + + + + + Vlan1000 + fortyGigE0/4;fortyGigE0/8;fortyGigE0/12;fortyGigE0/16;fortyGigE0/20;fortyGigE0/24;fortyGigE0/28;fortyGigE0/32;fortyGigE0/36;fortyGigE0/40;fortyGigE0/44;fortyGigE0/48;fortyGigE0/52;fortyGigE0/56;fortyGigE0/60;fortyGigE0/64;fortyGigE0/68;fortyGigE0/72;fortyGigE0/76;fortyGigE0/80;fortyGigE0/84;fortyGigE0/88;fortyGigE0/92;fortyGigE0/96 + False + 0.0.0.0/0 + + 1000 + 1000 + 192.168.0.0/27 + + + + + + PortChannel01 + 10.0.0.56/31 + + + + PortChannel01 + FC00::71/126 + + + + PortChannel02 + 10.0.0.58/31 + + + + PortChannel02 + FC00::75/126 + + + + PortChannel03 + 10.0.0.60/31 + + + + PortChannel03 + FC00::79/126 + + + + PortChannel04 + 10.0.0.62/31 + + + + PortChannel04 + FC00::7D/126 + + + + Vlan1000 + 192.168.0.1/27 + + + + + + ERSPAN + everflow + + + + + + + + + + DeviceInterfaceLink + ARISTA01T1 + Ethernet1/1 + switch-t0 + fortyGigE0/112 + + + DeviceInterfaceLink + ARISTA02T1 + Ethernet1/1 + switch-t0 + fortyGigE0/116 + + + DeviceInterfaceLink + ARISTA03T1 + Ethernet1/1 + switch-t0 + fortyGigE0/120 + + + DeviceInterfaceLink + ARISTA04T1 + Ethernet1/1 + switch-t0 + fortyGigE0/124 + + + + + switch-t0 + Force10-S6000 + + + ARISTA01T1 + Arista + + + ARISTA02T1 + Arista + + + ARISTA03T1 + Arista + + + ARISTA04T1 + Arista + + + + switch-t0 + Force10-S6000 +
diff --git a/src/sonic-config-engine/tests/test_acl.py b/src/sonic-config-engine/tests/test_acl.py new file mode 100644 index 0000000000..319b2c1f73 --- /dev/null +++ b/src/sonic-config-engine/tests/test_acl.py @@ -0,0 +1,58 @@ +import filecmp +import os +import subprocess + +from unittest import TestCase + +class TestAcl(TestCase): + def setUp(self): + self.test_dir = os.path.dirname(os.path.realpath(__file__)) + self.script_file = os.path.join(self.test_dir, '..', 'sonic-cfggen') + self.acl_script_file = os.path.join(self.test_dir, '..', 'translate_acl') + self.t0_minigraph = os.path.join(self.test_dir, 't0-sample-graph.xml') + self.t0_minigraph_everflow = os.path.join(self.test_dir, 't0-sample-graph-everflow.xml') + self.t0_acl = os.path.join(self.test_dir, 't0-sample-acl.json') + self.t0_port_config = os.path.join(self.test_dir, 't0-sample-port-config.ini') + + def run_script(self, argument): + print 'CMD: sonic-cfggen ' + argument + output = '' + try: + output = subprocess.check_output(self.script_file + ' ' + argument, shell=True, stderr=subprocess.STDOUT) + except subprocess.CalledProcessError, (p): + print 'CalledProcessError: CMD:%s returncode:%s' % (p.cmd, p.returncode) + print p.output + return output + + def run_acl_script(self, argument): + print 'CMD: translate_acl ' + argument + output = '' + try: + output = subprocess.check_output(self.acl_script_file + ' ' + argument, shell=True, stderr=subprocess.STDOUT) + except subprocess.CalledProcessError, (p): + print 'CalledProcessError: CMD:%s returncode:%s' % (p.cmd, p.returncode) + print p.output + return output + + def test_translate_acl(self): + argument = '-m ' + self.t0_minigraph + ' -p ' + self.t0_port_config + ' -o ' + self.test_dir + ' ' + self.t0_acl + self.run_acl_script(argument) + for filename in ['rules_for_dataacl.json','table_dataacl.json']: + sample_output_file = os.path.join(self.test_dir, 'sample_output', filename) + output_file = os.path.join(self.test_dir, filename) + assert filecmp.cmp(sample_output_file, output_file) + + def test_translate_everflow(self): + argument = '-m ' + self.t0_minigraph_everflow + ' -p ' + self.t0_port_config + ' -o ' + self.test_dir + ' ' + self.t0_acl + self.run_acl_script(argument) + for filename in ['rules_for_everflow.json','table_everflow.json']: + sample_output_file = os.path.join(self.test_dir, 'sample_output', filename) + output_file = os.path.join(self.test_dir, filename) + assert filecmp.cmp(sample_output_file, output_file) + + def tearDown(self): + for filename in ['rules_for_dataacl.json','table_dataacl.json','rules_for_everflow.json','table_everflow.json']: + try: + os.remove(os.path.join(self.test_dir, filename)) + except OSError: + pass diff --git a/src/sonic-config-engine/tests/test_cfggen.py b/src/sonic-config-engine/tests/test_cfggen.py index e4017a51f8..83c678fff7 100644 --- a/src/sonic-config-engine/tests/test_cfggen.py +++ b/src/sonic-config-engine/tests/test_cfggen.py @@ -60,7 +60,7 @@ class TestCfgGen(TestCase): def test_minigraph_acl(self): argument = '-m "' + self.sample_graph_t0 + '" -p "' + self.port_config + '" -v minigraph_acls' output = self.run_script(argument) - self.assertEqual(output.strip(), "{'DataAcl': ['Ethernet112', 'Ethernet116', 'Ethernet120', 'Ethernet124']}") + self.assertEqual(output.strip(), "{'dataacl': {'IsMirror': False, 'AttachTo': ['Ethernet112', 'Ethernet116', 'Ethernet120', 'Ethernet124']}}") def test_minigraph_interfaces(self): argument = '-m "' + self.sample_graph_simple + '" -p "' + self.port_config + '" -v minigraph_interfaces' diff --git a/src/sonic-config-engine/translate_acl b/src/sonic-config-engine/translate_acl index 2c4410615d..6ee89cfd46 100755 --- a/src/sonic-config-engine/translate_acl +++ b/src/sonic-config-engine/translate_acl @@ -13,7 +13,7 @@ def dump_json(filename, data): with open(filename, 'w') as outfile: json.dump(data, outfile, indent=4, sort_keys=True, separators=(',', ':')) -def generate_rule_json(table_name, rule, max_priority): +def generate_rule_json(table_name, rule, max_priority, mirror): rule_idx = rule.config.sequence_id rule_props = {} rule_data = {} @@ -22,7 +22,10 @@ def generate_rule_json(table_name, rule, max_priority): rule_props["priority"] = max_priority - rule_idx if rule.actions.config.forwarding_action == "ACCEPT": - rule_props["PACKET_ACTION"] = "FORWARD" + if mirror: + rule_props["MIRROR_ACTION"] = "everflow" + else: + rule_props["PACKET_ACTION"] = "FORWARD" elif rule.actions.config.forwarding_action == "DROP": rule_props["PACKET_ACTION"] = "DROP" elif rule.actions.config.forwarding_action == "REJECT": @@ -97,14 +100,14 @@ def generate_rule_json(table_name, rule, max_priority): rule_props["TCP_FLAGS"] = '0x{:02x}'.format(tcp_flags) return rule_data -def generate_table_json(aclset, aclname, port, max_priority, output_path='.'): +def generate_table_json(aclset, aclname, ports, mirror, max_priority, output_path='.'): table_name = aclname.replace(" ", "_").replace("-", "_") #table_name = generate_random_table_name() table_props = {} table_props["policy_desc"] = table_name - table_props["type"] = "L3" - table_props["ports"] = port + table_props["type"] = "mirror" if mirror else "L3" + table_props["ports"] = ports table_data = [{}] table_data[0]["ACL_TABLE:"+table_name] = table_props @@ -114,7 +117,7 @@ def generate_table_json(aclset, aclname, port, max_priority, output_path='.'): rule_data = [] for aclentryname in aclset.acl_entries.acl_entry: aclentry = aclset.acl_entries.acl_entry[aclentryname] - rule_props = generate_rule_json(table_name, aclentry, max_priority) + rule_props = generate_rule_json(table_name, aclentry, max_priority, mirror) if rule_props: rule_data.append(rule_props) @@ -127,29 +130,31 @@ def translate_acl_fixed_port(filename, output_path, port, max_priority): generate_table_json(aclset, aclsetname, port, max_priority, output_path) return -def translate_acl(filename, output_path, attach_to, max_priority): +def translate_acl(filename, output_path, mini_acl, max_priority): yang_acl = pybindJSON.load(filename, openconfig_acl, "openconfig_acl") for aclsetname in yang_acl.acl.acl_sets.acl_set: tablename = aclsetname.replace(" ", "_").replace("-", "_") - if attach_to.has_key(tablename): - port = ','.join(attach_to[tablename]) + if mini_acl.has_key(tablename): + is_mirror = mini_acl[tablename]['IsMirror'] + ports = ','.join(mini_acl[tablename]['AttachTo']) aclset = yang_acl.acl.acl_sets.acl_set[aclsetname] - generate_table_json(aclset, aclsetname, port, max_priority, output_path) + generate_table_json(aclset, aclsetname, ports, is_mirror, max_priority, output_path) return def main(): parser = argparse.ArgumentParser(description="Translate openconfig ACL json into SONiC ACL jsons") parser.add_argument('input', metavar='INPUT', help='input json file in openconfig format') group = parser.add_mutually_exclusive_group(required=True) - group.add_argument('-p', '--port', help='the port(s) that this ACL is attached to') + group.add_argument('-a', '--attach-to', help='the port(s) that this ACL is attached to') group.add_argument('-m', '--minigraph', help='read ACL attaching information from minigraph') + parser.add_argument("-p", "--port-config", help="port config file, used with -m") parser.add_argument('-n', '--max-priority', type=int, default=10000, help='the priority number of the first rule in ACL entries') parser.add_argument('-o', '--output-path', default='.', help='output directory where SONiC ACL jsons will be generated') args = parser.parse_args() - if args.port: + if args.attach_to: translate_acl_fixed_port(args.input, args.output_path, args.port, args.max_priority) elif args.minigraph: - mini_data = parse_xml(args.minigraph) + mini_data = parse_xml(args.minigraph, port_config_file=args.port_config) if mini_data['minigraph_acls']: translate_acl(args.input, args.output_path, mini_data['minigraph_acls'], args.max_priority)