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