X-Git-Url: https://gerrit.fd.io/r/gitweb?p=csit.git;a=blobdiff_plain;f=resources%2Flibraries%2Fpython%2Ftopology.py;h=0f26d2c6e95ad5ac11feb4b52567cfe02675e201;hp=2b202e5b4a9f2b362f8db15077d8c26e83f9111b;hb=6d9fe9bb4bd353b1d4fec3b2569bcd743f87fc4a;hpb=b92a827b1c7f48da4214e992e5503ebe1c182416 diff --git a/resources/libraries/python/topology.py b/resources/libraries/python/topology.py index 2b202e5b4a..0f26d2c6e9 100644 --- a/resources/libraries/python/topology.py +++ b/resources/libraries/python/topology.py @@ -13,10 +13,6 @@ """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 robot.api import logger from robot.libraries.BuiltIn import BuiltIn from robot.api.deco import keyword @@ -26,7 +22,7 @@ __all__ = ["DICT__nodes", 'Topology'] def load_topo_from_yaml(): - """Loads topology from file defined in "${TOPOLOGY_PATH}" variable + """Load topology from file defined in "${TOPOLOGY_PATH}" variable :return: nodes from loaded topology """ @@ -42,6 +38,17 @@ class NodeType(object): 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): + #T-Rex traffic generator + TREX = 'TREX' + # Moongen + MOONGEN = 'MOONGEN' + # IxNetwork + IXNET = 'IXNET' DICT__nodes = load_topo_from_yaml() @@ -53,9 +60,6 @@ class Topology(object): the used topology. """ - def __init__(self): - pass - @staticmethod def get_node_by_hostname(nodes, hostname): """Get node from nodes of the topology by hostname. @@ -94,7 +98,7 @@ class Topology(object): @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. @@ -129,8 +133,6 @@ class Topology(object): This method returns the interface names asociated with given links 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 connected to. :param node: the node topology directory @@ -161,187 +163,56 @@ class Topology(object): 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): - """Returns 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(list_mac_address.__str__()) - 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) - ifc['name'] = interface_dict["interface_name"] - ifc['vpp_sw_index'] = interface_dict["sw_if_index"] - - 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 + def get_interface_sw_index(node, interface): + """Get VPP sw_if_index for the interface. - :param node: Node selected from DICT__nodes. + :param node: Node to get interface sw_if_index on. + :param interface: Interface identifier. :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 + :type interface: str or int + :return: Return sw_if_index or None if not found. """ - # 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 - - :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 vpe_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. - """ - - 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) + 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_sw_index(node, interface): - """Get VPP sw_index for the interface. + def get_interface_mtu(node, interface): + """Get interface MTU. - :param node: Node to get interface sw_index on. + Returns physical layer MTU (max. size of Ethernet frame). + :param node: Node to get interface MTU on. :param interface: Interface name. :type node: dict :type interface: str - :return: Return sw_index or None if not found. + :return: MTU or None if not found. + :rtype: int """ for port in node['interfaces'].values(): port_name = port.get('name') - if port_name is None: - continue if port_name == interface: - return port.get('vpp_sw_index') + return port.get('mtu') + + return None + + @staticmethod + def get_interface_mac_by_port_key(node, port_key): + """Get MAC address for the interface based on port key. + + :param node: Node to get interface mac on. + :param port_key: Dictionary key name of interface. + :type node: dict + :type port_key: str + :return: Return MAC or None if not found. + """ + for port_name, port_data in node['interfaces'].iteritems(): + if port_name == port_key: + return port_data['mac_address'] return None @@ -357,13 +228,50 @@ class Topology(object): """ for port in node['interfaces'].values(): port_name = port.get('name') - if port_name is None: - continue if port_name == interface: return port.get('mac_address') return None + @staticmethod + def get_adjacent_node_and_interface_by_key(nodes_info, node, port_key): + """Get node and interface adjacent to specified interface + on local network. + + :param nodes_info: Dictionary containing information on all nodes + in topology. + :param node: Node that contains specified interface. + :param port_key: Interface port key. + :type nodes_info: dict + :type node: dict + :type port_key: str + :return: Return (node, interface info) tuple or None if not found. + :rtype: (dict, dict) + """ + 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_name == port_key: + link_name = port_data['link'] + break + + if link_name is None: + return None + + # find link + for node_data in nodes_info.values(): + # skip self + if node_data['host'] == node['host']: + continue + for interface, interface_data \ + in node_data['interfaces'].iteritems(): + if 'link' not in interface_data: + continue + if interface_data['link'] == link_name: + return node_data, node_data['interfaces'][interface] + @staticmethod def get_adjacent_node_and_interface(nodes_info, node, interface_name): """Get node and interface adjacent to specified interface @@ -450,7 +358,7 @@ class Topology(object): @staticmethod def _get_node_active_link_names(node): - """Returns list of link names that are other than mgmt links + """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 @@ -466,7 +374,7 @@ class Topology(object): @keyword('Get active links connecting "${node1}" and "${node2}"') def get_active_connecting_links(self, node1, node2): - """Returns list of link names that connect together node1 and node2 + """Return list of link names that connect together node1 and node2 :param node1: node topology dictionary :param node2: node topology dictionary @@ -508,7 +416,7 @@ class Topology(object): :param node2: Second node. :type node1: dict :type node2: dict - :return: Engress interfaces. + :return: Egress interfaces. :rtype: list """ interfaces = [] @@ -536,25 +444,25 @@ class Topology(object): :param node2: Second node. :type node1: dict :type node2: dict - :return: Engress interface. + :return: Egress interface. :rtype: str """ interfaces = self.get_egress_interfaces_for_nodes(node1, node2) if not interfaces: - raise RuntimeError('No engress interface for nodes') + raise RuntimeError('No egress interface for nodes') return interfaces[0] @keyword('Get link data useful in circular topology test from tg "${tgen}"' ' dut1 "${dut1}" dut2 "${dut2}"') def get_links_dict_from_nodes(self, tgen, dut1, dut2): - """Returns link combinations used in tests in circular topology. + """Return link combinations used in tests in circular topology. For the time being it returns links from the Node path: TG->DUT1->DUT2->TG - :param tg: traffic generator node data + :param tgen: traffic generator node data :param dut1: DUT1 node data :param dut2: DUT2 node data - :type tg: dict + :type tgen: dict :type dut1: dict :type dut2: dict :return: dictionary of possible link combinations @@ -584,3 +492,22 @@ class Topology(object): 'DUT1_BD_LINKS': dut1_bd_links, 'DUT2_BD_LINKS': dut2_bd_links} return topology_links + + @staticmethod + def is_tg_node(node): + """Find out whether the node is TG + + :param node: node to examine + :return: True if node is type of TG; False otherwise + """ + return node['type'] == NodeType.TG + + @staticmethod + def get_node_hostname(node): + """Return host (hostname/ip address) of the node. + + :param node: Node created from topology. + :type node: dict + :return: host as 'str' type + """ + return node['host']