aboutsummaryrefslogtreecommitdiffstats
path: root/resources/libraries/python/topology.py
diff options
context:
space:
mode:
authorStefan Kobza <skobza@cisco.com>2016-01-11 18:03:25 +0100
committerStefan Kobza <skobza@cisco.com>2016-02-08 22:38:32 +0100
commit33499c81c94c2d3baef9d3e9f061cd76ef86fa74 (patch)
tree2d000ba17b821339a05e0c039f71e48e09553de9 /resources/libraries/python/topology.py
parent5cbeca02602061d32212e14f289d65cf648920e4 (diff)
New version of RF tests.
Change-Id: I241a2b7a7706e65f71cfd4a62e2a40f053fc5d07 Signed-off-by: Stefan Kobza <skobza@cisco.com>
Diffstat (limited to 'resources/libraries/python/topology.py')
-rw-r--r--resources/libraries/python/topology.py539
1 files changed, 539 insertions, 0 deletions
diff --git a/resources/libraries/python/topology.py b/resources/libraries/python/topology.py
new file mode 100644
index 0000000000..522de37d13
--- /dev/null
+++ b/resources/libraries/python/topology.py
@@ -0,0 +1,539 @@
+# 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.
+
+"""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
+from yaml import load
+
+__all__ = ["DICT__nodes", 'Topology']
+
+
+def load_topo_from_yaml():
+ """Loads topology from file defined in "${TOPOLOGY_PATH}" variable
+
+ :return: nodes from loaded topology
+ """
+ topo_path = BuiltIn().get_variable_value("${TOPOLOGY_PATH}")
+
+ with open(topo_path) as work_file:
+ return load(work_file.read())['nodes']
+
+
+class NodeType(object):
+ """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'
+
+DICT__nodes = load_topo_from_yaml()
+
+
+class Topology(object):
+ """Topology data manipulation and extraction methods
+
+ Defines methods used for manipulation and extraction of data from
+ the used topology.
+ """
+
+ def __init__(self):
+ pass
+
+ @staticmethod
+ def get_node_by_hostname(nodes, hostname):
+ """Get node from nodes of the topology by hostname.
+
+ :param nodes: Nodes of the test topology.
+ :param hostname: Host name.
+ :type nodes: dict
+ :type hostname: str
+ :return: Node dictionary or None if not found.
+ """
+ for node in nodes.values():
+ if node['host'] == hostname:
+ return node
+
+ return None
+
+ @staticmethod
+ def get_links(nodes):
+ """Get list of links(networks) in the topology.
+
+ :param nodes: Nodes of the test topology.
+ :type nodes: dict
+ :return: Links in the topology.
+ :rtype: list
+ """
+ links = []
+
+ for node in nodes.values():
+ for interface in node['interfaces'].values():
+ link = interface.get('link')
+ if link is not None:
+ if link not in links:
+ links.append(link)
+
+ return links
+
+ @staticmethod
+ def _get_interface_by_key_value(node, key, 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.
+ :return:
+ """
+
+ interfaces = node['interfaces']
+ retval = None
+ for interface in interfaces.values():
+ k_val = interface.get(key)
+ if k_val is not None:
+ if k_val == value:
+ retval = interface['name']
+ break
+ return retval
+
+ 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
+ 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
+ """
+
+ 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}.
+
+ 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
+ :return: dictionary of interface names that are connected to the given
+ links
+ """
+
+ retval = {}
+ interface_key_tpl = "interface{}"
+ interface_number = 1
+ for link_name in link_names:
+ interface_name = self.get_interface_by_link_name(node, link_name)
+ interface_key = interface_key_tpl.format(str(interface_number))
+ retval[interface_key] = interface_name
+ interface_number += 1
+ return retval
+
+ 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):
+ """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
+
+ :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
+
+ :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)
+
+ @staticmethod
+ def get_interface_sw_index(node, interface):
+ """Get VPP sw_index for the interface.
+
+ :param node: Node to get interface sw_index on.
+ :param interface: Interface name.
+ :type node: dict
+ :type interface: str
+ :return: Return sw_index or None if not found.
+ """
+ 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 None
+
+ @staticmethod
+ def get_interface_mac(node, interface):
+ """Get MAC address for the interface.
+
+ :param node: Node to get interface sw_index on.
+ :param interface: Interface name.
+ :type node: dict
+ :type interface: str
+ :return: Return MAC or None if not found.
+ """
+ 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_interface(node, interface_name):
+ """Get interface adjacent to specified interface on local network.
+
+ :param node: Node that contains specified interface.
+ :param interface_name: Interface name.
+ :type node: dict
+ :type interface_name: str
+ :return: Return interface or None if not found.
+ :rtype: dict
+ """
+ link_name = None
+ # get link name where the interface belongs to
+ for _, port_data in node['interfaces'].iteritems():
+ if port_data['name'] == interface_name:
+ link_name = port_data['link']
+ break
+
+ if link_name is None:
+ return None
+
+ # find link
+ for _, node_data in DICT__nodes.iteritems():
+ # 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['interfaces'][interface]
+
+ @staticmethod
+ def get_interface_pci_addr(node, interface):
+ """Get interface PCI address.
+
+ :param node: Node to get interface PCI address on.
+ :param interface: Interface name.
+ :type node: dict
+ :type interface: str
+ :return: Return PCI address or None if not found.
+ """
+ for port in node['interfaces'].values():
+ if interface == port.get('name'):
+ return port.get('pci_address')
+ return None
+
+ @staticmethod
+ def get_interface_driver(node, interface):
+ """Get interface driver.
+
+ :param node: Node to get interface driver on.
+ :param interface: Interface name.
+ :type node: dict
+ :type interface: str
+ :return: Return interface driver or None if not found.
+ """
+ for port in node['interfaces'].values():
+ if interface == port.get('name'):
+ return port.get('driver')
+ return None
+
+ @staticmethod
+ def get_node_link_mac(node, link_name):
+ """Return interface mac address by link name
+
+ :param node: Node to get interface sw_index on
+ :param link_name: link name
+ :type node: dict
+ :type link_name: string
+ :return: mac address string
+ """
+ for port in node['interfaces'].values():
+ if port.get('link') == link_name:
+ return port.get('mac_address')
+ return None
+
+ @staticmethod
+ def _get_node_active_link_names(node):
+ """Returns 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
+ """
+ interfaces = node['interfaces']
+ link_names = []
+ for interface in interfaces.values():
+ if 'link' in interface:
+ link_names.append(interface['link'])
+ 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):
+ """Returns 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
+ """
+
+ 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))
+
+ return connecting_links
+
+ @keyword('Get first active connecting link between node "${node1}" and '
+ '"${node2}"')
+ def get_first_active_connecting_link(self, node1, node2):
+ """
+
+ :param node1: Connected node
+ :type node1: dict
+ :param node2: Connected node
+ :type node2: dict
+ :return: name of link connecting the two nodes together
+ :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")
+ else:
+ return connecting_links[0]
+
+ @keyword('Get egress interfaces on "${node1}" for link with "${node2}"')
+ def get_egress_interfaces_for_nodes(self, node1, node2):
+ """Get egress interfaces on node1 for link with node2.
+
+ :param node1: First node, node to get egress interface on.
+ :param node2: Second node.
+ :type node1: dict
+ :type node2: dict
+ :return: Engress interfaces.
+ :rtype: list
+ """
+ interfaces = []
+ links = self.get_active_connecting_links(node1, node2)
+ if len(links) == 0:
+ raise RuntimeError('No link between nodes')
+ for interface in node1['interfaces'].values():
+ link = interface.get('link')
+ if link is None:
+ continue
+ if link in links:
+ continue
+ name = interface.get('name')
+ if name is None:
+ continue
+ interfaces.append(name)
+ return interfaces
+
+ @keyword('Get first egress interface on "${node1}" for link with '
+ '"${node2}"')
+ def get_first_egress_interface_for_nodes(self, node1, node2):
+ """Get first egress interface on node1 for link with node2.
+
+ :param node1: First node, node to get egress interface on.
+ :param node2: Second node.
+ :type node1: dict
+ :type node2: dict
+ :return: Engress interface.
+ :rtype: str
+ """
+ interfaces = self.get_egress_interfaces_for_nodes(node1, node2)
+ if not interfaces:
+ raise RuntimeError('No engress interface for nodes')
+ return interfaces[0]