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