Revert "CSIT-986: Use MLRsearch from pip"
[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         """
398         try:
399             if isinstance(iface_key, basestring):
400                 return node['interfaces'][iface_key].get('vpp_sw_index')
401             # TODO: use only iface_key, do not use integer
402             else:
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 isinstance(iface_name, basestring):
420                 iface_key = Topology.get_interface_by_name(node, iface_name)
421                 return node['interfaces'][iface_key].get('vpp_sw_index')
422             else:
423                 raise TypeError("Interface name must be a string.")
424         except (KeyError, ValueError):
425             return None
426
427     @staticmethod
428     def get_interface_mtu(node, iface_key):
429         """Get interface MTU.
430
431         Returns physical layer MTU (max. size of Ethernet frame).
432         :param node: Node to get interface MTU on.
433         :param iface_key: Interface key from topology file.
434         :type node: dict
435         :type iface_key: str
436         :returns: MTU or None if not found.
437         :rtype: int
438         """
439         try:
440             return node['interfaces'][iface_key].get('mtu')
441         except KeyError:
442             return None
443
444     @staticmethod
445     def get_interface_name(node, iface_key):
446         """Get interface name (retrieved from DUT/TG).
447
448         Returns name in string format, retrieved from the node.
449         :param node: Node to get interface name on.
450         :param iface_key: Interface key from topology file.
451         :type node: dict
452         :type iface_key: str
453         :returns: Interface name or None if not found.
454         :rtype: str
455         """
456         try:
457             return node['interfaces'][iface_key].get('name')
458         except KeyError:
459             return None
460
461     @staticmethod
462     def convert_interface_reference_to_key(node, interface):
463         """Takes interface reference in any format
464         (name, link name, interface key or sw_if_index)
465         and converts to interface key using Topology methods.
466
467         :param node: Node in topology.
468         :param interface: Name, sw_if_index, link name or key of an interface
469             on the node.
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         :returns: Interface name, interface key or sw_if_index.
515         :rtype: str or int
516         :raises TypeError, ValueError: If provided with invalid arguments.
517         :raises RuntimeError: If the interface does not exist in topology.
518         """
519
520         key = Topology.convert_interface_reference_to_key(node, interface)
521
522         conversions = {
523             "key": lambda x, y: y,
524             "name": Topology.get_interface_name,
525             "sw_if_index": Topology.get_interface_sw_index
526         }
527
528         try:
529             return conversions[wanted_format](node, key)
530         except KeyError:
531             raise ValueError("Unrecognized return value wanted: {0}."
532                              "Valid options are key, name, sw_if_index"
533                              .format(wanted_format))
534
535     @staticmethod
536     def get_interface_numa_node(node, iface_key):
537         """Get interface numa node.
538
539         Returns physical relation to numa node, numa_id.
540
541         :param node: Node to get numa id on.
542         :param iface_key: Interface key from topology file.
543         :type node: dict
544         :type iface_key: str
545         :returns: numa node id, None if not available.
546         :rtype: int
547         """
548         try:
549             return node['interfaces'][iface_key].get('numa_node')
550         except KeyError:
551             return None
552
553     @staticmethod
554     def get_interfaces_numa_node(node, *iface_keys):
555         """Get numa node on which are located most of the interfaces.
556
557         Return numa node with highest count of interfaces provided as arguments.
558         Return 0 if the interface does not have numa_node information available.
559         If all interfaces have unknown location (-1), then return 0.
560         If most of interfaces have unknown location (-1), but there are
561         some interfaces with known location, then return the second most
562         location of the provided interfaces.
563
564         :param node: Node from DICT__nodes.
565         :param iface_keys: Interface keys for lookup.
566         :type node: dict
567         :type iface_keys: strings
568         """
569         numa_list = []
570         for if_key in iface_keys:
571             try:
572                 numa_list.append(node['interfaces'][if_key].get('numa_node'))
573             except KeyError:
574                 pass
575
576         numa_cnt_mc = Counter(numa_list).most_common()
577
578         if len(numa_cnt_mc) > 0 and numa_cnt_mc[0][0] != -1:
579             return numa_cnt_mc[0][0]
580         elif len(numa_cnt_mc) > 1 and numa_cnt_mc[0][0] == -1:
581             return numa_cnt_mc[1][0]
582         else:
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
654     @staticmethod
655     def get_interface_pci_addr(node, iface_key):
656         """Get interface PCI address.
657
658         :param node: Node to get interface PCI address on.
659         :param iface_key: Interface key from topology file.
660         :type node: dict
661         :type iface_key: str
662         :returns: Return PCI address or None if not found.
663         """
664         try:
665             return node['interfaces'][iface_key].get('pci_address')
666         except KeyError:
667             return None
668
669     @staticmethod
670     def get_interface_driver(node, iface_key):
671         """Get interface driver.
672
673         :param node: Node to get interface driver on.
674         :param iface_key: Interface key from topology file.
675         :type node: dict
676         :type iface_key: str
677         :returns: Return interface driver or None if not found.
678         """
679         try:
680             return node['interfaces'][iface_key].get('driver')
681         except KeyError:
682             return None
683
684     @staticmethod
685     def get_node_interfaces(node):
686         """Get all node interfaces.
687
688         :param node: Node to get list of interfaces from.
689         :type node: dict
690         :returns: Return list of keys of all interfaces.
691         :rtype: list
692         """
693         return node['interfaces'].keys()
694
695     @staticmethod
696     def get_node_link_mac(node, link_name):
697         """Return interface mac address by link name.
698
699         :param node: Node to get interface sw_index on.
700         :param link_name: Link name.
701         :type node: dict
702         :type link_name: str
703         :returns: MAC address string.
704         :rtype: str
705         """
706         for port in node['interfaces'].values():
707             if port.get('link') == link_name:
708                 return port.get('mac_address')
709         return None
710
711     @staticmethod
712     def _get_node_active_link_names(node, filter_list=None):
713         """Return list of link names that are other than mgmt links.
714
715         :param node: Node topology dictionary.
716         :param filter_list: Link filter criteria.
717         :type node: dict
718         :type filter_list: list of strings
719         :returns: List of strings representing link names occupied by the node.
720         :rtype: list
721         """
722         interfaces = node['interfaces']
723         link_names = []
724         for interface in interfaces.values():
725             if 'link' in interface:
726                 if (filter_list is not None) and ('model' in interface):
727                     for filt in filter_list:
728                         if filt == interface['model']:
729                             link_names.append(interface['link'])
730                 elif (filter_list is not None) and ('model' not in interface):
731                     logger.trace('Cannot apply filter on interface: {}'
732                                  .format(str(interface)))
733                 else:
734                     link_names.append(interface['link'])
735         if len(link_names) == 0:
736             link_names = None
737         return link_names
738
739     @keyword('Get active links connecting "${node1}" and "${node2}"')
740     def get_active_connecting_links(self, node1, node2,
741                                     filter_list_node1=None,
742                                     filter_list_node2=None):
743         """Return list of link names that connect together node1 and node2.
744
745         :param node1: Node topology dictionary.
746         :param node2: Node topology dictionary.
747         :param filter_list_node1: Link filter criteria for node1.
748         :param filter_list_node2: Link filter criteria for node2.
749         :type node1: dict
750         :type node2: dict
751         :type filter_list_node1: list of strings
752         :type filter_list_node2: list of strings
753         :returns: List of strings that represent connecting link names.
754         :rtype: list
755         """
756
757         logger.trace("node1: {}".format(str(node1)))
758         logger.trace("node2: {}".format(str(node2)))
759         node1_links = self._get_node_active_link_names(
760             node1,
761             filter_list=filter_list_node1)
762         node2_links = self._get_node_active_link_names(
763             node2,
764             filter_list=filter_list_node2)
765
766         connecting_links = None
767         if node1_links is None:
768             logger.error("Unable to find active links for node1")
769         elif node2_links is None:
770             logger.error("Unable to find active links for node2")
771         else:
772             connecting_links = list(set(node1_links).intersection(node2_links))
773
774         return connecting_links
775
776     @keyword('Get first active connecting link between node "${node1}" and '
777              '"${node2}"')
778     def get_first_active_connecting_link(self, node1, node2):
779         """
780
781         :param node1: Connected node.
782         :param node2: Connected node.
783         :type node1: dict
784         :type node2: dict
785         :returns: Name of link connecting the two nodes together.
786         :rtype: str
787         :raises RuntimeError: If no links are found.
788         """
789         connecting_links = self.get_active_connecting_links(node1, node2)
790         if len(connecting_links) == 0:
791             raise RuntimeError("No links connecting the nodes were found")
792         else:
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 len(links) == 0:
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