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