CSIT-427: Honeycomb ietf-ACL tests - L2 58/3358/3
authorselias <samelias@cisco.com>
Fri, 7 Oct 2016 11:43:45 +0000 (13:43 +0200)
committerSamuel Eliáš <samelias@cisco.com>
Thu, 20 Oct 2016 08:53:16 +0000 (08:53 +0000)
 - add keywords for accessing Honeycomb's ietf-acl node
 - add variable file with ietf-acl test data
 - add ietf-acl traffic test suite
 - modify bridge domain teardown keyword to unassign interfaces
   from the bridge domain before delete

Change-Id: I6df1771f2fb9b42f30b5af8f54a384c6714f5949
Signed-off-by: selias <samelias@cisco.com>
12 files changed:
resources/libraries/python/honeycomb/HcAPIKwACL.py
resources/libraries/python/honeycomb/HcAPIKwBridgeDomain.py
resources/libraries/python/honeycomb/HcAPIKwInterfaces.py
resources/libraries/python/topology.py
resources/libraries/robot/honeycomb/access_control_lists.robot
resources/libraries/robot/honeycomb/bridge_domain.robot
resources/libraries/robot/honeycomb/interfaces.robot
resources/templates/honeycomb/config_ietf_classify_chain.url [new file with mode: 0644]
resources/test_data/honeycomb/ietf_acl.py [new file with mode: 0644]
tests/func/honeycomb/020_bridge_domain.robot
tests/func/honeycomb/021_l2_fib.robot
tests/func/honeycomb/081_ietf_acl_traffic.robot [new file with mode: 0644]

index c11e325..1042adc 100644 (file)
@@ -14,6 +14,7 @@
 """This module implements keywords to manipulate ACL data structures using
 Honeycomb REST API."""
 
+from resources.libraries.python.topology import Topology
 from resources.libraries.python.HTTPRequest import HTTPCodes
 from resources.libraries.python.honeycomb.HoneycombSetup import HoneycombError
 from resources.libraries.python.honeycomb.HoneycombUtil \
@@ -262,3 +263,154 @@ class ACLKeywords(object):
             return resp["classify-session"][0]
         except (KeyError, TypeError):
             return {}
+
+    @staticmethod
+    def create_ietf_classify_chain(node, list_name, layer, data):
+        """Create classify chain using the ietf-acl node.
+
+        :param node: Honeycomb node.
+        :param list_name: Name for the classify list.
+        :param layer: Network layer to classify on.
+        :param data: Dictionary of settings to send to Honeycomb.
+        :type node: dict
+        :type list_name: str
+        :type layer: string
+        :type data: dict
+
+        :return: Content of response.
+        :rtype: bytearray
+        :raises HoneycombError: If the operation fails.
+        """
+        if layer.lower() == "l2":
+            suffix = "eth"
+        elif layer.lower() in ("l3_ip4", "l3_ip6", "l4"):
+            raise NotImplementedError
+        else:
+            raise ValueError("Unexpected value of layer argument {0}."
+                             "Valid options are: L2, L3_IP4, L3_IP6, L4."
+                             .format(layer))
+
+        path = "/acl/ietf-access-control-list:{0}-acl/{1}".format(
+            suffix, list_name)
+
+        status_code, resp = HcUtil.put_honeycomb_data(
+            node, "config_ietf_classify_chain", data, path)
+
+        if status_code != HTTPCodes.OK:
+            raise HoneycombError(
+                "Could not create classify chain."
+                "Status code: {0}.".format(status_code))
+
+        return resp
+
+    @staticmethod
+    def set_ietf_interface_acl(node, interface, layer, direction, list_name,
+                               default_action):
+        """Assign an interface to an ietf-acl classify chain.
+
+        :param node: Honeycomb node.
+        :param interface: Name of an interface on the node.
+        :param layer: Network layer to classify packets on.
+        Valid options are: L2, L3, L4. Mixed ACL not supported yet.
+        :param direction: Classify incoming or outgiong packets.
+        Valid options are: ingress, egress
+        :param list_name: Name of an ietf-acl classify chain.
+        :param default_action: Default classifier action: permit or deny.
+        :type node: dict
+        :type interface: str or int
+        :type layer: str
+        :type direction: str
+        :type list_name: str
+        :type default_action: str
+
+        :return: Content of response.
+        :rtype: bytearray
+        :raises HoneycombError: If the operation fails.
+        """
+
+        interface = Topology.convert_interface_reference(
+            node, interface, "name")
+
+        interface = interface.replace("/", "%2F")
+
+        if direction not in ("ingress", "egress"):
+            raise ValueError("Unknown traffic direction {0}. "
+                             "Valid options are: ingress, egress."
+                             .format(direction))
+
+        path = "/interface/{0}/ietf-acl/{1}/access-lists".format(
+            interface, direction)
+
+        data = {
+                "access-lists": {
+                    "acl": [{
+                        "type": None,
+                        "name": list_name
+                    }],
+                    "default-action": default_action,
+                    "mode": None
+                    }
+                }
+
+        acl_type = "ietf-access-control-list:{suffix}-acl"
+
+        if layer.lower() == "l2":
+            data["access-lists"]["mode"] = "l2"
+            data["access-lists"]["acl"][0]["type"] = \
+                acl_type.format(suffix="eth")
+
+        elif layer.lower() in ("l3_ip4", "l3_ip6", "L4"):
+            raise NotImplementedError
+        else:
+            raise ValueError("Unknown network layer {0}. "
+                             "Valid options are: "
+                             "L2, L3_IP4, L3_IP6, L4.".format(layer))
+
+        status_code, resp = HcUtil.put_honeycomb_data(
+            node, "config_vpp_interfaces", data, path)
+
+        if status_code != HTTPCodes.OK:
+            raise HoneycombError(
+                "Could not configure ACL on interface. "
+                "Status code: {0}.".format(status_code))
+
+        return resp
+
+    @staticmethod
+    def delete_ietf_interface_acls(node, interface):
+        """Remove all ietf-acl assignments from an interface.
+
+        :param node: Honeycomb node.
+        :param interface: Name of an interface on the node.
+        :type node: dict
+        :type interface: str or int"""
+
+        interface = Topology.convert_interface_reference(
+            node, interface, "name")
+
+        interface = interface.replace("/", "%2F")
+
+        path = "/interface/{0}/ietf-acl/".format(interface)
+        status_code, _ = HcUtil.delete_honeycomb_data(
+            node, "config_vpp_interfaces", path)
+
+        if status_code != HTTPCodes.OK:
+            raise HoneycombError(
+                "Could not remove ACL assignment from interface. "
+                "Status code: {0}.".format(status_code))
+
+    @staticmethod
+    def delete_ietf_classify_chains(node):
+        """Remove all classify chains from the ietf-acl node.
+
+        :param node: Honeycomb node.
+        :type node: dict
+        """
+
+        status_code, _ = HcUtil.delete_honeycomb_data(
+            node, "config_ietf_classify_chain")
+
+        if status_code != HTTPCodes.OK:
+            raise HoneycombError(
+                "Could not remove ietf-acl chain. "
+                "Status code: {0}.".format(status_code))
index e3fd6fb..f156f09 100644 (file)
@@ -254,7 +254,7 @@ class BridgeDomainKeywords(object):
                                                        bridge_domain)
 
     @staticmethod
-    def remove_all_bds(node):
+    def remove_all_bridge_domains(node):
         """Remove all bridge domains.
 
         :param node: Honeycomb node.
@@ -266,8 +266,10 @@ class BridgeDomainKeywords(object):
         """
 
         data = {"bridge-domains": {"bridge-domain": []}}
+
         status_code, resp = HcUtil.\
             put_honeycomb_data(node, "config_bridge_domain", data)
+
         if status_code != HTTPCodes.OK:
             raise HoneycombError("Not possible to remove all bridge domains. "
                                  "Status code: {0}.".format(status_code))
index fdb9b90..83267b5 100644 (file)
@@ -16,7 +16,7 @@
 The keywords make possible to put and get configuration data and to get
 operational data.
 """
-
+from resources.libraries.python.topology import Topology
 from resources.libraries.python.HTTPRequest import HTTPCodes
 from resources.libraries.python.honeycomb.HoneycombSetup import HoneycombError
 from resources.libraries.python.honeycomb.HoneycombUtil \
@@ -222,7 +222,7 @@ class InterfaceKeywords(object):
         depending on the parameter "state".
 
         :param node: Honeycomb node.
-        :param interface: The name of interface.
+        :param interface: Interface name, key, link name or sw_if_index.
         :param state: The requested state, only "up" and "down" are valid
         values.
         :type node: dict
@@ -237,6 +237,9 @@ class InterfaceKeywords(object):
         intf_state = {"up": "true",
                       "down": "false"}
 
+        interface = Topology.convert_interface_reference(
+            node, interface, "name")
+
         intf = interface.replace("/", "%2F")
         path = "/interface/{0}".format(intf)
 
@@ -292,7 +295,7 @@ class InterfaceKeywords(object):
         """Add a new bridge domain to an interface and set its parameters.
 
         :param node: Honeycomb node.
-        :param interface: The name of interface.
+        :param interface: Interface name, key, link name or sw_if_index.
         :param bd_name: Bridge domain name.
         :param split_horizon_group: Split-horizon group name.
         :param bvi: The bridged virtual interface.
@@ -306,6 +309,9 @@ class InterfaceKeywords(object):
         :raises HoneycombError: If the interface is not present on the node.
         """
 
+        interface = Topology.convert_interface_reference(
+            node, interface, "name")
+
         v3po_l2 = {"bridge-domain": str(bd_name)}
         if split_horizon_group:
             v3po_l2["split-horizon-group"] = str(split_horizon_group)
@@ -317,6 +323,33 @@ class InterfaceKeywords(object):
         return InterfaceKeywords._set_interface_properties(
             node, interface, path, v3po_l2)
 
+    @staticmethod
+    def remove_bridge_domain_from_interface(node, interface):
+        """Remove bridge domain assignment from interface.
+
+        :param node: Honeycomb node.
+        :param interface: Interface name, key, link name or sw_if_index.
+        :type node: dict
+        :type interface: str or int
+
+        :raises HoneycombError: If the operation fails.
+        """
+
+        interface = Topology.convert_interface_reference(
+            node, interface, "name")
+
+        intf = interface.replace("/", "%2F")
+
+        path = "/interface/{0}/v3po:l2".format(intf)
+
+        status_code, _ = HcUtil.delete_honeycomb_data(
+            node, "config_vpp_interfaces", path)
+
+        if status_code != HTTPCodes.OK:
+            raise HoneycombError(
+                "Could not remove bridge domain assignment from interface "
+                "'{0}'. Status code: {1}.".format(interface, status_code))
+
     @staticmethod
     def get_bd_oper_data_from_interface(node, interface):
         """Returns operational data about bridge domain settings in the
@@ -367,7 +400,7 @@ class InterfaceKeywords(object):
 
     @staticmethod
     def configure_interface_ipv4(node, interface, param, value):
-        """Configure IPv4 parameters of interface
+        """Configure IPv4 parameters of interface.
 
         :param node: Honeycomb node.
         :param interface: The name of interface.
@@ -383,6 +416,9 @@ class InterfaceKeywords(object):
         :raises HoneycombError: If the parameter is not valid.
         """
 
+        interface = Topology.convert_interface_reference(
+            node, interface, "name")
+
         if param not in InterfaceKeywords.IPV4_PARAMS:
             raise HoneycombError("The parameter {0} is invalid.".format(param))
 
@@ -410,6 +446,9 @@ class InterfaceKeywords(object):
         :raises HoneycombError: If the provided netmask or prefix is not valid.
         """
 
+        interface = Topology.convert_interface_reference(
+            node, interface, "name")
+
         path = ("interfaces", ("interface", "name", interface), "ietf-ip:ipv4")
         if isinstance(network, basestring):
             address = {"address": [{"ip": ip_addr, "netmask": network}, ]}
@@ -438,6 +477,9 @@ class InterfaceKeywords(object):
         :raises HoneycombError: If the provided netmask or prefix is not valid.
         """
 
+        interface = Topology.convert_interface_reference(
+            node, interface, "name")
+
         path = ("interfaces", ("interface", "name", interface), "ietf-ip:ipv4",
                 "address")
         if isinstance(network, basestring):
index 0b40967..5884ddf 100644 (file)
@@ -372,7 +372,7 @@ class Topology(object):
         :return: Interface key.
         :rtype: str
 
-        :raises TypeError: If provided with invalid arguments.
+        :raises TypeError: If provided with invalid interface argument.
         :raises RuntimeError: If the interface does not exist in topology.
         """
 
index 0fd1c7e..0898a93 100644 (file)
 | | ... | \| Clear all ACL settings \| ${nodes['DUT1']} \|
 | | [Arguments] | ${node}
 | | Remove all classify tables | ${node}
+
+| Honeycomb creates ACL chain through IETF node
+| | [Documentation] | Creates classify chain through the high-level\
+| | ... | IETF-ACL node.
+| | ...
+| | ... | *Arguments:*
+| | ... | - node - Information about a DUT node. Type: dictionary
+| | ... | - acl_list_name - Name for the classify chain. Type: string
+| | ... | - layer - Classification layer (L2, L3, L4, mixed). Type: string
+| | ... | - acl_list_settings - classify rules. Type: dictionary
+| | ...
+| | ... | *Example:*
+| | ...
+| | ... | \| Honeycomb creates ACL chain through IETF node \
+| | ... | \| ${nodes['DUT1']} \| acl_test \| ${settings} \|
+| | [Arguments] | ${node} | ${acl_list_name} | ${layer} | ${acl_list_settings}
+| | Create IETF classify chain
+| | ... | ${node} | ${acl_list_name} | ${layer} | ${acl_list_settings}
+
+| Honeycomb assigns IETF-ACL chain to interface
+| | [Documentation] | Applies classification through the high-level\
+| | ... | IETF-ACL node to an interface.
+| | ...
+| | ... | *Arguments:*
+| | ... | - node - Information about a DUT node. Type: dictionary
+| | ... | - interface - Interface to apply classifier to. | Type: string
+| | ... | - layer - Classification layer (L2, L3, L4, mixed). Type: string
+| | ... | - direction - Ingress or Egress ACL. Type: string
+| | ... | - acl_list_name - Name of the classify chain to apply. Type: string
+| | ... | - default_action - Default classify action: permit or deny.\
+| | ... | Type: string
+| | ...
+| | ... | *Example:*
+| | ...
+| | ... | \| Honeycomb assigns IETF-ACL chain to interface \
+| | ... | \| ${nodes['DUT1']} \| GigabitEthernet0/8/0 \| L2 \| ingress \
+| | ... | \| acl_test \| permit \|
+| | [Arguments]
+| | ... | ${node} | ${interface} | ${layer} | ${direction} | ${acl_list_name}
+| | ... | ${default-action}
+| | Set IETF interface ACL
+| | ... | ${node} | ${interface} | ${layer} | ${direction} | ${acl_list_name}
+| | ... | ${default-action}
+
+| Clear IETF-ACL settings
+| | [Documentation] | Removes ACl assignment from interface, then deletes\
+| | ... | IETF-ACL chain.
+| | ...
+| | ... | *Arguments:*
+| | ... | - node - Information about a DUT node. Type: dictionary
+| | ... | - interface - Interface to clean classifiers from. | Type: string
+| | ...
+| | ... | *Example:*
+| | ...
+| | ... | Clear IETF-ACL settings | ${nodes['DUT1']} \| GigabitEthernet0/8/0 \|
+| | [Arguments] | ${node} | ${interface}
+| | Delete IETF interface ACLs | ${node} | ${interface}
+| | Delete IETF classify chains | ${node}
\ No newline at end of file
index 99befc9..2edf307 100644 (file)
 | | | Should contain | ${if_indices} | ${interface['sw_if_index']}
 | | | Should be equal | ${interface['shg']} | ${settings['split_horizon_group']}
 
+| Honeycomb should not show interfaces assigned to bridge domain
+| | [Documentation] | Uses Honeycomb API to verify interfaces are not assigned\
+| | ... | to bridge domain.
+| | ...
+| | ... | *Arguments:*
+| | ... | - node - Information about a DUT node. Type: dictionary
+| | ... | - interface1, interface2 - Names of interfaces to check\
+| | ... | bridge domain assignment on. Type: string
+| | ... | - bd_name - Name of the bridge domain. Type: string
+| | ...
+| | ... | *Example:*
+| | ...
+| | ... | \| Honeycomb should not show interfaces assigned to bridge domain \
+| | ... | \| ${nodes['DUT1']} \| GigabitEthernet0/8/0 \| GigabitEthernet0/9/0 \
+| | ... | \| bd-04 \|
+| | [Arguments] | ${node} | ${interface1} | ${interface2} | ${bd_name}
+| | ${if1_data_oper}= | interfaceAPI.Get interface oper data
+| | ... | ${node} | ${interface1}
+| | ${if2_data_oper}= | interfaceAPI.Get interface oper data
+| | ... | ${node} | ${interface2}
+| | ${if1_data_cfg}=
+| | ... | interfaceAPI.Get interface cfg data | ${node} | ${interface1}
+| | ${if1_data_cfg}=
+| | ... | interfaceAPI.Get interface cfg data | ${node} | ${interface2}
+| | Run keyword and expect error | *KeyError: 'v3po:l2'*
+| | ... | Set Variable | ${if1_data_oper['v3po:l2']}
+| | Run keyword and expect error | *KeyError: 'v3po:l2'*
+| | ... | Set Variable | ${if2_data_oper['v3po:l2']}
+| | Run keyword and expect error | *KeyError: 'v3po:l2'*
+| | ... | Set Variable | ${if1_data_cfg['v3po:l2']}
+| | Run keyword and expect error | *KeyError: 'v3po:l2'*
+| | ... | Set Variable | ${if2_data_cfg['v3po:l2']}
+
 | Honeycomb removes all bridge domains
 | | [Documentation] | Uses Honeycomb API to remove all bridge domains from the \
 | | ... | VPP node.
 | | ... | *Example:*
 | | ...
 | | ... | \| Honeycomb removes all bridge domains \| ${nodes['DUT1']} \|
-| | [Arguments] | ${node}
-| | Remove all bds | ${node}
+| | [Arguments] | ${node} | @{interfaces}
+| | :FOR | ${interface} | IN | @{interfaces}
+| | | Remove bridge domain from interface | ${node} | ${interface}
+| | Remove all bridge domains | ${node}
 
 | Honeycomb should show no bridge domains
 | | [Documentation] | Uses Honeycomb API to verify the removal of all\
 | | [Arguments] | ${node} | ${interface} | ${bd_name} | ${settings}
 | | ...
 | | interfaceAPI.Add bridge domain to interface
-| | ... | ${node} | ${interface} | ${settings['bridge-domain']}
+| | ... | ${node} | ${interface} | ${bd_name}
 | | ... | ${settings['split-horizon-group']}
 | | ... | ${settings['bridged-virtual-interface']}
 
index f94d03d..1ffc2e4 100644 (file)
 | | :FOR | ${key} | IN | @{settings.keys()}
 | | | interfaceAPI.Configure interface ipv4
 | | | ... | ${node} | ${interface} | ${key} | ${settings['${key}']}
-| | | ${api_data}= | interfaceAPI.Get interface oper data | ${node} | ${interface}
 
 | Honeycomb sets interface ipv4 address with prefix
 | | [Documentation] | Uses Honeycomb API to assign an ipv4 address to the\
diff --git a/resources/templates/honeycomb/config_ietf_classify_chain.url b/resources/templates/honeycomb/config_ietf_classify_chain.url
new file mode 100644 (file)
index 0000000..79a9670
--- /dev/null
@@ -0,0 +1 @@
+/restconf/config/ietf-access-control-list:access-lists
\ No newline at end of file
diff --git a/resources/test_data/honeycomb/ietf_acl.py b/resources/test_data/honeycomb/ietf_acl.py
new file mode 100644 (file)
index 0000000..d5c3040
--- /dev/null
@@ -0,0 +1,82 @@
+# Copyright (c) 2016 Cisco and/or its affiliates.
+# 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.
+
+"""Test variables for ietf-ACL test suite."""
+
+
+def get_variables(test_case, name):
+    """Create and return a dictionary of test variables for the specified
+    test case.
+
+    :param test_case: Determines which test variables to return.
+    :param name: Name for the classify chain used in test.
+    :type test_case: str
+    :type name: str
+
+    :return: Dictionary of test variables - settings for Honeycomb's
+    ietf-acl node and packet fields to use in verification.
+    :rtype: dict
+    """
+
+    variables = {
+        # generic packet data
+        "src_ip": "16.0.0.1",
+        "dst_ip": "16.0.1.1",
+        "dst_net": "16.0.1.0",
+        "src_port": "1234",
+        "dst_port": "1234",
+        "src_mac": "01:02:03:04:05:06",
+        "dst_mac": "10:20:30:40:50:60"}
+
+    if test_case.lower() == "l2":
+        classify_vars = {
+            "classify_src": "12:23:34:45:56:67",
+            "classify_dst": "89:9A:AB:BC:CD:DE",
+            "classify_src2": "01:02:03:04:56:67",
+            "classify_dst2": "89:9A:AB:BC:50:60",
+            "src_mask": "00:00:00:00:FF:FF",
+            "dst_mask": "FF:FF:FF:FF:00:00",
+            }
+
+        acl_settings = {
+            "acl": [{
+                "acl-type":
+                    "ietf-access-control-list:eth-acl",
+                "acl-name": name,
+                "access-list-entries": {"ace": [{
+                    "rule-name": "rule1",
+                    "matches": {
+                        "source-mac-address":
+                            classify_vars["classify_src"],
+                        "source-mac-address-mask":
+                            classify_vars["src_mask"],
+                        "destination-mac-address":
+                            classify_vars["classify_dst"],
+                        "destination-mac-address-mask":
+                            classify_vars["dst_mask"]
+                    },
+                    "actions": {
+                        "deny": {}
+                    }
+                }]}
+            }]
+        }
+
+    elif test_case.lower() in ("l3_ip4", "l3_ip6", "l4"):
+        raise NotImplementedError
+    else:
+        raise Exception("Unrecognized test case {0}".format(test_case))
+
+    variables.update(classify_vars)
+    variables["acl_settings"] = acl_settings
+    return variables
index bd182e3..850b81e 100644 (file)
@@ -31,7 +31,7 @@
 | Suite Teardown | Run keywords
 | ... | Run Keyword If Any Tests Failed
 | ... | Restart Honeycomb And VPP And Clear Persisted Configuration | ${node}
-| ... | AND | Honeycomb removes all bridge domains | ${node}
+| ... | AND | Honeycomb removes all bridge domains | ${node} | @{interfaces}
 | Force Tags | honeycomb_sanity
 | Documentation | *Honeycomb bridge domain management test suite.*
 | ...
@@ -94,3 +94,5 @@
 | | When Honeycomb removes all bridge domains | ${node}
 | | Then Honeycomb should show no bridge domains | ${node}
 | | And VAT should show no bridge domains | ${node}
+| | And Honeycomb should not show interfaces assigned to bridge domain
+| | ... | ${node} | @{interfaces} | ${bd1_name}
index b7e0f8d..81b8260 100644 (file)
@@ -23,7 +23,7 @@
 | Suite Setup | Run keywords
 | ... | Set test interface down
 | ... | AND
-| ... | Honeycomb removes all bridge domains | ${node}
+| ... | Honeycomb removes all bridge domains | ${node} | ${interface}
 | Suite Teardown | Run keywords
 | ... | Run Keyword If Any Tests Failed
 | ... | Restart Honeycomb And VPP And Clear Persisted Configuration | ${node}
diff --git a/tests/func/honeycomb/081_ietf_acl_traffic.robot b/tests/func/honeycomb/081_ietf_acl_traffic.robot
new file mode 100644 (file)
index 0000000..a6672ea
--- /dev/null
@@ -0,0 +1,83 @@
+# Copyright (c) 2016 Cisco and/or its affiliates.
+# 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.
+
+*** Variables ***
+| &{if_settings}= | enabled=True
+# Bridge domain settings
+| ${bd_name}= | bd1
+| &{bd_settings}= | flood=${True} | forward=${True} | learn=${True}
+| ... | unknown-unicast-flood=${True} | arp-termination=${False}
+| &{bd_if_settings}= | split_horizon_group=${0} | bvi=${False}
+# Names for AC lists
+| ${acl_name_l2}= | acl_l2
+
+*** Settings ***
+| Resource | resources/libraries/robot/default.robot
+| Resource | resources/libraries/robot/honeycomb/honeycomb.robot
+| Resource | resources/libraries/robot/honeycomb/interfaces.robot
+| Resource | resources/libraries/robot/honeycomb/bridge_domain.robot
+| Resource | resources/libraries/robot/honeycomb/access_control_lists.robot
+| Resource | resources/libraries/robot/testing_path.robot
+| Resource | resources/libraries/robot/traffic.robot
+| Library | resources.libraries.python.honeycomb.HcAPIKwACL.ACLKeywords
+| Library | resources.libraries.python.Trace
+| Suite Teardown | Run Keyword If Any Tests Failed
+| ... | Restart Honeycomb And VPP And Clear Persisted Configuration | ${node}
+| Documentation | *Honeycomb access control lists test suite for IETF-ACL node.*
+| Force Tags | Honeycomb_sanity
+
+*** Test Cases ***
+| TC01: Honeycomb can configure L2 ACL MAC filtering through IETF-ACL node
+| | [Documentation]
+| | ... | [Top] TG=DUT1=TG.
+| | ... | [Enc] Eth-IPv4-TCP.
+| | ... | [Cfg] (Using Honeycomb API) On DUT1 bridge both interfaces to TG\
+| | ... | and configure L2 MAC ACL on ingress interface.
+| | ... | [Ver] Send simple TCP packets from one TG interface to the other,\
+| | ... | using different MACs. Receive all packets except those with\
+| | ... | MACs in the filtered ranges.
+| | [Teardown] | Run Keywords
+| | ... | Clear IETF-ACL settings | ${node} | ${dut_to_tg_if1} | AND
+| | ... | Show Packet Trace on All DUTs | ${nodes} | AND
+| | ... | Honeycomb removes all bridge domains
+| | ... | ${node} | ${dut_to_tg_if1} | ${dut_to_tg_if2}
+| | Given Path For 2-node Testing Is Set
+| | ... | ${nodes['TG']} | ${nodes['DUT1']} | ${nodes['TG']}
+| | And Import Variables | resources/test_data/honeycomb/ietf_acl.py
+| | ... | L2 | ${acl_name_l2}
+| | And Honeycomb Sets Interface State | ${dut_node} | ${dut_to_tg_if1} | up
+| | And Honeycomb Sets Interface State | ${dut_node} | ${dut_to_tg_if2} | up
+| | And Honeycomb Creates L2 Bridge Domain
+| | ... | ${dut_node} | ${bd_name} | ${bd_settings}
+| | And Honeycomb Adds Interfaces To Bridge Domain
+| | ... | ${dut_node} | ${dut_to_tg_if1} | ${dut_to_tg_if2}
+| | ... | ${bd_name} | ${bd_if_settings}
+| | When Honeycomb creates ACL chain through IETF node
+| | ... | ${dut_node} | ${acl_name_l2} | L2 | ${acl_settings}
+| | And Honeycomb assigns IETF-ACL chain to interface
+| | ... | ${dut_node} | ${dut_to_tg_if1} | L2 | ingress | ${acl_name_l2}
+| | ... | permit
+| | Then Send TCP or UDP packet | ${tg_node} | ${src_ip} | ${dst_ip}
+| | ... | ${tg_to_dut_if1} | ${src_mac}
+| | ... | ${tg_to_dut_if2} | ${dst_mac}
+| | ... | TCP | ${src_port} | ${dst_port}
+| | And Run keyword and expect error | TCP/UDP Rx timeout
+| | ... | Send TCP or UDP packet | ${tg_node} | ${src_ip} | ${dst_ip}
+| | ... | ${tg_to_dut_if1} | ${classify_src}
+| | ... | ${tg_to_dut_if2} | ${classify_dst}
+| | ... | TCP | ${src_port} | ${dst_port}
+| | And Run keyword and expect error | TCP/UDP Rx timeout
+| | ... | Send TCP or UDP packet | ${tg_node} | ${src_ip} | ${dst_ip}
+| | ... | ${tg_to_dut_if1} | ${classify_src2}
+| | ... | ${tg_to_dut_if2} | ${classify_dst2}
+| | ... | TCP | ${src_port} | ${dst_port}