Refactor getting telemetry
[csit.git] / resources / libraries / python / topology.py
1 # Copyright (c) 2019 Cisco and/or its affiliates.
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at:
5 #
6 #     http://www.apache.org/licenses/LICENSE-2.0
7 #
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
13
14 """Defines nodes and topology structure."""
15
16 import re
17
18 from collections import Counter
19
20 from yaml import load
21
22 from robot.api import logger
23 from robot.libraries.BuiltIn import BuiltIn, RobotNotRunningError
24 from robot.api.deco import keyword
25
26 __all__ = ["DICT__nodes", 'Topology', 'NodeType', 'SocketType']
27
28
29 def load_topo_from_yaml():
30     """Load topology from file defined in "${TOPOLOGY_PATH}" variable.
31
32     :returns: Nodes from loaded topology.
33     """
34     try:
35         topo_path = BuiltIn().get_variable_value("${TOPOLOGY_PATH}")
36     except RobotNotRunningError:
37         return ''
38
39     with open(topo_path) as work_file:
40         return load(work_file.read())['nodes']
41
42
43 # pylint: disable=invalid-name
44
45 class NodeType(object):
46     """Defines node types used in topology dictionaries."""
47     # Device Under Test (this node has VPP running on it)
48     DUT = 'DUT'
49     # Traffic Generator (this node has traffic generator on it)
50     TG = 'TG'
51     # Virtual Machine (this node running on DUT node)
52     VM = 'VM'
53
54
55 class NodeSubTypeTG(object):
56     """Defines node sub-type TG - traffic generator."""
57     # T-Rex traffic generator
58     TREX = 'TREX'
59     # Moongen
60     MOONGEN = 'MOONGEN'
61     # IxNetwork
62     IXNET = 'IXNET'
63
64 class SocketType(object):
65     """Defines socket types used in topology dictionaries."""
66     # VPP Socket PAPI
67     PAPI = 'PAPI'
68     # VPP PAPI Stats (legacy option until stats are migrated to Socket PAPI)
69     STATS = 'STATS'
70
71 DICT__nodes = load_topo_from_yaml()
72
73
74 class Topology(object):
75     """Topology data manipulation and extraction methods.
76
77     Defines methods used for manipulation and extraction of data from
78     the active topology.
79
80     "Active topology" contains initially data from the topology file and can be
81     extended with additional data from the DUTs like internal interface indexes
82     or names. Additional data which can be filled to the active topology are
83
84         - additional internal representation (index, name, ...)
85         - operational data (dynamic ports)
86
87     To access the port data it is recommended to use a port key because the key
88     does not rely on the data retrieved from nodes, this allows to call most of
89     the methods without having filled active topology with internal nodes data.
90     """
91
92     def add_node_item(self, node, value, path):
93         """Add item to topology node.
94
95         :param node: Topology node.
96         :param value: Value to insert.
97         :param path: Path where to insert item.
98         :type node: dict
99         :type value: str
100         :type path: list
101         """
102         if len(path) == 1:
103             node[path[0]] = value
104             return
105         if path[0] not in node:
106             node[path[0]] = {}
107         elif isinstance(node[path[0]], str):
108             node[path[0]] = {} if node[path[0]] == '' \
109                 else {node[path[0]]: ''}
110         self.add_node_item(node[path[0]], value, path[1:])
111
112     @staticmethod
113     def add_new_port(node, ptype):
114         """Add new port to the node to active topology.
115
116         :param node: Node to add new port on.
117         :param ptype: Port type, used as key prefix.
118         :type node: dict
119         :type ptype: str
120         :returns: Port key or None
121         :rtype: string or None
122         """
123         max_ports = 1000000
124         iface = None
125         for i in range(1, max_ports):
126             if node['interfaces'].get(str(ptype) + str(i)) is None:
127                 iface = str(ptype) + str(i)
128                 node['interfaces'][iface] = dict()
129                 break
130         return iface
131
132     @staticmethod
133     def remove_port(node, iface_key):
134         """Remove required port from active topology.
135
136         :param node: Node to remove port on.
137         :param: iface_key: Topology key of the interface.
138         :type node: dict
139         :type iface_key: str
140         :returns: Nothing
141         """
142         try:
143             node['interfaces'].pop(iface_key)
144         except KeyError:
145             pass
146
147     @staticmethod
148     def remove_all_ports(node, ptype):
149         """Remove all ports with ptype as prefix.
150
151         :param node: Node to remove ports on.
152         :param: ptype: Port type, used as key prefix.
153         :type node: dict
154         :type ptype: str
155         :returns: Nothing
156         """
157         for if_key in list(node['interfaces']):
158             if if_key.startswith(str(ptype)):
159                 node['interfaces'].pop(if_key)
160
161     @staticmethod
162     def remove_all_added_ports_on_all_duts_from_topology(nodes):
163         """Remove all added ports on all DUT nodes in the topology.
164
165         :param nodes: Nodes in the topology.
166         :type nodes: dict
167         :returns: Nothing
168         """
169         port_types = ('subinterface', 'vlan_subif', 'memif', 'tap', 'vhost',
170                       'loopback', 'gre_tunnel', 'vxlan_tunnel', 'eth_bond',
171                       'eth_avf')
172
173         for node_data in nodes.values():
174             if node_data['type'] == NodeType.DUT:
175                 for ptype in port_types:
176                     Topology.remove_all_ports(node_data, ptype)
177
178     @staticmethod
179     def remove_all_vif_ports(node):
180         """Remove all Virtual Interfaces on DUT node.
181
182         :param node: Node to remove VIF ports on.
183         :type node: dict
184         :returns: Nothing
185         """
186         reg_ex = re.compile(r'port\d+_vif\d+')
187         for if_key in list(node['interfaces']):
188             if re.match(reg_ex, if_key):
189                 node['interfaces'].pop(if_key)
190
191     @staticmethod
192     def remove_all_added_vif_ports_on_all_duts_from_topology(nodes):
193         """Remove all added Virtual Interfaces on all DUT nodes in
194         the topology.
195
196         :param nodes: Nodes in the topology.
197         :type nodes: dict
198         :returns: Nothing
199         """
200         for node_data in nodes.values():
201             if node_data['type'] == NodeType.DUT:
202                 Topology.remove_all_vif_ports(node_data)
203
204     @staticmethod
205     def update_interface_sw_if_index(node, iface_key, sw_if_index):
206         """Update sw_if_index on the interface from the node.
207
208         :param node: Node to update sw_if_index on.
209         :param iface_key: Topology key of the interface.
210         :param sw_if_index: Internal index to store.
211         :type node: dict
212         :type iface_key: str
213         :type sw_if_index: int
214         """
215         node['interfaces'][iface_key]['vpp_sw_index'] = int(sw_if_index)
216
217     @staticmethod
218     def update_interface_name(node, iface_key, name):
219         """Update name on the interface from the node.
220
221         :param node: Node to update name on.
222         :param iface_key: Topology key of the interface.
223         :param name: Interface name to store.
224         :type node: dict
225         :type iface_key: str
226         :type name: str
227         """
228         node['interfaces'][iface_key]['name'] = str(name)
229
230     @staticmethod
231     def update_interface_mac_address(node, iface_key, mac_address):
232         """Update mac_address on the interface from the node.
233
234         :param node: Node to update MAC on.
235         :param iface_key: Topology key of the interface.
236         :param mac_address: MAC address.
237         :type node: dict
238         :type iface_key: str
239         :type mac_address: str
240         """
241         node['interfaces'][iface_key]['mac_address'] = str(mac_address)
242
243     @staticmethod
244     def update_interface_pci_address(node, iface_key, pci_address):
245         """Update pci_address on the interface from the node.
246
247         :param node: Node to update PCI on.
248         :param iface_key: Topology key of the interface.
249         :param pci_address: PCI address.
250         :type node: dict
251         :type iface_key: str
252         :type pci_address: str
253         """
254         node['interfaces'][iface_key]['pci_address'] = str(pci_address)
255
256     @staticmethod
257     def update_interface_vlan(node, iface_key, vlan):
258         """Update VLAN on the interface from the node.
259
260         :param node: Node to update VLAN on.
261         :param iface_key: Topology key of the interface.
262         :param vlan: VLAN ID.
263         :type node: dict
264         :type iface_key: str
265         :type vlan: str
266         """
267         node['interfaces'][iface_key]['vlan'] = int(vlan)
268
269     @staticmethod
270     def update_interface_vhost_socket(node, iface_key, vhost_socket):
271         """Update vhost socket name on the interface from the node.
272
273         :param node: Node to update socket name on.
274         :param iface_key: Topology key of the interface.
275         :param vhost_socket: Path to named socket on node.
276         :type node: dict
277         :type iface_key: str
278         :type vhost_socket: str
279         """
280         node['interfaces'][iface_key]['vhost_socket'] = str(vhost_socket)
281
282     @staticmethod
283     def update_interface_memif_socket(node, iface_key, memif_socket):
284         """Update memif socket name on the interface from the node.
285
286         :param node: Node to update socket name on.
287         :param iface_key: Topology key of the interface.
288         :param memif_socket: Path to named socket on node.
289         :type node: dict
290         :type iface_key: str
291         :type memif_socket: str
292         """
293         node['interfaces'][iface_key]['memif_socket'] = str(memif_socket)
294
295     @staticmethod
296     def update_interface_memif_id(node, iface_key, memif_id):
297         """Update memif ID on the interface from the node.
298
299         :param node: Node to update memif ID on.
300         :param iface_key: Topology key of the interface.
301         :param memif_id: Memif interface ID.
302         :type node: dict
303         :type iface_key: str
304         :type memif_id: str
305         """
306         node['interfaces'][iface_key]['memif_id'] = str(memif_id)
307
308     @staticmethod
309     def update_interface_memif_role(node, iface_key, memif_role):
310         """Update memif role on the interface from the node.
311
312         :param node: Node to update memif role on.
313         :param iface_key: Topology key of the interface.
314         :param memif_role: Memif role.
315         :type node: dict
316         :type iface_key: str
317         :type memif_role: str
318         """
319         node['interfaces'][iface_key]['memif_role'] = str(memif_role)
320
321     @staticmethod
322     def update_interface_tap_dev_name(node, iface_key, dev_name):
323         """Update device name on the tap interface from the node.
324
325         :param node: Node to update tap device name on.
326         :param iface_key: Topology key of the interface.
327         :param dev_name: Device name of the tap interface.
328         :type node: dict
329         :type iface_key: str
330         :type dev_name: str
331         :returns: Nothing
332         """
333         node['interfaces'][iface_key]['dev_name'] = str(dev_name)
334
335     @staticmethod
336     def get_node_by_hostname(nodes, hostname):
337         """Get node from nodes of the topology by hostname.
338
339         :param nodes: Nodes of the test topology.
340         :param hostname: Host name.
341         :type nodes: dict
342         :type hostname: str
343         :returns: Node dictionary or None if not found.
344         """
345         for node in nodes.values():
346             if node['host'] == hostname:
347                 return node
348
349         return None
350
351     @staticmethod
352     def get_links(nodes):
353         """Get list of links(networks) in the topology.
354
355         :param nodes: Nodes of the test topology.
356         :type nodes: dict
357         :returns: Links in the topology.
358         :rtype: list
359         """
360         links = []
361
362         for node in nodes.values():
363             for interface in node['interfaces'].values():
364                 link = interface.get('link')
365                 if link is not None:
366                     if link not in links:
367                         links.append(link)
368
369         return links
370
371     @staticmethod
372     def _get_interface_by_key_value(node, key, value):
373         """Return node interface key from topology file
374         according to key and value.
375
376         :param node: The node dictionary.
377         :param key: Key by which to select the interface.
378         :param value: Value that should be found using the key.
379         :type node: dict
380         :type key: string
381         :type value: string
382         :returns: Interface key from topology file
383         :rtype: string
384         """
385         interfaces = node['interfaces']
386         retval = None
387         for if_key, if_val in interfaces.iteritems():
388             k_val = if_val.get(key)
389             if k_val is not None:
390                 if k_val == value:
391                     retval = if_key
392                     break
393         return retval
394
395     @staticmethod
396     def get_interface_by_name(node, iface_name):
397         """Return interface key based on name from DUT/TG.
398
399         This method returns interface key based on interface name
400         retrieved from the DUT, or TG.
401
402         :param node: The node topology dictionary.
403         :param iface_name: Interface name (string form).
404         :type node: dict
405         :type iface_name: string
406         :returns: Interface key.
407         :rtype: str
408         """
409         return Topology._get_interface_by_key_value(node, "name", iface_name)
410
411     @staticmethod
412     def get_interface_by_link_name(node, link_name):
413         """Return interface key of link on node.
414
415         This method returns the interface name associated with a given link
416         for a given node.
417
418         :param node: The node topology dictionary.
419         :param link_name: Name of the link that a interface is connected to.
420         :type node: dict
421         :type link_name: string
422         :returns: Interface key of the interface connected to the given link.
423         :rtype: str
424         """
425         return Topology._get_interface_by_key_value(node, "link", link_name)
426
427     def get_interfaces_by_link_names(self, node, link_names):
428         """Return dictionary of dictionaries {"interfaceN", interface name}.
429
430         This method returns the interface names associated with given links
431         for a given node.
432
433         :param node: The node topology directory.
434         :param link_names: List of names of the link that a interface is
435             connected to.
436         :type node: dict
437         :type link_names: list
438         :returns: Dictionary of interface names that are connected to the given
439             links.
440         :rtype: dict
441         """
442         retval = {}
443         interface_key_tpl = "interface{}"
444         interface_number = 1
445         for link_name in link_names:
446             interface = self.get_interface_by_link_name(node, link_name)
447             interface_name = self.get_interface_name(node, interface)
448             interface_key = interface_key_tpl.format(str(interface_number))
449             retval[interface_key] = interface_name
450             interface_number += 1
451         return retval
452
453     @staticmethod
454     def get_interface_by_sw_index(node, sw_if_index):
455         """Return interface name of link on node.
456
457         This method returns the interface name associated with a software
458         interface index assigned to the interface by vpp for a given node.
459
460         :param node: The node topology dictionary.
461         :param sw_if_index: sw_if_index of the link that a interface is
462             connected to.
463         :type node: dict
464         :type sw_if_index: int
465         :returns: Interface name of the interface connected to the given link.
466         :rtype: str
467         """
468         return Topology._get_interface_by_key_value(node, "vpp_sw_index",
469                                                     sw_if_index)
470
471     @staticmethod
472     def get_interface_sw_index(node, iface_key):
473         """Get VPP sw_if_index for the interface using interface key.
474
475         :param node: Node to get interface sw_if_index on.
476         :param iface_key: Interface key from topology file, or sw_if_index.
477         :type node: dict
478         :type iface_key: str/int
479         :returns: Return sw_if_index or None if not found.
480         :rtype: int or None
481         """
482         try:
483             if isinstance(iface_key, basestring):
484                 return node['interfaces'][iface_key].get('vpp_sw_index')
485             # TODO: use only iface_key, do not use integer
486             return int(iface_key)
487         except (KeyError, ValueError):
488             return None
489
490     @staticmethod
491     def get_interface_sw_index_by_name(node, iface_name):
492         """Get VPP sw_if_index for the interface using interface name.
493
494         :param node: Node to get interface sw_if_index on.
495         :param iface_name: Interface name.
496         :type node: dict
497         :type iface_name: str
498         :returns: Return sw_if_index or None if not found.
499         :raises TypeError: If provided interface name is not a string.
500         """
501         try:
502             if not isinstance(iface_name, basestring):
503                 raise TypeError("Interface name must be a string.")
504             iface_key = Topology.get_interface_by_name(node, iface_name)
505             return node['interfaces'][iface_key].get('vpp_sw_index')
506         except (KeyError, ValueError):
507             return None
508
509     @staticmethod
510     def get_interface_mtu(node, iface_key):
511         """Get interface MTU.
512
513         Returns physical layer MTU (max. size of Ethernet frame).
514         :param node: Node to get interface MTU on.
515         :param iface_key: Interface key from topology file.
516         :type node: dict
517         :type iface_key: str
518         :returns: MTU or None if not found.
519         :rtype: int
520         """
521         try:
522             return node['interfaces'][iface_key].get('mtu')
523         except KeyError:
524             return None
525
526     @staticmethod
527     def get_interface_name(node, iface_key):
528         """Get interface name (retrieved from DUT/TG).
529
530         Returns name in string format, retrieved from the node.
531         :param node: Node to get interface name on.
532         :param iface_key: Interface key from topology file.
533         :type node: dict
534         :type iface_key: str
535         :returns: Interface name or None if not found.
536         :rtype: str
537         """
538         try:
539             return node['interfaces'][iface_key].get('name')
540         except KeyError:
541             return None
542
543     @staticmethod
544     def convert_interface_reference_to_key(node, interface):
545         """Takes interface reference in any format
546         (name, link name, interface key or sw_if_index)
547         and converts to interface key using Topology methods.
548
549         :param node: Node in topology.
550         :param interface: Name, sw_if_index, link name or key of an interface
551             on the node.
552         :type node: dict
553         :type interface: str or int
554
555         :returns: Interface key.
556         :rtype: str
557
558         :raises TypeError: If provided with invalid interface argument.
559         :raises RuntimeError: If the interface does not exist in topology.
560         """
561
562         if isinstance(interface, int):
563             key = Topology.get_interface_by_sw_index(node, interface)
564             if key is None:
565                 raise RuntimeError("Interface with sw_if_index={0} does not "
566                                    "exist in topology.".format(interface))
567         elif interface in Topology.get_node_interfaces(node):
568             key = interface
569         elif interface in Topology.get_links({"dut": node}):
570             key = Topology.get_interface_by_link_name(node, interface)
571         elif isinstance(interface, basestring):
572             key = Topology.get_interface_by_name(node, interface)
573             if key is None:
574                 raise RuntimeError("Interface with key, name or link name "
575                                    "\"{0}\" does not exist in topology."
576                                    .format(interface))
577         else:
578             raise TypeError("Type of interface argument must be integer"
579                             " or string.")
580         return key
581
582     @staticmethod
583     def convert_interface_reference(node, interface, wanted_format):
584         """Takes interface reference in any format
585         (name, link name, topology key or sw_if_index) and returns
586         its equivalent in the desired format.
587
588         :param node: Node in topology.
589         :param interface: Name, sw_if_index, link name or key of an interface
590             on the node.
591         :param wanted_format: Format of return value wanted.
592             Valid options are: sw_if_index, key, name.
593         :type node: dict
594         :type interface: str or int
595         :type wanted_format: str
596         :returns: Interface name, interface key or sw_if_index.
597         :rtype: str or int
598         :raises TypeError, ValueError: If provided with invalid arguments.
599         :raises RuntimeError: If the interface does not exist in topology.
600         """
601
602         key = Topology.convert_interface_reference_to_key(node, interface)
603
604         conversions = {
605             "key": lambda x, y: y,
606             "name": Topology.get_interface_name,
607             "sw_if_index": Topology.get_interface_sw_index
608         }
609
610         try:
611             return conversions[wanted_format](node, key)
612         except KeyError:
613             raise ValueError("Unrecognized return value wanted: {0}."
614                              "Valid options are key, name, sw_if_index"
615                              .format(wanted_format))
616
617     @staticmethod
618     def get_interface_numa_node(node, iface_key):
619         """Get interface numa node.
620
621         Returns physical relation to numa node, numa_id.
622
623         :param node: Node to get numa id on.
624         :param iface_key: Interface key from topology file.
625         :type node: dict
626         :type iface_key: str
627         :returns: numa node id, None if not available.
628         :rtype: int
629         """
630         try:
631             return node['interfaces'][iface_key].get('numa_node')
632         except KeyError:
633             return None
634
635     @staticmethod
636     def get_interfaces_numa_node(node, *iface_keys):
637         """Get numa node on which are located most of the interfaces.
638
639         Return numa node with highest count of interfaces provided as arguments.
640         Return 0 if the interface does not have numa_node information available.
641         If all interfaces have unknown location (-1), then return 0.
642         If most of interfaces have unknown location (-1), but there are
643         some interfaces with known location, then return the second most
644         location of the provided interfaces.
645
646         :param node: Node from DICT__nodes.
647         :param iface_keys: Interface keys for lookup.
648         :type node: dict
649         :type iface_keys: strings
650         :returns: Numa node of most given interfaces or 0.
651         :rtype: int
652         """
653         numa_list = []
654         for if_key in iface_keys:
655             try:
656                 numa_list.append(node['interfaces'][if_key].get('numa_node'))
657             except KeyError:
658                 pass
659
660         numa_cnt_mc = Counter(numa_list).most_common()
661
662         if numa_cnt_mc and numa_cnt_mc[0][0] != -1:
663             return numa_cnt_mc[0][0]
664         if len(numa_cnt_mc) > 1 and numa_cnt_mc[0][0] == -1:
665             return numa_cnt_mc[1][0]
666         return 0
667
668     @staticmethod
669     def get_interface_mac(node, iface_key):
670         """Get MAC address for the interface.
671
672         :param node: Node to get interface mac on.
673         :param iface_key: Interface key from topology file.
674         :type node: dict
675         :type iface_key: str
676         :returns: Return MAC or None if not found.
677         """
678         try:
679             return node['interfaces'][iface_key].get('mac_address')
680         except KeyError:
681             return None
682
683     @staticmethod
684     def get_interface_ip4(node, iface_key):
685         """Get IP4 address for the interface.
686
687         :param node: Node to get interface mac on.
688         :param iface_key: Interface key from topology file.
689         :type node: dict
690         :type iface_key: str
691         :returns: Return IP4 or None if not found.
692         """
693         try:
694             return node['interfaces'][iface_key].get('ip4_address', None)
695         except KeyError:
696             return None
697
698     @staticmethod
699     def get_adjacent_node_and_interface(nodes_info, node, iface_key):
700         """Get node and interface adjacent to specified interface
701         on local network.
702
703         :param nodes_info: Dictionary containing information on all nodes
704             in topology.
705         :param node: Node that contains specified interface.
706         :param iface_key: Interface key from topology file.
707         :type nodes_info: dict
708         :type node: dict
709         :type iface_key: str
710         :returns: Return (node, interface_key) tuple or None if not found.
711         :rtype: (dict, str)
712         """
713         link_name = None
714         # get link name where the interface belongs to
715         for if_key, if_val in node['interfaces'].iteritems():
716             if if_key == 'mgmt':
717                 continue
718             if if_key == iface_key:
719                 link_name = if_val['link']
720                 break
721
722         if link_name is None:
723             return None
724
725         # find link
726         for node_data in nodes_info.values():
727             # skip self
728             if node_data['host'] == node['host']:
729                 continue
730             for if_key, if_val \
731                     in node_data['interfaces'].iteritems():
732                 if 'link' not in if_val:
733                     continue
734                 if if_val['link'] == link_name:
735                     return node_data, if_key
736         return None
737
738     @staticmethod
739     def get_interface_pci_addr(node, iface_key):
740         """Get interface PCI address.
741
742         :param node: Node to get interface PCI address on.
743         :param iface_key: Interface key from topology file.
744         :type node: dict
745         :type iface_key: str
746         :returns: Return PCI address or None if not found.
747         """
748         try:
749             return node['interfaces'][iface_key].get('pci_address')
750         except KeyError:
751             return None
752
753     @staticmethod
754     def get_interface_driver(node, iface_key):
755         """Get interface driver.
756
757         :param node: Node to get interface driver on.
758         :param iface_key: Interface key from topology file.
759         :type node: dict
760         :type iface_key: str
761         :returns: Return interface driver or None if not found.
762         """
763         try:
764             return node['interfaces'][iface_key].get('driver')
765         except KeyError:
766             return None
767
768     @staticmethod
769     def get_interface_vlan(node, iface_key):
770         """Get interface vlan.
771
772         :param node: Node to get interface driver on.
773         :param iface_key: Interface key from topology file.
774         :type node: dict
775         :type iface_key: str
776         :returns: Return interface vlan or None if not found.
777         """
778         try:
779             return node['interfaces'][iface_key].get('vlan')
780         except KeyError:
781             return None
782
783     @staticmethod
784     def get_node_interfaces(node):
785         """Get all node interfaces.
786
787         :param node: Node to get list of interfaces from.
788         :type node: dict
789         :returns: Return list of keys of all interfaces.
790         :rtype: list
791         """
792         return node['interfaces'].keys()
793
794     @staticmethod
795     def get_node_link_mac(node, link_name):
796         """Return interface mac address by link name.
797
798         :param node: Node to get interface sw_if_index on.
799         :param link_name: Link name.
800         :type node: dict
801         :type link_name: str
802         :returns: MAC address string.
803         :rtype: str
804         """
805         for port in node['interfaces'].values():
806             if port.get('link') == link_name:
807                 return port.get('mac_address')
808         return None
809
810     @staticmethod
811     def _get_node_active_link_names(node, filter_list=None):
812         """Return list of link names that are other than mgmt links.
813
814         :param node: Node topology dictionary.
815         :param filter_list: Link filter criteria.
816         :type node: dict
817         :type filter_list: list of strings
818         :returns: List of link names occupied by the node.
819         :rtype: None or list of string
820         """
821         interfaces = node['interfaces']
822         link_names = []
823         for interface in interfaces.values():
824             if 'link' in interface:
825                 if (filter_list is not None) and ('model' in interface):
826                     for filt in filter_list:
827                         if filt == interface['model']:
828                             link_names.append(interface['link'])
829                 elif (filter_list is not None) and ('model' not in interface):
830                     logger.trace('Cannot apply filter on interface: {}'
831                                  .format(str(interface)))
832                 else:
833                     link_names.append(interface['link'])
834         if not link_names:
835             link_names = None
836         return link_names
837
838     @keyword('Get active links connecting "${node1}" and "${node2}"')
839     def get_active_connecting_links(self, node1, node2,
840                                     filter_list_node1=None,
841                                     filter_list_node2=None):
842         """Return list of link names that connect together node1 and node2.
843
844         :param node1: Node topology dictionary.
845         :param node2: Node topology dictionary.
846         :param filter_list_node1: Link filter criteria for node1.
847         :param filter_list_node2: Link filter criteria for node2.
848         :type node1: dict
849         :type node2: dict
850         :type filter_list_node1: list of strings
851         :type filter_list_node2: list of strings
852         :returns: List of strings that represent connecting link names.
853         :rtype: list
854         """
855
856         logger.trace("node1: {}".format(str(node1)))
857         logger.trace("node2: {}".format(str(node2)))
858         node1_links = self._get_node_active_link_names(
859             node1,
860             filter_list=filter_list_node1)
861         node2_links = self._get_node_active_link_names(
862             node2,
863             filter_list=filter_list_node2)
864
865         connecting_links = None
866         if node1_links is None:
867             logger.error("Unable to find active links for node1")
868         elif node2_links is None:
869             logger.error("Unable to find active links for node2")
870         else:
871             connecting_links = list(set(node1_links).intersection(node2_links))
872
873         return connecting_links
874
875     @keyword('Get first active connecting link between node "${node1}" and '
876              '"${node2}"')
877     def get_first_active_connecting_link(self, node1, node2):
878         """
879
880         :param node1: Connected node.
881         :param node2: Connected node.
882         :type node1: dict
883         :type node2: dict
884         :returns: Name of a link connecting the two nodes together.
885         :rtype: str
886         :raises RuntimeError: If no links are found.
887         """
888         connecting_links = self.get_active_connecting_links(node1, node2)
889         if not connecting_links:
890             raise RuntimeError("No links connecting the nodes were found")
891         return connecting_links[0]
892
893     @keyword('Get egress interfaces name on "${node1}" for link with '
894              '"${node2}"')
895     def get_egress_interfaces_name_for_nodes(self, node1, node2):
896         """Get egress interfaces on node1 for link with node2.
897
898         :param node1: First node, node to get egress interface on.
899         :param node2: Second node.
900         :type node1: dict
901         :type node2: dict
902         :returns: Egress interfaces.
903         :rtype: list
904         """
905         interfaces = []
906         links = self.get_active_connecting_links(node1, node2)
907         if not links:
908             raise RuntimeError('No link between nodes')
909         for interface in node1['interfaces'].values():
910             link = interface.get('link')
911             if link is None:
912                 continue
913             if link in links:
914                 continue
915             name = interface.get('name')
916             if name is None:
917                 continue
918             interfaces.append(name)
919         return interfaces
920
921     @keyword('Get first egress interface name on "${node1}" for link with '
922              '"${node2}"')
923     def get_first_egress_interface_for_nodes(self, node1, node2):
924         """Get first egress interface on node1 for link with node2.
925
926         :param node1: First node, node to get egress interface name on.
927         :param node2: Second node.
928         :type node1: dict
929         :type node2: dict
930         :returns: Egress interface name.
931         :rtype: str
932         """
933         interfaces = self.get_egress_interfaces_name_for_nodes(node1, node2)
934         if not interfaces:
935             raise RuntimeError('No egress interface for nodes')
936         return interfaces[0]
937
938     @keyword('Get link data useful in circular topology test from tg "${tgen}"'
939              ' dut1 "${dut1}" dut2 "${dut2}"')
940     def get_links_dict_from_nodes(self, tgen, dut1, dut2):
941         """Return link combinations used in tests in circular topology.
942
943         For the time being it returns links from the Node path:
944         TG->DUT1->DUT2->TG
945         The naming convention until changed to something more general is
946         implemented is this:
947         DUT1_DUT2_LINK: link name between DUT! and DUT2
948         DUT1_TG_LINK: link name between DUT1 and TG
949         DUT2_TG_LINK: link name between DUT2 and TG
950         TG_TRAFFIC_LINKS: list of link names that generated traffic is sent
951         to and from
952         DUT1_BD_LINKS: list of link names that will be connected by the bridge
953         domain on DUT1
954         DUT2_BD_LINKS: list of link names that will be connected by the bridge
955         domain on DUT2
956
957         :param tgen: Traffic generator node data.
958         :param dut1: DUT1 node data.
959         :param dut2: DUT2 node data.
960         :type tgen: dict
961         :type dut1: dict
962         :type dut2: dict
963         :returns: Dictionary of possible link combinations.
964         :rtype: dict
965         """
966         # TODO: replace with generic function.
967         dut1_dut2_link = self.get_first_active_connecting_link(dut1, dut2)
968         dut1_tg_link = self.get_first_active_connecting_link(dut1, tgen)
969         dut2_tg_link = self.get_first_active_connecting_link(dut2, tgen)
970         tg_traffic_links = [dut1_tg_link, dut2_tg_link]
971         dut1_bd_links = [dut1_dut2_link, dut1_tg_link]
972         dut2_bd_links = [dut1_dut2_link, dut2_tg_link]
973         topology_links = {'DUT1_DUT2_LINK': dut1_dut2_link,
974                           'DUT1_TG_LINK': dut1_tg_link,
975                           'DUT2_TG_LINK': dut2_tg_link,
976                           'TG_TRAFFIC_LINKS': tg_traffic_links,
977                           'DUT1_BD_LINKS': dut1_bd_links,
978                           'DUT2_BD_LINKS': dut2_bd_links}
979         return topology_links
980
981     @staticmethod
982     def is_tg_node(node):
983         """Find out whether the node is TG.
984
985         :param node: Node to examine.
986         :type node: dict
987         :returns: True if node is type of TG, otherwise False.
988         :rtype: bool
989         """
990         return node['type'] == NodeType.TG
991
992     @staticmethod
993     def get_node_hostname(node):
994         """Return host (hostname/ip address) of the node.
995
996         :param node: Node created from topology.
997         :type node: dict
998         :returns: Hostname or IP address.
999         :rtype: str
1000         """
1001         return node['host']
1002
1003     @staticmethod
1004     def get_node_arch(node):
1005         """Return arch of the node.
1006            Default to x86_64 if no arch present
1007
1008         :param node: Node created from topology.
1009         :type node: dict
1010         :returns: Node architecture
1011         :rtype: str
1012         """
1013         try:
1014             return node['arch']
1015         except KeyError:
1016             node['arch'] = 'x86_64'
1017             return 'x86_64'
1018
1019     @staticmethod
1020     def get_cryptodev(node):
1021         """Return Crytodev configuration of the node.
1022
1023         :param node: Node created from topology.
1024         :type node: dict
1025         :returns: Cryptodev configuration string.
1026         :rtype: str
1027         """
1028         try:
1029             return node['cryptodev']
1030         except KeyError:
1031             return None
1032
1033     @staticmethod
1034     def get_uio_driver(node):
1035         """Return uio-driver configuration of the node.
1036
1037         :param node: Node created from topology.
1038         :type node: dict
1039         :returns: uio-driver configuration string.
1040         :rtype: str
1041         """
1042         try:
1043             return node['uio_driver']
1044         except KeyError:
1045             return None
1046
1047     @staticmethod
1048     def set_interface_numa_node(node, iface_key, numa_node_id):
1049         """Set interface numa_node location.
1050
1051         :param node: Node to set numa_node on.
1052         :param iface_key: Interface key from topology file.
1053         :type node: dict
1054         :type iface_key: str
1055         :returns: Return iface_key or None if not found.
1056         """
1057         try:
1058             node['interfaces'][iface_key]['numa_node'] = numa_node_id
1059             return iface_key
1060         except KeyError:
1061             return None
1062
1063     def add_new_socket(self, node, socket_type, socket_id, socket_path):
1064         """Add socket file of specific SocketType and ID to node.
1065
1066         :param node: Node to add socket on.
1067         :param socket_type: Socket type.
1068         :param socket_id: Socket id.
1069         :param socket_path: Socket absolute path.
1070         :type node: dict
1071         :type socket_type: SocketType
1072         :type socket_id: str
1073         :type socket path: str
1074         """
1075         path = ['sockets', socket_type, socket_id]
1076         self.add_node_item(node, socket_path, path)
1077
1078     @staticmethod
1079     def get_node_sockets(node, socket_type=None):
1080         """Get node socket files.
1081
1082         :param node: Node to get sockets from.
1083         :param socket_type: Socket type or None for all sockets.
1084         :type node: dict
1085         :type socket_type: SocketType
1086         :returns: Node sockets or None if not found.
1087         :rtype: list
1088         """
1089         try:
1090             if socket_type:
1091                 return node['sockets'][socket_type]
1092             return node['sockets']
1093         except KeyError:
1094             return None
1095
1096     @staticmethod
1097     def clean_sockets_on_all_nodes(nodes):
1098         """Remove temporary socket files from topology file.
1099
1100         :param nodes: SUT nodes.
1101         :type node: dict
1102         """
1103         for node in nodes.values():
1104             if 'sockets' in node.keys():
1105                 node.pop('sockets')