698c97b42683ee1f2d9df46c2938dace4e9fe84a
[csit.git] / resources / libraries / python / topology.py
1 # Copyright (c) 2019 Cisco and/or its affiliates.
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at:
5 #
6 #     http://www.apache.org/licenses/LICENSE-2.0
7 #
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
13
14 """Defines nodes and topology structure."""
15
16 from collections import Counter
17
18 from yaml import load
19
20 from robot.api import logger
21 from robot.libraries.BuiltIn import BuiltIn, RobotNotRunningError
22 from robot.api.deco import keyword
23
24 __all__ = ["DICT__nodes", 'Topology', 'NodeType']
25
26
27 def load_topo_from_yaml():
28     """Load topology from file defined in "${TOPOLOGY_PATH}" variable.
29
30     :returns: Nodes from loaded topology.
31     """
32     try:
33         topo_path = BuiltIn().get_variable_value("${TOPOLOGY_PATH}")
34     except RobotNotRunningError:
35         return ''
36
37     with open(topo_path) as work_file:
38         return load(work_file.read())['nodes']
39
40
41 # pylint: disable=invalid-name
42
43 class NodeType(object):
44     """Defines node types used in topology dictionaries."""
45     # Device Under Test (this node has VPP running on it)
46     DUT = 'DUT'
47     # Traffic Generator (this node has traffic generator on it)
48     TG = 'TG'
49     # Virtual Machine (this node running on DUT node)
50     VM = 'VM'
51
52
53 class NodeSubTypeTG(object):
54     """Defines node sub-type TG - traffic generator."""
55     # T-Rex traffic generator
56     TREX = 'TREX'
57     # Moongen
58     MOONGEN = 'MOONGEN'
59     # IxNetwork
60     IXNET = 'IXNET'
61
62 DICT__nodes = load_topo_from_yaml()
63
64
65 class Topology(object):
66     """Topology data manipulation and extraction methods.
67
68     Defines methods used for manipulation and extraction of data from
69     the active topology.
70
71     "Active topology" contains initially data from the topology file and can be
72     extended with additional data from the DUTs like internal interface indexes
73     or names. Additional data which can be filled to the active topology are
74
75         - additional internal representation (index, name, ...)
76         - operational data (dynamic ports)
77
78     To access the port data it is recommended to use a port key because the key
79     does not rely on the data retrieved from nodes, this allows to call most of
80     the methods without having filled active topology with internal nodes data.
81     """
82
83     @staticmethod
84     def add_new_port(node, ptype):
85         """Add new port to the node to active topology.
86
87         :param node: Node to add new port on.
88         :param ptype: Port type, used as key prefix.
89         :type node: dict
90         :type ptype: str
91         :returns: Port key or None
92         :rtype: string or None
93         """
94         max_ports = 1000000
95         iface = None
96         for i in range(1, max_ports):
97             if node['interfaces'].get(str(ptype) + str(i)) is None:
98                 iface = str(ptype) + str(i)
99                 node['interfaces'][iface] = dict()
100                 break
101         return iface
102
103     @staticmethod
104     def remove_port(node, iface_key):
105         """Remove required port from active topology.
106
107         :param node: Node to remove port on.
108         :param: iface_key: Topology key of the interface.
109         :type node: dict
110         :type iface_key: str
111         :returns: Nothing
112         """
113         try:
114             node['interfaces'].pop(iface_key)
115         except KeyError:
116             pass
117
118     @staticmethod
119     def remove_all_ports(node, ptype):
120         """Remove all ports with ptype as prefix.
121
122         :param node: Node to remove ports on.
123         :param: ptype: Port type, used as key prefix.
124         :type node: dict
125         :type ptype: str
126         :returns: Nothing
127         """
128         for if_key in list(node['interfaces']):
129             if if_key.startswith(str(ptype)):
130                 node['interfaces'].pop(if_key)
131
132     @staticmethod
133     def remove_all_added_ports_on_all_duts_from_topology(nodes):
134         """Remove all added ports on all DUT nodes in the topology.
135
136         :param nodes: Nodes in the topology.
137         :type nodes: dict
138         :returns: Nothing
139         """
140         port_types = ('subinterface', 'vlan_subif', 'memif', 'tap', 'vhost',
141                       'loopback', 'gre_tunnel', 'vxlan_tunnel', 'eth_bond',
142                       'avf')
143
144         for node_data in nodes.values():
145             if node_data['type'] == NodeType.DUT:
146                 for ptype in port_types:
147                     Topology.remove_all_ports(node_data, ptype)
148
149     @staticmethod
150     def update_interface_sw_if_index(node, iface_key, sw_if_index):
151         """Update sw_if_index on the interface from the node.
152
153         :param node: Node to update sw_if_index on.
154         :param iface_key: Topology key of the interface.
155         :param sw_if_index: Internal index to store.
156         :type node: dict
157         :type iface_key: str
158         :type sw_if_index: int
159         """
160         node['interfaces'][iface_key]['vpp_sw_index'] = int(sw_if_index)
161
162     @staticmethod
163     def update_interface_name(node, iface_key, name):
164         """Update name on the interface from the node.
165
166         :param node: Node to update name on.
167         :param iface_key: Topology key of the interface.
168         :param name: Interface name to store.
169         :type node: dict
170         :type iface_key: str
171         :type name: str
172         """
173         node['interfaces'][iface_key]['name'] = str(name)
174
175     @staticmethod
176     def update_interface_mac_address(node, iface_key, mac_address):
177         """Update mac_address on the interface from the node.
178
179         :param node: Node to update MAC on.
180         :param iface_key: Topology key of the interface.
181         :param mac_address: MAC address.
182         :type node: dict
183         :type iface_key: str
184         :type mac_address: str
185         """
186         node['interfaces'][iface_key]['mac_address'] = str(mac_address)
187
188     @staticmethod
189     def update_interface_pci_address(node, iface_key, pci_address):
190         """Update pci_address on the interface from the node.
191
192         :param node: Node to update PCI on.
193         :param iface_key: Topology key of the interface.
194         :param pci_address: PCI address.
195         :type node: dict
196         :type iface_key: str
197         :type pci_address: str
198         """
199         node['interfaces'][iface_key]['pci_address'] = str(pci_address)
200
201     @staticmethod
202     def update_interface_vlan(node, iface_key, vlan):
203         """Update VLAN on the interface from the node.
204
205         :param node: Node to update VLAN on.
206         :param iface_key: Topology key of the interface.
207         :param vlan: VLAN ID.
208         :type node: dict
209         :type iface_key: str
210         :type vlan: str
211         """
212         node['interfaces'][iface_key]['vlan'] = int(vlan)
213
214     @staticmethod
215     def update_interface_vhost_socket(node, iface_key, vhost_socket):
216         """Update vhost socket name on the interface from the node.
217
218         :param node: Node to update socket name on.
219         :param iface_key: Topology key of the interface.
220         :param vhost_socket: Path to named socket on node.
221         :type node: dict
222         :type iface_key: str
223         :type vhost_socket: str
224         """
225         node['interfaces'][iface_key]['vhost_socket'] = str(vhost_socket)
226
227     @staticmethod
228     def update_interface_memif_socket(node, iface_key, memif_socket):
229         """Update memif socket name on the interface from the node.
230
231         :param node: Node to update socket name on.
232         :param iface_key: Topology key of the interface.
233         :param memif_socket: Path to named socket on node.
234         :type node: dict
235         :type iface_key: str
236         :type memif_socket: str
237         """
238         node['interfaces'][iface_key]['memif_socket'] = str(memif_socket)
239
240     @staticmethod
241     def update_interface_memif_id(node, iface_key, memif_id):
242         """Update memif ID on the interface from the node.
243
244         :param node: Node to update memif ID on.
245         :param iface_key: Topology key of the interface.
246         :param memif_id: Memif interface ID.
247         :type node: dict
248         :type iface_key: str
249         :type memif_id: str
250         """
251         node['interfaces'][iface_key]['memif_id'] = str(memif_id)
252
253     @staticmethod
254     def update_interface_memif_role(node, iface_key, memif_role):
255         """Update memif role on the interface from the node.
256
257         :param node: Node to update memif role on.
258         :param iface_key: Topology key of the interface.
259         :param memif_role: Memif role.
260         :type node: dict
261         :type iface_key: str
262         :type memif_role: str
263         """
264         node['interfaces'][iface_key]['memif_role'] = str(memif_role)
265
266     @staticmethod
267     def update_interface_tap_dev_name(node, iface_key, dev_name):
268         """Update device name on the tap interface from the node.
269
270         :param node: Node to update tap device name on.
271         :param iface_key: Topology key of the interface.
272         :param dev_name: Device name of the tap interface.
273         :type node: dict
274         :type iface_key: str
275         :type dev_name: str
276         :returns: Nothing
277         """
278         node['interfaces'][iface_key]['dev_name'] = str(dev_name)
279
280     @staticmethod
281     def get_node_by_hostname(nodes, hostname):
282         """Get node from nodes of the topology by hostname.
283
284         :param nodes: Nodes of the test topology.
285         :param hostname: Host name.
286         :type nodes: dict
287         :type hostname: str
288         :returns: Node dictionary or None if not found.
289         """
290         for node in nodes.values():
291             if node['host'] == hostname:
292                 return node
293
294         return None
295
296     @staticmethod
297     def get_links(nodes):
298         """Get list of links(networks) in the topology.
299
300         :param nodes: Nodes of the test topology.
301         :type nodes: dict
302         :returns: Links in the topology.
303         :rtype: list
304         """
305         links = []
306
307         for node in nodes.values():
308             for interface in node['interfaces'].values():
309                 link = interface.get('link')
310                 if link is not None:
311                     if link not in links:
312                         links.append(link)
313
314         return links
315
316     @staticmethod
317     def _get_interface_by_key_value(node, key, value):
318         """Return node interface key from topology file
319         according to key and value.
320
321         :param node: The node dictionary.
322         :param key: Key by which to select the interface.
323         :param value: Value that should be found using the key.
324         :type node: dict
325         :type key: string
326         :type value: string
327         :returns: Interface key from topology file
328         :rtype: string
329         """
330         interfaces = node['interfaces']
331         retval = None
332         for if_key, if_val in interfaces.iteritems():
333             k_val = if_val.get(key)
334             if k_val is not None:
335                 if k_val == value:
336                     retval = if_key
337                     break
338         return retval
339
340     @staticmethod
341     def get_interface_by_name(node, iface_name):
342         """Return interface key based on name from DUT/TG.
343
344         This method returns interface key based on interface name
345         retrieved from the DUT, or TG.
346
347         :param node: The node topology dictionary.
348         :param iface_name: Interface name (string form).
349         :type node: dict
350         :type iface_name: string
351         :returns: Interface key.
352         :rtype: str
353         """
354         return Topology._get_interface_by_key_value(node, "name", iface_name)
355
356     @staticmethod
357     def get_interface_by_link_name(node, link_name):
358         """Return interface key of link on node.
359
360         This method returns the interface name associated with a given link
361         for a given node.
362
363         :param node: The node topology dictionary.
364         :param link_name: Name of the link that a interface is connected to.
365         :type node: dict
366         :type link_name: string
367         :returns: Interface key of the interface connected to the given link.
368         :rtype: str
369         """
370         return Topology._get_interface_by_key_value(node, "link", link_name)
371
372     def get_interfaces_by_link_names(self, node, link_names):
373         """Return dictionary of dictionaries {"interfaceN", interface name}.
374
375         This method returns the interface names associated with given links
376         for a given node.
377
378         :param node: The node topology directory.
379         :param link_names: List of names of the link that a interface is
380             connected to.
381         :type node: dict
382         :type link_names: list
383         :returns: Dictionary of interface names that are connected to the given
384             links.
385         :rtype: dict
386         """
387         retval = {}
388         interface_key_tpl = "interface{}"
389         interface_number = 1
390         for link_name in link_names:
391             interface = self.get_interface_by_link_name(node, link_name)
392             interface_name = self.get_interface_name(node, interface)
393             interface_key = interface_key_tpl.format(str(interface_number))
394             retval[interface_key] = interface_name
395             interface_number += 1
396         return retval
397
398     @staticmethod
399     def get_interface_by_sw_index(node, sw_if_index):
400         """Return interface name of link on node.
401
402         This method returns the interface name associated with a software
403         interface index assigned to the interface by vpp for a given node.
404
405         :param node: The node topology dictionary.
406         :param sw_if_index: sw_if_index of the link that a interface is
407             connected to.
408         :type node: dict
409         :type sw_if_index: int
410         :returns: Interface name of the interface connected to the given link.
411         :rtype: str
412         """
413         return Topology._get_interface_by_key_value(node, "vpp_sw_index",
414                                                     sw_if_index)
415
416     @staticmethod
417     def get_interface_sw_index(node, iface_key):
418         """Get VPP sw_if_index for the interface using interface key.
419
420         :param node: Node to get interface sw_if_index on.
421         :param iface_key: Interface key from topology file, or sw_if_index.
422         :type node: dict
423         :type iface_key: str/int
424         :returns: Return sw_if_index or None if not found.
425         :rtype: int or None
426         """
427         try:
428             if isinstance(iface_key, basestring):
429                 return node['interfaces'][iface_key].get('vpp_sw_index')
430             # TODO: use only iface_key, do not use integer
431             return int(iface_key)
432         except (KeyError, ValueError):
433             return None
434
435     @staticmethod
436     def get_interface_sw_index_by_name(node, iface_name):
437         """Get VPP sw_if_index for the interface using interface name.
438
439         :param node: Node to get interface sw_if_index on.
440         :param iface_name: Interface name.
441         :type node: dict
442         :type iface_name: str
443         :returns: Return sw_if_index or None if not found.
444         :raises TypeError: If provided interface name is not a string.
445         """
446         try:
447             if not isinstance(iface_name, basestring):
448                 raise TypeError("Interface name must be a string.")
449             iface_key = Topology.get_interface_by_name(node, iface_name)
450             return node['interfaces'][iface_key].get('vpp_sw_index')
451         except (KeyError, ValueError):
452             return None
453
454     @staticmethod
455     def get_interface_mtu(node, iface_key):
456         """Get interface MTU.
457
458         Returns physical layer MTU (max. size of Ethernet frame).
459         :param node: Node to get interface MTU on.
460         :param iface_key: Interface key from topology file.
461         :type node: dict
462         :type iface_key: str
463         :returns: MTU or None if not found.
464         :rtype: int
465         """
466         try:
467             return node['interfaces'][iface_key].get('mtu')
468         except KeyError:
469             return None
470
471     @staticmethod
472     def get_interface_name(node, iface_key):
473         """Get interface name (retrieved from DUT/TG).
474
475         Returns name in string format, retrieved from the node.
476         :param node: Node to get interface name on.
477         :param iface_key: Interface key from topology file.
478         :type node: dict
479         :type iface_key: str
480         :returns: Interface name or None if not found.
481         :rtype: str
482         """
483         try:
484             return node['interfaces'][iface_key].get('name')
485         except KeyError:
486             return None
487
488     @staticmethod
489     def convert_interface_reference_to_key(node, interface):
490         """Takes interface reference in any format
491         (name, link name, interface key or sw_if_index)
492         and converts to interface key using Topology methods.
493
494         :param node: Node in topology.
495         :param interface: Name, sw_if_index, link name or key of an interface
496             on the node.
497         :type node: dict
498         :type interface: str or int
499
500         :returns: Interface key.
501         :rtype: str
502
503         :raises TypeError: If provided with invalid interface argument.
504         :raises RuntimeError: If the interface does not exist in topology.
505         """
506
507         if isinstance(interface, int):
508             key = Topology.get_interface_by_sw_index(node, interface)
509             if key is None:
510                 raise RuntimeError("Interface with sw_if_index={0} does not "
511                                    "exist in topology.".format(interface))
512         elif interface in Topology.get_node_interfaces(node):
513             key = interface
514         elif interface in Topology.get_links({"dut": node}):
515             key = Topology.get_interface_by_link_name(node, interface)
516         elif isinstance(interface, basestring):
517             key = Topology.get_interface_by_name(node, interface)
518             if key is None:
519                 raise RuntimeError("Interface with key, name or link name "
520                                    "\"{0}\" does not exist in topology."
521                                    .format(interface))
522         else:
523             raise TypeError("Type of interface argument must be integer"
524                             " or string.")
525         return key
526
527     @staticmethod
528     def convert_interface_reference(node, interface, wanted_format):
529         """Takes interface reference in any format
530         (name, link name, topology key or sw_if_index) and returns
531         its equivalent in the desired format.
532
533         :param node: Node in topology.
534         :param interface: Name, sw_if_index, link name or key of an interface
535             on the node.
536         :param wanted_format: Format of return value wanted.
537             Valid options are: sw_if_index, key, name.
538         :type node: dict
539         :type interface: str or int
540         :type wanted_format: str
541         :returns: Interface name, interface key or sw_if_index.
542         :rtype: str or int
543         :raises TypeError, ValueError: If provided with invalid arguments.
544         :raises RuntimeError: If the interface does not exist in topology.
545         """
546
547         key = Topology.convert_interface_reference_to_key(node, interface)
548
549         conversions = {
550             "key": lambda x, y: y,
551             "name": Topology.get_interface_name,
552             "sw_if_index": Topology.get_interface_sw_index
553         }
554
555         try:
556             return conversions[wanted_format](node, key)
557         except KeyError:
558             raise ValueError("Unrecognized return value wanted: {0}."
559                              "Valid options are key, name, sw_if_index"
560                              .format(wanted_format))
561
562     @staticmethod
563     def get_interface_numa_node(node, iface_key):
564         """Get interface numa node.
565
566         Returns physical relation to numa node, numa_id.
567
568         :param node: Node to get numa id on.
569         :param iface_key: Interface key from topology file.
570         :type node: dict
571         :type iface_key: str
572         :returns: numa node id, None if not available.
573         :rtype: int
574         """
575         try:
576             return node['interfaces'][iface_key].get('numa_node')
577         except KeyError:
578             return None
579
580     @staticmethod
581     def get_interfaces_numa_node(node, *iface_keys):
582         """Get numa node on which are located most of the interfaces.
583
584         Return numa node with highest count of interfaces provided as arguments.
585         Return 0 if the interface does not have numa_node information available.
586         If all interfaces have unknown location (-1), then return 0.
587         If most of interfaces have unknown location (-1), but there are
588         some interfaces with known location, then return the second most
589         location of the provided interfaces.
590
591         :param node: Node from DICT__nodes.
592         :param iface_keys: Interface keys for lookup.
593         :type node: dict
594         :type iface_keys: strings
595         :returns: Numa node of most given interfaces or 0.
596         :rtype: int
597         """
598         numa_list = []
599         for if_key in iface_keys:
600             try:
601                 numa_list.append(node['interfaces'][if_key].get('numa_node'))
602             except KeyError:
603                 pass
604
605         numa_cnt_mc = Counter(numa_list).most_common()
606
607         if numa_cnt_mc and numa_cnt_mc[0][0] != -1:
608             return numa_cnt_mc[0][0]
609         if len(numa_cnt_mc) > 1 and numa_cnt_mc[0][0] == -1:
610             return numa_cnt_mc[1][0]
611         return 0
612
613     @staticmethod
614     def get_interface_mac(node, iface_key):
615         """Get MAC address for the interface.
616
617         :param node: Node to get interface mac on.
618         :param iface_key: Interface key from topology file.
619         :type node: dict
620         :type iface_key: str
621         :returns: Return MAC or None if not found.
622         """
623         try:
624             return node['interfaces'][iface_key].get('mac_address')
625         except KeyError:
626             return None
627
628     @staticmethod
629     def get_interface_ip4(node, iface_key):
630         """Get IP4 address for the interface.
631
632         :param node: Node to get interface mac on.
633         :param iface_key: Interface key from topology file.
634         :type node: dict
635         :type iface_key: str
636         :returns: Return IP4 or None if not found.
637         """
638         try:
639             return node['interfaces'][iface_key].get('ip4_address', None)
640         except KeyError:
641             return None
642
643     @staticmethod
644     def get_adjacent_node_and_interface(nodes_info, node, iface_key):
645         """Get node and interface adjacent to specified interface
646         on local network.
647
648         :param nodes_info: Dictionary containing information on all nodes
649             in topology.
650         :param node: Node that contains specified interface.
651         :param iface_key: Interface key from topology file.
652         :type nodes_info: dict
653         :type node: dict
654         :type iface_key: str
655         :returns: Return (node, interface_key) tuple or None if not found.
656         :rtype: (dict, str)
657         """
658         link_name = None
659         # get link name where the interface belongs to
660         for if_key, if_val in node['interfaces'].iteritems():
661             if if_key == 'mgmt':
662                 continue
663             if if_key == iface_key:
664                 link_name = if_val['link']
665                 break
666
667         if link_name is None:
668             return None
669
670         # find link
671         for node_data in nodes_info.values():
672             # skip self
673             if node_data['host'] == node['host']:
674                 continue
675             for if_key, if_val \
676                     in node_data['interfaces'].iteritems():
677                 if 'link' not in if_val:
678                     continue
679                 if if_val['link'] == link_name:
680                     return node_data, if_key
681         return None
682
683     @staticmethod
684     def get_interface_pci_addr(node, iface_key):
685         """Get interface PCI address.
686
687         :param node: Node to get interface PCI address on.
688         :param iface_key: Interface key from topology file.
689         :type node: dict
690         :type iface_key: str
691         :returns: Return PCI address or None if not found.
692         """
693         try:
694             return node['interfaces'][iface_key].get('pci_address')
695         except KeyError:
696             return None
697
698     @staticmethod
699     def get_interface_driver(node, iface_key):
700         """Get interface driver.
701
702         :param node: Node to get interface driver on.
703         :param iface_key: Interface key from topology file.
704         :type node: dict
705         :type iface_key: str
706         :returns: Return interface driver or None if not found.
707         """
708         try:
709             return node['interfaces'][iface_key].get('driver')
710         except KeyError:
711             return None
712
713     @staticmethod
714     def get_interface_vlan(node, iface_key):
715         """Get interface vlan.
716
717         :param node: Node to get interface driver on.
718         :param iface_key: Interface key from topology file.
719         :type node: dict
720         :type iface_key: str
721         :returns: Return interface vlan or None if not found.
722         """
723         try:
724             return node['interfaces'][iface_key].get('vlan')
725         except KeyError:
726             return None
727
728     @staticmethod
729     def get_node_interfaces(node):
730         """Get all node interfaces.
731
732         :param node: Node to get list of interfaces from.
733         :type node: dict
734         :returns: Return list of keys of all interfaces.
735         :rtype: list
736         """
737         return node['interfaces'].keys()
738
739     @staticmethod
740     def get_node_link_mac(node, link_name):
741         """Return interface mac address by link name.
742
743         :param node: Node to get interface sw_if_index on.
744         :param link_name: Link name.
745         :type node: dict
746         :type link_name: str
747         :returns: MAC address string.
748         :rtype: str
749         """
750         for port in node['interfaces'].values():
751             if port.get('link') == link_name:
752                 return port.get('mac_address')
753         return None
754
755     @staticmethod
756     def _get_node_active_link_names(node, filter_list=None):
757         """Return list of link names that are other than mgmt links.
758
759         :param node: Node topology dictionary.
760         :param filter_list: Link filter criteria.
761         :type node: dict
762         :type filter_list: list of strings
763         :returns: List of link names occupied by the node.
764         :rtype: None or list of string
765         """
766         interfaces = node['interfaces']
767         link_names = []
768         for interface in interfaces.values():
769             if 'link' in interface:
770                 if (filter_list is not None) and ('model' in interface):
771                     for filt in filter_list:
772                         if filt == interface['model']:
773                             link_names.append(interface['link'])
774                 elif (filter_list is not None) and ('model' not in interface):
775                     logger.trace('Cannot apply filter on interface: {}'
776                                  .format(str(interface)))
777                 else:
778                     link_names.append(interface['link'])
779         if not link_names:
780             link_names = None
781         return link_names
782
783     @keyword('Get active links connecting "${node1}" and "${node2}"')
784     def get_active_connecting_links(self, node1, node2,
785                                     filter_list_node1=None,
786                                     filter_list_node2=None):
787         """Return list of link names that connect together node1 and node2.
788
789         :param node1: Node topology dictionary.
790         :param node2: Node topology dictionary.
791         :param filter_list_node1: Link filter criteria for node1.
792         :param filter_list_node2: Link filter criteria for node2.
793         :type node1: dict
794         :type node2: dict
795         :type filter_list_node1: list of strings
796         :type filter_list_node2: list of strings
797         :returns: List of strings that represent connecting link names.
798         :rtype: list
799         """
800
801         logger.trace("node1: {}".format(str(node1)))
802         logger.trace("node2: {}".format(str(node2)))
803         node1_links = self._get_node_active_link_names(
804             node1,
805             filter_list=filter_list_node1)
806         node2_links = self._get_node_active_link_names(
807             node2,
808             filter_list=filter_list_node2)
809
810         connecting_links = None
811         if node1_links is None:
812             logger.error("Unable to find active links for node1")
813         elif node2_links is None:
814             logger.error("Unable to find active links for node2")
815         else:
816             connecting_links = list(set(node1_links).intersection(node2_links))
817
818         return connecting_links
819
820     @keyword('Get first active connecting link between node "${node1}" and '
821              '"${node2}"')
822     def get_first_active_connecting_link(self, node1, node2):
823         """
824
825         :param node1: Connected node.
826         :param node2: Connected node.
827         :type node1: dict
828         :type node2: dict
829         :returns: Name of a link connecting the two nodes together.
830         :rtype: str
831         :raises RuntimeError: If no links are found.
832         """
833         connecting_links = self.get_active_connecting_links(node1, node2)
834         if not connecting_links:
835             raise RuntimeError("No links connecting the nodes were found")
836         return connecting_links[0]
837
838     @keyword('Get egress interfaces name on "${node1}" for link with '
839              '"${node2}"')
840     def get_egress_interfaces_name_for_nodes(self, node1, node2):
841         """Get egress interfaces on node1 for link with node2.
842
843         :param node1: First node, node to get egress interface on.
844         :param node2: Second node.
845         :type node1: dict
846         :type node2: dict
847         :returns: Egress interfaces.
848         :rtype: list
849         """
850         interfaces = []
851         links = self.get_active_connecting_links(node1, node2)
852         if not links:
853             raise RuntimeError('No link between nodes')
854         for interface in node1['interfaces'].values():
855             link = interface.get('link')
856             if link is None:
857                 continue
858             if link in links:
859                 continue
860             name = interface.get('name')
861             if name is None:
862                 continue
863             interfaces.append(name)
864         return interfaces
865
866     @keyword('Get first egress interface name on "${node1}" for link with '
867              '"${node2}"')
868     def get_first_egress_interface_for_nodes(self, node1, node2):
869         """Get first egress interface on node1 for link with node2.
870
871         :param node1: First node, node to get egress interface name on.
872         :param node2: Second node.
873         :type node1: dict
874         :type node2: dict
875         :returns: Egress interface name.
876         :rtype: str
877         """
878         interfaces = self.get_egress_interfaces_name_for_nodes(node1, node2)
879         if not interfaces:
880             raise RuntimeError('No egress interface for nodes')
881         return interfaces[0]
882
883     @keyword('Get link data useful in circular topology test from tg "${tgen}"'
884              ' dut1 "${dut1}" dut2 "${dut2}"')
885     def get_links_dict_from_nodes(self, tgen, dut1, dut2):
886         """Return link combinations used in tests in circular topology.
887
888         For the time being it returns links from the Node path:
889         TG->DUT1->DUT2->TG
890         The naming convention until changed to something more general is
891         implemented is this:
892         DUT1_DUT2_LINK: link name between DUT! and DUT2
893         DUT1_TG_LINK: link name between DUT1 and TG
894         DUT2_TG_LINK: link name between DUT2 and TG
895         TG_TRAFFIC_LINKS: list of link names that generated traffic is sent
896         to and from
897         DUT1_BD_LINKS: list of link names that will be connected by the bridge
898         domain on DUT1
899         DUT2_BD_LINKS: list of link names that will be connected by the bridge
900         domain on DUT2
901
902         :param tgen: Traffic generator node data.
903         :param dut1: DUT1 node data.
904         :param dut2: DUT2 node data.
905         :type tgen: dict
906         :type dut1: dict
907         :type dut2: dict
908         :returns: Dictionary of possible link combinations.
909         :rtype: dict
910         """
911         # TODO: replace with generic function.
912         dut1_dut2_link = self.get_first_active_connecting_link(dut1, dut2)
913         dut1_tg_link = self.get_first_active_connecting_link(dut1, tgen)
914         dut2_tg_link = self.get_first_active_connecting_link(dut2, tgen)
915         tg_traffic_links = [dut1_tg_link, dut2_tg_link]
916         dut1_bd_links = [dut1_dut2_link, dut1_tg_link]
917         dut2_bd_links = [dut1_dut2_link, dut2_tg_link]
918         topology_links = {'DUT1_DUT2_LINK': dut1_dut2_link,
919                           'DUT1_TG_LINK': dut1_tg_link,
920                           'DUT2_TG_LINK': dut2_tg_link,
921                           'TG_TRAFFIC_LINKS': tg_traffic_links,
922                           'DUT1_BD_LINKS': dut1_bd_links,
923                           'DUT2_BD_LINKS': dut2_bd_links}
924         return topology_links
925
926     @staticmethod
927     def is_tg_node(node):
928         """Find out whether the node is TG.
929
930         :param node: Node to examine.
931         :type node: dict
932         :returns: True if node is type of TG, otherwise False.
933         :rtype: bool
934         """
935         return node['type'] == NodeType.TG
936
937     @staticmethod
938     def get_node_hostname(node):
939         """Return host (hostname/ip address) of the node.
940
941         :param node: Node created from topology.
942         :type node: dict
943         :returns: Hostname or IP address.
944         :rtype: str
945         """
946         return node['host']
947
948     @staticmethod
949     def get_node_arch(node):
950         """Return arch of the node.
951            Default to x86_64 if no arch present
952
953         :param node: Node created from topology.
954         :type node: dict
955         :returns: Node architecture
956         :rtype: str
957         """
958         try:
959             return node['arch']
960         except KeyError:
961             node['arch'] = 'x86_64'
962             return 'x86_64'
963
964     @staticmethod
965     def get_cryptodev(node):
966         """Return Crytodev configuration of the node.
967
968         :param node: Node created from topology.
969         :type node: dict
970         :returns: Cryptodev configuration string.
971         :rtype: str
972         """
973         try:
974             return node['cryptodev']
975         except KeyError:
976             return None
977
978     @staticmethod
979     def get_uio_driver(node):
980         """Return uio-driver configuration of the node.
981
982         :param node: Node created from topology.
983         :type node: dict
984         :returns: uio-driver configuration string.
985         :rtype: str
986         """
987         try:
988             return node['uio_driver']
989         except KeyError:
990             return None
991
992     @staticmethod
993     def set_interface_numa_node(node, iface_key, numa_node_id):
994         """Set interface numa_node location.
995
996         :param node: Node to set numa_node on.
997         :param iface_key: Interface key from topology file.
998         :type node: dict
999         :type iface_key: str
1000         :returns: Return iface_key or None if not found.
1001         """
1002         try:
1003             node['interfaces'][iface_key]['numa_node'] = numa_node_id
1004             return iface_key
1005         except KeyError:
1006             return None