Extend host topology with NIC type filtering
[csit.git] / resources / libraries / python / topology.py
index 75542ad..7e94007 100644 (file)
 
 """Defines nodes and topology structure."""
 
 
 """Defines nodes and topology structure."""
 
-from resources.libraries.python.parsers.JsonParser import JsonParser
-from resources.libraries.python.VatExecutor import VatExecutor
-from resources.libraries.python.ssh import SSH
-from resources.libraries.python.InterfaceSetup import InterfaceSetup
+from yaml import load
+
 from robot.api import logger
 from robot.libraries.BuiltIn import BuiltIn
 from robot.api.deco import keyword
 from robot.api import logger
 from robot.libraries.BuiltIn import BuiltIn
 from robot.api.deco import keyword
-from yaml import load
 
 __all__ = ["DICT__nodes", 'Topology']
 
 
 def load_topo_from_yaml():
 
 __all__ = ["DICT__nodes", 'Topology']
 
 
 def load_topo_from_yaml():
-    """Load topology from file defined in "${TOPOLOGY_PATH}" variable
+    """Load topology from file defined in "${TOPOLOGY_PATH}" variable.
 
 
-    :return: nodes from loaded topology
+    :return: Nodes from loaded topology.
     """
     topo_path = BuiltIn().get_variable_value("${TOPOLOGY_PATH}")
 
     """
     topo_path = BuiltIn().get_variable_value("${TOPOLOGY_PATH}")
 
@@ -37,15 +34,17 @@ def load_topo_from_yaml():
 
 
 class NodeType(object):
 
 
 class NodeType(object):
-    """Defines node types used in topology dictionaries"""
+    """Defines node types used in topology dictionaries."""
     # Device Under Test (this node has VPP running on it)
     DUT = 'DUT'
     # Traffic Generator (this node has traffic generator on it)
     TG = 'TG'
     # Device Under Test (this node has VPP running on it)
     DUT = 'DUT'
     # Traffic Generator (this node has traffic generator on it)
     TG = 'TG'
+    # Virtual Machine (this node running on DUT node)
+    VM = 'VM'
 
 
 class NodeSubTypeTG(object):
 
 
 class NodeSubTypeTG(object):
-    #T-Rex traffic generator
+    # T-Rex traffic generator
     TREX = 'TREX'
     # Moongen
     MOONGEN = 'MOONGEN'
     TREX = 'TREX'
     # Moongen
     MOONGEN = 'MOONGEN'
@@ -56,7 +55,7 @@ DICT__nodes = load_topo_from_yaml()
 
 
 class Topology(object):
 
 
 class Topology(object):
-    """Topology data manipulation and extraction methods
+    """Topology data manipulation and extraction methods.
 
     Defines methods used for manipulation and extraction of data from
     the used topology.
 
     Defines methods used for manipulation and extraction of data from
     the used topology.
@@ -100,14 +99,13 @@ class Topology(object):
 
     @staticmethod
     def _get_interface_by_key_value(node, key, value):
 
     @staticmethod
     def _get_interface_by_key_value(node, key, value):
-        """Return node interface name according to key and value
+        """Return node interface name according to key and value.
 
 
-        :param node: :param node: the node dictionary
-        :param key: key by which to select the interface.
-        :param value: value that should be found using the key.
+        :param node: The node dictionary.
+        :param key: Key by which to select the interface.
+        :param value: Value that should be found using the key.
         :return:
         """
         :return:
         """
-
         interfaces = node['interfaces']
         retval = None
         for interface in interfaces.values():
         interfaces = node['interfaces']
         retval = None
         for interface in interfaces.values():
@@ -121,29 +119,29 @@ class Topology(object):
     def get_interface_by_link_name(self, node, link_name):
         """Return interface name of link on node.
 
     def get_interface_by_link_name(self, node, link_name):
         """Return interface name of link on node.
 
-        This method returns the interface name asociated with a given link
+        This method returns the interface name associated with a given link
         for a given node.
         for a given node.
-        :param link_name: name of the link that a interface is connected to.
-        :param node: the node topology dictionary
-        :return: interface name of the interface connected to the given link
-        """
 
 
+        :param link_name: Name of the link that a interface is connected to.
+        :param node: The node topology dictionary.
+        :return: Interface name of the interface connected to the given link.
+        :rtype: str
+        """
         return self._get_interface_by_key_value(node, "link", link_name)
 
     def get_interfaces_by_link_names(self, node, link_names):
         return self._get_interface_by_key_value(node, "link", link_name)
 
     def get_interfaces_by_link_names(self, node, link_names):
-        """Return dictionary of dicitonaries {"interfaceN", interface name}.
+        """Return dictionary of dictionaries {"interfaceN", interface name}.
 
 
-        This method returns the interface names asociated with given links
+        This method returns the interface names associated with given links
         for a given node.
         for a given node.
-        The resulting dictionary can be then used to with VatConfigGenerator
-        to generate a VAT script with proper interface names.
-        :param link_names: list of names of the link that a interface is
+
+        :param link_names: List of names of the link that a interface is
         connected to.
         connected to.
-        :param node: the node topology directory
-        :return: dictionary of interface names that are connected to the given
-        links
+        :param node: The node topology directory.
+        :return: Dictionary of interface names that are connected to the given
+        links.
+        :rtype: dict
         """
         """
-
         retval = {}
         interface_key_tpl = "interface{}"
         interface_number = 1
         retval = {}
         interface_key_tpl = "interface{}"
         interface_number = 1
@@ -157,201 +155,34 @@ class Topology(object):
     def get_interface_by_sw_index(self, node, sw_index):
         """Return interface name of link on node.
 
     def get_interface_by_sw_index(self, node, sw_index):
         """Return interface name of link on node.
 
-        This method returns the interface name asociated with a software index
-        assigned to the interface by vpp for a given node.
-        :param sw_index: sw_index of the link that a interface is connected to.
-        :param node: the node topology dictionary
-        :return: interface name of the interface connected to the given link
-        """
-
-        return self._get_interface_by_key_value(node, "vpp_sw_index", sw_index)
-
-    @staticmethod
-    def convert_mac_to_number_list(mac_address):
-        """Convert mac address string to list of decimal numbers.
-
-        Converts a : separated mac address to decimal number list as used
-        in json interface dump.
-        :param mac_address: string mac address
-        :return: list representation of mac address
-        """
-
-        list_mac = []
-        for num in mac_address.split(":"):
-            list_mac.append(int(num, 16))
-        return list_mac
-
-    def _extract_vpp_interface_by_mac(self, interfaces_list, mac_address):
-        """Return interface dictionary from interface_list by mac address.
-
-        Extracts interface dictionary from all of the interfaces in interfaces
-        list parsed from json according to mac_address of the interface
-        :param interfaces_list: dictionary of all interfaces parsed from json
-        :param mac_address: string mac address of interface we are looking for
-        :return: interface dictionary from json
-        """
-
-        interface_dict = {}
-        list_mac_address = self.convert_mac_to_number_list(mac_address)
-        logger.trace(str(list_mac_address))
-        for interface in interfaces_list:
-            # TODO: create vat json integrity checking and move there
-            if "l2_address" not in interface:
-                raise KeyError(
-                    "key l2_address not found in interface dict."
-                    "Probably input list is not parsed from correct VAT "
-                    "json output.")
-            if "l2_address_length" not in interface:
-                raise KeyError(
-                    "key l2_address_length not found in interface "
-                    "dict. Probably input list is not parsed from correct "
-                    "VAT json output.")
-            mac_from_json = interface["l2_address"][:6]
-            if mac_from_json == list_mac_address:
-                if interface["l2_address_length"] != 6:
-                    raise ValueError("l2_address_length value is not 6.")
-                interface_dict = interface
-                break
-        return interface_dict
-
-    def vpp_interface_name_from_json_by_mac(self, json_data, mac_address):
-        """Return vpp interface name string from VAT interface dump json output
-
-        Extracts the name given to an interface by VPP.
-        These interface names differ from what you would see if you
-        used the ipconfig or similar command.
-        Required json data can be obtained by calling :
-        VatExecutor.execute_script_json_out("dump_interfaces.vat", node)
-        :param json_data: string json data from sw_interface_dump VAT command
-        :param mac_address: string containing mac address of interface
-        whose vpp name we wish to discover.
-        :return: string vpp interface name
-        """
-
-        interfaces_list = JsonParser().parse_data(json_data)
-        # TODO: checking if json data is parsed correctly
-        interface_dict = self._extract_vpp_interface_by_mac(interfaces_list,
-                                                            mac_address)
-        interface_name = interface_dict["interface_name"]
-        return interface_name
-
-    def _update_node_interface_data_from_json(self, node, interface_dump_json):
-        """Update node vpp data in node__DICT from json interface dump.
-
-        This method updates vpp interface names and sw indexexs according to
-        interface mac addresses found in interface_dump_json
-        :param node: node dictionary
-        :param interface_dump_json: json output from dump_interface_list VAT
-        command
-        """
-
-        interface_list = JsonParser().parse_data(interface_dump_json)
-        for ifc in node['interfaces'].values():
-            if 'link' not in ifc:
-                continue
-            if_mac = ifc['mac_address']
-            interface_dict = self._extract_vpp_interface_by_mac(interface_list,
-                                                                if_mac)
-            if not interface_dict:
-                raise Exception('Interface {0} not found by MAC {1}'.
-                        format(ifc, if_mac))
-            ifc['name'] = interface_dict["interface_name"]
-            ifc['vpp_sw_index'] = interface_dict["sw_if_index"]
-            ifc['mtu'] = interface_dict["mtu"]
-
-    def update_vpp_interface_data_on_node(self, node):
-        """Update vpp generated interface data for a given node in DICT__nodes
-
-        Updates interface names, software index numbers and any other details
-        generated specifically by vpp that are unknown before testcase run.
-        :param node: Node selected from DICT__nodes
-        """
-
-        vat_executor = VatExecutor()
-        vat_executor.execute_script_json_out("dump_interfaces.vat", node)
-        interface_dump_json = vat_executor.get_script_stdout()
-        self._update_node_interface_data_from_json(node,
-                                                   interface_dump_json)
-
-    @staticmethod
-    def update_tg_interface_data_on_node(node):
-        """Update interface name for TG/linux node in DICT__nodes
-
-        :param node: Node selected from DICT__nodes.
-        :type node: dict
-
-        .. note::
-            # for dev in `ls /sys/class/net/`;
-            > do echo "\"`cat /sys/class/net/$dev/address`\": \"$dev\""; done
-            "52:54:00:9f:82:63": "eth0"
-            "52:54:00:77:ae:a9": "eth1"
-            "52:54:00:e1:8a:0f": "eth2"
-            "00:00:00:00:00:00": "lo"
-
-        .. todo:: parse lshw -json instead
-        """
-        # First setup interface driver specified in yaml file
-        InterfaceSetup.tg_set_interfaces_default_driver(node)
-
-        # Get interface names
-        ssh = SSH()
-        ssh.connect(node)
-
-        cmd = 'for dev in `ls /sys/class/net/`; do echo "\\"`cat ' \
-              '/sys/class/net/$dev/address`\\": \\"$dev\\""; done;'
-
-        (ret_code, stdout, _) = ssh.exec_command(cmd)
-        if int(ret_code) != 0:
-            raise Exception('Get interface name and MAC failed')
-        tmp = "{" + stdout.rstrip().replace('\n', ',') + "}"
-        interfaces = JsonParser().parse_data(tmp)
-        for if_k, if_v in node['interfaces'].items():
-            if if_k == 'mgmt':
-                continue
-            name = interfaces.get(if_v['mac_address'])
-            if name is None:
-                continue
-            if_v['name'] = name
-
-        # Set udev rules for interfaces
-        InterfaceSetup.tg_set_interfaces_udev_rules(node)
-
-    def update_all_interface_data_on_all_nodes(self, nodes):
-        """Update interface names on all nodes in DICT__nodes
+        This method returns the interface name associated with a software
+        interface index assigned to the interface by vpp for a given node.
 
 
-        :param nodes: Nodes in the topology.
-        :type nodes: dict
-
-        This method updates the topology dictionary by querying interface lists
-        of all nodes mentioned in the topology dictionary.
-        It does this by dumping interface list to json output from all devices
-        using vpp_api_test, and pairing known information from topology
-        (mac address/pci address of interface) to state from VPP.
-        For TG/linux nodes add interface name only.
+        :param sw_index: Sw_index of the link that a interface is connected to.
+        :param node: The node topology dictionary.
+        :return: Interface name of the interface connected to the given link.
+        :rtype: str
         """
         """
-
-        for node_data in nodes.values():
-            if node_data['type'] == NodeType.DUT:
-                self.update_vpp_interface_data_on_node(node_data)
-            elif node_data['type'] == NodeType.TG:
-                self.update_tg_interface_data_on_node(node_data)
+        return self._get_interface_by_key_value(node, "vpp_sw_index", sw_index)
 
     @staticmethod
     def get_interface_sw_index(node, interface):
 
     @staticmethod
     def get_interface_sw_index(node, interface):
-        """Get VPP sw_index for the interface.
+        """Get VPP sw_if_index for the interface.
 
 
-        :param node: Node to get interface sw_index on.
-        :param interface: Interface name.
+        :param node: Node to get interface sw_if_index on.
+        :param interface: Interface identifier.
         :type node: dict
         :type node: dict
-        :type interface: str
-        :return: Return sw_index or None if not found.
+        :type interface: str or int
+        :return: Return sw_if_index or None if not found.
         """
         """
-        for port in node['interfaces'].values():
-            port_name = port.get('name')
-            if port_name == interface:
-                return port.get('vpp_sw_index')
-
-        return None
+        try:
+            return int(interface)
+        except ValueError:
+            for port in node['interfaces'].values():
+                port_name = port.get('name')
+                if port_name == interface:
+                    return port.get('vpp_sw_index')
+            return None
 
     @staticmethod
     def get_interface_mtu(node, interface):
 
     @staticmethod
     def get_interface_mtu(node, interface):
@@ -429,7 +260,7 @@ class Topology(object):
                 link_name = port_data['link']
                 break
 
                 link_name = port_data['link']
                 break
 
-        if link_name is None: 
+        if link_name is None:
             return None
 
         # find link
             return None
 
         # find link
@@ -462,8 +293,6 @@ class Topology(object):
         link_name = None
         # get link name where the interface belongs to
         for port_name, port_data in node['interfaces'].iteritems():
         link_name = None
         # get link name where the interface belongs to
         for port_name, port_data in node['interfaces'].iteritems():
-            if port_name == 'mgmt':
-                continue
             if port_data['name'] == interface_name:
                 link_name = port_data['link']
                 break
             if port_data['name'] == interface_name:
                 link_name = port_data['link']
                 break
@@ -515,13 +344,14 @@ class Topology(object):
 
     @staticmethod
     def get_node_link_mac(node, link_name):
 
     @staticmethod
     def get_node_link_mac(node, link_name):
-        """Return interface mac address by link name
+        """Return interface mac address by link name.
 
 
-        :param node: Node to get interface sw_index on
-        :param link_name: link name
+        :param node: Node to get interface sw_index on.
+        :param link_name: Link name.
         :type node: dict
         :type node: dict
-        :type link_name: string
-        :return: mac address string
+        :type link_name: str
+        :return: MAC address string.
+        :rtype: str
         """
         for port in node['interfaces'].values():
             if port.get('link') == link_name:
         """
         for port in node['interfaces'].values():
             if port.get('link') == link_name:
@@ -529,35 +359,67 @@ class Topology(object):
         return None
 
     @staticmethod
         return None
 
     @staticmethod
-    def _get_node_active_link_names(node):
-        """Return list of link names that are other than mgmt links
+    def _get_node_active_link_names(node, filter_list=None):
+        """Return list of link names that are other than mgmt links.
 
 
-        :param node: node topology dictionary
-        :return: list of strings that represent link names occupied by the node
+        :param node: Node topology dictionary.
+        :param filter_list: Link filter criteria.
+        :type node: dict
+        :type filter_list: list of strings
+        :return: List of strings that represent link names occupied by the node.
+        :rtype: list
         """
         interfaces = node['interfaces']
         link_names = []
         for interface in interfaces.values():
             if 'link' in interface:
         """
         interfaces = node['interfaces']
         link_names = []
         for interface in interfaces.values():
             if 'link' in interface:
-                link_names.append(interface['link'])
+                if (filter_list is not None) and ('model' in interface):
+                    for filt in filter_list:
+                        if filt == interface['model']:
+                            link_names.append(interface['link'])
+                elif (filter_list is not None) and ('model' not in interface):
+                    logger.trace("Cannot apply filter on interface: {}" \
+                                 .format(str(interface)))
+                else:
+                    link_names.append(interface['link'])
         if len(link_names) == 0:
             link_names = None
         return link_names
 
     @keyword('Get active links connecting "${node1}" and "${node2}"')
         if len(link_names) == 0:
             link_names = None
         return link_names
 
     @keyword('Get active links connecting "${node1}" and "${node2}"')
-    def get_active_connecting_links(self, node1, node2):
-        """Return list of link names that connect together node1 and node2
-
-        :param node1: node topology dictionary
-        :param node2: node topology dictionary
-        :return: list of strings that represent connecting link names
+    def get_active_connecting_links(self, node1, node2,
+                                    filter_list_node1=None,
+                                    filter_list_node2=None):
+        """Return list of link names that connect together node1 and node2.
+
+        :param node1: Node topology dictionary.
+        :param node2: Node topology dictionary.
+        :param filter_list_node1: Link filter criteria for node1.
+        :param filter_list_node2: Link filter criteria for node2.
+        :type node1: dict
+        :type node2: dict
+        :type filter_list1: list of strings
+        :type filter_list2: list of strings
+        :return: List of strings that represent connecting link names.
+        :rtype: list
         """
 
         logger.trace("node1: {}".format(str(node1)))
         logger.trace("node2: {}".format(str(node2)))
         """
 
         logger.trace("node1: {}".format(str(node1)))
         logger.trace("node2: {}".format(str(node2)))
-        node1_links = self._get_node_active_link_names(node1)
-        node2_links = self._get_node_active_link_names(node2)
-        connecting_links = list(set(node1_links).intersection(node2_links))
+        node1_links = self._get_node_active_link_names(
+            node1,
+            filter_list=filter_list_node1)
+        node2_links = self._get_node_active_link_names(
+            node2,
+            filter_list=filter_list_node2)
+
+        connecting_links = None
+        if node1_links is None:
+            logger.error("Unable to find active links for node1")
+        elif node2_links is None:
+            logger.error("Unable to find active links for node2")
+        else:
+            connecting_links = list(set(node1_links).intersection(node2_links))
 
         return connecting_links
 
 
         return connecting_links
 
@@ -566,14 +428,14 @@ class Topology(object):
     def get_first_active_connecting_link(self, node1, node2):
         """
 
     def get_first_active_connecting_link(self, node1, node2):
         """
 
-        :param node1: Connected node
+        :param node1: Connected node.
+        :param node2: Connected node.
         :type node1: dict
         :type node1: dict
-        :param node2: Connected node
         :type node2: dict
         :type node2: dict
-        :return: name of link connecting the two nodes together
+        :return: Name of link connecting the two nodes together.
+        :rtype: str
         :raises: RuntimeError
         """
         :raises: RuntimeError
         """
-
         connecting_links = self.get_active_connecting_links(node1, node2)
         if len(connecting_links) == 0:
             raise RuntimeError("No links connecting the nodes were found")
         connecting_links = self.get_active_connecting_links(node1, node2)
         if len(connecting_links) == 0:
             raise RuntimeError("No links connecting the nodes were found")
@@ -631,14 +493,7 @@ class Topology(object):
 
         For the time being it returns links from the Node path:
         TG->DUT1->DUT2->TG
 
         For the time being it returns links from the Node path:
         TG->DUT1->DUT2->TG
-        :param tgen: traffic generator node data
-        :param dut1: DUT1 node data
-        :param dut2: DUT2 node data
-        :type tgen: dict
-        :type dut1: dict
-        :type dut2: dict
-        :return: dictionary of possible link combinations
-        the naming convention until changed to something more general is
+        The naming convention until changed to something more general is
         implemented is this:
         DUT1_DUT2_LINK: link name between DUT! and DUT2
         DUT1_TG_LINK: link name between DUT1 and TG
         implemented is this:
         DUT1_DUT2_LINK: link name between DUT! and DUT2
         DUT1_TG_LINK: link name between DUT1 and TG
@@ -649,6 +504,15 @@ class Topology(object):
         domain on DUT1
         DUT2_BD_LINKS: list of link names that will be connected by the bridge
         domain on DUT2
         domain on DUT1
         DUT2_BD_LINKS: list of link names that will be connected by the bridge
         domain on DUT2
+
+        :param tgen: Traffic generator node data.
+        :param dut1: DUT1 node data.
+        :param dut2: DUT2 node data.
+        :type tgen: dict
+        :type dut1: dict
+        :type dut2: dict
+        :return: Dictionary of possible link combinations.
+        :rtype: dict
         """
         # TODO: replace with generic function.
         dut1_dut2_link = self.get_first_active_connecting_link(dut1, dut2)
         """
         # TODO: replace with generic function.
         dut1_dut2_link = self.get_first_active_connecting_link(dut1, dut2)
@@ -667,10 +531,12 @@ class Topology(object):
 
     @staticmethod
     def is_tg_node(node):
 
     @staticmethod
     def is_tg_node(node):
-        """Find out whether the node is TG
+        """Find out whether the node is TG.
 
 
-        :param node: node to examine
-        :return: True if node is type of TG; False otherwise
+        :param node: Node to examine.
+        :type node: dict
+        :return: True if node is type of TG, otherwise False.
+        :rtype: bool
         """
         return node['type'] == NodeType.TG
 
         """
         return node['type'] == NodeType.TG
 
@@ -680,6 +546,7 @@ class Topology(object):
 
         :param node: Node created from topology.
         :type node: dict
 
         :param node: Node created from topology.
         :type node: dict
-        :return: host as 'str' type
+        :return: Hostname or IP address.
+        :rtype: str
         """
         return node['host']
         """
         return node['host']