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:
6 # http://www.apache.org/licenses/LICENSE-2.0
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.
14 """Defines nodes and topology structure."""
18 from robot.api import logger
19 from robot.libraries.BuiltIn import BuiltIn
20 from robot.api.deco import keyword
22 __all__ = ["DICT__nodes", 'Topology']
25 def load_topo_from_yaml():
26 """Load topology from file defined in "${TOPOLOGY_PATH}" variable.
28 :return: Nodes from loaded topology.
30 topo_path = BuiltIn().get_variable_value("${TOPOLOGY_PATH}")
32 with open(topo_path) as work_file:
33 return load(work_file.read())['nodes']
36 class NodeType(object):
37 """Defines node types used in topology dictionaries."""
38 # Device Under Test (this node has VPP running on it)
40 # Traffic Generator (this node has traffic generator on it)
42 # Virtual Machine (this node running on DUT node)
46 class NodeSubTypeTG(object):
47 # T-Rex traffic generator
54 DICT__nodes = load_topo_from_yaml()
57 class Topology(object):
58 """Topology data manipulation and extraction methods.
60 Defines methods used for manipulation and extraction of data from
65 def get_node_by_hostname(nodes, hostname):
66 """Get node from nodes of the topology by hostname.
68 :param nodes: Nodes of the test topology.
69 :param hostname: Host name.
72 :return: Node dictionary or None if not found.
74 for node in nodes.values():
75 if node['host'] == hostname:
82 """Get list of links(networks) in the topology.
84 :param nodes: Nodes of the test topology.
86 :return: Links in the topology.
91 for node in nodes.values():
92 for interface in node['interfaces'].values():
93 link = interface.get('link')
101 def _get_interface_by_key_value(node, key, value):
102 """Return node interface name according to key and value.
104 :param node: The node dictionary.
105 :param key: Key by which to select the interface.
106 :param value: Value that should be found using the key.
109 interfaces = node['interfaces']
111 for interface in interfaces.values():
112 k_val = interface.get(key)
113 if k_val is not None:
115 retval = interface['name']
119 def get_interface_by_link_name(self, node, link_name):
120 """Return interface name of link on node.
122 This method returns the interface name associated with a given link
125 :param link_name: Name of the link that a interface is connected to.
126 :param node: The node topology dictionary.
127 :return: Interface name of the interface connected to the given link.
130 return self._get_interface_by_key_value(node, "link", link_name)
132 def get_interfaces_by_link_names(self, node, link_names):
133 """Return dictionary of dictionaries {"interfaceN", interface name}.
135 This method returns the interface names associated with given links
138 :param link_names: List of names of the link that a interface is
140 :param node: The node topology directory.
141 :return: Dictionary of interface names that are connected to the given
146 interface_key_tpl = "interface{}"
148 for link_name in link_names:
149 interface_name = self.get_interface_by_link_name(node, link_name)
150 interface_key = interface_key_tpl.format(str(interface_number))
151 retval[interface_key] = interface_name
152 interface_number += 1
155 def get_interface_by_sw_index(self, node, sw_index):
156 """Return interface name of link on node.
158 This method returns the interface name associated with a software
159 interface index assigned to the interface by vpp for a given node.
161 :param sw_index: Sw_index of the link that a interface is connected to.
162 :param node: The node topology dictionary.
163 :return: Interface name of the interface connected to the given link.
166 return self._get_interface_by_key_value(node, "vpp_sw_index", sw_index)
169 def get_interface_sw_index(node, interface):
170 """Get VPP sw_if_index for the interface.
172 :param node: Node to get interface sw_if_index on.
173 :param interface: Interface identifier.
175 :type interface: str or int
176 :return: Return sw_if_index or None if not found.
179 return int(interface)
181 for port in node['interfaces'].values():
182 port_name = port.get('name')
183 if port_name == interface:
184 return port.get('vpp_sw_index')
188 def get_interface_mtu(node, interface):
189 """Get interface MTU.
191 Returns physical layer MTU (max. size of Ethernet frame).
192 :param node: Node to get interface MTU on.
193 :param interface: Interface name.
196 :return: MTU or None if not found.
199 for port in node['interfaces'].values():
200 port_name = port.get('name')
201 if port_name == interface:
202 return port.get('mtu')
207 def get_interface_mac_by_port_key(node, port_key):
208 """Get MAC address for the interface based on port key.
210 :param node: Node to get interface mac on.
211 :param port_key: Dictionary key name of interface.
214 :return: Return MAC or None if not found.
216 for port_name, port_data in node['interfaces'].iteritems():
217 if port_name == port_key:
218 return port_data['mac_address']
223 def get_interface_mac(node, interface):
224 """Get MAC address for the interface.
226 :param node: Node to get interface sw_index on.
227 :param interface: Interface name.
230 :return: Return MAC or None if not found.
232 for port in node['interfaces'].values():
233 port_name = port.get('name')
234 if port_name == interface:
235 return port.get('mac_address')
240 def get_adjacent_node_and_interface_by_key(nodes_info, node, port_key):
241 """Get node and interface adjacent to specified interface
244 :param nodes_info: Dictionary containing information on all nodes
246 :param node: Node that contains specified interface.
247 :param port_key: Interface port key.
248 :type nodes_info: dict
251 :return: Return (node, interface info) tuple or None if not found.
255 # get link name where the interface belongs to
256 for port_name, port_data in node['interfaces'].iteritems():
257 if port_name == 'mgmt':
259 if port_name == port_key:
260 link_name = port_data['link']
263 if link_name is None:
267 for node_data in nodes_info.values():
269 if node_data['host'] == node['host']:
271 for interface, interface_data \
272 in node_data['interfaces'].iteritems():
273 if 'link' not in interface_data:
275 if interface_data['link'] == link_name:
276 return node_data, node_data['interfaces'][interface]
279 def get_adjacent_node_and_interface(nodes_info, node, interface_name):
280 """Get node and interface adjacent to specified interface
283 :param nodes_info: Dictionary containing information on all nodes
285 :param node: Node that contains specified interface.
286 :param interface_name: Interface name.
287 :type nodes_info: dict
289 :type interface_name: str
290 :return: Return (node, interface info) tuple or None if not found.
294 # get link name where the interface belongs to
295 for port_name, port_data in node['interfaces'].iteritems():
296 if port_data['name'] == interface_name:
297 link_name = port_data['link']
300 if link_name is None:
304 for node_data in nodes_info.values():
306 if node_data['host'] == node['host']:
308 for interface, interface_data \
309 in node_data['interfaces'].iteritems():
310 if 'link' not in interface_data:
312 if interface_data['link'] == link_name:
313 return node_data, node_data['interfaces'][interface]
316 def get_interface_pci_addr(node, interface):
317 """Get interface PCI address.
319 :param node: Node to get interface PCI address on.
320 :param interface: Interface name.
323 :return: Return PCI address or None if not found.
325 for port in node['interfaces'].values():
326 if interface == port.get('name'):
327 return port.get('pci_address')
331 def get_interface_driver(node, interface):
332 """Get interface driver.
334 :param node: Node to get interface driver on.
335 :param interface: Interface name.
338 :return: Return interface driver or None if not found.
340 for port in node['interfaces'].values():
341 if interface == port.get('name'):
342 return port.get('driver')
346 def get_node_link_mac(node, link_name):
347 """Return interface mac address by link name.
349 :param node: Node to get interface sw_index on.
350 :param link_name: Link name.
353 :return: MAC address string.
356 for port in node['interfaces'].values():
357 if port.get('link') == link_name:
358 return port.get('mac_address')
362 def _get_node_active_link_names(node, filter_list=None):
363 """Return list of link names that are other than mgmt links.
365 :param node: Node topology dictionary.
366 :param filter_list: Link filter criteria.
368 :type filter_list: list of strings
369 :return: List of strings that represent link names occupied by the node.
372 interfaces = node['interfaces']
374 for interface in interfaces.values():
375 if 'link' in interface:
376 if (filter_list is not None) and ('model' in interface):
377 for filt in filter_list:
378 if filt == interface['model']:
379 link_names.append(interface['link'])
380 elif (filter_list is not None) and ('model' not in interface):
381 logger.trace("Cannot apply filter on interface: {}" \
382 .format(str(interface)))
384 link_names.append(interface['link'])
385 if len(link_names) == 0:
389 @keyword('Get active links connecting "${node1}" and "${node2}"')
390 def get_active_connecting_links(self, node1, node2,
391 filter_list_node1=None,
392 filter_list_node2=None):
393 """Return list of link names that connect together node1 and node2.
395 :param node1: Node topology dictionary.
396 :param node2: Node topology dictionary.
397 :param filter_list_node1: Link filter criteria for node1.
398 :param filter_list_node2: Link filter criteria for node2.
401 :type filter_list1: list of strings
402 :type filter_list2: list of strings
403 :return: List of strings that represent connecting link names.
407 logger.trace("node1: {}".format(str(node1)))
408 logger.trace("node2: {}".format(str(node2)))
409 node1_links = self._get_node_active_link_names(
411 filter_list=filter_list_node1)
412 node2_links = self._get_node_active_link_names(
414 filter_list=filter_list_node2)
416 connecting_links = None
417 if node1_links is None:
418 logger.error("Unable to find active links for node1")
419 elif node2_links is None:
420 logger.error("Unable to find active links for node2")
422 connecting_links = list(set(node1_links).intersection(node2_links))
424 return connecting_links
426 @keyword('Get first active connecting link between node "${node1}" and '
428 def get_first_active_connecting_link(self, node1, node2):
431 :param node1: Connected node.
432 :param node2: Connected node.
435 :return: Name of link connecting the two nodes together.
437 :raises: RuntimeError
439 connecting_links = self.get_active_connecting_links(node1, node2)
440 if len(connecting_links) == 0:
441 raise RuntimeError("No links connecting the nodes were found")
443 return connecting_links[0]
445 @keyword('Get egress interfaces on "${node1}" for link with "${node2}"')
446 def get_egress_interfaces_for_nodes(self, node1, node2):
447 """Get egress interfaces on node1 for link with node2.
449 :param node1: First node, node to get egress interface on.
450 :param node2: Second node.
453 :return: Egress interfaces.
457 links = self.get_active_connecting_links(node1, node2)
459 raise RuntimeError('No link between nodes')
460 for interface in node1['interfaces'].values():
461 link = interface.get('link')
466 name = interface.get('name')
469 interfaces.append(name)
472 @keyword('Get first egress interface on "${node1}" for link with '
474 def get_first_egress_interface_for_nodes(self, node1, node2):
475 """Get first egress interface on node1 for link with node2.
477 :param node1: First node, node to get egress interface on.
478 :param node2: Second node.
481 :return: Egress interface.
484 interfaces = self.get_egress_interfaces_for_nodes(node1, node2)
486 raise RuntimeError('No egress interface for nodes')
489 @keyword('Get link data useful in circular topology test from tg "${tgen}"'
490 ' dut1 "${dut1}" dut2 "${dut2}"')
491 def get_links_dict_from_nodes(self, tgen, dut1, dut2):
492 """Return link combinations used in tests in circular topology.
494 For the time being it returns links from the Node path:
496 The naming convention until changed to something more general is
498 DUT1_DUT2_LINK: link name between DUT! and DUT2
499 DUT1_TG_LINK: link name between DUT1 and TG
500 DUT2_TG_LINK: link name between DUT2 and TG
501 TG_TRAFFIC_LINKS: list of link names that generated traffic is sent
503 DUT1_BD_LINKS: list of link names that will be connected by the bridge
505 DUT2_BD_LINKS: list of link names that will be connected by the bridge
508 :param tgen: Traffic generator node data.
509 :param dut1: DUT1 node data.
510 :param dut2: DUT2 node data.
514 :return: Dictionary of possible link combinations.
517 # TODO: replace with generic function.
518 dut1_dut2_link = self.get_first_active_connecting_link(dut1, dut2)
519 dut1_tg_link = self.get_first_active_connecting_link(dut1, tgen)
520 dut2_tg_link = self.get_first_active_connecting_link(dut2, tgen)
521 tg_traffic_links = [dut1_tg_link, dut2_tg_link]
522 dut1_bd_links = [dut1_dut2_link, dut1_tg_link]
523 dut2_bd_links = [dut1_dut2_link, dut2_tg_link]
524 topology_links = {'DUT1_DUT2_LINK': dut1_dut2_link,
525 'DUT1_TG_LINK': dut1_tg_link,
526 'DUT2_TG_LINK': dut2_tg_link,
527 'TG_TRAFFIC_LINKS': tg_traffic_links,
528 'DUT1_BD_LINKS': dut1_bd_links,
529 'DUT2_BD_LINKS': dut2_bd_links}
530 return topology_links
533 def is_tg_node(node):
534 """Find out whether the node is TG.
536 :param node: Node to examine.
538 :return: True if node is type of TG, otherwise False.
541 return node['type'] == NodeType.TG
544 def get_node_hostname(node):
545 """Return host (hostname/ip address) of the node.
547 :param node: Node created from topology.
549 :return: Hostname or IP address.