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