Revert "fix(jobspec): Delete ipsec nfv density tests"
[csit.git] / resources / libraries / python / L2Util.py
1 # Copyright (c) 2023 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_v2"
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_v2"
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             # Cannot use get_replies due to VPP-2203.
258             papi_exec.add(cmd1, **args1).get_reply(err_msg)
259             papi_exec.add(cmd2, **args2).get_reply(err_msg)
260             papi_exec.add(cmd2, **args3).get_reply(err_msg)
261
262     @staticmethod
263     def vpp_setup_bidirectional_cross_connect(node, interface1, interface2):
264         """Create bidirectional cross-connect between 2 interfaces on vpp node.
265
266         :param node: Node to add bidirectional cross-connect.
267         :param interface1: First interface name or sw_if_index.
268         :param interface2: Second interface name or sw_if_index.
269         :type node: dict
270         :type interface1: str or int
271         :type interface2: str or int
272         """
273         if isinstance(interface1, str):
274             sw_iface1 = Topology().get_interface_sw_index(node, interface1)
275         else:
276             sw_iface1 = interface1
277
278         if isinstance(interface2, str):
279             sw_iface2 = Topology().get_interface_sw_index(node, interface2)
280         else:
281             sw_iface2 = interface2
282
283         cmd = u"sw_interface_set_l2_xconnect"
284         args1 = dict(
285             rx_sw_if_index=sw_iface1,
286             tx_sw_if_index=sw_iface2,
287             enable=True
288         )
289         args2 = dict(
290             rx_sw_if_index=sw_iface2,
291             tx_sw_if_index=sw_iface1,
292             enable=True
293         )
294         err_msg = f"Failed to add L2 cross-connect between two interfaces " \
295             f"on host {node['host']}"
296
297         with PapiSocketExecutor(node) as papi_exec:
298             # Cannot use get_replies due to VPP-2203.
299             papi_exec.add(cmd, **args1).get_reply(err_msg)
300             papi_exec.add(cmd, **args2).get_reply(err_msg)
301
302     @staticmethod
303     def vpp_setup_bidirectional_l2_patch(node, interface1, interface2):
304         """Create bidirectional l2 patch between 2 interfaces on vpp node.
305
306         :param node: Node to add bidirectional l2 patch.
307         :param interface1: First interface name or sw_if_index.
308         :param interface2: Second interface name or sw_if_index.
309         :type node: dict
310         :type interface1: str or int
311         :type interface2: str or int
312         """
313         if isinstance(interface1, str):
314             sw_iface1 = Topology().get_interface_sw_index(node, interface1)
315         else:
316             sw_iface1 = interface1
317
318         if isinstance(interface2, str):
319             sw_iface2 = Topology().get_interface_sw_index(node, interface2)
320         else:
321             sw_iface2 = interface2
322
323         cmd = u"l2_patch_add_del"
324         args1 = dict(
325             rx_sw_if_index=sw_iface1,
326             tx_sw_if_index=sw_iface2,
327             is_add=True
328         )
329         args2 = dict(
330             rx_sw_if_index=sw_iface2,
331             tx_sw_if_index=sw_iface1,
332             is_add=True
333         )
334         err_msg = f"Failed to add L2 patch between two interfaces " \
335             f"on host {node['host']}"
336
337         with PapiSocketExecutor(node) as papi_exec:
338             # Cannot use get_replies due to VPP-2203.
339             papi_exec.add(cmd, **args1).get_reply(err_msg)
340             papi_exec.add(cmd, **args2).get_reply(err_msg)
341
342     @staticmethod
343     def linux_add_bridge(node, br_name, if_1, if_2, set_up=True):
344         """Bridge two interfaces on linux node.
345
346         :param node: Node to add bridge on.
347         :param br_name: Bridge name.
348         :param if_1: First interface to be added to the bridge.
349         :param if_2: Second interface to be added to the bridge.
350         :param set_up: Change bridge interface state to up after create bridge.
351             Optional. Default: True.
352         :type node: dict
353         :type br_name: str
354         :type if_1: str
355         :type if_2: str
356         :type set_up: bool
357         """
358         cmd = f"brctl addbr {br_name}"
359         exec_cmd_no_error(node, cmd, sudo=True)
360
361         cmd = f"brctl addif {br_name} {if_1}"
362         exec_cmd_no_error(node, cmd, sudo=True)
363
364         cmd = f"brctl addif {br_name} {if_2}"
365         exec_cmd_no_error(node, cmd, sudo=True)
366
367         if set_up:
368             cmd = f"ip link set dev {br_name} up"
369             exec_cmd_no_error(node, cmd, sudo=True)
370
371     @staticmethod
372     def linux_del_bridge(node, br_name, set_down=True):
373         """Delete bridge from linux node.
374
375         ..note:: The network interface corresponding to the bridge must be
376             down before it can be deleted!
377
378         :param node: Node to delete bridge from.
379         :param br_name: Bridge name.
380         :param set_down: Change bridge interface state to down before delbr
381             command. Optional. Default: True.
382         :type node: dict
383         :type br_name: str
384         :type set_down: bool
385         """
386         if set_down:
387             cmd = f"ip link set dev {br_name} down"
388             exec_cmd_no_error(node, cmd, sudo=True)
389
390         cmd = f"brctl delbr {br_name}"
391         exec_cmd_no_error(node, cmd, sudo=True)
392
393     @staticmethod
394     def vpp_get_bridge_domain_data(node, bd_id=Constants.BITWISE_NON_ZERO):
395         """Get all bridge domain data from a VPP node. If a domain ID number is
396         provided, return only data for the matching bridge domain.
397
398         :param node: VPP node to get bridge domain data from.
399         :param bd_id: Numeric ID of a specific bridge domain.
400         :type node: dict
401         :type bd_id: int
402         :returns: List of dictionaries containing data for each bridge domain,
403             or a single dictionary for the specified bridge domain.
404         :rtype: list or dict
405         """
406         cmd = u"bridge_domain_dump"
407         args = dict(
408             bd_id=int(bd_id)
409         )
410         err_msg = f"Failed to get L2FIB dump on host {node[u'host']}"
411
412         with PapiSocketExecutor(node) as papi_exec:
413             details = papi_exec.add(cmd, **args).get_details(err_msg)
414
415         retval = details if bd_id == Constants.BITWISE_NON_ZERO else None
416
417         for bridge_domain in details:
418             if bridge_domain[u"bd_id"] == bd_id:
419                 retval = bridge_domain
420
421         return retval
422
423     @staticmethod
424     def l2_vlan_tag_rewrite(
425             node, interface, tag_rewrite_method, push_dot1q=True, tag1_id=None,
426             tag2_id=None):
427         """Rewrite tags in ethernet frame.
428
429         :param node: Node to rewrite tags.
430         :param interface: Interface on which rewrite tags.
431         :param tag_rewrite_method: Method of tag rewrite.
432         :param push_dot1q: Optional parameter to disable to push dot1q tag
433             instead of dot1ad.
434         :param tag1_id: Optional tag1 ID for VLAN.
435         :param tag2_id: Optional tag2 ID for VLAN.
436         :type node: dict
437         :type interface: str or int
438         :type tag_rewrite_method: str
439         :type push_dot1q: bool
440         :type tag1_id: int
441         :type tag2_id: int
442         """
443         tag1_id = int(tag1_id) if tag1_id else 0
444         tag2_id = int(tag2_id) if tag2_id else 0
445
446         vtr_oper = getattr(
447             L2VtrOp, f"L2_VTR_{tag_rewrite_method.replace(u'-', u'_').upper()}"
448         )
449
450         if isinstance(interface, str):
451             iface_key = Topology.get_interface_by_name(node, interface)
452             sw_if_index = Topology.get_interface_sw_index(node, iface_key)
453         else:
454             sw_if_index = interface
455
456         cmd = u"l2_interface_vlan_tag_rewrite"
457         args = dict(
458             sw_if_index=sw_if_index,
459             vtr_op=int(vtr_oper),
460             push_dot1q=int(push_dot1q),
461             tag1=tag1_id,
462             tag2=tag2_id
463         )
464         err_msg = f"Failed to set VLAN TAG rewrite on host {node['host']}"
465
466         with PapiSocketExecutor(node) as papi_exec:
467             papi_exec.add(cmd, **args).get_reply(err_msg)
468
469     @staticmethod
470     def get_l2_fib_table(node, bd_id):
471         """Retrieves the L2 FIB table.
472
473         :param node: VPP node.
474         :param bd_id: Index of the bridge domain.
475         :type node: dict
476         :type bd_id: int
477         :returns: L2 FIB table.
478         :rtype: list
479         """
480         cmd = u"l2_fib_table_dump"
481         args = dict(
482             bd_id=int(bd_id)
483         )
484         err_msg = f"Failed to get L2FIB dump on host {node['host']}"
485
486         with PapiSocketExecutor(node) as papi_exec:
487             details = papi_exec.add(cmd, **args).get_details(err_msg)
488
489         for fib_item in details:
490             fib_item[u"mac"] = L2Util.bin_to_mac(fib_item[u"mac"])
491
492         return details
493
494     @staticmethod
495     def get_l2_fib_entry_by_mac(node, bd_index, mac):
496         """Retrieves the L2 FIB entry specified by MAC address using PAPI.
497
498         :param node: VPP node.
499         :param bd_index: Index of the bridge domain.
500         :param mac: MAC address used as the key in L2 FIB data structure.
501         :type node: dict
502         :type bd_index: int
503         :type mac: str
504         :returns: L2 FIB entry
505         :rtype: dict
506         """
507         bd_data = L2Util.vpp_get_bridge_domain_data(node)
508         bd_id = bd_data[bd_index-1][u"bd_id"]
509
510         table = L2Util.get_l2_fib_table(node, bd_id)
511
512         for entry in table:
513             if entry[u"mac"] == mac:
514                 return entry
515         return {}