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