diff options
Diffstat (limited to 'resources/libraries/python')
-rw-r--r-- | resources/libraries/python/CrossConnectSetup.py | 45 | ||||
-rw-r--r-- | resources/libraries/python/IPv4NodeAddress.py | 6 | ||||
-rw-r--r-- | resources/libraries/python/IPv4Util.py | 92 | ||||
-rw-r--r-- | resources/libraries/python/L2Util.py | 119 | ||||
-rw-r--r-- | resources/libraries/python/NodePath.py | 192 | ||||
-rw-r--r-- | resources/libraries/python/PacketVerifier.py | 8 | ||||
-rw-r--r-- | resources/libraries/python/TrafficScriptExecutor.py | 4 | ||||
-rw-r--r-- | resources/libraries/python/topology.py | 71 |
8 files changed, 502 insertions, 35 deletions
diff --git a/resources/libraries/python/CrossConnectSetup.py b/resources/libraries/python/CrossConnectSetup.py new file mode 100644 index 0000000000..3f1f7d42b6 --- /dev/null +++ b/resources/libraries/python/CrossConnectSetup.py @@ -0,0 +1,45 @@ +# 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. + +"""Library to set up cross-connect in topology.""" + +from resources.libraries.python.VatExecutor import VatExecutor +from resources.libraries.python.topology import Topology + +__all__ = ['CrossConnectSetup'] + +class CrossConnectSetup(object): + """Crossconnect setup in topology.""" + + def __init__(self): + pass + + @staticmethod + def vpp_setup_bidirectional_cross_connect(node, interface1, interface2): + """Create crossconnect between 2 interfaces on vpp node. + + :param node: Node to add bidirectional crossconnect + :param interface1: first interface + :param interface2: second interface + :type node: dict + :type interface1: str + :type interface2: str + """ + sw_iface1 = Topology().get_interface_sw_index(node, interface1) + sw_iface2 = Topology().get_interface_sw_index(node, interface2) + VatExecutor.cmd_from_template(node, "l2_xconnect.vat", + interface1=sw_iface1, + interface2=sw_iface2) + VatExecutor.cmd_from_template(node, "l2_xconnect.vat", + interface1=sw_iface2, + interface2=sw_iface1) diff --git a/resources/libraries/python/IPv4NodeAddress.py b/resources/libraries/python/IPv4NodeAddress.py index 0f2c1d9cd3..8db9ffafe3 100644 --- a/resources/libraries/python/IPv4NodeAddress.py +++ b/resources/libraries/python/IPv4NodeAddress.py @@ -19,9 +19,9 @@ from ipaddress import IPv4Network # Default list of IPv4 subnets -IPV4_NETWORKS = ['192.168.1.0/24', - '192.168.2.0/24', - '192.168.3.0/24'] +IPV4_NETWORKS = ['20.20.20.0/24', + '10.10.10.0/24', + '1.1.1.0/30'] class IPv4NetworkGenerator(object): diff --git a/resources/libraries/python/IPv4Util.py b/resources/libraries/python/IPv4Util.py index 5480bfcea3..d3a016d767 100644 --- a/resources/libraries/python/IPv4Util.py +++ b/resources/libraries/python/IPv4Util.py @@ -45,6 +45,7 @@ class IPv4Node(object): @abstractmethod def set_ip(self, interface, address, prefix_length): """Configure IPv4 address on interface + :param interface: interface name :param address: :param prefix_length: @@ -58,6 +59,7 @@ class IPv4Node(object): @abstractmethod def set_interface_state(self, interface, state): """Set interface state + :param interface: interface name string :param state: one of following values: "up" or "down" :return: nothing @@ -67,6 +69,7 @@ class IPv4Node(object): @abstractmethod def set_route(self, network, prefix_length, gateway, interface): """Configure IPv4 route + :param network: network IPv4 address :param prefix_length: mask length :param gateway: IPv4 address of the gateway @@ -82,6 +85,7 @@ class IPv4Node(object): @abstractmethod def unset_route(self, network, prefix_length, gateway, interface): """Remove specified IPv4 route + :param network: network IPv4 address :param prefix_length: mask length :param gateway: IPv4 address of the gateway @@ -97,6 +101,7 @@ class IPv4Node(object): @abstractmethod def flush_ip_addresses(self, interface): """Flush all IPv4 addresses from specified interface + :param interface: interface name :type interface: str :return: nothing @@ -106,6 +111,7 @@ class IPv4Node(object): @abstractmethod def ping(self, destination_address, source_interface): """Send an ICMP request to destination node + :param destination_address: address to send the ICMP request :param source_interface: :type destination_address: str @@ -167,6 +173,7 @@ class Dut(IPv4Node): def get_sw_if_index(self, interface): """Get sw_if_index of specified interface from current node + :param interface: interface name :type interface: str :return: sw_if_index of 'int' type @@ -175,6 +182,7 @@ class Dut(IPv4Node): def exec_vat(self, script, **args): """Wrapper for VAT executor. + :param script: script to execute :param args: parameters to the script :type script: str @@ -184,6 +192,20 @@ class Dut(IPv4Node): # TODO: check return value VatExecutor.cmd_from_template(self.node_info, script, **args) + def set_arp(self, interface, ip_address, mac_address): + """Set entry in ARP cache. + + :param interface: Interface name. + :param ip_address: IP address. + :param mac_address: MAC address. + :type interface: str + :type ip_address: str + :type mac_address: str + """ + self.exec_vat('add_ip_neighbor.vat', + sw_if_index=self.get_sw_if_index(interface), + ip_address=ip_address, mac_address=mac_address) + def set_ip(self, interface, address, prefix_length): self.exec_vat('add_ip_address.vat', sw_if_index=self.get_sw_if_index(interface), @@ -224,6 +246,7 @@ class Dut(IPv4Node): def get_node(node_info): """Creates a class instance derived from Node based on type. + :param node_info: dictionary containing information on nodes in topology :return: Class instance that is derived from Node """ @@ -238,6 +261,7 @@ def get_node(node_info): def get_node_hostname(node_info): """Get string identifying specifed node. + :param node_info: Node in the topology. :type node_info: Dict :return: String identifying node. @@ -264,8 +288,32 @@ class IPv4Util(object): topology_helper = None @staticmethod + def setup_arp_on_all_duts(nodes_info): + """For all DUT nodes extract MAC and IP addresses of adjacent interfaces + from topology and use them to setup ARP entries. + + :param nodes_info: Dictionary containing information on all nodes + in topology. + :type nodes_info: dict + """ + for node in nodes_info.values(): + if node['type'] == NodeType.TG: + continue + for interface, interface_data in node['interfaces'].iteritems(): + if interface == 'mgmt': + continue + interface_name = interface_data['name'] + adj_node, adj_int = Topology.\ + get_adjacent_node_and_interface(nodes_info, node, + interface_name) + ip_address = IPv4Util.get_ip_addr(adj_node, adj_int['name']) + mac_address = adj_int['mac_address'] + get_node(node).set_arp(interface_name, ip_address, mac_address) + + @staticmethod def next_address(subnet): """Get next unused IPv4 address from a subnet + :param subnet: holds available IPv4 addresses :return: tuple (ipv4_address, prefix_length) """ @@ -281,6 +329,7 @@ class IPv4Util(object): @staticmethod def next_network(nodes_addr): """Get next unused network from dictionary + :param nodes_addr: dictionary of available networks :return: dictionary describing an IPv4 subnet with addresses """ @@ -290,7 +339,9 @@ class IPv4Util(object): @staticmethod def configure_ipv4_addr_on_node(node, nodes_addr): - """Configure IPv4 address for all interfaces on a node in topology + """Configure IPv4 address for all non-management interfaces + on a node in topology. + :param node: dictionary containing information about node :param nodes_addr: dictionary containing IPv4 addresses :return: @@ -305,16 +356,20 @@ class IPv4Util(object): network = IPv4Util.topology_helper[interface_data['link']] address, prefix = IPv4Util.next_address(network) - get_node(node).set_ip(interface_data['name'], address, prefix) + if node['type'] != NodeType.TG: + get_node(node).set_ip(interface_data['name'], address, prefix) + get_node(node).set_interface_state(interface_data['name'], 'up') + key = (get_node_hostname(node), interface_data['name']) IPv4Util.ADDRESSES[key] = address IPv4Util.PREFIXES[key] = prefix IPv4Util.SUBNETS[key] = network['subnet'] @staticmethod - def nodes_setup_ipv4_addresses(nodes_info, nodes_addr): + def dut_nodes_setup_ipv4_addresses(nodes_info, nodes_addr): """Configure IPv4 addresses on all non-management interfaces for each - node in nodes_info + node in nodes_info if node type is not traffic generator + :param nodes_info: dictionary containing information on all nodes in topology :param nodes_addr: dictionary containing IPv4 addresses @@ -323,16 +378,17 @@ class IPv4Util(object): IPv4Util.topology_helper = {} # make a deep copy of nodes_addr because of modifications nodes_addr_copy = copy.deepcopy(nodes_addr) - for _, node in nodes_info.iteritems(): + for node in nodes_info.values(): IPv4Util.configure_ipv4_addr_on_node(node, nodes_addr_copy) @staticmethod def nodes_clear_ipv4_addresses(nodes): """Clear all addresses from all nodes in topology + :param nodes: dictionary containing information on all nodes :return: nothing """ - for _, node in nodes.iteritems(): + for node in nodes.values(): for interface, interface_data in node['interfaces'].iteritems(): if interface == 'mgmt': continue @@ -343,6 +399,7 @@ class IPv4Util(object): @keyword('Node "${node}" interface "${interface}" is in "${state}" state') def set_interface_state(node, interface, state): """See IPv4Node.set_interface_state for more information. + :param node: :param interface: :param state: @@ -357,6 +414,7 @@ class IPv4Util(object): '"${address}" with prefix length "${prefix_length}"') def set_interface_address(node, interface, address, length): """See IPv4Node.set_ip for more information. + :param node: :param interface: :param address: @@ -387,6 +445,7 @@ class IPv4Util(object): '"${gateway}"') def set_route(node, network, prefix_length, interface, gateway): """See IPv4Node.set_route for more information. + :param node: :param network: :param prefix_length: @@ -407,6 +466,7 @@ class IPv4Util(object): '"${gateway}"') def unset_route(node, network, prefix_length, interface, gateway): """See IPv4Node.unset_route for more information. + :param node: :param network: :param prefix_length: @@ -417,12 +477,15 @@ class IPv4Util(object): get_node(node).unset_route(network, prefix_length, gateway, interface) @staticmethod - @keyword('After ping is sent from node "${src_node}" interface ' - '"${src_port}" with destination IPv4 address of node ' - '"${dst_node}" interface "${dst_port}" a ping response arrives ' - 'and TTL is decreased by "${ttl_dec}"') - def send_ping(src_node, src_port, dst_node, dst_port, hops): + @keyword('After ping is sent in topology "${nodes_info}" from node ' + '"${src_node}" interface "${src_port}" with destination IPv4 ' + 'address of node "${dst_node}" interface "${dst_port}" a ping ' + 'response arrives and TTL is decreased by "${hops}"') + def send_ping(nodes_info, src_node, src_port, dst_node, dst_port, hops): """Send IPv4 ping and wait for response. + + :param nodes_info: Dictionary containing information on all nodes + in topology. :param src_node: Source node. :param src_port: Source interface. :param dst_node: Destination node. @@ -438,7 +501,8 @@ class IPv4Util(object): src_mac = Topology.get_interface_mac(src_node, src_port) if dst_node['type'] == NodeType.TG: dst_mac = Topology.get_interface_mac(src_node, src_port) - adj_int = Topology.get_adjacent_interface(src_node, src_port) + _, adj_int = Topology.\ + get_adjacent_node_and_interface(nodes_info, src_node, src_port) first_hop_mac = adj_int['mac_address'] src_ip = IPv4Util.get_ip_addr(src_node, src_port) dst_ip = IPv4Util.get_ip_addr(dst_node, dst_port) @@ -454,6 +518,7 @@ class IPv4Util(object): @keyword('Get IPv4 address of node "${node}" interface "${port}"') def get_ip_addr(node, port): """Get IPv4 address configured on specified interface + :param node: node dictionary :param port: interface name :return: IPv4 address of specified interface as a 'str' type @@ -466,6 +531,7 @@ class IPv4Util(object): @keyword('Get IPv4 address prefix of node "${node}" interface "${port}"') def get_ip_addr_prefix(node, port): """ Get IPv4 address prefix for specified interface. + :param node: Node dictionary. :param port: Interface name. """ @@ -477,6 +543,7 @@ class IPv4Util(object): @keyword('Get IPv4 subnet of node "${node}" interface "${port}"') def get_ip_addr_subnet(node, port): """ Get IPv4 subnet of specified interface. + :param node: Node dictionary. :param port: Interface name. """ @@ -488,6 +555,7 @@ class IPv4Util(object): @keyword('Flush IPv4 addresses "${port}" "${node}"') def flush_ip_addresses(port, node): """See IPv4Node.flush_ip_addresses for more information. + :param port: :param node: :return: diff --git a/resources/libraries/python/L2Util.py b/resources/libraries/python/L2Util.py new file mode 100644 index 0000000000..8581b1e879 --- /dev/null +++ b/resources/libraries/python/L2Util.py @@ -0,0 +1,119 @@ +# 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. + +"""L2 bridge domain utilities Library.""" + +from robot.api.deco import keyword +from resources.libraries.python.topology import Topology +from resources.libraries.python.VatExecutor import VatExecutor + + +class L2Util(object): + """Utilities for l2 bridge domain configuration""" + + def __init__(self): + pass + + @staticmethod + @keyword('Setup static L2FIB entry on node "${node}" for MAC "${dst_mac}"' + ' link "${link}" pair on bd_index "${bd_id}"') + def static_l2_fib_entry_via_links(node, dst_mac, link, bd_id): + """ Creates a static fib entry on a vpp node + + :param node: node where we wish to add the static fib entry + :param dst_mac: destination mac address in the entry + :param link: link name of the node destination interface + :param bd_id: l2 bridge domain id + :type node: dict + :type dst_mac: str + :type link: str + :type bd_id: str + """ + topology = Topology() + interface_name = topology.get_interface_by_link_name(node, link) + sw_if_index = topology.get_interface_sw_index(node, interface_name) + VatExecutor.cmd_from_template(node, "add_l2_fib_entry.vat", + mac=dst_mac, bd=bd_id, + interface=sw_if_index) + + @staticmethod + @keyword('Setup l2 bridge domain with id "${bd_id}" flooding "${flood}" ' + 'forwarding "${forward}" learning "${learn}" and arp termination ' + '"${arp_term}" on vpp node "${node}"') + def setup_vpp_l2_bridge_domain(node, bd_id, flood, forward, learn, + arp_term): + """Create a l2 bridge domain on the chosen vpp node + + Executes "bridge_domain_add_del bd_id {bd_id} flood {flood} uu-flood 1 + forward {forward} learn {learn} arp-term {arp_term}" VAT command on + the node. + For the moment acts as a placeholder + :param node: node where we wish to crate the l2 bridge domain + :param bd_id: bridge domain id + :param flood: enable flooding + :param forward: enable forwarding + :param learn: enable mac address learning to fib + :param arp_term: enable arp_termination + :type node: str + :type bd_id: str + :type flood: bool + :type forward: bool + :type learn: bool + :type arp_term:bool + :return: + """ + pass + + @keyword('Add interface "${interface}" to l2 bridge domain with index ' + '"${bd_id}" and shg "${shg}" on vpp node "${node}"') + def add_interface_to_l2_bd(self, node, interface, bd_id, shg): + """Adds interface to l2 bridge domain. + + Executes the "sw_interface_set_l2_bridge {interface1} bd_id {bd_id} + shg {shg} enable" VAT command on the given node. + For the moment acts as a placeholder + :param node: node where we want to execute the command that does this. + :param interface: + :param bd_id: + :param shg: + :type node: dict + :type interface: str + :type bd_id: str + :type shg: str + :return: + """ + pass + + @staticmethod + @keyword('Create dict used in bridge domain template file for node ' + '"${node}" with links "${link_names}" and bd_id "${bd_id}"') + def create_bridge_domain_vat_dict(node, link_names, bd_id): + """Creates dictionary that can be used in l2 bridge domain template. + + :param node: node data dictionary + :param link_names: list of names of links the bridge domain should be + connecting + :param bd_id: bridge domain index number + :type node: dict + :type link_names: list + :return: dictionary used to generate l2 bridge domain VAT configuration + from template file + The resulting dictionary looks like this: + 'interface1': interface name of first interface + 'interface2': interface name of second interface + 'bd_id': bridge domian index + """ + bd_dict = Topology().get_interfaces_by_link_names(node, link_names) + bd_dict['bd_id'] = bd_id + return bd_dict + diff --git a/resources/libraries/python/NodePath.py b/resources/libraries/python/NodePath.py new file mode 100644 index 0000000000..d1aa1f76d4 --- /dev/null +++ b/resources/libraries/python/NodePath.py @@ -0,0 +1,192 @@ +# 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. + +"""Path utilities library for nodes in the topology.""" + +from topology import Topology + + +class NodePath(object): + """Path utilities for nodes in the topology. + + :Example: + + node1--link1-->node2--link2-->node3--link3-->node2--link4-->node1 + RobotFramework: + | Library | resources/libraries/python/NodePath.py + + | Path test + | | [Arguments] | ${node1} | ${node2} | ${node3} + | | Append Node | ${nodes1} + | | Append Node | ${nodes2} + | | Append Nodes | ${nodes3} | ${nodes2} + | | Append Node | ${nodes1} + | | Compute Path | ${FALSE} + | | ${first_int} | ${node}= | First Interface + | | ${last_int} | ${node}= | Last Interface + | | ${first_ingress} | ${node}= | First Ingress Interface + | | ${last_egress} | ${node}= | Last Egress Interface + | | ${next} | ${node}= | Next Interface + + Python: + >>> from NodePath import NodePath + >>> path = NodePath() + >>> path.append_node(node1) + >>> path.append_node(node2) + >>> path.append_nodes(node3, node2) + >>> path.append_node(node1) + >>> path.compute_path() + >>> (interface, node) = path.first_interface() + >>> (interface, node) = path.last_interface() + >>> (interface, node) = path.first_ingress_interface() + >>> (interface, node) = path.last_egress_interface() + >>> (interface, node) = path.next_interface() + """ + + def __init__(self): + self._nodes = [] + self._links = [] + self._path = [] + self._path_iter = [] + + def append_node(self, node): + """Append node to the path. + + :param node: Node to append to the path. + :type node: dict + """ + self._nodes.append(node) + + def append_nodes(self, *nodes): + """Append nodes to the path. + + :param nodes: Nodes to append to the path. + :type nodes: dict + + .. note:: Node order does matter. + """ + for node in nodes: + self.append_node(node) + + def clear_path(self): + """Clear path.""" + self._nodes = [] + self._links = [] + self._path = [] + self._path_iter = [] + + def compute_path(self, always_same_link=True): + """Compute path for added nodes. + + :param always_same_link: If True use always same link between two nodes + in path. If False use different link (if available) between two + nodes if one link was used before. + :type always_same_link: bool + + .. note:: First add at least two nodes to the topology. + """ + nodes = self._nodes + if len(nodes) < 2: + raise RuntimeError('Not enough nodes to compute path') + + for idx in range(0, len(nodes) - 1): + topo = Topology() + node1 = nodes[idx] + node2 = nodes[idx + 1] + links = topo.get_active_connecting_links(node1, node2) + if not links: + raise RuntimeError('No link between {0} and {1}'.format( + node1['host'], node2['host'])) + + link = None + l_set = set() + + if always_same_link: + l_set = set(links).intersection(self._links) + else: + l_set = set(links).difference(self._links) + + if not l_set: + link = links.pop() + else: + link = l_set.pop() + + self._links.append(link) + interface1 = topo.get_interface_by_link_name(node1, link) + interface2 = topo.get_interface_by_link_name(node2, link) + self._path.append((interface1, node1)) + self._path.append((interface2, node2)) + + self._path_iter.extend(self._path) + self._path_iter.reverse() + + def next_interface(self): + """Path interface iterator. + + :return: Interface and node or None if not next interface. + :rtype: tuple (str, dict) + + .. note:: Call compute_path before. + """ + if not self._path_iter: + return (None, None) + else: + return self._path_iter.pop() + + def first_interface(self): + """Return first interface on the path. + + :return: Interface and node. + :rtype: tuple (str, dict) + + .. note:: Call compute_path before. + """ + if not self._path: + raise RuntimeError('No path for topology') + return self._path[0] + + def last_interface(self): + """Return last interface on the path. + + :return: Interface and node. + :rtype: tuple (str, dict) + + .. note:: Call compute_path before. + """ + if not self._path: + raise RuntimeError('No path for topology') + return self._path[-1] + + def first_ingress_interface(self): + """Return first ingress interface on the path. + + :return: Interface and node. + :rtype: tuple (str, dict) + + .. note:: Call compute_path before. + """ + if not self._path: + raise RuntimeError('No path for topology') + return self._path[1] + + def last_egress_interface(self): + """Return last egress interface on the path. + + :return: Interface and node. + :rtype: tuple (str, dict) + + .. note:: Call compute_path before. + """ + if not self._path: + raise RuntimeError('No path for topology') + return self._path[-2] diff --git a/resources/libraries/python/PacketVerifier.py b/resources/libraries/python/PacketVerifier.py index 81798e1f68..54f719fa9a 100644 --- a/resources/libraries/python/PacketVerifier.py +++ b/resources/libraries/python/PacketVerifier.py @@ -188,7 +188,7 @@ def packet_reader(interface_name, queue): buf = "" while True: - recvd = sock.recv(1500) + recvd = sock.recv(1514) buf = buf + recvd pkt = extract_one_packet(buf) @@ -285,11 +285,7 @@ class Interface(object): self.txq.send(pkt) def recv_pkt(self, timeout=3): - while True: - pkt = self.rxq.recv(timeout, self.sent_packets) - # TODO: FIX FOLLOWING: DO NOT SKIP RARP IN ALL TESTS!!! - if pkt.type != 32821: # Skip RARP packets - return pkt + return self.rxq.recv(timeout, self.sent_packets) def close(self): self.rxq._proc.terminate() diff --git a/resources/libraries/python/TrafficScriptExecutor.py b/resources/libraries/python/TrafficScriptExecutor.py index 2e65a520d0..ee29695812 100644 --- a/resources/libraries/python/TrafficScriptExecutor.py +++ b/resources/libraries/python/TrafficScriptExecutor.py @@ -70,8 +70,8 @@ class TrafficScriptExecutor(object): def traffic_script_gen_arg(rx_if, tx_if, src_mac, dst_mac, src_ip, dst_ip): """Generate traffic script basic arguments string. - :param rx_if: Interface that sends traffic. - :param tx_if: Interface that receives traffic. + :param rx_if: Interface that receives traffic. + :param tx_if: Interface that sends traffic. :param src_mac: Source MAC address. :param dst_mac: Destination MAC address. :param src_ip: Source IP address. diff --git a/resources/libraries/python/topology.py b/resources/libraries/python/topology.py index 522de37d13..2b202e5b4a 100644 --- a/resources/libraries/python/topology.py +++ b/resources/libraries/python/topology.py @@ -365,19 +365,25 @@ class Topology(object): 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 + def get_adjacent_node_and_interface(nodes_info, node, interface_name): + """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 interface_name: Interface name. + :type nodes_info: dict + :type node: dict + :type interface_name: 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_data in node['interfaces'].iteritems(): + 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 @@ -386,7 +392,7 @@ class Topology(object): return None # find link - for _, node_data in DICT__nodes.iteritems(): + for node_data in nodes_info.values(): # skip self if node_data['host'] == node['host']: continue @@ -395,7 +401,7 @@ class Topology(object): if 'link' not in interface_data: continue if interface_data['link'] == link_name: - return node_data['interfaces'][interface] + return node_data, node_data['interfaces'][interface] @staticmethod def get_interface_pci_addr(node, interface): @@ -537,3 +543,44 @@ class Topology(object): if not interfaces: raise RuntimeError('No engress 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. + + For the time being it returns links from the Node path: + TG->DUT1->DUT2->TG + :param tg: traffic generator node data + :param dut1: DUT1 node data + :param dut2: DUT2 node data + :type tg: dict + :type dut1: dict + :type dut2: dict + :return: dictionary of possible link combinations + 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 + DUT2_TG_LINK: link name between DUT2 and TG + TG_TRAFFIC_LINKS: list of link names that generated traffic is sent + to and from + DUT1_BD_LINKS: list of link names that will be connected by the bridge + domain on DUT1 + DUT2_BD_LINKS: list of link names that will be connected by the bridge + domain on DUT2 + """ + # TODO: replace with generic function. + dut1_dut2_link = self.get_first_active_connecting_link(dut1, dut2) + dut1_tg_link = self.get_first_active_connecting_link(dut1, tgen) + dut2_tg_link = self.get_first_active_connecting_link(dut2, tgen) + tg_traffic_links = [dut1_tg_link, dut2_tg_link] + dut1_bd_links = [dut1_dut2_link, dut1_tg_link] + dut2_bd_links = [dut1_dut2_link, dut2_tg_link] + topology_links = {'DUT1_DUT2_LINK': dut1_dut2_link, + 'DUT1_TG_LINK': dut1_tg_link, + 'DUT2_TG_LINK': dut2_tg_link, + 'TG_TRAFFIC_LINKS': tg_traffic_links, + 'DUT1_BD_LINKS': dut1_bd_links, + 'DUT2_BD_LINKS': dut2_bd_links} + return topology_links |