HC Test: update HC config file locations
[csit.git] / resources / libraries / python / honeycomb / HcAPIKwInterfaces.py
index 01a2eba..6f0bdda 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (c) 2016 Cisco and/or its affiliates.
+# Copyright (c) 2017 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:
 # 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:
@@ -27,8 +27,6 @@ from resources.libraries.python.honeycomb.HoneycombUtil \
     import HoneycombUtil as HcUtil
 
 
     import HoneycombUtil as HcUtil
 
 
-# pylint: disable=too-many-public-methods
-# pylint: disable=too-many-lines
 class InterfaceKeywords(object):
     """Keywords for Interface manipulation.
 
 class InterfaceKeywords(object):
     """Keywords for Interface manipulation.
 
@@ -46,7 +44,7 @@ class InterfaceKeywords(object):
                             "temporary-valid-lifetime",
                             "temporary-preferred-lifetime")
     ETH_PARAMS = ("mtu", )
                             "temporary-valid-lifetime",
                             "temporary-preferred-lifetime")
     ETH_PARAMS = ("mtu", )
-    ROUTING_PARAMS = ("vrf-id", )
+    ROUTING_PARAMS = ("ipv4-vrf-id", "ipv6-vrf-id")
     VXLAN_PARAMS = ("src", "dst", "vni", "encap-vrf-id")
     L2_PARAMS = ("bridge-domain", "split-horizon-group",
                  "bridged-virtual-interface")
     VXLAN_PARAMS = ("src", "dst", "vni", "encap-vrf-id")
     L2_PARAMS = ("bridge-domain", "split-horizon-group",
                  "bridged-virtual-interface")
@@ -203,6 +201,16 @@ class InterfaceKeywords(object):
         :rtype: dict
         """
 
         :rtype: dict
         """
 
+        try:
+            interface = Topology.convert_interface_reference(
+                node, interface, "name")
+        except RuntimeError:
+            if isinstance(interface, basestring):
+                # Probably name of a custom interface (TAP, VxLAN, Vhost, ...)
+                pass
+            else:
+                raise
+
         intfs = InterfaceKeywords.get_all_interfaces_oper_data(node)
         for intf in intfs:
             if intf["name"] == interface:
         intfs = InterfaceKeywords.get_all_interfaces_oper_data(node)
         for intf in intfs:
             if intf["name"] == interface:
@@ -244,7 +252,7 @@ class InterfaceKeywords(object):
         return InterfaceKeywords._configure_interface(node, interface, new_data)
 
     @staticmethod
         return InterfaceKeywords._configure_interface(node, interface, new_data)
 
     @staticmethod
-    def set_interface_state(node, interface, state="up"):
+    def honeycomb_set_interface_state(node, interface, state="up"):
         """Set VPP interface state.
 
         The keyword changes the administration state of interface to up or down
         """Set VPP interface state.
 
         The keyword changes the administration state of interface to up or down
@@ -302,7 +310,8 @@ class InterfaceKeywords(object):
         :rtype: bytearray
         """
 
         :rtype: bytearray
         """
 
-        return InterfaceKeywords.set_interface_state(node, interface, "up")
+        return InterfaceKeywords.honeycomb_set_interface_state(
+            node, interface, "up")
 
     @staticmethod
     def set_interface_down(node, interface):
 
     @staticmethod
     def set_interface_down(node, interface):
@@ -316,7 +325,8 @@ class InterfaceKeywords(object):
         :rtype: bytearray
         """
 
         :rtype: bytearray
         """
 
-        return InterfaceKeywords.set_interface_state(node, interface, "down")
+        return InterfaceKeywords.honeycomb_set_interface_state(
+            node, interface, "down")
 
     @staticmethod
     def add_bridge_domain_to_interface(node, interface, bd_name,
 
     @staticmethod
     def add_bridge_domain_to_interface(node, interface, bd_name,
@@ -475,22 +485,32 @@ class InterfaceKeywords(object):
         :type network: str or int
         :returns: Content of response.
         :rtype: bytearray
         :type network: str or int
         :returns: Content of response.
         :rtype: bytearray
-        :raises HoneycombError: If the provided netmask or prefix is not valid.
+        :raises ValueError: If the provided netmask or prefix is not valid.
+        :raises HoneycombError: If the operation fails.
         """
 
         """
 
-        interface = Topology.convert_interface_reference(
-            node, interface, "name")
+        interface = InterfaceKeywords.handle_interface_reference(
+            node, interface)
 
 
-        path = ("interfaces", ("interface", "name", interface), "ietf-ip:ipv4")
+        path = "/interface/{0}/ietf-ip:ipv4".format(interface)
         if isinstance(network, basestring):
         if isinstance(network, basestring):
-            address = {"address": [{"ip": ip_addr, "netmask": network}, ]}
+            data = {
+                "ietf-ip:ipv4": {
+                    "address": [{"ip": ip_addr, "netmask": network}, ]}}
         elif isinstance(network, int) and (0 < network < 33):
         elif isinstance(network, int) and (0 < network < 33):
-            address = {"address": [{"ip": ip_addr, "prefix-length": network}, ]}
+            data = {
+                "ietf-ip:ipv4": {
+                    "address": [{"ip": ip_addr, "prefix-length": network}, ]}}
         else:
         else:
-            raise HoneycombError("Value {0} is not a valid netmask or network "
-                                 "prefix length.".format(network))
-        return InterfaceKeywords._set_interface_properties(
-            node, interface, path, address)
+            raise ValueError("Value {0} is not a valid netmask or network "
+                             "prefix length.".format(network))
+        status_code, _ = HcUtil.put_honeycomb_data(
+            node, "config_vpp_interfaces", data, path)
+
+        if status_code not in (HTTPCodes.OK, HTTPCodes.ACCEPTED):
+            raise HoneycombError(
+                "Configuring IPv4 address failed. "
+                "Status code:{0}".format(status_code))
 
     @staticmethod
     def add_ipv4_address(node, interface, ip_addr, network):
 
     @staticmethod
     def add_ipv4_address(node, interface, ip_addr, network):
@@ -515,9 +535,9 @@ class InterfaceKeywords(object):
         path = ("interfaces", ("interface", "name", interface), "ietf-ip:ipv4",
                 "address")
         if isinstance(network, basestring):
         path = ("interfaces", ("interface", "name", interface), "ietf-ip:ipv4",
                 "address")
         if isinstance(network, basestring):
-            address = {"address": [{"ip": ip_addr, "netmask": network}, ]}
+            address = [{"ip": ip_addr, "netmask": network}]
         elif isinstance(network, int) and (0 < network < 33):
         elif isinstance(network, int) and (0 < network < 33):
-            address = {"address": [{"ip": ip_addr, "prefix-length": network}, ]}
+            address = [{"ip": ip_addr, "prefix-length": network}]
         else:
             raise HoneycombError("Value {0} is not a valid netmask or network "
                                  "prefix length.".format(network))
         else:
             raise HoneycombError("Value {0} is not a valid netmask or network "
                                  "prefix length.".format(network))
@@ -557,6 +577,9 @@ class InterfaceKeywords(object):
         :rtype: bytearray
         """
 
         :rtype: bytearray
         """
 
+        interface = Topology.convert_interface_reference(
+            node, interface, "name")
+
         path = ("interfaces", ("interface", "name", interface), "ietf-ip:ipv4",
                 "neighbor")
         neighbor = [{"ip": ip_addr, "link-layer-address": link_layer_address}, ]
         path = ("interfaces", ("interface", "name", interface), "ietf-ip:ipv4",
                 "neighbor")
         neighbor = [{"ip": ip_addr, "link-layer-address": link_layer_address}, ]
@@ -628,6 +651,9 @@ class InterfaceKeywords(object):
         :rtype: bytearray
         """
 
         :rtype: bytearray
         """
 
+        interface = Topology.convert_interface_reference(
+            node, interface, "name")
+
         path = ("interfaces", ("interface", "name", interface), "ietf-ip:ipv6")
         address = {"address": [{"ip": ip_addr, "prefix-length": prefix_len}, ]}
         return InterfaceKeywords._set_interface_properties(
         path = ("interfaces", ("interface", "name", interface), "ietf-ip:ipv6")
         address = {"address": [{"ip": ip_addr, "prefix-length": prefix_len}, ]}
         return InterfaceKeywords._set_interface_properties(
@@ -649,6 +675,9 @@ class InterfaceKeywords(object):
         :rtype: bytearray
         """
 
         :rtype: bytearray
         """
 
+        interface = Topology.convert_interface_reference(
+            node, interface, "name")
+
         path = ("interfaces", ("interface", "name", interface), "ietf-ip:ipv6",
                 "address")
         address = [{"ip": ip_addr, "prefix-length": prefix_len}, ]
         path = ("interfaces", ("interface", "name", interface), "ietf-ip:ipv6",
                 "address")
         address = [{"ip": ip_addr, "prefix-length": prefix_len}, ]
@@ -688,6 +717,9 @@ class InterfaceKeywords(object):
         :rtype: bytearray
         """
 
         :rtype: bytearray
         """
 
+        interface = Topology.convert_interface_reference(
+            node, interface, "name")
+
         path = ("interfaces", ("interface", "name", interface), "ietf-ip:ipv6",
                 "neighbor")
         neighbor = [{"ip": ip_addr, "link-layer-address": link_layer_address}, ]
         path = ("interfaces", ("interface", "name", interface), "ietf-ip:ipv6",
                 "neighbor")
         neighbor = [{"ip": ip_addr, "link-layer-address": link_layer_address}, ]
@@ -754,6 +786,9 @@ class InterfaceKeywords(object):
         :raises HoneycombError: If the parameter is not valid.
         """
 
         :raises HoneycombError: If the parameter is not valid.
         """
 
+        interface = Topology.convert_interface_reference(
+            node, interface, "name")
+
         if param not in InterfaceKeywords.ROUTING_PARAMS:
             raise HoneycombError("The parameter {0} is invalid.".format(param))
 
         if param not in InterfaceKeywords.ROUTING_PARAMS:
             raise HoneycombError("The parameter {0} is invalid.".format(param))
 
@@ -763,7 +798,7 @@ class InterfaceKeywords(object):
             node, interface, path, value)
 
     @staticmethod
             node, interface, path, value)
 
     @staticmethod
-    def create_vxlan_interface(node, interface, **kwargs):
+    def honeycomb_create_vxlan_interface(node, interface, **kwargs):
         """Create a new VxLAN interface.
 
         :param node: Honeycomb node.
         """Create a new VxLAN interface.
 
         :param node: Honeycomb node.
@@ -827,7 +862,7 @@ class InterfaceKeywords(object):
         return resp
 
     @staticmethod
         return resp
 
     @staticmethod
-    def configure_interface_vxlan(node, interface, **kwargs):
+    def honeycomb_configure_interface_vxlan(node, interface, **kwargs):
         """Configure VxLAN on the interface.
 
         The keyword configures VxLAN parameters on the given interface. The type
         """Configure VxLAN on the interface.
 
         The keyword configures VxLAN parameters on the given interface. The type
@@ -1019,7 +1054,8 @@ class InterfaceKeywords(object):
             node, interface, path, new_vhost_structure)
 
     @staticmethod
             node, interface, path, new_vhost_structure)
 
     @staticmethod
-    def create_sub_interface(node, super_interface, match, tags=None, **kwargs):
+    def honeycomb_create_sub_interface(node, super_interface, match, tags=None,
+                                       **kwargs):
         """Create a new sub-interface.
 
         :param node: Honeycomb node.
         """Create a new sub-interface.
 
         :param node: Honeycomb node.
@@ -1139,13 +1175,16 @@ class InterfaceKeywords(object):
         :rtype: bytearray
         """
 
         :rtype: bytearray
         """
 
+        super_interface = Topology.convert_interface_reference(
+            node, super_interface, "name")
+
         intf_state = {"up": "true",
                       "down": "false"}
 
         path = ("interfaces",
                 ("interface", "name", super_interface),
                 "vpp-vlan:sub-interfaces",
         intf_state = {"up": "true",
                       "down": "false"}
 
         path = ("interfaces",
                 ("interface", "name", super_interface),
                 "vpp-vlan:sub-interfaces",
-                ("sub-interface", "identifier", identifier),
+                ("sub-interface", "identifier", int(identifier)),
                 "enabled")
 
         return InterfaceKeywords._set_interface_properties(
                 "enabled")
 
         return InterfaceKeywords._set_interface_properties(
@@ -1250,8 +1289,8 @@ class InterfaceKeywords(object):
                                  "information about the tag-rewrite.")
 
     @staticmethod
                                  "information about the tag-rewrite.")
 
     @staticmethod
-    def add_ipv4_address_to_sub_interface(node, super_interface, identifier,
-                                          ip_addr, network):
+    def add_ip_address_to_sub_interface(node, super_interface, identifier,
+                                        ip_addr, network, ip_version):
         """Add an ipv4 address to the specified sub-interface, with the provided
         netmask or network prefix length. Any existing ipv4 addresses on the
         sub-interface will be replaced.
         """Add an ipv4 address to the specified sub-interface, with the provided
         netmask or network prefix length. Any existing ipv4 addresses on the
         sub-interface will be replaced.
@@ -1261,11 +1300,13 @@ class InterfaceKeywords(object):
         :param identifier: The ID of sub-interface.
         :param ip_addr: IPv4 address to be set.
         :param network: Network mask or network prefix length.
         :param identifier: The ID of sub-interface.
         :param ip_addr: IPv4 address to be set.
         :param network: Network mask or network prefix length.
+        :param ip_version: ipv4 or ipv6
         :type node: dict
         :type super_interface: str
         :type identifier: int
         :type ip_addr: str
         :type network: str or int
         :type node: dict
         :type super_interface: str
         :type identifier: int
         :type ip_addr: str
         :type network: str or int
+        :type ip_version: string
         :returns: Content of response.
         :rtype: bytearray
         :raises HoneycombError: If the provided netmask or prefix is not valid.
         :returns: Content of response.
         :rtype: bytearray
         :raises HoneycombError: If the provided netmask or prefix is not valid.
@@ -1275,9 +1316,9 @@ class InterfaceKeywords(object):
                 ("interface", "name", super_interface),
                 "vpp-vlan:sub-interfaces",
                 ("sub-interface", "identifier", int(identifier)),
                 ("interface", "name", super_interface),
                 "vpp-vlan:sub-interfaces",
                 ("sub-interface", "identifier", int(identifier)),
-                "ipv4")
+                ip_version.lower())
 
 
-        if isinstance(network, basestring):
+        if isinstance(network, basestring) and ip_version.lower() == "ipv4":
             address = {"address": [{"ip": ip_addr, "netmask": network}, ]}
 
         elif isinstance(network, int) and 0 < network < 33:
             address = {"address": [{"ip": ip_addr, "netmask": network}, ]}
 
         elif isinstance(network, int) and 0 < network < 33:
@@ -1291,16 +1332,18 @@ class InterfaceKeywords(object):
             node, super_interface, path, address)
 
     @staticmethod
             node, super_interface, path, address)
 
     @staticmethod
-    def remove_all_ipv4_addresses_from_sub_interface(node, super_interface, # pylint: disable=invalid-name
-                                                     identifier):
+    def remove_all_ip_addresses_from_sub_interface(node, super_interface,
+                                                   identifier, ip_version):
         """Remove all ipv4 addresses from the specified sub-interface.
 
         :param node: Honeycomb node.
         :param super_interface: Super interface.
         :param identifier: The ID of sub-interface.
         """Remove all ipv4 addresses from the specified sub-interface.
 
         :param node: Honeycomb node.
         :param super_interface: Super interface.
         :param identifier: The ID of sub-interface.
+        :param ip_version: ipv4 or ipv6
         :type node: dict
         :type super_interface: str
         :type identifier: int
         :type node: dict
         :type super_interface: str
         :type identifier: int
+        :type ip_version: string
         :returns: Content of response.
         :rtype: bytearray
         """
         :returns: Content of response.
         :rtype: bytearray
         """
@@ -1309,49 +1352,79 @@ class InterfaceKeywords(object):
                 ("interface", "name", super_interface),
                 "vpp-vlan:sub-interfaces",
                 ("sub-interface", "identifier", int(identifier)),
                 ("interface", "name", super_interface),
                 "vpp-vlan:sub-interfaces",
                 ("sub-interface", "identifier", int(identifier)),
-                "ipv4", "address")
+                str(ip_version), "address")
 
         return InterfaceKeywords._set_interface_properties(
             node, super_interface, path, None)
 
     @staticmethod
 
         return InterfaceKeywords._set_interface_properties(
             node, super_interface, path, None)
 
     @staticmethod
-    def compare_data_structures(data, ref, ignore=(), list_order=True):
-        """Checks if data obtained from UUT is as expected.
+    def compare_data_structures(data, ref, _path=''):
+        """Checks if data obtained from UUT is as expected. If it is not,
+        proceeds down the list/dictionary tree and finds the point of mismatch.
 
         :param data: Data to be checked.
         :param ref: Referential data used for comparison.
 
         :param data: Data to be checked.
         :param ref: Referential data used for comparison.
-        :param ignore: Dictionary keys to be ignored.
-        :param list_order: Whether to consider the order of list items\
-        in comparison.
+        :param _path: Used in recursive calls, stores the path taken down
+        the JSON tree.
         :type data: dict
         :type ref: dict
         :type data: dict
         :type ref: dict
-        :type ignore: iterable
-        :type list_order: bool
+        :type _path: str
 
 
-        :raises HoneycombError: If a parameter from referential data is not
-        present in operational data or if it has different value.
+        :raises HoneycombError: If the data structures do not match in some way,
+        or if they are not in deserialized JSON format.
         """
 
         """
 
-        errors = ""
-
-        for key, item in ref.items():
-            if key in ignore:
-                continue
-            try:
-                if data[key] != item:
-                    if not list_order and sorted(data[key]) == sorted(item):
-                        pass
+        if data == ref:
+            return True
+
+        elif isinstance(data, dict) and isinstance(ref, dict):
+            for key in ref:
+                if key not in data:
+                    raise HoneycombError(
+                        "Key {key} is not present in path {path}. Keys in path:"
+                        "{data_keys}".format(
+                            key=key,
+                            path=_path,
+                            data_keys=data.keys()))
+
+                if data[key] != ref[key]:
+                    if isinstance(data[key], list) \
+                            or isinstance(data[key], dict):
+                        InterfaceKeywords.compare_data_structures(
+                            data[key], ref[key],
+                            _path + '[{0}]'.format(key))
                     else:
                     else:
-                        errors += ("\nThe value of parameter '{0}' is "
-                                   "incorrect. It should be "
-                                   "'{1}' but it is '{2}'".
-                                   format(key, item, data[key]))
-            except KeyError:
-                errors += ("\nThe parameter '{0}' is not present in "
-                           "operational data".format(key))
+                        raise HoneycombError(
+                            "Data mismatch, key {key} in path {path} has value"
+                            " {data}, but should be {ref}".format(
+                                key=key,
+                                path=_path,
+                                data=data[key],
+                                ref=ref[key]))
+
+        elif isinstance(data, list) and isinstance(ref, list):
+            for item in ref:
+                if item not in data:
+                    if isinstance(item, dict):
+                        InterfaceKeywords.compare_data_structures(
+                            data[0], item,
+                            _path + '[{0}]'.format(ref.index(item)))
+                    else:
+                        raise HoneycombError(
+                            "Data mismatch, list item {index} in path {path}"
+                            " has value {data}, but should be {ref}".format(
+                                index=ref.index(item),
+                                path=_path,
+                                data=data[0],
+                                ref=item))
 
 
-        if errors:
-            raise HoneycombError(errors)
+        else:
+            raise HoneycombError(
+                "Unexpected data type {data_type} in path {path}, reference"
+                " type is {ref_type}. Must be list or dictionary.".format(
+                    data_type=type(data),
+                    ref_type=type(ref),
+                    path=_path))
 
     @staticmethod
     def compare_interface_lists(list1, list2):
 
     @staticmethod
     def compare_interface_lists(list1, list2):
@@ -1432,7 +1505,7 @@ class InterfaceKeywords(object):
         interface = interface.replace("/", "%2F")
 
         data = {
         interface = interface.replace("/", "%2F")
 
         data = {
-            "v3po:acl": {
+            "vpp-interface-acl:acl": {
                 "ingress": {
                     "ip4-acl": {
                         "classify-table": table_name
                 "ingress": {
                     "ip4-acl": {
                         "classify-table": table_name
@@ -1444,7 +1517,7 @@ class InterfaceKeywords(object):
             }
         }
 
             }
         }
 
-        path = "/interface/" + interface + "/v3po:acl"
+        path = "/interface/" + interface + "/vpp-interface-acl:acl"
         status_code, resp = HcUtil.\
             put_honeycomb_data(node, "config_vpp_interfaces", data, path,
                                data_representation=DataRepresentation.JSON)
         status_code, resp = HcUtil.\
             put_honeycomb_data(node, "config_vpp_interfaces", data, path,
                                data_representation=DataRepresentation.JSON)
@@ -1454,6 +1527,67 @@ class InterfaceKeywords(object):
                 "Status code: {1}.".format(interface, status_code))
         return resp
 
                 "Status code: {1}.".format(interface, status_code))
         return resp
 
+    @staticmethod
+    def enable_policer_on_interface(node, interface, table_name):
+        """Enable Policer on the given interface.
+
+        :param node: Honeycomb node.
+        :param interface: The interface where policer will be enabled.
+        :param table_name: Name of the classify table.
+        :type node: dict
+        :type interface: str
+        :type table_name: str
+        :returns: Content of response.
+        :rtype: bytearray
+        :raises HoneycombError: If the configuration of interface is not
+        successful.
+        """
+        interface = Topology.convert_interface_reference(
+            node, interface, "name")
+        interface = interface.replace("/", "%2F")
+
+        data = {
+            "interface-policer:policer": {
+                "ip4-table": table_name
+                }
+            }
+
+        path = "/interface/" + interface + "/interface-policer:policer"
+        status_code, resp = HcUtil.\
+            put_honeycomb_data(node, "config_vpp_interfaces", data, path,
+                               data_representation=DataRepresentation.JSON)
+        if status_code not in (HTTPCodes.OK, HTTPCodes.ACCEPTED):
+            raise HoneycombError(
+                "The configuration of interface '{0}' was not successful. "
+                "Status code: {1}.".format(interface, status_code))
+        return resp
+
+    @staticmethod
+    def disable_policer_on_interface(node, interface):
+        """Disable Policer on the given interface.
+
+        :param node: Honeycomb node.
+        :param interface: The interface where policer will be disabled.
+        :type node: dict
+        :type interface: str
+        :returns: Content of response.
+        :rtype: bytearray
+        :raises HoneycombError: If the configuration of interface is not
+        successful.
+        """
+        interface = Topology.convert_interface_reference(
+            node, interface, "name")
+        interface = interface.replace("/", "%2F")
+
+        path = "/interface/" + interface + "/interface-policer:policer"
+        status_code, resp = HcUtil.\
+            delete_honeycomb_data(node, "config_vpp_interfaces", path)
+        if status_code != HTTPCodes.OK:
+            raise HoneycombError(
+                "The configuration of interface '{0}' was not successful. "
+                "Status code: {1}.".format(interface, status_code))
+        return resp
+
     @staticmethod
     def disable_acl_on_interface(node, interface):
         """Disable ACL on the given interface.
     @staticmethod
     def disable_acl_on_interface(node, interface):
         """Disable ACL on the given interface.
@@ -1470,7 +1604,7 @@ class InterfaceKeywords(object):
 
         interface = interface.replace("/", "%2F")
 
 
         interface = interface.replace("/", "%2F")
 
-        path = "/interface/" + interface + "/v3po:acl"
+        path = "/interface/" + interface + "/vpp-interface-acl:acl"
 
         status_code, resp = HcUtil.\
             delete_honeycomb_data(node, "config_vpp_interfaces", path)
 
         status_code, resp = HcUtil.\
             delete_honeycomb_data(node, "config_vpp_interfaces", path)
@@ -1500,7 +1634,7 @@ class InterfaceKeywords(object):
         """
 
         interface = intf.replace("/", "%2F")
         """
 
         interface = intf.replace("/", "%2F")
-        path = "/interface/{0}/pbb-rewrite/".format(interface)
+        path = "/interface/{0}/pbb-rewrite".format(interface)
         status_code, resp = HcUtil. \
             put_honeycomb_data(node, "config_vpp_interfaces", params, path,
                                data_representation=DataRepresentation.JSON)
         status_code, resp = HcUtil. \
             put_honeycomb_data(node, "config_vpp_interfaces", params, path,
                                data_representation=DataRepresentation.JSON)
@@ -1578,7 +1712,7 @@ class InterfaceKeywords(object):
                              " of disabled interfaces.".format(interface))
 
     @staticmethod
                              " of disabled interfaces.".format(interface))
 
     @staticmethod
-    def configure_interface_span(node, dst_interface, *src_interfaces):
+    def configure_interface_span(node, dst_interface, src_interfaces=None):
         """Configure SPAN port mirroring on the specified interfaces. If no
          source interface is provided, SPAN will be disabled.
 
         """Configure SPAN port mirroring on the specified interfaces. If no
          source interface is provided, SPAN will be disabled.
 
@@ -1586,30 +1720,88 @@ class InterfaceKeywords(object):
         :param dst_interface: Interface to mirror packets to.
         :param src_interfaces: List of interfaces to mirror packets from.
         :type node: dict
         :param dst_interface: Interface to mirror packets to.
         :param src_interfaces: List of interfaces to mirror packets from.
         :type node: dict
-        :type dst_interface: str
-        :type src_interfaces: list of str
+        :type dst_interface: str or int
+        :type src_interfaces: list of dict
         :returns: Content of response.
         :rtype: bytearray
         :raises HoneycombError: If SPAN could not be configured.
         """
 
         :returns: Content of response.
         :rtype: bytearray
         :raises HoneycombError: If SPAN could not be configured.
         """
 
-        interface = dst_interface.replace("/", "%2F")
+        interface = Topology.convert_interface_reference(
+            node, dst_interface, "name")
+        interface = interface.replace("/", "%2F")
         path = "/interface/" + interface + "/span"
 
         if not src_interfaces:
             status_code, _ = HcUtil.delete_honeycomb_data(
                 node, "config_vpp_interfaces", path)
         path = "/interface/" + interface + "/span"
 
         if not src_interfaces:
             status_code, _ = HcUtil.delete_honeycomb_data(
                 node, "config_vpp_interfaces", path)
+        else:
+            for src_interface in src_interfaces:
+                src_interface["iface-ref"] = Topology.\
+                    convert_interface_reference(
+                        node, src_interface["iface-ref"], "name")
+            data = {
+                "span": {
+                    "mirrored-interfaces": {
+                        "mirrored-interface": src_interfaces
+                    }
+                }
+            }
 
 
-        data = {
-            "span": {
-                "mirrored-interfaces": {
-                    "mirrored-interface": src_interfaces
+            status_code, _ = HcUtil.put_honeycomb_data(
+                node, "config_vpp_interfaces", data, path)
+
+        if status_code not in (HTTPCodes.OK, HTTPCodes.ACCEPTED):
+            raise HoneycombError(
+                "Configuring SPAN failed. Status code:{0}".format(status_code))
+
+    @staticmethod
+    def configure_sub_interface_span(node, super_interface, dst_interface_index,
+                                     src_interfaces=None):
+        """Configure SPAN port mirroring on the specified sub-interface. If no
+         source interface is provided, SPAN will be disabled.
+
+        Note: Does not support source sub-interfaces, only destination.
+
+        :param node: Honeycomb node.
+        :param super_interface: Name, link name or sw_if_index
+        of the destination interface's super-interface.
+        :param dst_interface_index: Index of sub-interface to mirror packets to.
+        :param src_interfaces: List of interfaces to mirror packets from.
+        :type node: dict
+        :type super_interface: str or int
+        :type dst_interface_index: int
+        :type src_interfaces: list of dict
+        :returns: Content of response.
+        :rtype: bytearray
+        :raises HoneycombError: If SPAN could not be configured.
+        """
+
+        super_interface = Topology.convert_interface_reference(
+            node, super_interface, "name")
+        super_interface = super_interface.replace("/", "%2F")
+
+        path = "/interface/{0}/vpp-vlan:sub-interfaces/sub-interface/{1}/span"\
+            .format(super_interface, dst_interface_index)
+
+        if not src_interfaces:
+            status_code, _ = HcUtil.delete_honeycomb_data(
+                node, "config_vpp_interfaces", path)
+        else:
+            for src_interface in src_interfaces:
+                src_interface["iface-ref"] = Topology. \
+                    convert_interface_reference(
+                        node, src_interface["iface-ref"], "name")
+            data = {
+                "span": {
+                    "mirrored-interfaces": {
+                        "mirrored-interface": src_interfaces
+                    }
                 }
             }
                 }
             }
-        }
 
 
-        status_code, _ = HcUtil.put_honeycomb_data(
-            node, "config_vpp_interfaces", data, path)
+            status_code, _ = HcUtil.put_honeycomb_data(
+                node, "config_vpp_interfaces", data, path)
 
         if status_code not in (HTTPCodes.OK, HTTPCodes.ACCEPTED):
             raise HoneycombError(
 
         if status_code not in (HTTPCodes.OK, HTTPCodes.ACCEPTED):
             raise HoneycombError(
@@ -1628,3 +1820,72 @@ class InterfaceKeywords(object):
             local0_key = Topology.add_new_port(node, "localzero")
             Topology.update_interface_sw_if_index(node, local0_key, 0)
             Topology.update_interface_name(node, local0_key, "local0")
             local0_key = Topology.add_new_port(node, "localzero")
             Topology.update_interface_sw_if_index(node, local0_key, 0)
             Topology.update_interface_name(node, local0_key, "local0")
+
+    @staticmethod
+    def configure_interface_unnumbered(node, interface, interface_src=None):
+        """Configure the specified interface as unnumbered. The interface
+        borrows IP address from the specified source interface. If not source
+        interface is provided, unnumbered configuration will be removed.
+
+        :param node: Honeycomb node.
+        :param interface: Name, link name or sw_if_index of an interface.
+        :param interface_src: Name of source interface.
+        :type node: dict
+        :type interface: str or int
+        :type interface_src: str
+        :raises HoneycombError: If the configuration fails.
+        """
+
+        interface = InterfaceKeywords.handle_interface_reference(
+            node, interface)
+
+        path = "/interface/{0}/unnumbered-interfaces:unnumbered"\
+            .format(interface)
+
+        if interface_src:
+            data = {
+                "unnumbered": {
+                    "use": interface_src
+                }
+            }
+            status_code, _ = HcUtil.put_honeycomb_data(
+                node, "config_vpp_interfaces", data, path)
+        else:
+            status_code, _ = HcUtil.delete_honeycomb_data(
+                node, "config_vpp_interfaces", path)
+
+        if status_code not in (HTTPCodes.OK, HTTPCodes.ACCEPTED):
+            raise HoneycombError(
+                "Configuring unnumbered interface failed. "
+                "Status code:{0}".format(status_code))
+
+    @staticmethod
+    def handle_interface_reference(node, interface):
+        """Convert any interface reference to interface name used by Honeycomb.
+
+        :param node: Honeycomb node.
+        :param interface: Name, link name or sw_if_index of an interface,
+        name of a custom interface or name of a sub-interface.
+        :type node: Honeycomb node.
+        :type interface: str or int
+        :returns: Name of interface that can be used in Honeycomb requests.
+        :rtype: str
+        """
+
+        try:
+            interface = Topology.convert_interface_reference(
+                node, interface, "name")
+            interface = interface.replace("/", "%2F")
+        except RuntimeError:
+            # interface is not in topology
+            if "." in interface:
+                # Assume it's the name of a sub-interface
+                interface, index = interface.split(".")
+                interface = interface.replace("/", "%2F")
+                interface = "{0}/vpp-vlan:sub-interfaces/sub-interface/{1}".\
+                    format(interface, index)
+            else:
+                # Assume it's the name of a custom interface (pbb, vxlan, etc.)
+                interface = interface.replace("/", "%2F")
+
+        return interface