791b07053c2a7ae3180e4e970acaad5bc6cf593f
[csit.git] / resources / libraries / python / topology.py
1 # Copyright (c) 2016 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']
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         - additional internal representation (index, name, ...)
75         - operational data (dynamic ports)
76
77     To access the port data it is recommended to use a port key because the key
78     does not rely on the data retrieved from nodes, this allows to call most of
79     the methods without having filled active topology with internal nodes data.
80     """
81
82     @staticmethod
83     def add_new_port(node, ptype):
84         """Add new port to the node to active topology.
85
86         :param node: Node to add new port on.
87         :param ptype: Port type, used as key prefix.
88         :type node: dict
89         :type ptype: str
90         :return: Port key or None
91         :rtype: string or None
92         """
93         max_ports = 1000000
94         iface = None
95         for i in range(1, max_ports):
96             if node['interfaces'].get(str(ptype) + str(i)) is None:
97                 iface = str(ptype) + str(i)
98                 node['interfaces'][iface] = dict()
99                 break
100         return iface
101
102     @staticmethod
103     def remove_all_ports(node, ptype):
104         """Remove all ports with ptype as prefix.
105
106         :param node: Node to remove ports on.
107         :param: ptype: Port type, used as key prefix.
108         :type node: dict
109         :type ptype: str
110         :return: Nothing
111         """
112         for if_key in list(node['interfaces']):
113             if if_key.startswith(str(ptype)):
114                 node['interfaces'].pop(if_key)
115
116     @staticmethod
117     def update_interface_sw_if_index(node, iface_key, sw_if_index):
118         """Update sw_if_index on the interface from the node.
119
120         :param node: Node to update sw_if_index on.
121         :param iface_key: Topology key of the interface.
122         :param sw_if_index: Internal index to store.
123         :type node: dict
124         :type iface_key: str
125         :type sw_if_index: int
126         """
127         node['interfaces'][iface_key]['vpp_sw_index'] = int(sw_if_index)
128
129     @staticmethod
130     def update_interface_name(node, iface_key, name):
131         """Update name on the interface from the node.
132
133         :param node: Node to update name on.
134         :param iface_key: Topology key of the interface.
135         :param name: Interface name to store.
136         :type node: dict
137         :type iface_key: str
138         :type name: str
139         """
140         node['interfaces'][iface_key]['name'] = str(name)
141
142     @staticmethod
143     def update_interface_mac_address(node, iface_key, mac_address):
144         """Update mac_address on the interface from the node.
145
146         :param node: Node to update MAC on.
147         :param iface_key: Topology key of the interface.
148         :param mac_address: MAC address.
149         :type node: dict
150         :type iface_key: str
151         :type mac_address: str
152         """
153         node['interfaces'][iface_key]['mac_address'] = str(mac_address)
154
155     @staticmethod
156     def update_interface_vhost_socket(node, iface_key, vhost_socket):
157         """Update vhost socket name on the interface from the node.
158
159         :param node: Node to update socket name on.
160         :param iface_key: Topology key of the interface.
161         :param vhost_socket: Path to named socket on node.
162         :type node: dict
163         :type iface_key: str
164         :type vhost_socket: str
165         """
166         node['interfaces'][iface_key]['vhost_socket'] = str(vhost_socket)
167
168     @staticmethod
169     def get_node_by_hostname(nodes, hostname):
170         """Get node from nodes of the topology by hostname.
171
172         :param nodes: Nodes of the test topology.
173         :param hostname: Host name.
174         :type nodes: dict
175         :type hostname: str
176         :return: Node dictionary or None if not found.
177         """
178         for node in nodes.values():
179             if node['host'] == hostname:
180                 return node
181
182         return None
183
184     @staticmethod
185     def get_links(nodes):
186         """Get list of links(networks) in the topology.
187
188         :param nodes: Nodes of the test topology.
189         :type nodes: dict
190         :return: Links in the topology.
191         :rtype: list
192         """
193         links = []
194
195         for node in nodes.values():
196             for interface in node['interfaces'].values():
197                 link = interface.get('link')
198                 if link is not None:
199                     if link not in links:
200                         links.append(link)
201
202         return links
203
204     @staticmethod
205     def _get_interface_by_key_value(node, key, value):
206         """Return node interface key from topology file
207         according to key and value.
208
209         :param node: The node dictionary.
210         :param key: Key by which to select the interface.
211         :param value: Value that should be found using the key.
212         :type node: dict
213         :type key: string
214         :type value: string
215         :return: Interface key from topology file
216         :rtype: string
217         """
218         interfaces = node['interfaces']
219         retval = None
220         for if_key, if_val in interfaces.iteritems():
221             k_val = if_val.get(key)
222             if k_val is not None:
223                 if k_val == value:
224                     retval = if_key
225                     break
226         return retval
227
228     @staticmethod
229     def get_interface_by_name(node, iface_name):
230         """Return interface key based on name from DUT/TG.
231
232         This method returns interface key based on interface name
233         retrieved from the DUT, or TG.
234
235         :param node: The node topology dictionary.
236         :param iface_name: Interface name (string form).
237         :type node: dict
238         :type iface_name: string
239         :return: Interface key.
240         :rtype: str
241         """
242         return Topology._get_interface_by_key_value(node, "name", iface_name)
243
244     @staticmethod
245     def get_interface_by_link_name(node, link_name):
246         """Return interface key of link on node.
247
248         This method returns the interface name associated with a given link
249         for a given node.
250
251         :param node: The node topology dictionary.
252         :param link_name: Name of the link that a interface is connected to.
253         :type node: dict
254         :type link_name: string
255         :return: Interface key of the interface connected to the given link.
256         :rtype: str
257         """
258         return Topology._get_interface_by_key_value(node, "link", link_name)
259
260     def get_interfaces_by_link_names(self, node, link_names):
261         """Return dictionary of dictionaries {"interfaceN", interface name}.
262
263         This method returns the interface names associated with given links
264         for a given node.
265
266         :param node: The node topology directory.
267         :param link_names: List of names of the link that a interface is
268         connected to.
269         :type node: dict
270         :type link_names: list
271         :return: Dictionary of interface names that are connected to the given
272         links.
273         :rtype: dict
274         """
275         retval = {}
276         interface_key_tpl = "interface{}"
277         interface_number = 1
278         for link_name in link_names:
279             interface = self.get_interface_by_link_name(node, link_name)
280             interface_name = self.get_interface_name(node, interface)
281             interface_key = interface_key_tpl.format(str(interface_number))
282             retval[interface_key] = interface_name
283             interface_number += 1
284         return retval
285
286     @staticmethod
287     def get_interface_by_sw_index(node, sw_index):
288         """Return interface name of link on node.
289
290         This method returns the interface name associated with a software
291         interface index assigned to the interface by vpp for a given node.
292
293         :param node: The node topology dictionary.
294         :param sw_index: Sw_index of the link that a interface is connected to.
295         :type node: dict
296         :type sw_index: int
297         :return: Interface name of the interface connected to the given link.
298         :rtype: str
299         """
300         return Topology._get_interface_by_key_value(node, "vpp_sw_index",
301                                                     sw_index)
302
303     @staticmethod
304     def get_interface_sw_index(node, iface_key):
305         """Get VPP sw_if_index for the interface using interface key.
306
307         :param node: Node to get interface sw_if_index on.
308         :param iface_key: Interface key from topology file, or sw_index.
309         :type node: dict
310         :type iface_key: str/int
311         :return: Return sw_if_index or None if not found.
312         """
313         try:
314             if isinstance(iface_key, basestring):
315                 return node['interfaces'][iface_key].get('vpp_sw_index')
316             # TODO: use only iface_key, do not use integer
317             else:
318                 return int(iface_key)
319         except (KeyError, ValueError):
320             return None
321
322     @staticmethod
323     def get_interface_sw_index_by_name(node, iface_name):
324         """Get VPP sw_if_index for the interface using interface name.
325
326         :param node: Node to get interface sw_if_index on.
327         :param iface_name: Interface name.
328         :type node: dict
329         :type iface_name: str
330         :return: Return sw_if_index or None if not found.
331         :raises TypeError: If provided interface name is not a string.
332         """
333         try:
334             if isinstance(iface_name, basestring):
335                 iface_key = Topology.get_interface_by_name(node, iface_name)
336                 return node['interfaces'][iface_key].get('vpp_sw_index')
337             else:
338                 raise TypeError("Interface name must be a string.")
339         except (KeyError, ValueError):
340             return None
341
342     @staticmethod
343     def get_interface_mtu(node, iface_key):
344         """Get interface MTU.
345
346         Returns physical layer MTU (max. size of Ethernet frame).
347         :param node: Node to get interface MTU on.
348         :param iface_key: Interface key from topology file.
349         :type node: dict
350         :type iface_key: str
351         :return: MTU or None if not found.
352         :rtype: int
353         """
354         try:
355             return node['interfaces'][iface_key].get('mtu')
356         except KeyError:
357             return None
358
359     @staticmethod
360     def get_interface_name(node, iface_key):
361         """Get interface name (retrieved from DUT/TG).
362
363         Returns name in string format, retrieved from the node.
364         :param node: Node to get interface name on.
365         :param iface_key: Interface key from topology file.
366         :type node: dict
367         :type iface_key: str
368         :return: Interface name or None if not found.
369         :rtype: str
370         """
371         try:
372             return node['interfaces'][iface_key].get('name')
373         except KeyError:
374             return None
375
376     @staticmethod
377     def convert_interface_reference_to_key(node, interface):
378         """Takes interface reference in any format
379         (name, link name, interface key or sw_if_index)
380         and converts to interface key using Topology methods.
381
382         :param node: Node in topology.
383         :param interface: Name, sw_if_index, link name or key of an interface
384         on the node.
385         Valid formats are: sw_if_index, key, name.
386         :type node: dict
387         :type interface: str or int
388
389         :return: Interface key.
390         :rtype: str
391
392         :raises TypeError: If provided with invalid interface argument.
393         :raises RuntimeError: If the interface does not exist in topology.
394         """
395
396         if isinstance(interface, int):
397             key = Topology.get_interface_by_sw_index(node, interface)
398             if key is None:
399                 raise RuntimeError("Interface with sw_if_index={0} does not "
400                                    "exist in topology.".format(interface))
401         elif interface in Topology.get_node_interfaces(node):
402             key = interface
403         elif interface in Topology.get_links({"dut": node}):
404             key = Topology.get_interface_by_link_name(node, interface)
405         elif isinstance(interface, basestring):
406             key = Topology.get_interface_by_name(node, interface)
407             if key is None:
408                 raise RuntimeError("Interface with key, name or link name "
409                                    "\"{0}\" does not exist in topology."
410                                    .format(interface))
411         else:
412             raise TypeError("Type of interface argument must be integer"
413                             " or string.")
414         return key
415
416     @staticmethod
417     def convert_interface_reference(node, interface, wanted_format):
418         """Takes interface reference in any format
419         (name, link name, topology key or sw_if_index) and returns
420         its equivalent in the desired format.
421
422         :param node: Node in topology.
423         :param interface: Name, sw_if_index, link name or key of an interface
424         on the node.
425         :param wanted_format: Format of return value wanted.
426         Valid options are: sw_if_index, key, name.
427         :type node: dict
428         :type interface: str or int
429         :type wanted_format: str
430
431         :return: Interface name, interface key or sw_if_index.
432         :rtype: str or int
433
434         :raises TypeError, ValueError: If provided with invalid arguments.
435         :raises RuntimeError: If the interface does not exist in topology.
436         """
437
438         key = Topology.convert_interface_reference_to_key(node, interface)
439
440         conversions = {
441             "key": lambda x, y: y,
442             "name": Topology.get_interface_name,
443             "sw_if_index": Topology.get_interface_sw_index
444         }
445
446         try:
447             return conversions[wanted_format](node, key)
448         except KeyError:
449             raise ValueError("Unrecognized return value wanted: {0}."
450                              "Valid options are key, name, sw_if_index"
451                              .format(wanted_format))
452
453     @staticmethod
454     def get_interface_numa_node(node, iface_key):
455         """Get interface numa node.
456
457         Returns physical relation to numa node, numa_id.
458
459         :param node: Node to get numa id on.
460         :param iface_key: Interface key from topology file.
461         :type node: dict
462         :type iface_key: str
463         :return: numa node id, None if not available.
464         :rtype: int
465         """
466         try:
467             return node['interfaces'][iface_key].get('numa_node')
468         except KeyError:
469             return None
470
471     @staticmethod
472     def get_interfaces_numa_node(node, *iface_keys):
473         """Get numa node on which are located most of the interfaces.
474
475         Return numa node with highest count of interfaces provided as arguments.
476         Return 0 if the interface does not have numa_node information available.
477         If all interfaces have unknown location (-1), then return 0.
478         If most of interfaces have unknown location (-1), but there are
479         some interfaces with known location, then return the second most
480         location of the provided interfaces.
481
482         :param node: Node from DICT__nodes.
483         :param iface_keys: Interface keys for lookup.
484         :type node: dict
485         :type iface_keys: strings
486         """
487         numa_list = []
488         for if_key in iface_keys:
489             try:
490                 numa_list.append(node['interfaces'][if_key].get('numa_node'))
491             except KeyError:
492                 pass
493
494         numa_cnt_mc = Counter(numa_list).most_common()
495
496         if len(numa_cnt_mc) > 0 and numa_cnt_mc[0][0] != -1:
497             return numa_cnt_mc[0][0]
498         elif len(numa_cnt_mc) > 1 and numa_cnt_mc[0][0] == -1:
499             return numa_cnt_mc[1][0]
500         else:
501             return 0
502
503     @staticmethod
504     def get_interface_mac(node, iface_key):
505         """Get MAC address for the interface.
506
507         :param node: Node to get interface mac on.
508         :param iface_key: Interface key from topology file.
509         :type node: dict
510         :type iface_key: str
511         :return: Return MAC or None if not found.
512         """
513         try:
514             return node['interfaces'][iface_key].get('mac_address')
515         except KeyError:
516             return None
517
518     @staticmethod
519     def get_adjacent_node_and_interface(nodes_info, node, iface_key):
520         """Get node and interface adjacent to specified interface
521         on local network.
522
523         :param nodes_info: Dictionary containing information on all nodes
524         in topology.
525         :param node: Node that contains specified interface.
526         :param iface_key: Interface key from topology file.
527         :type nodes_info: dict
528         :type node: dict
529         :type iface_key: str
530         :return: Return (node, interface_key) tuple or None if not found.
531         :rtype: (dict, str)
532         """
533         link_name = None
534         # get link name where the interface belongs to
535         for if_key, if_val in node['interfaces'].iteritems():
536             if if_key == 'mgmt':
537                 continue
538             if if_key == iface_key:
539                 link_name = if_val['link']
540                 break
541
542         if link_name is None:
543             return None
544
545         # find link
546         for node_data in nodes_info.values():
547             # skip self
548             if node_data['host'] == node['host']:
549                 continue
550             for if_key, if_val \
551                     in node_data['interfaces'].iteritems():
552                 if 'link' not in if_val:
553                     continue
554                 if if_val['link'] == link_name:
555                     return node_data, if_key
556
557     @staticmethod
558     def get_interface_pci_addr(node, iface_key):
559         """Get interface PCI address.
560
561         :param node: Node to get interface PCI address on.
562         :param iface_key: Interface key from topology file.
563         :type node: dict
564         :type iface_key: str
565         :return: Return PCI address or None if not found.
566         """
567         try:
568             return node['interfaces'][iface_key].get('pci_address')
569         except KeyError:
570             return None
571
572     @staticmethod
573     def get_interface_driver(node, iface_key):
574         """Get interface driver.
575
576         :param node: Node to get interface driver on.
577         :param iface_key: Interface key from topology file.
578         :type node: dict
579         :type iface_key: str
580         :return: Return interface driver or None if not found.
581         """
582         try:
583             return node['interfaces'][iface_key].get('driver')
584         except KeyError:
585             return None
586
587     @staticmethod
588     def get_node_interfaces(node):
589         """Get all node interfaces.
590
591         :param node: Node to get list of interfaces from.
592         :type node: dict
593         :return: Return list of keys of all interfaces.
594         :rtype: list
595         """
596         return node['interfaces'].keys()
597
598     @staticmethod
599     def get_node_link_mac(node, link_name):
600         """Return interface mac address by link name.
601
602         :param node: Node to get interface sw_index on.
603         :param link_name: Link name.
604         :type node: dict
605         :type link_name: str
606         :return: MAC address string.
607         :rtype: str
608         """
609         for port in node['interfaces'].values():
610             if port.get('link') == link_name:
611                 return port.get('mac_address')
612         return None
613
614     @staticmethod
615     def _get_node_active_link_names(node, filter_list=None):
616         """Return list of link names that are other than mgmt links.
617
618         :param node: Node topology dictionary.
619         :param filter_list: Link filter criteria.
620         :type node: dict
621         :type filter_list: list of strings
622         :return: List of strings that represent link names occupied by the node.
623         :rtype: list
624         """
625         interfaces = node['interfaces']
626         link_names = []
627         for interface in interfaces.values():
628             if 'link' in interface:
629                 if (filter_list is not None) and ('model' in interface):
630                     for filt in filter_list:
631                         if filt == interface['model']:
632                             link_names.append(interface['link'])
633                 elif (filter_list is not None) and ('model' not in interface):
634                     logger.trace("Cannot apply filter on interface: {}"
635                                  .format(str(interface)))
636                 else:
637                     link_names.append(interface['link'])
638         if len(link_names) == 0:
639             link_names = None
640         return link_names
641
642     @keyword('Get active links connecting "${node1}" and "${node2}"')
643     def get_active_connecting_links(self, node1, node2,
644                                     filter_list_node1=None,
645                                     filter_list_node2=None):
646         """Return list of link names that connect together node1 and node2.
647
648         :param node1: Node topology dictionary.
649         :param node2: Node topology dictionary.
650         :param filter_list_node1: Link filter criteria for node1.
651         :param filter_list_node2: Link filter criteria for node2.
652         :type node1: dict
653         :type node2: dict
654         :type filter_list_node1: list of strings
655         :type filter_list_node2: list of strings
656         :return: List of strings that represent connecting link names.
657         :rtype: list
658         """
659
660         logger.trace("node1: {}".format(str(node1)))
661         logger.trace("node2: {}".format(str(node2)))
662         node1_links = self._get_node_active_link_names(
663             node1,
664             filter_list=filter_list_node1)
665         node2_links = self._get_node_active_link_names(
666             node2,
667             filter_list=filter_list_node2)
668
669         connecting_links = None
670         if node1_links is None:
671             logger.error("Unable to find active links for node1")
672         elif node2_links is None:
673             logger.error("Unable to find active links for node2")
674         else:
675             connecting_links = list(set(node1_links).intersection(node2_links))
676
677         return connecting_links
678
679     @keyword('Get first active connecting link between node "${node1}" and '
680              '"${node2}"')
681     def get_first_active_connecting_link(self, node1, node2):
682         """
683
684         :param node1: Connected node.
685         :param node2: Connected node.
686         :type node1: dict
687         :type node2: dict
688         :return: Name of link connecting the two nodes together.
689         :rtype: str
690         :raises: RuntimeError
691         """
692         connecting_links = self.get_active_connecting_links(node1, node2)
693         if len(connecting_links) == 0:
694             raise RuntimeError("No links connecting the nodes were found")
695         else:
696             return connecting_links[0]
697
698     @keyword('Get egress interfaces name on "${node1}" for link with '
699              '"${node2}"')
700     def get_egress_interfaces_name_for_nodes(self, node1, node2):
701         """Get egress interfaces on node1 for link with node2.
702
703         :param node1: First node, node to get egress interface on.
704         :param node2: Second node.
705         :type node1: dict
706         :type node2: dict
707         :return: Egress interfaces.
708         :rtype: list
709         """
710         interfaces = []
711         links = self.get_active_connecting_links(node1, node2)
712         if len(links) == 0:
713             raise RuntimeError('No link between nodes')
714         for interface in node1['interfaces'].values():
715             link = interface.get('link')
716             if link is None:
717                 continue
718             if link in links:
719                 continue
720             name = interface.get('name')
721             if name is None:
722                 continue
723             interfaces.append(name)
724         return interfaces
725
726     @keyword('Get first egress interface name on "${node1}" for link with '
727              '"${node2}"')
728     def get_first_egress_interface_for_nodes(self, node1, node2):
729         """Get first egress interface on node1 for link with node2.
730
731         :param node1: First node, node to get egress interface name on.
732         :param node2: Second node.
733         :type node1: dict
734         :type node2: dict
735         :return: Egress interface name.
736         :rtype: str
737         """
738         interfaces = self.get_egress_interfaces_name_for_nodes(node1, node2)
739         if not interfaces:
740             raise RuntimeError('No egress interface for nodes')
741         return interfaces[0]
742
743     @keyword('Get link data useful in circular topology test from tg "${tgen}"'
744              ' dut1 "${dut1}" dut2 "${dut2}"')
745     def get_links_dict_from_nodes(self, tgen, dut1, dut2):
746         """Return link combinations used in tests in circular topology.
747
748         For the time being it returns links from the Node path:
749         TG->DUT1->DUT2->TG
750         The naming convention until changed to something more general is
751         implemented is this:
752         DUT1_DUT2_LINK: link name between DUT! and DUT2
753         DUT1_TG_LINK: link name between DUT1 and TG
754         DUT2_TG_LINK: link name between DUT2 and TG
755         TG_TRAFFIC_LINKS: list of link names that generated traffic is sent
756         to and from
757         DUT1_BD_LINKS: list of link names that will be connected by the bridge
758         domain on DUT1
759         DUT2_BD_LINKS: list of link names that will be connected by the bridge
760         domain on DUT2
761
762         :param tgen: Traffic generator node data.
763         :param dut1: DUT1 node data.
764         :param dut2: DUT2 node data.
765         :type tgen: dict
766         :type dut1: dict
767         :type dut2: dict
768         :return: Dictionary of possible link combinations.
769         :rtype: dict
770         """
771         # TODO: replace with generic function.
772         dut1_dut2_link = self.get_first_active_connecting_link(dut1, dut2)
773         dut1_tg_link = self.get_first_active_connecting_link(dut1, tgen)
774         dut2_tg_link = self.get_first_active_connecting_link(dut2, tgen)
775         tg_traffic_links = [dut1_tg_link, dut2_tg_link]
776         dut1_bd_links = [dut1_dut2_link, dut1_tg_link]
777         dut2_bd_links = [dut1_dut2_link, dut2_tg_link]
778         topology_links = {'DUT1_DUT2_LINK': dut1_dut2_link,
779                           'DUT1_TG_LINK': dut1_tg_link,
780                           'DUT2_TG_LINK': dut2_tg_link,
781                           'TG_TRAFFIC_LINKS': tg_traffic_links,
782                           'DUT1_BD_LINKS': dut1_bd_links,
783                           'DUT2_BD_LINKS': dut2_bd_links}
784         return topology_links
785
786     @staticmethod
787     def is_tg_node(node):
788         """Find out whether the node is TG.
789
790         :param node: Node to examine.
791         :type node: dict
792         :return: True if node is type of TG, otherwise False.
793         :rtype: bool
794         """
795         return node['type'] == NodeType.TG
796
797     @staticmethod
798     def get_node_hostname(node):
799         """Return host (hostname/ip address) of the node.
800
801         :param node: Node created from topology.
802         :type node: dict
803         :return: Hostname or IP address.
804         :rtype: str
805         """
806         return node['host']
807
808     @staticmethod
809     def get_cryptodev(node):
810         """Return Crytodev configuration of the node.
811
812         :param node: Node created from topology.
813         :type node: dict
814         :return: Cryptodev configuration string.
815         :rtype: str
816         """
817         try:
818             return node['cryptodev']
819         except KeyError:
820             return None
821
822     @staticmethod
823     def get_uio_driver(node):
824         """Return uio-driver configuration of the node.
825
826         :param node: Node created from topology.
827         :type node: dict
828         :return: uio-driver configuration string.
829         :rtype: str
830         """
831         try:
832             return node['uio_driver']
833         except KeyError:
834             return None
835
836     @staticmethod
837     def set_interface_numa_node(node, iface_key, numa_node_id):
838         """Set interface numa_node location.
839
840         :param node: Node to set numa_node on.
841         :param iface_key: Interface key from topology file.
842         :type node: dict
843         :type iface_key: str
844         :return: Return iface_key or None if not found.
845         """
846         try:
847             node['interfaces'][iface_key]['numa_node'] = numa_node_id
848             return iface_key
849         except KeyError:
850             return None