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