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