VPP_Device - add baseline tests - part IIa)
[csit.git] / resources / libraries / python / L2Util.py
1 # Copyright (c) 2018 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 """L2 Utilities Library."""
15
16 from textwrap import wrap
17
18 from robot.api.deco import keyword
19
20 from resources.libraries.python.topology import Topology
21 from resources.libraries.python.VatExecutor import VatExecutor, VatTerminal
22 from resources.libraries.python.ssh import exec_cmd_no_error
23
24
25 class L2Util(object):
26     """Utilities for l2 configuration."""
27
28     @staticmethod
29     def mac_to_int(mac_str):
30         """Convert MAC address from string format (e.g. 01:02:03:04:05:06) to
31         integer representation (1108152157446).
32
33         :param mac_str: MAC address in string representation.
34         :type mac_str: str
35         :returns: Integer representation of MAC address.
36         :rtype: int
37         """
38         return int(mac_str.replace(':', ''), 16)
39
40     @staticmethod
41     def int_to_mac(mac_int):
42         """Convert MAC address from integer representation (e.g. 1108152157446)
43         to string format (01:02:03:04:05:06).
44
45         :param mac_int: MAC address in integer representation.
46         :type mac_int: int
47         :returns: String representation of MAC address.
48         :rtype: str
49         """
50         return ':'.join(wrap("{:012x}".format(mac_int), width=2))
51
52     @staticmethod
53     def vpp_add_l2fib_entry(node, mac, interface, bd_id):
54         """ Create a static L2FIB entry on a vpp node.
55
56         :param node: Node to add L2FIB entry on.
57         :param mac: Destination mac address.
58         :param interface: Interface name or sw_if_index.
59         :param bd_id: Bridge domain id.
60         :type node: dict
61         :type mac: str
62         :type interface: str or int
63         :type bd_id: int
64         """
65         if isinstance(interface, basestring):
66             sw_if_index = Topology.get_interface_sw_index(node, interface)
67         else:
68             sw_if_index = interface
69         VatExecutor.cmd_from_template(node, "add_l2_fib_entry.vat",
70                                       mac=mac, bd=bd_id,
71                                       interface=sw_if_index)
72
73     @staticmethod
74     def create_l2_bd(node, bd_id, flood=1, uu_flood=1, forward=1, learn=1,
75                      arp_term=0):
76         """Create a l2 bridge domain on the chosen VPP node
77
78         Execute "bridge_domain_add_del bd_id {bd_id} flood {flood} uu-flood 1
79         forward {forward} learn {learn} arp-term {arp_term}" VAT command on
80         the node.
81
82         :param node: Node where we wish to crate the l2 bridge domain.
83         :param bd_id: Bridge domain index number.
84         :param flood: Enable flooding.
85         :param uu_flood: Enable uu_flood.
86         :param forward: Enable forwarding.
87         :param learn: Enable mac address learning to fib.
88         :param arp_term: Enable arp_termination.
89         :type node: dict
90         :type bd_id: int
91         :type flood: bool
92         :type uu_flood: bool
93         :type forward: bool
94         :type learn: bool
95         :type arp_term: bool
96         """
97         VatExecutor.cmd_from_template(node, "l2_bd_create.vat",
98                                       bd_id=bd_id, flood=flood,
99                                       uu_flood=uu_flood, forward=forward,
100                                       learn=learn, arp_term=arp_term)
101
102     @staticmethod
103     def add_interface_to_l2_bd(node, interface, bd_id, shg=0):
104         """Add a interface to the l2 bridge domain.
105
106         Get SW IF ID and add it to the bridge domain.
107
108         :param node: Node where we want to execute the command that does this.
109         :param interface: Interface name.
110         :param bd_id: Bridge domain index number to add Interface name to.
111         :param shg: Split horizon group.
112         :type node: dict
113         :type interface: str
114         :type bd_id: int
115         :type shg: int
116         """
117         sw_if_index = Topology.get_interface_sw_index(node, interface)
118         L2Util.add_sw_if_index_to_l2_bd(node, sw_if_index, bd_id, shg)
119
120     @staticmethod
121     def add_sw_if_index_to_l2_bd(node, sw_if_index, bd_id, shg=0):
122         """Add interface with sw_if_index to l2 bridge domain.
123
124         Execute the "sw_interface_set_l2_bridge sw_if_index {sw_if_index}
125         bd_id {bd_id} shg {shg} enable" VAT command on the given node.
126
127         :param node: Node where we want to execute the command that does this.
128         :param sw_if_index: Interface index.
129         :param bd_id: Bridge domain index number to add SW IF ID to.
130         :param shg: Split horizon group.
131         :type node: dict
132         :type sw_if_index: int
133         :type bd_id: int
134         :type shg: int
135         """
136         VatExecutor.cmd_from_template(node, "l2_bd_add_sw_if_index.vat",
137                                       bd_id=bd_id, sw_if_index=sw_if_index,
138                                       shg=shg)
139
140     @staticmethod
141     @keyword('Create dict used in bridge domain template file for node '
142              '"${node}" with links "${link_names}" and bd_id "${bd_id}"')
143     def create_bridge_domain_vat_dict(node, link_names, bd_id):
144         """Create dictionary that can be used in l2 bridge domain template.
145
146         The resulting dictionary looks like this:
147         'interface1': interface name of first interface
148         'interface2': interface name of second interface
149         'bd_id': bridge domain index
150
151         :param node: Node data dictionary.
152         :param link_names: List of names of links the bridge domain should be
153             connecting.
154         :param bd_id: Bridge domain index number.
155         :type node: dict
156         :type link_names: list
157         :returns: Dictionary used to generate l2 bridge domain VAT configuration
158             from template file.
159         :rtype: dict
160         """
161         bd_dict = Topology().get_interfaces_by_link_names(node, link_names)
162         bd_dict['bd_id'] = bd_id
163         return bd_dict
164
165     @staticmethod
166     def vpp_add_l2_bridge_domain(node, bd_id, port_1, port_2, learn=True):
167         """Add L2 bridge domain with 2 interfaces to the VPP node.
168
169         :param node: Node to add L2BD on.
170         :param bd_id: Bridge domain ID.
171         :param port_1: First interface name added to L2BD.
172         :param port_2: Second interface name added to L2BD.
173         :param learn: Enable/disable MAC learn.
174         :type node: dict
175         :type bd_id: int
176         :type port_1: str
177         :type port_2: str
178         :type learn: bool
179         """
180         sw_if_index1 = Topology.get_interface_sw_index(node, port_1)
181         sw_if_index2 = Topology.get_interface_sw_index(node, port_2)
182         VatExecutor.cmd_from_template(node,
183                                       'l2_bridge_domain.vat',
184                                       sw_if_id1=sw_if_index1,
185                                       sw_if_id2=sw_if_index2,
186                                       bd_id=bd_id,
187                                       learn=int(learn))
188
189     @staticmethod
190     def vpp_setup_bidirectional_cross_connect(node, interface1, interface2):
191         """Create bidirectional cross-connect between 2 interfaces on vpp node.
192
193         :param node: Node to add bidirectional cross-connect.
194         :param interface1: First interface name or sw_if_index.
195         :param interface2: Second interface name or sw_if_index.
196         :type node: dict
197         :type interface1: str or int
198         :type interface2: str or int
199         """
200
201         if isinstance(interface1, basestring):
202             sw_iface1 = Topology().get_interface_sw_index(node, interface1)
203         else:
204             sw_iface1 = interface1
205
206         if isinstance(interface2, basestring):
207             sw_iface2 = Topology().get_interface_sw_index(node, interface2)
208         else:
209             sw_iface2 = interface2
210
211         with VatTerminal(node) as vat:
212             vat.vat_terminal_exec_cmd_from_template('l2_xconnect.vat',
213                                                     interface1=sw_iface1,
214                                                     interface2=sw_iface2)
215             vat.vat_terminal_exec_cmd_from_template('l2_xconnect.vat',
216                                                     interface1=sw_iface2,
217                                                     interface2=sw_iface1)
218
219     @staticmethod
220     def vpp_setup_bidirectional_l2_patch(node, interface1, interface2):
221         """Create bidirectional l2 patch between 2 interfaces on vpp node.
222
223         :param node: Node to add bidirectional l2 patch.
224         :param interface1: First interface name or sw_if_index.
225         :param interface2: Second interface name or sw_if_index.
226         :type node: dict
227         :type interface1: str or int
228         :type interface2: str or int
229         """
230
231         if isinstance(interface1, basestring):
232             sw_iface1 = Topology().get_interface_sw_index(node, interface1)
233         else:
234             sw_iface1 = interface1
235
236         if isinstance(interface2, basestring):
237             sw_iface2 = Topology().get_interface_sw_index(node, interface2)
238         else:
239             sw_iface2 = interface2
240
241         with VatTerminal(node) as vat:
242             vat.vat_terminal_exec_cmd_from_template('l2_patch.vat',
243                                                     interface1=sw_iface1,
244                                                     interface2=sw_iface2)
245             vat.vat_terminal_exec_cmd_from_template('l2_patch.vat',
246                                                     interface1=sw_iface2,
247                                                     interface2=sw_iface1)
248
249     @staticmethod
250     def linux_add_bridge(node, br_name, if_1, if_2, set_up=True):
251         """Bridge two interfaces on linux node.
252
253         :param node: Node to add bridge on.
254         :param br_name: Bridge name.
255         :param if_1: First interface to be added to the bridge.
256         :param if_2: Second interface to be added to the bridge.
257         :param set_up: Change bridge interface state to up after create bridge.
258             Optional. Default: True.
259         :type node: dict
260         :type br_name: str
261         :type if_1: str
262         :type if_2: str
263         :type set_up: bool
264         """
265         cmd = 'brctl addbr {0}'.format(br_name)
266         exec_cmd_no_error(node, cmd, sudo=True)
267         cmd = 'brctl addif {0} {1}'.format(br_name, if_1)
268         exec_cmd_no_error(node, cmd, sudo=True)
269         cmd = 'brctl addif {0} {1}'.format(br_name, if_2)
270         exec_cmd_no_error(node, cmd, sudo=True)
271         if set_up:
272             cmd = 'ip link set dev {0} up'.format(br_name)
273             exec_cmd_no_error(node, cmd, sudo=True)
274
275     @staticmethod
276     def linux_del_bridge(node, br_name, set_down=True):
277         """Delete bridge from linux node.
278
279         ..note:: The network interface corresponding to the bridge must be
280             down before it can be deleted!
281
282         :param node: Node to delete bridge from.
283         :param br_name: Bridge name.
284         :param set_down: Change bridge interface state to down before delbr
285             command. Optional. Default: True.
286         :type node: dict
287         :type br_name: str
288         :type set_down: bool
289         """
290         if set_down:
291             cmd = 'ip link set dev {0} down'.format(br_name)
292             exec_cmd_no_error(node, cmd, sudo=True)
293         cmd = 'brctl delbr {0}'.format(br_name)
294         exec_cmd_no_error(node, cmd, sudo=True)
295
296     @staticmethod
297     def vpp_get_bridge_domain_data(node, bd_id=None):
298         """Get all bridge domain data from a VPP node. If a domain ID number is
299         provided, return only data for the matching bridge domain.
300
301         :param node: VPP node to get bridge domain data from.
302         :param bd_id: Numeric ID of a specific bridge domain.
303         :type node: dict
304         :type bd_id: int
305         :returns: List of dictionaries containing data for each bridge domain,
306             or a single dictionary for the specified bridge domain.
307         :rtype: list or dict
308         """
309         with VatTerminal(node) as vat:
310             response = vat.vat_terminal_exec_cmd_from_template("l2_bd_dump.vat")
311
312         data = response[0]
313
314         if bd_id is not None:
315             for bridge_domain in data:
316                 if bridge_domain["bd_id"] == bd_id:
317
318                     return bridge_domain
319
320         return data
321
322     @staticmethod
323     def l2_vlan_tag_rewrite(node, interface, tag_rewrite_method,
324                             push_dot1q=True, tag1_id=None, tag2_id=None):
325         """Rewrite tags in ethernet frame.
326
327         :param node: Node to rewrite tags.
328         :param interface: Interface on which rewrite tags.
329         :param tag_rewrite_method: Method of tag rewrite.
330         :param push_dot1q: Optional parameter to disable to push dot1q tag
331             instead of dot1ad.
332         :param tag1_id: Optional tag1 ID for VLAN.
333         :param tag2_id: Optional tag2 ID for VLAN.
334         :type node: dict
335         :type interface: str or int
336         :type tag_rewrite_method: str
337         :type push_dot1q: bool
338         :type tag1_id: int
339         :type tag2_id: int
340         """
341         push_dot1q = 'push_dot1q 0' if not push_dot1q else ''
342
343         tag1_id = 'tag1 {0}'.format(tag1_id) if tag1_id else ''
344         tag2_id = 'tag2 {0}'.format(tag2_id) if tag2_id else ''
345
346         if isinstance(interface, basestring):
347             iface_key = Topology.get_interface_by_name(node, interface)
348             sw_if_index = Topology.get_interface_sw_index(node, iface_key)
349         else:
350             sw_if_index = interface
351
352         with VatTerminal(node) as vat:
353             vat.vat_terminal_exec_cmd_from_template("l2_vlan_tag_rewrite.vat",
354                                                     sw_if_index=sw_if_index,
355                                                     tag_rewrite_method=
356                                                     tag_rewrite_method,
357                                                     push_dot1q=push_dot1q,
358                                                     tag1_optional=tag1_id,
359                                                     tag2_optional=tag2_id)
360
361     @staticmethod
362     def delete_bridge_domain_vat(node, bd_id):
363         """Delete the specified bridge domain from the node.
364
365         :param node: VPP node to delete a bridge domain from.
366         :param bd_id: Bridge domain ID.
367         :type node: dict
368         :type bd_id: int
369         """
370
371         with VatTerminal(node) as vat:
372             vat.vat_terminal_exec_cmd_from_template(
373                 "l2_bridge_domain_delete.vat", bd_id=bd_id)
374
375     @staticmethod
376     def delete_l2_fib_entry(node, bd_id, mac):
377         """Delete the specified L2 FIB entry.
378
379         :param node: VPP node.
380         :param bd_id: Bridge domain ID.
381         :param mac: MAC address used as the key in L2 FIB entry.
382         :type node: dict
383         :type bd_id: int
384         :type mac: str
385         """
386
387         with VatTerminal(node) as vat:
388             vat.vat_terminal_exec_cmd_from_template("l2_fib_entry_delete.vat",
389                                                     mac=mac,
390                                                     bd_id=bd_id)
391
392     @staticmethod
393     def get_l2_fib_table_vat(node, bd_index):
394         """Retrieves the L2 FIB table using VAT.
395
396         :param node: VPP node.
397         :param bd_index: Index of the bridge domain.
398         :type node: dict
399         :type bd_index: int
400         :returns: L2 FIB table.
401         :rtype: list
402         """
403
404         bd_data = L2Util.vpp_get_bridge_domain_data(node)
405         bd_id = bd_data[bd_index-1]["bd_id"]
406
407         try:
408             with VatTerminal(node) as vat:
409                 table = vat.vat_terminal_exec_cmd_from_template(
410                     "l2_fib_table_dump.vat", bd_id=bd_id)
411
412             return table[0]
413         except ValueError:
414             return []
415
416     @staticmethod
417     def get_l2_fib_entry_vat(node, bd_index, mac):
418         """Retrieves the L2 FIB entry specified by MAC address using VAT.
419
420         :param node: VPP node.
421         :param bd_index: Index of the bridge domain.
422         :param mac: MAC address used as the key in L2 FIB data structure.
423         :type node: dict
424         :type bd_index: int
425         :type mac: str
426         :returns: L2 FIB entry
427         :rtype: dict
428         """
429
430         table = L2Util.get_l2_fib_table_vat(node, bd_index)
431         for entry in table:
432             if entry["mac"] == mac:
433                 return entry
434         return {}