JumpAvg: Fix string format
[csit.git] / resources / libraries / python / Tap.py
1 # Copyright (c) 2021 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 """Tap utilities library."""
15
16 from enum import IntEnum
17
18 from robot.api import logger
19
20 from resources.libraries.python.Constants import Constants
21 from resources.libraries.python.InterfaceUtil import InterfaceUtil
22 from resources.libraries.python.L2Util import L2Util
23 from resources.libraries.python.PapiExecutor import PapiSocketExecutor
24 from resources.libraries.python.topology import Topology
25
26
27 class TapFeaturesFlags(IntEnum):
28     """TAP Features Flags."""
29     TAP_API_FLAG_GSO = 1
30     TAP_API_FLAG_CSUM_OFFLOAD = 2
31     TAP_API_FLAG_PERSIST = 4
32     TAP_API_FLAG_ATTACH = 8
33     TAP_API_FLAG_TUN = 16
34     TAP_API_FLAG_GRO_COALESCE = 32
35     TAP_API_FLAG_PACKED = 64
36     TAP_API_FLAG_IN_ORDER = 128
37
38
39 class Tap:
40     """Tap utilities."""
41
42     @staticmethod
43     def add_tap_interface(
44             node, tap_name, mac=None, host_namespace=None, num_rx_queues=1,
45             rxq_size=0, txq_size=0, tap_feature_mask=0):
46         """Add tap interface with name and optionally with MAC.
47
48         :param node: Node to add tap on.
49         :param tap_name: Tap interface name for linux tap.
50         :param mac: Optional MAC address for VPP tap.
51         :param host_namespace: Namespace.
52         :param num_rx_queues: Number of RX queues.
53         :param rxq_size: Size of RXQ (0 = Default API; 256 = Default VPP).
54         :param txq_size: Size of TXQ (0 = Default API; 256 = Default VPP).
55         :param tap_feature_mask: Mask of tap features to be enabled.
56         :type node: dict
57         :type tap_name: str
58         :type mac: str
59         :type host_namespace: str
60         :type num_rx_queues: int
61         :type rxq_size: int
62         :type txq_size: int
63         :type tap_feature_mask: int
64         :returns: Returns a interface index.
65         :rtype: int
66         """
67         cmd = u"tap_create_v2"
68         args = dict(
69             id=Constants.BITWISE_NON_ZERO,
70             use_random_mac=bool(mac is None),
71             mac_address=L2Util.mac_to_bin(mac) if mac else None,
72             num_rx_queues=int(num_rx_queues),
73             tx_ring_sz=int(txq_size),
74             rx_ring_sz=int(rxq_size),
75             host_mtu_set=False,
76             host_mac_addr_set=False,
77             host_ip4_prefix_set=False,
78             host_ip6_prefix_set=False,
79             host_ip4_gw_set=False,
80             host_ip6_gw_set=False,
81             host_namespace_set=bool(host_namespace),
82             host_namespace=host_namespace,
83             host_if_name_set=True,
84             host_if_name=tap_name,
85             host_bridge_set=False,
86             tap_flags=tap_feature_mask
87         )
88         err_msg = f"Failed to create tap interface {tap_name} " \
89             f"on host {node[u'host']}"
90
91         with PapiSocketExecutor(node) as papi_exec:
92             sw_if_index = papi_exec.add(cmd, **args).get_sw_if_index(err_msg)
93
94         if_key = Topology.add_new_port(node, u"tap")
95         Topology.update_interface_sw_if_index(node, if_key, sw_if_index)
96         Topology.update_interface_name(node, if_key, tap_name)
97         if mac is None:
98             mac = Tap.vpp_get_tap_interface_mac(node, tap_name)
99         Topology.update_interface_mac_address(node, if_key, mac)
100         tap_dev_name = Tap.vpp_get_tap_dev_name(node, tap_name)
101         Topology.update_interface_tap_dev_name(node, if_key, tap_dev_name)
102
103         return sw_if_index
104
105     @staticmethod
106     def vpp_get_tap_dev_name(node, host_if_name):
107         """Get VPP tap interface name from hardware interfaces dump.
108
109         :param node: DUT node.
110         :param host_if_name: Tap host interface name.
111         :type node: dict
112         :type host_if_name: str
113         :returns: VPP tap interface dev_name.
114         :rtype: str
115         """
116         return Tap.tap_dump(node, host_if_name).get(u"dev_name")
117
118     @staticmethod
119     def vpp_get_tap_interface_mac(node, interface_name):
120         """Get tap interface MAC address from interfaces dump.
121
122         :param node: DUT node.
123         :param interface_name: Tap interface name.
124         :type node: dict
125         :type interface_name: str
126         :returns: Tap interface MAC address.
127         :rtype: str
128         """
129         return InterfaceUtil.vpp_get_interface_mac(node, interface_name)
130
131     @staticmethod
132     def tap_dump(node, name=None):
133         """Get all TAP interface data from the given node, or data about
134         a specific TAP interface.
135
136         :param node: VPP node to get data from.
137         :param name: Optional name of a specific TAP interface.
138         :type node: dict
139         :type name: str
140         :returns: Dictionary of information about a specific TAP interface, or
141             a List of dictionaries containing all TAP data for the given node.
142         :rtype: dict or list
143         """
144         def process_tap_dump(tap_dump):
145             """Process tap dump.
146
147             :param tap_dump: Tap interface dump.
148             :type tap_dump: dict
149             :returns: Processed tap interface dump.
150             :rtype: dict
151             """
152             tap_dump[u"host_mac_addr"] = str(tap_dump[u"host_mac_addr"])
153             tap_dump[u"host_ip4_prefix"] = str(tap_dump[u"host_ip4_prefix"])
154             tap_dump[u"host_ip6_prefix"] = str(tap_dump[u"host_ip6_prefix"])
155             tap_dump[u"tap_flags"] = tap_dump[u"tap_flags"].value \
156                 if hasattr(tap_dump[u"tap_flags"], u"value") \
157                 else int(tap_dump[u"tap_flags"])
158             tap_dump[u"host_namespace"] = None \
159                 if tap_dump[u"host_namespace"] == u"(nil)" \
160                 else tap_dump[u"host_namespace"]
161             tap_dump[u"host_bridge"] = None \
162                 if tap_dump[u"host_bridge"] == u"(nil)" \
163                 else tap_dump[u"host_bridge"]
164
165             return tap_dump
166
167         cmd = u"sw_interface_tap_v2_dump"
168         err_msg = f"Failed to get TAP dump on host {node[u'host']}"
169
170         with PapiSocketExecutor(node) as papi_exec:
171             details = papi_exec.add(cmd).get_details(err_msg)
172
173         data = list() if name is None else dict()
174         for dump in details:
175             if name is None:
176                 data.append(process_tap_dump(dump))
177             elif dump.get(u"host_if_name") == name:
178                 data = process_tap_dump(dump)
179                 break
180
181         logger.debug(f"TAP data:\n{data}")
182         return data
183
184
185 class TapFeatureMask:
186     """Tap features utilities"""
187
188     @staticmethod
189     def create_tap_feature_mask(**kwargs):
190         """Create tap feature mask with feature bits set according to kwargs.
191         :param kwargs: Key-value pairs of feature names and it's state
192         :type kwargs: dict
193         """
194         tap_feature_mask = 0
195
196         if u"all" in kwargs and kwargs[u"all"] is True:
197             for tap_feature_flag in TapFeaturesFlags:
198                 tap_feature_mask |= 1 << (tap_feature_flag.value - 1)
199         else:
200             for feature_name, enabled in kwargs.items():
201                 tap_feature_name = u"TAP_API_FLAG_" + feature_name.upper()
202                 if tap_feature_name not in TapFeaturesFlags.__members__:
203                     raise ValueError(u"Unsupported tap feature flag name")
204                 if enabled:
205                     tap_feature_mask |= \
206                         1 << (TapFeaturesFlags[tap_feature_name].value - 1)
207
208         return tap_feature_mask
209
210     @staticmethod
211     def is_feature_enabled(tap_feature_mask, tap_feature_flag):
212         """Checks if concrete tap feature is enabled within
213         tap_feature_mask
214         :param tap_feature_mask: Mask of enabled tap features
215         :param tap_feature_flag: Checked tap feature
216         :type tap_feature_mask: int
217         :type tap_feature_flag: TapFeaturesFlags
218         """
219         feature_flag_bit = 1 << tap_feature_flag.value
220         return (tap_feature_mask & feature_flag_bit) > 0