1 # Copyright (c) 2021 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 """Path utilities library for nodes in the topology."""
16 from resources.libraries.python.topology import Topology
20 """Path utilities for nodes in the topology.
24 node1--link1-->node2--link2-->node3--link3-->node2--link4-->node1
26 | Library | resources/libraries/python/NodePath.py
29 | | [Arguments] | ${node1} | ${node2} | ${node3}
30 | | Append Node | ${nodes1}
31 | | Append Node | ${nodes2}
32 | | Append Nodes | ${nodes3} | ${nodes2}
33 | | Append Node | ${nodes1}
34 | | Compute Path | ${FALSE}
35 | | ${first_int} | ${node}= | First Interface
36 | | ${last_int} | ${node}= | Last Interface
37 | | ${first_ingress} | ${node}= | First Ingress Interface
38 | | ${last_egress} | ${node}= | Last Egress Interface
39 | | ${next} | ${node}= | Next Interface
42 >>> from NodePath import NodePath
44 >>> path.append_node(node1)
45 >>> path.append_node(node2)
46 >>> path.append_nodes(node3, node2)
47 >>> path.append_node(node1)
48 >>> path.compute_path()
49 >>> (interface, node) = path.first_interface()
50 >>> (interface, node) = path.last_interface()
51 >>> (interface, node) = path.first_ingress_interface()
52 >>> (interface, node) = path.last_egress_interface()
53 >>> (interface, node) = path.next_interface()
58 self._nodes_filter = []
63 def append_node(self, node, filter_list=None):
64 """Append node to the path.
66 :param node: Node to append to the path.
67 :param filter_list: Filter criteria list.
69 :type filter_list: list of strings
71 self._nodes_filter.append(filter_list)
72 self._nodes.append(node)
74 def append_nodes(self, *nodes, filter_list=None):
75 """Append nodes to the path.
77 :param nodes: Nodes to append to the path.
78 :param filter_list: Filter criteria list.
80 :type filter_list: list of strings
82 .. note:: Node order does matter.
85 self.append_node(node, filter_list=filter_list)
90 self._nodes_filter = []
95 def compute_path(self, always_same_link=True):
96 """Compute path for added nodes.
98 .. note:: First add at least two nodes to the topology.
100 :param always_same_link: If True use always same link between two nodes
101 in path. If False use different link (if available)
102 between two nodes if one link was used before.
103 :type always_same_link: bool
104 :raises RuntimeError: If not enough nodes for path.
108 raise RuntimeError(u"Not enough nodes to compute path")
110 for idx in range(0, len(nodes) - 1):
113 node2 = nodes[idx + 1]
114 n1_list = self._nodes_filter[idx]
115 n2_list = self._nodes_filter[idx + 1]
116 links = topo.get_active_connecting_links(
117 node1, node2, filter_list_node1=n1_list,
118 filter_list_node2=n2_list
122 f"No link between {node1[u'host']} and {node2[u'host']}"
125 # Not using set operations, as we need deterministic order.
127 l_set = [link for link in links if link in self._links]
129 l_set = [link for link in links if link not in self._links]
132 f"No free link between {node1[u'host']} and "
133 f"{node2[u'host']}, all links already used"
141 self._links.append(link)
142 interface1 = topo.get_interface_by_link_name(node1, link)
143 interface2 = topo.get_interface_by_link_name(node2, link)
144 self._path.append((interface1, node1))
145 self._path.append((interface2, node2))
147 self._path_iter.extend(self._path)
148 self._path_iter.reverse()
150 def next_interface(self):
151 """Path interface iterator.
153 :returns: Interface and node or None if not next interface.
154 :rtype: tuple (str, dict)
156 .. note:: Call compute_path before.
158 if not self._path_iter:
160 return self._path_iter.pop()
162 def first_interface(self):
163 """Return first interface on the path.
165 :returns: Interface and node.
166 :rtype: tuple (str, dict)
168 .. note:: Call compute_path before.
171 raise RuntimeError(u"No path for topology")
174 def last_interface(self):
175 """Return last interface on the path.
177 :returns: Interface and node.
178 :rtype: tuple (str, dict)
180 .. note:: Call compute_path before.
183 raise RuntimeError(u"No path for topology")
184 return self._path[-1]
186 def first_ingress_interface(self):
187 """Return first ingress interface on the path.
189 :returns: Interface and node.
190 :rtype: tuple (str, dict)
192 .. note:: Call compute_path before.
195 raise RuntimeError(u"No path for topology")
198 def last_egress_interface(self):
199 """Return last egress interface on the path.
201 :returns: Interface and node.
202 :rtype: tuple (str, dict)
204 .. note:: Call compute_path before.
207 raise RuntimeError(u"No path for topology")
208 return self._path[-2]
210 def compute_circular_topology(self, nodes, filter_list=None, nic_pfs=1,
211 always_same_link=False, topo_has_tg=True):
212 """Return computed circular path.
214 :param nodes: Nodes to append to the path.
215 :param filter_list: Filter criteria list.
216 :param nic_pfs: Number of PF of NIC.
217 :param always_same_link: If True use always same link between two nodes
218 in path. If False use different link (if available)
219 between two nodes if one link was used before.
220 :param topo_has_tg: If True, the topology has a TG node. If False,
221 the topology consists entirely of DUT nodes.
223 :type filter_list: list of strings
225 :type always_same_link: bool
226 :type topo_has_tg: bool
227 :returns: Topology information dictionary.
229 :raises RuntimeError: If unsupported combination of parameters.
232 duts = [key for key in nodes if u"DUT" in key]
233 t_dict[u"duts"] = duts
234 t_dict[u"duts_count"] = len(duts)
235 t_dict[u"int"] = u"pf"
237 for _ in range(0, nic_pfs // 2):
239 self.append_node(nodes[u"TG"])
241 self.append_node(nodes[dut], filter_list=filter_list)
243 self.append_node(nodes[u"TG"])
244 self.compute_path(always_same_link)
246 n_idx = 0 # node index
247 t_idx = 1 # TG interface index
248 d_idx = 0 # DUT interface index
251 interface, node = self.next_interface()
254 if topo_has_tg and node.get(u"type") == u"TG":
255 n_pfx = f"TG" # node prefix
256 p_pfx = f"pf{t_idx}" # physical interface prefix
257 i_pfx = f"if{t_idx}" # [backwards compatible] interface prefix
261 # Each node has 2 interfaces, starting with 1
262 # Calculate prefixes appropriately for current
263 # path topology nomenclature:
264 # tg1_if1 -> dut1_if1 -> dut1_if2 ->
265 # [dut2_if1 -> dut2_if2 ...] -> tg1_if2
266 n_pfx = f"DUT{n_idx // 2 + 1}"
267 p_pfx = f"pf{d_idx % 2 + t_idx - 1}"
268 i_pfx = f"if{d_idx % 2 + t_idx - 1}"
271 elif not topo_has_tg and always_same_link:
272 this_host = node.get(u"host")
273 if prev_host != this_host:
274 # When moving to a new host in the path,
275 # increment the node index (n_idx) and
276 # reset DUT interface index (d_idx) to 1.
279 n_pfx = f"DUT{n_idx}"
284 raise RuntimeError(u"Unsupported combination of paramters")
286 t_dict[f"{n_pfx}"] = node
287 t_dict[f"{n_pfx}_{p_pfx}"] = [interface]
288 t_dict[f"{n_pfx}_{p_pfx}_mac"] = \
289 [Topology.get_interface_mac(node, interface)]
290 t_dict[f"{n_pfx}_{p_pfx}_vlan"] = \
291 [Topology.get_interface_vlan(node, interface)]
292 t_dict[f"{n_pfx}_{p_pfx}_pci"] = \
293 [Topology.get_interface_pci_addr(node, interface)]
294 t_dict[f"{n_pfx}_{p_pfx}_ip4_addr"] = \
295 [Topology.get_interface_ip4(node, interface)]
296 t_dict[f"{n_pfx}_{p_pfx}_ip4_prefix"] = \
297 [Topology.get_interface_ip4_prefix_length(node, interface)]
298 if f"{n_pfx}_pf_pci" not in t_dict:
299 t_dict[f"{n_pfx}_pf_pci"] = []
300 t_dict[f"{n_pfx}_pf_pci"].append(
301 Topology.get_interface_pci_addr(node, interface))
302 if f"{n_pfx}_pf_keys" not in t_dict:
303 t_dict[f"{n_pfx}_pf_keys"] = []
304 t_dict[f"{n_pfx}_pf_keys"].append(interface)
305 # Backward compatibility below
306 t_dict[f"{n_pfx.lower()}_{i_pfx}"] = interface
307 t_dict[f"{n_pfx.lower()}_{i_pfx}_mac"] = \
308 Topology.get_interface_mac(node, interface)
309 t_dict[f"{n_pfx.lower()}_{i_pfx}_pci"] = \
310 Topology.get_interface_pci_addr(node, interface)
311 t_dict[f"{n_pfx.lower()}_{i_pfx}_ip4_addr"] = \
312 Topology.get_interface_ip4(node, interface)
313 t_dict[f"{n_pfx.lower()}_{i_pfx}_ip4_prefix"] = \
314 Topology.get_interface_ip4_prefix_length(node, interface)