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