PAPI: Fix PyLint errors
[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_replies(err_msg).\
134                 verify_reply(err_msg=err_msg)
135
136     @staticmethod
137     def create_l2_bd(node, bd_id, flood=1, uu_flood=1, forward=1, learn=1,
138                      arp_term=0):
139         """Create an L2 bridge domain on a VPP node.
140
141         :param node: Node where we wish to crate the L2 bridge domain.
142         :param bd_id: Bridge domain index.
143         :param flood: Enable/disable bcast/mcast flooding in the BD.
144             (Default value = 1)
145         :param uu_flood: Enable/disable unknown unicast flood in the BD.
146             (Default value = 1)
147         :param forward: Enable/disable forwarding on all interfaces in
148             the BD. (Default value = 1)
149         :param learn: Enable/disable MAC learning on all interfaces in the BD.
150             (Default value = 1)
151         :param arp_term: Enable/disable arp termination in the BD.
152             (Default value = 1)
153         :type node: dict
154         :type bd_id: int or str
155         :type flood: int or str
156         :type uu_flood: int or str
157         :type forward: int or str
158         :type learn: int or str
159         :type arp_term: int or str
160         """
161
162         cmd = 'bridge_domain_add_del'
163         err_msg = 'Failed to create L2 bridge domain on host {host}'.format(
164             host=node['host'])
165         args = dict(bd_id=int(bd_id),
166                     flood=int(flood),
167                     uu_flood=int(uu_flood),
168                     forward=int(forward),
169                     learn=int(learn),
170                     arp_term=int(arp_term),
171                     is_add=1)
172         with PapiExecutor(node) as papi_exec:
173             papi_exec.add(cmd, **args).get_replies(err_msg).\
174                 verify_reply(err_msg=err_msg)
175
176     @staticmethod
177     def add_interface_to_l2_bd(node, interface, bd_id, shg=0, port_type=0):
178         """Add an interface to the L2 bridge domain.
179
180         Get SW IF ID and add it to the bridge domain.
181
182         :param node: Node where we want to execute the command that does this.
183         :param interface: Interface name.
184         :param bd_id: Bridge domain index.
185         :param shg: Split-horizon group index. (Default value = 0)
186         :param port_type: Port mode: 0 - normal, 1 - BVI, 2 - UU_FWD.
187             (Default value = 0)
188         :type node: dict
189         :type interface: str
190         :type bd_id: int or str
191         :type shg: int or str
192         :type port_type: int or str
193         """
194
195         sw_if_index = Topology.get_interface_sw_index(node, interface)
196
197         cmd = 'sw_interface_set_l2_bridge'
198         err_msg = 'Failed to add interface {ifc} to L2 bridge domain on host ' \
199                   '{host}'.format(ifc=interface, host=node['host'])
200         args = dict(rx_sw_if_index=sw_if_index,
201                     bd_id=int(bd_id),
202                     shg=int(shg),
203                     port_type=int(port_type),
204                     enable=1)
205         with PapiExecutor(node) as papi_exec:
206             papi_exec.add(cmd, **args).get_replies(err_msg).\
207                 verify_reply(err_msg=err_msg)
208
209     @staticmethod
210     def vpp_add_l2_bridge_domain(node, bd_id, port_1, port_2, learn=True):
211         """Add L2 bridge domain with 2 interfaces to the VPP node.
212
213         :param node: Node to add L2BD on.
214         :param bd_id: Bridge domain ID.
215         :param port_1: First interface name added to L2BD.
216         :param port_2: Second interface name added to L2BD.
217         :param learn: Enable/disable MAC learn.
218         :type node: dict
219         :type bd_id: int
220         :type port_1: str
221         :type port_2: str
222         :type learn: bool
223         """
224
225         sw_if_index1 = Topology.get_interface_sw_index(node, port_1)
226         sw_if_index2 = Topology.get_interface_sw_index(node, port_2)
227         learn_int = 1 if learn else 0
228
229         cmd1 = 'bridge_domain_add_del'
230         args1 = dict(bd_id=int(bd_id),
231                      flood=1,
232                      uu_flood=1,
233                      forward=1,
234                      learn=learn_int,
235                      arp_term=0,
236                      is_add=1)
237
238         cmd2 = 'sw_interface_set_l2_bridge'
239         args2 = dict(rx_sw_if_index=sw_if_index1,
240                      bd_id=int(bd_id),
241                      shg=0,
242                      port_type=0,
243                      enable=1)
244
245         args3 = dict(rx_sw_if_index=sw_if_index2,
246                      bd_id=int(bd_id),
247                      shg=0,
248                      port_type=0,
249                      enable=1)
250
251         err_msg = 'Failed to add L2 bridge domain with 2 interfaces on host' \
252                   ' {host}'.format(host=node['host'])
253
254         with PapiExecutor(node) as papi_exec:
255             papi_exec.add(cmd1, **args1).add(cmd2, **args2).add(cmd2, **args3).\
256                 get_replies(err_msg).verify_replies(err_msg=err_msg)
257
258     @staticmethod
259     def vpp_setup_bidirectional_cross_connect(node, interface1, interface2):
260         """Create bidirectional cross-connect between 2 interfaces on vpp node.
261
262         :param node: Node to add bidirectional cross-connect.
263         :param interface1: First interface name or sw_if_index.
264         :param interface2: Second interface name or sw_if_index.
265         :type node: dict
266         :type interface1: str or int
267         :type interface2: str or int
268         """
269
270         if isinstance(interface1, basestring):
271             sw_iface1 = Topology().get_interface_sw_index(node, interface1)
272         else:
273             sw_iface1 = interface1
274
275         if isinstance(interface2, basestring):
276             sw_iface2 = Topology().get_interface_sw_index(node, interface2)
277         else:
278             sw_iface2 = interface2
279
280         cmd = 'sw_interface_set_l2_xconnect'
281         args1 = dict(rx_sw_if_index=sw_iface1,
282                      tx_sw_if_index=sw_iface2,
283                      enable=1)
284         args2 = dict(rx_sw_if_index=sw_iface2,
285                      tx_sw_if_index=sw_iface1,
286                      enable=1)
287
288         err_msg = 'Failed to add L2 cross-connect between two interfaces on' \
289                   ' host {host}'.format(host=node['host'])
290
291         with PapiExecutor(node) as papi_exec:
292             papi_exec.add(cmd, **args1).add(cmd, **args2).get_replies(err_msg).\
293                 verify_replies(err_msg=err_msg)
294
295     @staticmethod
296     def vpp_setup_bidirectional_l2_patch(node, interface1, interface2):
297         """Create bidirectional l2 patch between 2 interfaces on vpp node.
298
299         :param node: Node to add bidirectional l2 patch.
300         :param interface1: First interface name or sw_if_index.
301         :param interface2: Second interface name or sw_if_index.
302         :type node: dict
303         :type interface1: str or int
304         :type interface2: str or int
305         """
306
307         if isinstance(interface1, basestring):
308             sw_iface1 = Topology().get_interface_sw_index(node, interface1)
309         else:
310             sw_iface1 = interface1
311
312         if isinstance(interface2, basestring):
313             sw_iface2 = Topology().get_interface_sw_index(node, interface2)
314         else:
315             sw_iface2 = interface2
316
317         cmd = 'l2_patch_add_del'
318         args1 = dict(rx_sw_if_index=sw_iface1,
319                      tx_sw_if_index=sw_iface2,
320                      is_add=1)
321         args2 = dict(rx_sw_if_index=sw_iface2,
322                      tx_sw_if_index=sw_iface1,
323                      is_add=1)
324
325         err_msg = 'Failed to add L2 patch between two interfaces on' \
326                   ' host {host}'.format(host=node['host'])
327
328         with PapiExecutor(node) as papi_exec:
329             papi_exec.add(cmd, **args1).add(cmd, **args2).get_replies(err_msg).\
330                 verify_replies(err_msg=err_msg)
331
332     @staticmethod
333     def linux_add_bridge(node, br_name, if_1, if_2, set_up=True):
334         """Bridge two interfaces on linux node.
335
336         :param node: Node to add bridge on.
337         :param br_name: Bridge name.
338         :param if_1: First interface to be added to the bridge.
339         :param if_2: Second interface to be added to the bridge.
340         :param set_up: Change bridge interface state to up after create bridge.
341             Optional. Default: True.
342         :type node: dict
343         :type br_name: str
344         :type if_1: str
345         :type if_2: str
346         :type set_up: bool
347         """
348
349         cmd = 'brctl addbr {0}'.format(br_name)
350         exec_cmd_no_error(node, cmd, sudo=True)
351         cmd = 'brctl addif {0} {1}'.format(br_name, if_1)
352         exec_cmd_no_error(node, cmd, sudo=True)
353         cmd = 'brctl addif {0} {1}'.format(br_name, if_2)
354         exec_cmd_no_error(node, cmd, sudo=True)
355         if set_up:
356             cmd = 'ip link set dev {0} up'.format(br_name)
357             exec_cmd_no_error(node, cmd, sudo=True)
358
359     @staticmethod
360     def linux_del_bridge(node, br_name, set_down=True):
361         """Delete bridge from linux node.
362
363         ..note:: The network interface corresponding to the bridge must be
364             down before it can be deleted!
365
366         :param node: Node to delete bridge from.
367         :param br_name: Bridge name.
368         :param set_down: Change bridge interface state to down before delbr
369             command. Optional. Default: True.
370         :type node: dict
371         :type br_name: str
372         :type set_down: bool
373         """
374
375         if set_down:
376             cmd = 'ip link set dev {0} down'.format(br_name)
377             exec_cmd_no_error(node, cmd, sudo=True)
378         cmd = 'brctl delbr {0}'.format(br_name)
379         exec_cmd_no_error(node, cmd, sudo=True)
380
381     @staticmethod
382     def vpp_get_bridge_domain_data(node, bd_id=0xffffffff):
383         """Get all bridge domain data from a VPP node. If a domain ID number is
384         provided, return only data for the matching bridge domain.
385
386         :param node: VPP node to get bridge domain data from.
387         :param bd_id: Numeric ID of a specific bridge domain.
388         :type node: dict
389         :type bd_id: int
390         :returns: List of dictionaries containing data for each bridge domain,
391             or a single dictionary for the specified bridge domain.
392         :rtype: list or dict
393         """
394
395         cmd = 'bridge_domain_dump'
396         cmd_reply = 'bridge_domain_details'
397         args = dict(bd_id=int(bd_id))
398         err_msg = 'Failed to get L2FIB dump on host {host}'.format(
399             host=node['host'])
400         with PapiExecutor(node) as papi_exec:
401             papi_resp = papi_exec.add(cmd, **args).get_dump(err_msg)
402
403         data = papi_resp.reply[0]['api_reply']
404
405         bd_data = list() if bd_id == Constants.BITWISE_NON_ZERO else dict()
406         for bridge_domain in data:
407             if bd_id == Constants.BITWISE_NON_ZERO:
408                 bd_data.append(bridge_domain[cmd_reply])
409             else:
410                 if bridge_domain[cmd_reply]['bd_id'] == bd_id:
411                     return bridge_domain[cmd_reply]
412
413         return bd_data
414
415     @staticmethod
416     def l2_vlan_tag_rewrite(node, interface, tag_rewrite_method,
417                             push_dot1q=True, tag1_id=None, tag2_id=None):
418         """Rewrite tags in ethernet frame.
419
420         :param node: Node to rewrite tags.
421         :param interface: Interface on which rewrite tags.
422         :param tag_rewrite_method: Method of tag rewrite.
423         :param push_dot1q: Optional parameter to disable to push dot1q tag
424             instead of dot1ad.
425         :param tag1_id: Optional tag1 ID for VLAN.
426         :param tag2_id: Optional tag2 ID for VLAN.
427         :type node: dict
428         :type interface: str or int
429         :type tag_rewrite_method: str
430         :type push_dot1q: bool
431         :type tag1_id: int
432         :type tag2_id: int
433         """
434
435         tag1_id = int(tag1_id) if tag1_id else 0
436         tag2_id = int(tag2_id) if tag2_id else 0
437
438         vtr_oper = getattr(L2VtrOp, 'L2_VTR_{}'.format(
439             tag_rewrite_method.replace('-', '_').upper()))
440
441         if isinstance(interface, basestring):
442             iface_key = Topology.get_interface_by_name(node, interface)
443             sw_if_index = Topology.get_interface_sw_index(node, iface_key)
444         else:
445             sw_if_index = interface
446
447         cmd = 'l2_interface_vlan_tag_rewrite'
448         args = dict(sw_if_index=sw_if_index,
449                     vtr_op=int(vtr_oper),
450                     push_dot1q=int(push_dot1q),
451                     tag1=tag1_id,
452                     tag2=tag2_id)
453         err_msg = 'Failed to set VLAN TAG rewrite on host {host}'.format(
454             host=node['host'])
455         with PapiExecutor(node) as papi_exec:
456             papi_exec.add(cmd, **args).get_replies(err_msg).\
457                 verify_reply(err_msg=err_msg)
458
459     @staticmethod
460     def get_l2_fib_table(node, bd_id):
461         """Retrieves the L2 FIB table.
462
463         :param node: VPP node.
464         :param bd_id: Index of the bridge domain.
465         :type node: dict
466         :type bd_id: int
467         :returns: L2 FIB table.
468         :rtype: list
469         """
470
471         cmd = 'l2_fib_table_dump'
472         cmd_reply = 'l2_fib_table_details'
473         args = dict(bd_id=int(bd_id))
474         err_msg = 'Failed to get L2FIB dump on host {host}'.format(
475             host=node['host'])
476         with PapiExecutor(node) as papi_exec:
477             papi_resp = papi_exec.add(cmd, **args).get_dump(err_msg)
478
479         data = papi_resp.reply[0]['api_reply']
480
481         fib_data = list()
482         for fib in data:
483             fib_item = fib[cmd_reply]
484             fib_item['mac'] = L2Util.bin_to_mac(fib_item['mac'])
485             fib_data.append(fib_item)
486
487         return fib_data
488
489     @staticmethod
490     def get_l2_fib_entry_by_mac(node, bd_index, mac):
491         """Retrieves the L2 FIB entry specified by MAC address using PAPI.
492
493         :param node: VPP node.
494         :param bd_index: Index of the bridge domain.
495         :param mac: MAC address used as the key in L2 FIB data structure.
496         :type node: dict
497         :type bd_index: int
498         :type mac: str
499         :returns: L2 FIB entry
500         :rtype: dict
501         """
502
503         bd_data = L2Util.vpp_get_bridge_domain_data(node)
504         bd_id = bd_data[bd_index-1]['bd_id']
505
506         table = L2Util.get_l2_fib_table(node, bd_id)
507
508         for entry in table:
509             if entry['mac'] == mac:
510                 return entry
511         return {}