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."""
16 from robot.api import logger
17 from robot.libraries.BuiltIn import BuiltIn
18 from robot.api.deco import keyword
21 __all__ = ["DICT__nodes", 'Topology']
24 def load_topo_from_yaml():
25 """Load topology from file defined in "${TOPOLOGY_PATH}" variable
27 :return: nodes from loaded topology
29 topo_path = BuiltIn().get_variable_value("${TOPOLOGY_PATH}")
31 with open(topo_path) as work_file:
32 return load(work_file.read())['nodes']
35 class NodeType(object):
36 """Defines node types used in topology dictionaries"""
37 # Device Under Test (this node has VPP running on it)
39 # Traffic Generator (this node has traffic generator on it)
41 # Virtual Machine (this node running on DUT node)
45 class NodeSubTypeTG(object):
46 #T-Rex traffic generator
53 DICT__nodes = load_topo_from_yaml()
56 class Topology(object):
57 """Topology data manipulation and extraction methods
59 Defines methods used for manipulation and extraction of data from
64 def get_node_by_hostname(nodes, hostname):
65 """Get node from nodes of the topology by hostname.
67 :param nodes: Nodes of the test topology.
68 :param hostname: Host name.
71 :return: Node dictionary or None if not found.
73 for node in nodes.values():
74 if node['host'] == hostname:
81 """Get list of links(networks) in the topology.
83 :param nodes: Nodes of the test topology.
85 :return: Links in the topology.
90 for node in nodes.values():
91 for interface in node['interfaces'].values():
92 link = interface.get('link')
100 def _get_interface_by_key_value(node, key, value):
101 """Return node interface name according to key and value
103 :param node: :param node: the node dictionary
104 :param key: key by which to select the interface.
105 :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 asociated with a given link
124 :param link_name: name of the link that a interface is connected to.
125 :param node: the node topology dictionary
126 :return: interface name of the interface connected to the given link
129 return self._get_interface_by_key_value(node, "link", link_name)
131 def get_interfaces_by_link_names(self, node, link_names):
132 """Return dictionary of dicitonaries {"interfaceN", interface name}.
134 This method returns the interface names asociated with given links
136 :param link_names: list of names of the link that a interface is
138 :param node: the node topology directory
139 :return: dictionary of interface names that are connected to the given
144 interface_key_tpl = "interface{}"
146 for link_name in link_names:
147 interface_name = self.get_interface_by_link_name(node, link_name)
148 interface_key = interface_key_tpl.format(str(interface_number))
149 retval[interface_key] = interface_name
150 interface_number += 1
153 def get_interface_by_sw_index(self, node, sw_index):
154 """Return interface name of link on node.
156 This method returns the interface name asociated with a software index
157 assigned to the interface by vpp for a given node.
158 :param sw_index: sw_index of the link that a interface is connected to.
159 :param node: the node topology dictionary
160 :return: interface name of the interface connected to the given link
163 return self._get_interface_by_key_value(node, "vpp_sw_index", sw_index)
166 def get_interface_sw_index(node, interface):
167 """Get VPP sw_index for the interface.
169 :param node: Node to get interface sw_index on.
170 :param interface: Interface name.
173 :return: Return sw_index or None if not found.
175 for port in node['interfaces'].values():
176 port_name = port.get('name')
177 if port_name == interface:
178 return port.get('vpp_sw_index')
183 def get_interface_mtu(node, interface):
184 """Get interface MTU.
186 Returns physical layer MTU (max. size of Ethernet frame).
187 :param node: Node to get interface MTU on.
188 :param interface: Interface name.
191 :return: MTU or None if not found.
194 for port in node['interfaces'].values():
195 port_name = port.get('name')
196 if port_name == interface:
197 return port.get('mtu')
202 def get_interface_mac_by_port_key(node, port_key):
203 """Get MAC address for the interface based on port key.
205 :param node: Node to get interface mac on.
206 :param port_key: Dictionary key name of interface.
209 :return: Return MAC or None if not found.
211 for port_name, port_data in node['interfaces'].iteritems():
212 if port_name == port_key:
213 return port_data['mac_address']
218 def get_interface_mac(node, interface):
219 """Get MAC address for the interface.
221 :param node: Node to get interface sw_index on.
222 :param interface: Interface name.
225 :return: Return MAC or None if not found.
227 for port in node['interfaces'].values():
228 port_name = port.get('name')
229 if port_name == interface:
230 return port.get('mac_address')
235 def get_adjacent_node_and_interface_by_key(nodes_info, node, port_key):
236 """Get node and interface adjacent to specified interface
239 :param nodes_info: Dictionary containing information on all nodes
241 :param node: Node that contains specified interface.
242 :param port_key: Interface port key.
243 :type nodes_info: dict
246 :return: Return (node, interface info) tuple or None if not found.
250 # get link name where the interface belongs to
251 for port_name, port_data in node['interfaces'].iteritems():
252 if port_name == 'mgmt':
254 if port_name == port_key:
255 link_name = port_data['link']
258 if link_name is None:
262 for node_data in nodes_info.values():
264 if node_data['host'] == node['host']:
266 for interface, interface_data \
267 in node_data['interfaces'].iteritems():
268 if 'link' not in interface_data:
270 if interface_data['link'] == link_name:
271 return node_data, node_data['interfaces'][interface]
274 def get_adjacent_node_and_interface(nodes_info, node, interface_name):
275 """Get node and interface adjacent to specified interface
278 :param nodes_info: Dictionary containing information on all nodes
280 :param node: Node that contains specified interface.
281 :param interface_name: Interface name.
282 :type nodes_info: dict
284 :type interface_name: str
285 :return: Return (node, interface info) tuple or None if not found.
289 # get link name where the interface belongs to
290 for port_name, port_data in node['interfaces'].iteritems():
291 if port_name == 'mgmt':
293 if port_data['name'] == interface_name:
294 link_name = port_data['link']
297 if link_name is None:
301 for node_data in nodes_info.values():
303 if node_data['host'] == node['host']:
305 for interface, interface_data \
306 in node_data['interfaces'].iteritems():
307 if 'link' not in interface_data:
309 if interface_data['link'] == link_name:
310 return node_data, node_data['interfaces'][interface]
313 def get_interface_pci_addr(node, interface):
314 """Get interface PCI address.
316 :param node: Node to get interface PCI address on.
317 :param interface: Interface name.
320 :return: Return PCI address or None if not found.
322 for port in node['interfaces'].values():
323 if interface == port.get('name'):
324 return port.get('pci_address')
328 def get_interface_driver(node, interface):
329 """Get interface driver.
331 :param node: Node to get interface driver on.
332 :param interface: Interface name.
335 :return: Return interface driver or None if not found.
337 for port in node['interfaces'].values():
338 if interface == port.get('name'):
339 return port.get('driver')
343 def get_node_link_mac(node, link_name):
344 """Return interface mac address by link name
346 :param node: Node to get interface sw_index on
347 :param link_name: link name
349 :type link_name: string
350 :return: mac address string
352 for port in node['interfaces'].values():
353 if port.get('link') == link_name:
354 return port.get('mac_address')
358 def _get_node_active_link_names(node):
359 """Return list of link names that are other than mgmt links
361 :param node: node topology dictionary
362 :return: list of strings that represent link names occupied by the node
364 interfaces = node['interfaces']
366 for interface in interfaces.values():
367 if 'link' in interface:
368 link_names.append(interface['link'])
369 if len(link_names) == 0:
373 @keyword('Get active links connecting "${node1}" and "${node2}"')
374 def get_active_connecting_links(self, node1, node2):
375 """Return list of link names that connect together node1 and node2
377 :param node1: node topology dictionary
378 :param node2: node topology dictionary
379 :return: list of strings that represent connecting link names
382 logger.trace("node1: {}".format(str(node1)))
383 logger.trace("node2: {}".format(str(node2)))
384 node1_links = self._get_node_active_link_names(node1)
385 node2_links = self._get_node_active_link_names(node2)
386 connecting_links = list(set(node1_links).intersection(node2_links))
388 return connecting_links
390 @keyword('Get first active connecting link between node "${node1}" and '
392 def get_first_active_connecting_link(self, node1, node2):
395 :param node1: Connected node
397 :param node2: Connected node
399 :return: name of link connecting the two nodes together
400 :raises: RuntimeError
403 connecting_links = self.get_active_connecting_links(node1, node2)
404 if len(connecting_links) == 0:
405 raise RuntimeError("No links connecting the nodes were found")
407 return connecting_links[0]
409 @keyword('Get egress interfaces on "${node1}" for link with "${node2}"')
410 def get_egress_interfaces_for_nodes(self, node1, node2):
411 """Get egress interfaces on node1 for link with node2.
413 :param node1: First node, node to get egress interface on.
414 :param node2: Second node.
417 :return: Egress interfaces.
421 links = self.get_active_connecting_links(node1, node2)
423 raise RuntimeError('No link between nodes')
424 for interface in node1['interfaces'].values():
425 link = interface.get('link')
430 name = interface.get('name')
433 interfaces.append(name)
436 @keyword('Get first egress interface on "${node1}" for link with '
438 def get_first_egress_interface_for_nodes(self, node1, node2):
439 """Get first egress interface on node1 for link with node2.
441 :param node1: First node, node to get egress interface on.
442 :param node2: Second node.
445 :return: Egress interface.
448 interfaces = self.get_egress_interfaces_for_nodes(node1, node2)
450 raise RuntimeError('No egress interface for nodes')
453 @keyword('Get link data useful in circular topology test from tg "${tgen}"'
454 ' dut1 "${dut1}" dut2 "${dut2}"')
455 def get_links_dict_from_nodes(self, tgen, dut1, dut2):
456 """Return link combinations used in tests in circular topology.
458 For the time being it returns links from the Node path:
460 :param tgen: traffic generator node data
461 :param dut1: DUT1 node data
462 :param dut2: DUT2 node data
466 :return: dictionary of possible link combinations
467 the naming convention until changed to something more general is
469 DUT1_DUT2_LINK: link name between DUT! and DUT2
470 DUT1_TG_LINK: link name between DUT1 and TG
471 DUT2_TG_LINK: link name between DUT2 and TG
472 TG_TRAFFIC_LINKS: list of link names that generated traffic is sent
474 DUT1_BD_LINKS: list of link names that will be connected by the bridge
476 DUT2_BD_LINKS: list of link names that will be connected by the bridge
479 # TODO: replace with generic function.
480 dut1_dut2_link = self.get_first_active_connecting_link(dut1, dut2)
481 dut1_tg_link = self.get_first_active_connecting_link(dut1, tgen)
482 dut2_tg_link = self.get_first_active_connecting_link(dut2, tgen)
483 tg_traffic_links = [dut1_tg_link, dut2_tg_link]
484 dut1_bd_links = [dut1_dut2_link, dut1_tg_link]
485 dut2_bd_links = [dut1_dut2_link, dut2_tg_link]
486 topology_links = {'DUT1_DUT2_LINK': dut1_dut2_link,
487 'DUT1_TG_LINK': dut1_tg_link,
488 'DUT2_TG_LINK': dut2_tg_link,
489 'TG_TRAFFIC_LINKS': tg_traffic_links,
490 'DUT1_BD_LINKS': dut1_bd_links,
491 'DUT2_BD_LINKS': dut2_bd_links}
492 return topology_links
495 def is_tg_node(node):
496 """Find out whether the node is TG
498 :param node: node to examine
499 :return: True if node is type of TG; False otherwise
501 return node['type'] == NodeType.TG
504 def get_node_hostname(node):
505 """Return host (hostname/ip address) of the node.
507 :param node: Node created from topology.
509 :return: host as 'str' type