TRex ASTF onboarding Part I
[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_interface_ip4(node, iface_key):
604         """Get IP4 address for the interface.
605
606         :param node: Node to get interface mac on.
607         :param iface_key: Interface key from topology file.
608         :type node: dict
609         :type iface_key: str
610         :returns: Return IP4 or None if not found.
611         """
612         try:
613             return node['interfaces'][iface_key].get('ip4_address', None)
614         except KeyError:
615             return None
616
617     @staticmethod
618     def get_adjacent_node_and_interface(nodes_info, node, iface_key):
619         """Get node and interface adjacent to specified interface
620         on local network.
621
622         :param nodes_info: Dictionary containing information on all nodes
623         in topology.
624         :param node: Node that contains specified interface.
625         :param iface_key: Interface key from topology file.
626         :type nodes_info: dict
627         :type node: dict
628         :type iface_key: str
629         :returns: Return (node, interface_key) tuple or None if not found.
630         :rtype: (dict, str)
631         """
632         link_name = None
633         # get link name where the interface belongs to
634         for if_key, if_val in node['interfaces'].iteritems():
635             if if_key == 'mgmt':
636                 continue
637             if if_key == iface_key:
638                 link_name = if_val['link']
639                 break
640
641         if link_name is None:
642             return None
643
644         # find link
645         for node_data in nodes_info.values():
646             # skip self
647             if node_data['host'] == node['host']:
648                 continue
649             for if_key, if_val \
650                     in node_data['interfaces'].iteritems():
651                 if 'link' not in if_val:
652                     continue
653                 if if_val['link'] == link_name:
654                     return node_data, if_key
655
656     @staticmethod
657     def get_interface_pci_addr(node, iface_key):
658         """Get interface PCI address.
659
660         :param node: Node to get interface PCI address on.
661         :param iface_key: Interface key from topology file.
662         :type node: dict
663         :type iface_key: str
664         :returns: Return PCI address or None if not found.
665         """
666         try:
667             return node['interfaces'][iface_key].get('pci_address')
668         except KeyError:
669             return None
670
671     @staticmethod
672     def get_interface_driver(node, iface_key):
673         """Get interface driver.
674
675         :param node: Node to get interface driver on.
676         :param iface_key: Interface key from topology file.
677         :type node: dict
678         :type iface_key: str
679         :returns: Return interface driver or None if not found.
680         """
681         try:
682             return node['interfaces'][iface_key].get('driver')
683         except KeyError:
684             return None
685
686     @staticmethod
687     def get_node_interfaces(node):
688         """Get all node interfaces.
689
690         :param node: Node to get list of interfaces from.
691         :type node: dict
692         :returns: Return list of keys of all interfaces.
693         :rtype: list
694         """
695         return node['interfaces'].keys()
696
697     @staticmethod
698     def get_node_link_mac(node, link_name):
699         """Return interface mac address by link name.
700
701         :param node: Node to get interface sw_index on.
702         :param link_name: Link name.
703         :type node: dict
704         :type link_name: str
705         :returns: MAC address string.
706         :rtype: str
707         """
708         for port in node['interfaces'].values():
709             if port.get('link') == link_name:
710                 return port.get('mac_address')
711         return None
712
713     @staticmethod
714     def _get_node_active_link_names(node, filter_list=None):
715         """Return list of link names that are other than mgmt links.
716
717         :param node: Node topology dictionary.
718         :param filter_list: Link filter criteria.
719         :type node: dict
720         :type filter_list: list of strings
721         :returns: List of strings representing link names occupied by the node.
722         :rtype: list
723         """
724         interfaces = node['interfaces']
725         link_names = []
726         for interface in interfaces.values():
727             if 'link' in interface:
728                 if (filter_list is not None) and ('model' in interface):
729                     for filt in filter_list:
730                         if filt == interface['model']:
731                             link_names.append(interface['link'])
732                 elif (filter_list is not None) and ('model' not in interface):
733                     logger.trace('Cannot apply filter on interface: {}'
734                                  .format(str(interface)))
735                 else:
736                     link_names.append(interface['link'])
737         if len(link_names) == 0:
738             link_names = None
739         return link_names
740
741     @keyword('Get active links connecting "${node1}" and "${node2}"')
742     def get_active_connecting_links(self, node1, node2,
743                                     filter_list_node1=None,
744                                     filter_list_node2=None):
745         """Return list of link names that connect together node1 and node2.
746
747         :param node1: Node topology dictionary.
748         :param node2: Node topology dictionary.
749         :param filter_list_node1: Link filter criteria for node1.
750         :param filter_list_node2: Link filter criteria for node2.
751         :type node1: dict
752         :type node2: dict
753         :type filter_list_node1: list of strings
754         :type filter_list_node2: list of strings
755         :returns: List of strings that represent connecting link names.
756         :rtype: list
757         """
758
759         logger.trace("node1: {}".format(str(node1)))
760         logger.trace("node2: {}".format(str(node2)))
761         node1_links = self._get_node_active_link_names(
762             node1,
763             filter_list=filter_list_node1)
764         node2_links = self._get_node_active_link_names(
765             node2,
766             filter_list=filter_list_node2)
767
768         connecting_links = None
769         if node1_links is None:
770             logger.error("Unable to find active links for node1")
771         elif node2_links is None:
772             logger.error("Unable to find active links for node2")
773         else:
774             connecting_links = list(set(node1_links).intersection(node2_links))
775
776         return connecting_links
777
778     @keyword('Get first active connecting link between node "${node1}" and '
779              '"${node2}"')
780     def get_first_active_connecting_link(self, node1, node2):
781         """
782
783         :param node1: Connected node.
784         :param node2: Connected node.
785         :type node1: dict
786         :type node2: dict
787         :returns: Name of link connecting the two nodes together.
788         :rtype: str
789         :raises: RuntimeError
790         """
791         connecting_links = self.get_active_connecting_links(node1, node2)
792         if len(connecting_links) == 0:
793             raise RuntimeError("No links connecting the nodes were found")
794         else:
795             return connecting_links[0]
796
797     @keyword('Get egress interfaces name on "${node1}" for link with '
798              '"${node2}"')
799     def get_egress_interfaces_name_for_nodes(self, node1, node2):
800         """Get egress interfaces on node1 for link with node2.
801
802         :param node1: First node, node to get egress interface on.
803         :param node2: Second node.
804         :type node1: dict
805         :type node2: dict
806         :returns: Egress interfaces.
807         :rtype: list
808         """
809         interfaces = []
810         links = self.get_active_connecting_links(node1, node2)
811         if len(links) == 0:
812             raise RuntimeError('No link between nodes')
813         for interface in node1['interfaces'].values():
814             link = interface.get('link')
815             if link is None:
816                 continue
817             if link in links:
818                 continue
819             name = interface.get('name')
820             if name is None:
821                 continue
822             interfaces.append(name)
823         return interfaces
824
825     @keyword('Get first egress interface name on "${node1}" for link with '
826              '"${node2}"')
827     def get_first_egress_interface_for_nodes(self, node1, node2):
828         """Get first egress interface on node1 for link with node2.
829
830         :param node1: First node, node to get egress interface name on.
831         :param node2: Second node.
832         :type node1: dict
833         :type node2: dict
834         :returns: Egress interface name.
835         :rtype: str
836         """
837         interfaces = self.get_egress_interfaces_name_for_nodes(node1, node2)
838         if not interfaces:
839             raise RuntimeError('No egress interface for nodes')
840         return interfaces[0]
841
842     @keyword('Get link data useful in circular topology test from tg "${tgen}"'
843              ' dut1 "${dut1}" dut2 "${dut2}"')
844     def get_links_dict_from_nodes(self, tgen, dut1, dut2):
845         """Return link combinations used in tests in circular topology.
846
847         For the time being it returns links from the Node path:
848         TG->DUT1->DUT2->TG
849         The naming convention until changed to something more general is
850         implemented is this:
851         DUT1_DUT2_LINK: link name between DUT! and DUT2
852         DUT1_TG_LINK: link name between DUT1 and TG
853         DUT2_TG_LINK: link name between DUT2 and TG
854         TG_TRAFFIC_LINKS: list of link names that generated traffic is sent
855         to and from
856         DUT1_BD_LINKS: list of link names that will be connected by the bridge
857         domain on DUT1
858         DUT2_BD_LINKS: list of link names that will be connected by the bridge
859         domain on DUT2
860
861         :param tgen: Traffic generator node data.
862         :param dut1: DUT1 node data.
863         :param dut2: DUT2 node data.
864         :type tgen: dict
865         :type dut1: dict
866         :type dut2: dict
867         :returns: Dictionary of possible link combinations.
868         :rtype: dict
869         """
870         # TODO: replace with generic function.
871         dut1_dut2_link = self.get_first_active_connecting_link(dut1, dut2)
872         dut1_tg_link = self.get_first_active_connecting_link(dut1, tgen)
873         dut2_tg_link = self.get_first_active_connecting_link(dut2, tgen)
874         tg_traffic_links = [dut1_tg_link, dut2_tg_link]
875         dut1_bd_links = [dut1_dut2_link, dut1_tg_link]
876         dut2_bd_links = [dut1_dut2_link, dut2_tg_link]
877         topology_links = {'DUT1_DUT2_LINK': dut1_dut2_link,
878                           'DUT1_TG_LINK': dut1_tg_link,
879                           'DUT2_TG_LINK': dut2_tg_link,
880                           'TG_TRAFFIC_LINKS': tg_traffic_links,
881                           'DUT1_BD_LINKS': dut1_bd_links,
882                           'DUT2_BD_LINKS': dut2_bd_links}
883         return topology_links
884
885     @staticmethod
886     def is_tg_node(node):
887         """Find out whether the node is TG.
888
889         :param node: Node to examine.
890         :type node: dict
891         :returns: True if node is type of TG, otherwise False.
892         :rtype: bool
893         """
894         return node['type'] == NodeType.TG
895
896     @staticmethod
897     def get_node_hostname(node):
898         """Return host (hostname/ip address) of the node.
899
900         :param node: Node created from topology.
901         :type node: dict
902         :returns: Hostname or IP address.
903         :rtype: str
904         """
905         return node['host']
906
907     @staticmethod
908     def get_cryptodev(node):
909         """Return Crytodev configuration of the node.
910
911         :param node: Node created from topology.
912         :type node: dict
913         :returns: Cryptodev configuration string.
914         :rtype: str
915         """
916         try:
917             return node['cryptodev']
918         except KeyError:
919             return None
920
921     @staticmethod
922     def get_uio_driver(node):
923         """Return uio-driver configuration of the node.
924
925         :param node: Node created from topology.
926         :type node: dict
927         :returns: uio-driver configuration string.
928         :rtype: str
929         """
930         try:
931             return node['uio_driver']
932         except KeyError:
933             return None
934
935     @staticmethod
936     def set_interface_numa_node(node, iface_key, numa_node_id):
937         """Set interface numa_node location.
938
939         :param node: Node to set numa_node on.
940         :param iface_key: Interface key from topology file.
941         :type node: dict
942         :type iface_key: str
943         :returns: Return iface_key or None if not found.
944         """
945         try:
946             node['interfaces'][iface_key]['numa_node'] = numa_node_id
947             return iface_key
948         except KeyError:
949             return None