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