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