PapiExecutor always verifies
[csit.git] / resources / libraries / python / L2Util.py
1 # Copyright (c) 2019 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 import binascii
17 from textwrap import wrap
18
19 from enum import IntEnum
20
21 from resources.libraries.python.Constants import Constants
22 from resources.libraries.python.PapiExecutor import PapiExecutor
23 from resources.libraries.python.topology import Topology
24 from resources.libraries.python.ssh import exec_cmd_no_error
25
26
27 class L2VtrOp(IntEnum):
28     """VLAN tag rewrite operation."""
29     L2_VTR_DISABLED = 0
30     L2_VTR_PUSH_1 = 1
31     L2_VTR_PUSH_2 = 2
32     L2_VTR_POP_1 = 3
33     L2_VTR_POP_2 = 4
34     L2_VTR_TRANSLATE_1_1 = 5
35     L2_VTR_TRANSLATE_1_2 = 6
36     L2_VTR_TRANSLATE_2_1 = 7
37     L2_VTR_TRANSLATE_2_2 = 8
38
39
40 class L2Util(object):
41     """Utilities for l2 configuration."""
42
43     @staticmethod
44     def mac_to_int(mac_str):
45         """Convert MAC address from string format (e.g. 01:02:03:04:05:06) to
46         integer representation (1108152157446).
47
48         :param mac_str: MAC address in string representation.
49         :type mac_str: str
50         :returns: Integer representation of MAC address.
51         :rtype: int
52         """
53         return int(mac_str.replace(':', ''), 16)
54
55     @staticmethod
56     def int_to_mac(mac_int):
57         """Convert MAC address from integer representation (e.g. 1108152157446)
58         to string format (01:02:03:04:05:06).
59
60         :param mac_int: MAC address in integer representation.
61         :type mac_int: int
62         :returns: String representation of MAC address.
63         :rtype: str
64         """
65         return ':'.join(wrap("{:012x}".format(mac_int), width=2))
66
67     @staticmethod
68     def mac_to_bin(mac_str):
69         """Convert MAC address from string format (e.g. 01:02:03:04:05:06) to
70         binary representation (\x01\x02\x03\x04\x05\x06).
71
72         :param mac_str: MAC address in string representation.
73         :type mac_str: str
74         :returns: Binary representation of MAC address.
75         :rtype: binary
76         """
77         return binascii.unhexlify(mac_str.replace(':', ''))
78
79     @staticmethod
80     def bin_to_mac(mac_bin):
81         """Convert MAC address from binary representation
82         (\x01\x02\x03\x04\x05\x06) to string format (e.g. 01:02:03:04:05:06).
83
84         :param mac_bin: MAC address in binary representation.
85         :type mac_bin: binary
86         :returns: String representation of MAC address.
87         :rtype: str
88         """
89         mac_str = ':'.join(binascii.hexlify(mac_bin)[i:i + 2]
90                            for i in range(0, 12, 2))
91         return str(mac_str.decode('ascii'))
92
93     @staticmethod
94     def vpp_add_l2fib_entry(node, mac, interface, bd_id, static_mac=1,
95                             filter_mac=0, bvi_mac=0):
96         """ Create a static L2FIB entry on a VPP node.
97
98         :param node: Node to add L2FIB entry on.
99         :param mac: Destination mac address in string format 01:02:03:04:05:06.
100         :param interface: Interface name or sw_if_index.
101         :param bd_id: Bridge domain index.
102         :param static_mac: Set to 1 to create static MAC entry.
103             (Default value = 1)
104         :param filter_mac: Set to 1 to drop packet that's source or destination
105             MAC address contains defined MAC address. (Default value = 0)
106         :param bvi_mac: Set to 1 to create entry that points to BVI interface.
107             (Default value = 0)
108         :type node: dict
109         :type mac: str
110         :type interface: str or int
111         :type bd_id: int or str
112         :type static_mac: int or str
113         :type filter_mac: int or str
114         :type bvi_mac: int or str
115         """
116
117         if isinstance(interface, basestring):
118             sw_if_index = Topology.get_interface_sw_index(node, interface)
119         else:
120             sw_if_index = interface
121
122         cmd = 'l2fib_add_del'
123         err_msg = 'Failed to add L2FIB entry on host {host}'.format(
124             host=node['host'])
125         args = dict(mac=L2Util.mac_to_bin(mac),
126                     bd_id=int(bd_id),
127                     sw_if_index=sw_if_index,
128                     is_add=1,
129                     static_mac=int(static_mac),
130                     filter_mac=int(filter_mac),
131                     bvi_mac=int(bvi_mac))
132         with PapiExecutor(node) as papi_exec:
133             papi_exec.add(cmd, **args).get_reply(err_msg)
134
135     @staticmethod
136     def create_l2_bd(node, bd_id, flood=1, uu_flood=1, forward=1, learn=1,
137                      arp_term=0):
138         """Create an L2 bridge domain on a VPP node.
139
140         :param node: Node where we wish to crate the L2 bridge domain.
141         :param bd_id: Bridge domain index.
142         :param flood: Enable/disable bcast/mcast flooding in the BD.
143             (Default value = 1)
144         :param uu_flood: Enable/disable unknown unicast flood in the BD.
145             (Default value = 1)
146         :param forward: Enable/disable forwarding on all interfaces in
147             the BD. (Default value = 1)
148         :param learn: Enable/disable MAC learning on all interfaces in the BD.
149             (Default value = 1)
150         :param arp_term: Enable/disable arp termination in the BD.
151             (Default value = 1)
152         :type node: dict
153         :type bd_id: int or str
154         :type flood: int or str
155         :type uu_flood: int or str
156         :type forward: int or str
157         :type learn: int or str
158         :type arp_term: int or str
159         """
160
161         cmd = 'bridge_domain_add_del'
162         err_msg = 'Failed to create L2 bridge domain on host {host}'.format(
163             host=node['host'])
164         args = dict(bd_id=int(bd_id),
165                     flood=int(flood),
166                     uu_flood=int(uu_flood),
167                     forward=int(forward),
168                     learn=int(learn),
169                     arp_term=int(arp_term),
170                     is_add=1)
171         with PapiExecutor(node) as papi_exec:
172             papi_exec.add(cmd, **args).get_reply(err_msg)
173
174     @staticmethod
175     def add_interface_to_l2_bd(node, interface, bd_id, shg=0, port_type=0):
176         """Add an interface to the L2 bridge domain.
177
178         Get SW IF ID and add it to the bridge domain.
179
180         :param node: Node where we want to execute the command that does this.
181         :param interface: Interface name.
182         :param bd_id: Bridge domain index.
183         :param shg: Split-horizon group index. (Default value = 0)
184         :param port_type: Port mode: 0 - normal, 1 - BVI, 2 - UU_FWD.
185             (Default value = 0)
186         :type node: dict
187         :type interface: str
188         :type bd_id: int or str
189         :type shg: int or str
190         :type port_type: int or str
191         """
192
193         sw_if_index = Topology.get_interface_sw_index(node, interface)
194
195         cmd = 'sw_interface_set_l2_bridge'
196         err_msg = 'Failed to add interface {ifc} to L2 bridge domain on host ' \
197                   '{host}'.format(ifc=interface, host=node['host'])
198         args = dict(rx_sw_if_index=sw_if_index,
199                     bd_id=int(bd_id),
200                     shg=int(shg),
201                     port_type=int(port_type),
202                     enable=1)
203         with PapiExecutor(node) as papi_exec:
204             papi_exec.add(cmd, **args).get_reply(err_msg)
205
206     @staticmethod
207     def vpp_add_l2_bridge_domain(node, bd_id, port_1, port_2, learn=True):
208         """Add L2 bridge domain with 2 interfaces to the VPP node.
209
210         :param node: Node to add L2BD on.
211         :param bd_id: Bridge domain ID.
212         :param port_1: First interface name added to L2BD.
213         :param port_2: Second interface name added to L2BD.
214         :param learn: Enable/disable MAC learn.
215         :type node: dict
216         :type bd_id: int
217         :type port_1: str
218         :type port_2: str
219         :type learn: bool
220         """
221
222         sw_if_index1 = Topology.get_interface_sw_index(node, port_1)
223         sw_if_index2 = Topology.get_interface_sw_index(node, port_2)
224         learn_int = 1 if learn else 0
225
226         cmd1 = 'bridge_domain_add_del'
227         args1 = dict(bd_id=int(bd_id),
228                      flood=1,
229                      uu_flood=1,
230                      forward=1,
231                      learn=learn_int,
232                      arp_term=0,
233                      is_add=1)
234
235         cmd2 = 'sw_interface_set_l2_bridge'
236         args2 = dict(rx_sw_if_index=sw_if_index1,
237                      bd_id=int(bd_id),
238                      shg=0,
239                      port_type=0,
240                      enable=1)
241
242         args3 = dict(rx_sw_if_index=sw_if_index2,
243                      bd_id=int(bd_id),
244                      shg=0,
245                      port_type=0,
246                      enable=1)
247
248         err_msg = 'Failed to add L2 bridge domain with 2 interfaces on host' \
249                   ' {host}'.format(host=node['host'])
250
251         with PapiExecutor(node) as papi_exec:
252             papi_exec.add(cmd1, **args1).add(cmd2, **args2).add(cmd2, **args3)
253             papi_exec.get_replies(err_msg)
254
255     @staticmethod
256     def vpp_setup_bidirectional_cross_connect(node, interface1, interface2):
257         """Create bidirectional cross-connect between 2 interfaces on vpp node.
258
259         :param node: Node to add bidirectional cross-connect.
260         :param interface1: First interface name or sw_if_index.
261         :param interface2: Second interface name or sw_if_index.
262         :type node: dict
263         :type interface1: str or int
264         :type interface2: str or int
265         """
266
267         if isinstance(interface1, basestring):
268             sw_iface1 = Topology().get_interface_sw_index(node, interface1)
269         else:
270             sw_iface1 = interface1
271
272         if isinstance(interface2, basestring):
273             sw_iface2 = Topology().get_interface_sw_index(node, interface2)
274         else:
275             sw_iface2 = interface2
276
277         cmd = 'sw_interface_set_l2_xconnect'
278         args1 = dict(rx_sw_if_index=sw_iface1,
279                      tx_sw_if_index=sw_iface2,
280                      enable=1)
281         args2 = dict(rx_sw_if_index=sw_iface2,
282                      tx_sw_if_index=sw_iface1,
283                      enable=1)
284
285         err_msg = 'Failed to add L2 cross-connect between two interfaces on' \
286                   ' host {host}'.format(host=node['host'])
287
288         with PapiExecutor(node) as papi_exec:
289             papi_exec.add(cmd, **args1).add(cmd, **args2).get_replies(err_msg)
290
291     @staticmethod
292     def vpp_setup_bidirectional_l2_patch(node, interface1, interface2):
293         """Create bidirectional l2 patch between 2 interfaces on vpp node.
294
295         :param node: Node to add bidirectional l2 patch.
296         :param interface1: First interface name or sw_if_index.
297         :param interface2: Second interface name or sw_if_index.
298         :type node: dict
299         :type interface1: str or int
300         :type interface2: str or int
301         """
302
303         if isinstance(interface1, basestring):
304             sw_iface1 = Topology().get_interface_sw_index(node, interface1)
305         else:
306             sw_iface1 = interface1
307
308         if isinstance(interface2, basestring):
309             sw_iface2 = Topology().get_interface_sw_index(node, interface2)
310         else:
311             sw_iface2 = interface2
312
313         cmd = 'l2_patch_add_del'
314         args1 = dict(rx_sw_if_index=sw_iface1,
315                      tx_sw_if_index=sw_iface2,
316                      is_add=1)
317         args2 = dict(rx_sw_if_index=sw_iface2,
318                      tx_sw_if_index=sw_iface1,
319                      is_add=1)
320
321         err_msg = 'Failed to add L2 patch between two interfaces on' \
322                   ' host {host}'.format(host=node['host'])
323
324         with PapiExecutor(node) as papi_exec:
325             papi_exec.add(cmd, **args1).add(cmd, **args2).get_replies(err_msg)
326
327     @staticmethod
328     def linux_add_bridge(node, br_name, if_1, if_2, set_up=True):
329         """Bridge two interfaces on linux node.
330
331         :param node: Node to add bridge on.
332         :param br_name: Bridge name.
333         :param if_1: First interface to be added to the bridge.
334         :param if_2: Second interface to be added to the bridge.
335         :param set_up: Change bridge interface state to up after create bridge.
336             Optional. Default: True.
337         :type node: dict
338         :type br_name: str
339         :type if_1: str
340         :type if_2: str
341         :type set_up: bool
342         """
343
344         cmd = 'brctl addbr {0}'.format(br_name)
345         exec_cmd_no_error(node, cmd, sudo=True)
346         cmd = 'brctl addif {0} {1}'.format(br_name, if_1)
347         exec_cmd_no_error(node, cmd, sudo=True)
348         cmd = 'brctl addif {0} {1}'.format(br_name, if_2)
349         exec_cmd_no_error(node, cmd, sudo=True)
350         if set_up:
351             cmd = 'ip link set dev {0} up'.format(br_name)
352             exec_cmd_no_error(node, cmd, sudo=True)
353
354     @staticmethod
355     def linux_del_bridge(node, br_name, set_down=True):
356         """Delete bridge from linux node.
357
358         ..note:: The network interface corresponding to the bridge must be
359             down before it can be deleted!
360
361         :param node: Node to delete bridge from.
362         :param br_name: Bridge name.
363         :param set_down: Change bridge interface state to down before delbr
364             command. Optional. Default: True.
365         :type node: dict
366         :type br_name: str
367         :type set_down: bool
368         """
369
370         if set_down:
371             cmd = 'ip link set dev {0} down'.format(br_name)
372             exec_cmd_no_error(node, cmd, sudo=True)
373         cmd = 'brctl delbr {0}'.format(br_name)
374         exec_cmd_no_error(node, cmd, sudo=True)
375
376     @staticmethod
377     def vpp_get_bridge_domain_data(node, bd_id=0xffffffff):
378         """Get all bridge domain data from a VPP node. If a domain ID number is
379         provided, return only data for the matching bridge domain.
380
381         :param node: VPP node to get bridge domain data from.
382         :param bd_id: Numeric ID of a specific bridge domain.
383         :type node: dict
384         :type bd_id: int
385         :returns: List of dictionaries containing data for each bridge domain,
386             or a single dictionary for the specified bridge domain.
387         :rtype: list or dict
388         """
389
390         cmd = 'bridge_domain_dump'
391         args = dict(bd_id=int(bd_id))
392         err_msg = 'Failed to get L2FIB dump on host {host}'.format(
393             host=node['host'])
394         with PapiExecutor(node) as papi_exec:
395             details = papi_exec.add(cmd, **args).get_details(err_msg)
396
397         bd_data = list() if bd_id == Constants.BITWISE_NON_ZERO else dict()
398         for bridge_domain in details:
399             if bd_id == Constants.BITWISE_NON_ZERO:
400                 bd_data.append(bridge_domain)
401             else:
402                 if bridge_domain['bd_id'] == bd_id:
403                     return bridge_domain
404
405         return bd_data
406
407     @staticmethod
408     def l2_vlan_tag_rewrite(node, interface, tag_rewrite_method,
409                             push_dot1q=True, tag1_id=None, tag2_id=None):
410         """Rewrite tags in ethernet frame.
411
412         :param node: Node to rewrite tags.
413         :param interface: Interface on which rewrite tags.
414         :param tag_rewrite_method: Method of tag rewrite.
415         :param push_dot1q: Optional parameter to disable to push dot1q tag
416             instead of dot1ad.
417         :param tag1_id: Optional tag1 ID for VLAN.
418         :param tag2_id: Optional tag2 ID for VLAN.
419         :type node: dict
420         :type interface: str or int
421         :type tag_rewrite_method: str
422         :type push_dot1q: bool
423         :type tag1_id: int
424         :type tag2_id: int
425         """
426
427         tag1_id = int(tag1_id) if tag1_id else 0
428         tag2_id = int(tag2_id) if tag2_id else 0
429
430         vtr_oper = getattr(L2VtrOp, 'L2_VTR_{}'.format(
431             tag_rewrite_method.replace('-', '_').upper()))
432
433         if isinstance(interface, basestring):
434             iface_key = Topology.get_interface_by_name(node, interface)
435             sw_if_index = Topology.get_interface_sw_index(node, iface_key)
436         else:
437             sw_if_index = interface
438
439         cmd = 'l2_interface_vlan_tag_rewrite'
440         args = dict(sw_if_index=sw_if_index,
441                     vtr_op=int(vtr_oper),
442                     push_dot1q=int(push_dot1q),
443                     tag1=tag1_id,
444                     tag2=tag2_id)
445         err_msg = 'Failed to set VLAN TAG rewrite on host {host}'.format(
446             host=node['host'])
447         with PapiExecutor(node) as papi_exec:
448             papi_exec.add(cmd, **args).get_reply(err_msg)
449
450     @staticmethod
451     def get_l2_fib_table(node, bd_id):
452         """Retrieves the L2 FIB table.
453
454         :param node: VPP node.
455         :param bd_id: Index of the bridge domain.
456         :type node: dict
457         :type bd_id: int
458         :returns: L2 FIB table.
459         :rtype: list
460         """
461
462         cmd = 'l2_fib_table_dump'
463         args = dict(bd_id=int(bd_id))
464         err_msg = 'Failed to get L2FIB dump on host {host}'.format(
465             host=node['host'])
466         with PapiExecutor(node) as papi_exec:
467             details = papi_exec.add(cmd, **args).get_details(err_msg)
468
469         for fib_item in details:
470             fib_item['mac'] = L2Util.bin_to_mac(fib_item['mac'])
471
472         return details
473
474     @staticmethod
475     def get_l2_fib_entry_by_mac(node, bd_index, mac):
476         """Retrieves the L2 FIB entry specified by MAC address using PAPI.
477
478         :param node: VPP node.
479         :param bd_index: Index of the bridge domain.
480         :param mac: MAC address used as the key in L2 FIB data structure.
481         :type node: dict
482         :type bd_index: int
483         :type mac: str
484         :returns: L2 FIB entry
485         :rtype: dict
486         """
487
488         bd_data = L2Util.vpp_get_bridge_domain_data(node)
489         bd_id = bd_data[bd_index-1]['bd_id']
490
491         table = L2Util.get_l2_fib_table(node, bd_id)
492
493         for entry in table:
494             if entry['mac'] == mac:
495                 return entry
496         return {}