CSIT-1597 API cleanup: srv6
[csit.git] / resources / libraries / python / IPUtil.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 """Common IP utilities library."""
15 import re
16
17 from enum import IntEnum
18
19 from ipaddress import ip_address
20
21 from resources.libraries.python.Constants import Constants
22 from resources.libraries.python.InterfaceUtil import InterfaceUtil
23 from resources.libraries.python.IPAddress import IPAddress
24 from resources.libraries.python.PapiExecutor import PapiSocketExecutor
25 from resources.libraries.python.ssh import exec_cmd_no_error, exec_cmd
26 from resources.libraries.python.topology import Topology
27 from resources.libraries.python.VatExecutor import VatTerminal
28 from resources.libraries.python.Namespaces import Namespaces
29
30
31 # from vpp/src/vnet/vnet/mpls/mpls_types.h
32 MPLS_IETF_MAX_LABEL = 0xfffff
33 MPLS_LABEL_INVALID = MPLS_IETF_MAX_LABEL + 1
34
35
36 class FibPathType(IntEnum):
37     """FIB path types."""
38     FIB_PATH_TYPE_NORMAL = 0
39     FIB_PATH_TYPE_LOCAL = 1
40     FIB_PATH_TYPE_DROP = 2
41     FIB_PATH_TYPE_UDP_ENCAP = 3
42     FIB_PATH_TYPE_BIER_IMP = 4
43     FIB_PATH_TYPE_ICMP_UNREACH = 5
44     FIB_PATH_TYPE_ICMP_PROHIBIT = 6
45     FIB_PATH_TYPE_SOURCE_LOOKUP = 7
46     FIB_PATH_TYPE_DVR = 8
47     FIB_PATH_TYPE_INTERFACE_RX = 9
48     FIB_PATH_TYPE_CLASSIFY = 10
49
50
51 class FibPathFlags(IntEnum):
52     """FIB path flags."""
53     FIB_PATH_FLAG_NONE = 0
54     # TODO: Name too long for pylint, fix in VPP.
55     FIB_PATH_FLAG_RESOLVE_VIA_ATTACHED = 1
56     FIB_PATH_FLAG_RESOLVE_VIA_HOST = 2
57
58
59 class FibPathNhProto(IntEnum):
60     """FIB path next-hop protocol."""
61     FIB_PATH_NH_PROTO_IP4 = 0
62     FIB_PATH_NH_PROTO_IP6 = 1
63     FIB_PATH_NH_PROTO_MPLS = 2
64     FIB_PATH_NH_PROTO_ETHERNET = 3
65     FIB_PATH_NH_PROTO_BIER = 4
66
67
68 class IPUtil:
69     """Common IP utilities"""
70
71     @staticmethod
72     def ip_to_int(ip_str):
73         """Convert IP address from string format (e.g. 10.0.0.1) to integer
74         representation (167772161).
75
76         :param ip_str: IP address in string representation.
77         :type ip_str: str
78         :returns: Integer representation of IP address.
79         :rtype: int
80         """
81         return int(ip_address(ip_str))
82
83     @staticmethod
84     def int_to_ip(ip_int):
85         """Convert IP address from integer representation (e.g. 167772161) to
86         string format (10.0.0.1).
87
88         :param ip_int: IP address in integer representation.
89         :type ip_int: int
90         :returns: String representation of IP address.
91         :rtype: str
92         """
93         return str(ip_address(ip_int))
94
95     @staticmethod
96     def vpp_get_interface_ip_addresses(node, interface, ip_version):
97         """Get list of IP addresses from an interface on a VPP node.
98
99         :param node: VPP node.
100         :param interface: Name of an interface on the VPP node.
101         :param ip_version: IP protocol version (ipv4 or ipv6).
102         :type node: dict
103         :type interface: str
104         :type ip_version: str
105         :returns: List of dictionaries, each containing IP address, subnet
106             prefix length and also the subnet mask for ipv4 addresses.
107             Note: A single interface may have multiple IP addresses assigned.
108         :rtype: list
109         """
110         sw_if_index = InterfaceUtil.get_interface_index(node, interface)
111
112         if not sw_if_index:
113             return list()
114
115         cmd = u"ip_address_dump"
116         args = dict(
117             sw_if_index=sw_if_index,
118             is_ipv6=bool(ip_version == u"ipv6")
119         )
120         err_msg = f"Failed to get L2FIB dump on host {node[u'host']}"
121
122         with PapiSocketExecutor(node) as papi_exec:
123             details = papi_exec.add(cmd, **args).get_details(err_msg)
124
125         # TODO: CSIT currently looks only whether the list is empty.
126         # Add proper value processing if values become important.
127
128         return details
129
130     @staticmethod
131     def vpp_get_ip_tables(node):
132         """Get dump of all IP FIB tables on a VPP node.
133
134         :param node: VPP node.
135         :type node: dict
136         """
137         PapiSocketExecutor.run_cli_cmd(node, u"show ip fib")
138         PapiSocketExecutor.run_cli_cmd(node, u"show ip fib summary")
139         PapiSocketExecutor.run_cli_cmd(node, u"show ip6 fib")
140         PapiSocketExecutor.run_cli_cmd(node, u"show ip6 fib summary")
141
142     @staticmethod
143     def vpp_get_ip_tables_prefix(node, address):
144         """Get dump of all IP FIB tables on a VPP node.
145
146         :param node: VPP node.
147         :param address: IP address.
148         :type node: dict
149         :type address: str
150         """
151         addr = ip_address(address)
152         ip_ver = u"ip6" if addr.version == 6 else u"ip"
153
154         PapiSocketExecutor.run_cli_cmd(
155             node, f"show {ip_ver} fib {addr}/{addr.max_prefixlen}"
156         )
157
158     @staticmethod
159     def get_interface_vrf_table(node, interface, ip_version='ipv4'):
160         """Get vrf ID for the given interface.
161
162         :param node: VPP node.
163         :param interface: Name or sw_if_index of a specific interface.
164         :type node: dict
165         :param ip_version: IP protocol version (ipv4 or ipv6).
166         :type interface: str or int
167         :type ip_version: str
168         :returns: vrf ID of the specified interface.
169         :rtype: int
170         """
171         sw_if_index = InterfaceUtil.get_interface_index(node, interface)
172
173         cmd = u"sw_interface_get_table"
174         args = dict(
175             sw_if_index=sw_if_index,
176             is_ipv6=bool(ip_version == u"ipv6")
177         )
178         err_msg = f"Failed to get VRF id assigned to interface {interface}"
179
180         with PapiSocketExecutor(node) as papi_exec:
181             reply = papi_exec.add(cmd, **args).get_reply(err_msg)
182
183         return reply[u"vrf_id"]
184
185     @staticmethod
186     def vpp_ip_source_check_setup(node, if_name):
187         """Setup Reverse Path Forwarding source check on interface.
188
189         :param node: VPP node.
190         :param if_name: Interface name to setup RPF source check.
191         :type node: dict
192         :type if_name: str
193         """
194         cmd = u"ip_source_check_interface_add_del"
195         args = dict(
196             sw_if_index=InterfaceUtil.get_interface_index(node, if_name),
197             is_add=1,
198             loose=0
199         )
200         err_msg = f"Failed to enable source check on interface {if_name}"
201         with PapiSocketExecutor(node) as papi_exec:
202             papi_exec.add(cmd, **args).get_reply(err_msg)
203
204     @staticmethod
205     def vpp_ip_probe(node, interface, addr):
206         """Run ip probe on VPP node.
207
208         :param node: VPP node.
209         :param interface: Interface key or name.
210         :param addr: IPv4/IPv6 address.
211         :type node: dict
212         :type interface: str
213         :type addr: str
214         """
215         cmd = u"ip_probe_neighbor"
216         args = dict(
217             sw_if_index=InterfaceUtil.get_interface_index(node, interface),
218             dst=str(addr)
219         )
220         err_msg = f"VPP ip probe {interface} {addr} failed on {node[u'host']}"
221
222         with PapiSocketExecutor(node) as papi_exec:
223             papi_exec.add(cmd, **args).get_reply(err_msg)
224
225     @staticmethod
226     def ip_addresses_should_be_equal(ip1, ip2):
227         """Fails if the given IP addresses are unequal.
228
229         :param ip1: IPv4 or IPv6 address.
230         :param ip2: IPv4 or IPv6 address.
231         :type ip1: str
232         :type ip2: str
233         """
234         addr1 = ip_address(ip1)
235         addr2 = ip_address(ip2)
236
237         if addr1 != addr2:
238             raise AssertionError(f"IP addresses are not equal: {ip1} != {ip2}")
239
240     @staticmethod
241     def setup_network_namespace(node, namespace_name, interface_name,
242                                 ip_addr_list, prefix_length):
243         """Setup namespace on given node and attach interface and IP to
244         this namespace. Applicable also on TG node.
245
246         :param node: VPP node.
247         :param namespace_name: Namespace name.
248         :param interface_name: Interface name.
249         :param ip_addr_list: List of IP addresses of namespace's interface.
250         :param prefix_length: IP address prefix length.
251         :type node: dict
252         :type namespace_name: str
253         :type interface_name: str
254         :type ip_addr_list: list
255         :type prefix_length: int
256         """
257         Namespaces.create_namespace(node, namespace_name)
258
259         cmd = f"ip netns exec {namespace_name} ip link set {interface_name} up"
260         exec_cmd_no_error(node, cmd, sudo=True)
261
262         for ip_addr in ip_addr_list:
263             cmd = f"ip netns exec {namespace_name} ip addr add " \
264                 f"{ip_addr}/{prefix_length} dev {interface_name}"
265             exec_cmd_no_error(node, cmd, sudo=True)
266
267     @staticmethod
268     def linux_enable_forwarding(node, ip_ver=u"ipv4"):
269         """Enable forwarding on a Linux node, e.g. VM.
270
271         :param node: VPP node.
272         :param ip_ver: IP version, 'ipv4' or 'ipv6'.
273         :type node: dict
274         :type ip_ver: str
275         """
276         cmd = f"sysctl -w net.{ip_ver}.ip_forward=1"
277         exec_cmd_no_error(node, cmd, sudo=True)
278
279     @staticmethod
280     def get_linux_interface_name(node, pci_addr):
281         """Get the interface name.
282
283         :param node: VPP/TG node.
284         :param pci_addr: PCI address
285         :type node: dict
286         :type pci_addr: str
287         :returns: Interface name
288         :rtype: str
289         :raises RuntimeError: If cannot get the information about interfaces.
290         """
291         regex_intf_info = \
292             r"pci@([0-9a-f]{4}:[0-9a-f]{2}:[0-9a-f]{2}.[0-9a-f])\s" \
293             r"*([a-zA-Z0-9]*)\s*network"
294
295         cmd = u"lshw -class network -businfo"
296         ret_code, stdout, stderr = exec_cmd(node, cmd, timeout=30, sudo=True)
297         if ret_code != 0:
298             raise RuntimeError(
299                 f"Could not get information about interfaces:\n{stderr}"
300             )
301
302         for line in stdout.splitlines()[2:]:
303             try:
304                 if re.search(regex_intf_info, line).group(1) == pci_addr:
305                     return re.search(regex_intf_info, line).group(2)
306             except AttributeError:
307                 continue
308         return None
309
310     @staticmethod
311     def set_linux_interface_up(node, interface):
312         """Set the specified interface up.
313
314         :param node: VPP/TG node.
315         :param interface: Interface in namespace.
316         :type node: dict
317         :type interface: str
318         :raises RuntimeError: If the interface could not be set up.
319         """
320         cmd = f"ip link set {interface} up"
321         exec_cmd_no_error(node, cmd, timeout=30, sudo=True)
322
323     @staticmethod
324     def set_linux_interface_ip(
325             node, interface, ip_addr, prefix, namespace=None):
326         """Set IP address to interface in linux.
327
328         :param node: VPP/TG node.
329         :param interface: Interface in namespace.
330         :param ip_addr: IP to be set on interface.
331         :param prefix: IP prefix.
332         :param namespace: Execute command in namespace. Optional
333         :type node: dict
334         :type interface: str
335         :type ip_addr: str
336         :type prefix: int
337         :type namespace: str
338         :raises RuntimeError: IP could not be set.
339         """
340         if namespace is not None:
341             cmd = f"ip netns exec {namespace} ip addr add {ip_addr}/{prefix}" \
342                 f" dev {interface}"
343         else:
344             cmd = f"ip addr add {ip_addr}/{prefix} dev {interface}"
345
346         exec_cmd_no_error(node, cmd, timeout=5, sudo=True)
347
348     @staticmethod
349     def delete_linux_interface_ip(
350             node, interface, ip_addr, prefix_length, namespace=None):
351         """Delete IP address from interface in linux.
352
353         :param node: VPP/TG node.
354         :param interface: Interface in namespace.
355         :param ip_addr: IP to be deleted from interface.
356         :param prefix_length: IP prefix length.
357         :param namespace: Execute command in namespace. Optional
358         :type node: dict
359         :type interface: str
360         :type ip_addr: str
361         :type prefix_length: int
362         :type namespace: str
363         :raises RuntimeError: IP could not be deleted.
364         """
365         # TODO: Refactor command execution in namespaces into central
366         # methods (e.g. Namespace.exec_cmd_in_namespace)
367         if namespace is not None:
368             cmd = f"ip netns exec {namespace} ip addr del " \
369                 f"{ip_addr}/{prefix_length} dev {interface}"
370         else:
371             cmd = f"ip addr del {ip_addr}/{prefix_length} dev {interface}"
372
373         exec_cmd_no_error(node, cmd, timeout=5, sudo=True)
374
375     @staticmethod
376     def linux_interface_has_ip(
377             node, interface, ip_addr, prefix_length, namespace=None):
378         """Return True if interface in linux has IP address.
379
380         :param node: VPP/TG node.
381         :param interface: Interface in namespace.
382         :param ip_addr: IP to be queried on interface.
383         :param prefix_length: IP prefix length.
384         :param namespace: Execute command in namespace. Optional
385         :type node: dict
386         :type interface: str
387         :type ip_addr: str
388         :type prefix_length: int
389         :type namespace: str
390         :rtype boolean
391         :raises RuntimeError: Request fails.
392         """
393         ip_addr_with_prefix = f"{ip_addr}/{prefix_length}"
394         if namespace is not None:
395             cmd = f"ip netns exec {namespace} ip addr show dev {interface}"
396         else:
397             cmd = f"ip addr show dev {interface}"
398
399         cmd += u" | grep 'inet ' | awk -e '{print $2}'"
400         cmd += f" | grep '{ip_addr_with_prefix}'"
401         _, stdout, _ = exec_cmd(node, cmd, timeout=5, sudo=True)
402
403         has_ip = stdout.rstrip()
404         return bool(has_ip == ip_addr_with_prefix)
405
406     @staticmethod
407     def add_linux_route(node, ip_addr, prefix, gateway, namespace=None):
408         """Add linux route in namespace.
409
410         :param node: Node where to execute command.
411         :param ip_addr: Route destination IP address.
412         :param prefix: IP prefix.
413         :param namespace: Execute command in namespace. Optional.
414         :param gateway: Gateway address.
415         :type node: dict
416         :type ip_addr: str
417         :type prefix: int
418         :type gateway: str
419         :type namespace: str
420         """
421         if namespace is not None:
422             cmd = f"ip netns exec {namespace} ip route add {ip_addr}/{prefix}" \
423                 f" via {gateway}"
424         else:
425             cmd = f"ip route add {ip_addr}/{prefix} via {gateway}"
426
427         exec_cmd_no_error(node, cmd, sudo=True)
428
429     @staticmethod
430     def vpp_interface_set_ip_address(
431             node, interface, address, prefix_length=None):
432         """Set IP address to VPP interface.
433
434         :param node: VPP node.
435         :param interface: Interface name.
436         :param address: IP address.
437         :param prefix_length: Prefix length.
438         :type node: dict
439         :type interface: str
440         :type address: str
441         :type prefix_length: int
442         """
443         ip_addr = ip_address(address)
444
445         cmd = u"sw_interface_add_del_address"
446         args = dict(
447             sw_if_index=InterfaceUtil.get_interface_index(node, interface),
448             is_add=True,
449             del_all=False,
450             prefix=IPUtil.create_prefix_object(
451                 ip_addr,
452                 prefix_length if prefix_length else 128
453                 if ip_addr.version == 6 else 32
454             )
455         )
456         err_msg = f"Failed to add IP address on interface {interface}"
457
458         with PapiSocketExecutor(node) as papi_exec:
459             papi_exec.add(cmd, **args).get_reply(err_msg)
460
461     @staticmethod
462     def vpp_interface_set_ip_addresses(node, interface, ip_addr_list,
463                                        prefix_length=None):
464         """Set IP addresses to VPP interface.
465
466         :param node: VPP node.
467         :param interface: Interface name.
468         :param ip_addr_list: IP addresses.
469         :param prefix_length: Prefix length.
470         :type node: dict
471         :type interface: str
472         :type ip_addr_list: list
473         :type prefix_length: int
474         """
475         for ip_addr in ip_addr_list:
476             IPUtil.vpp_interface_set_ip_address(node, interface, ip_addr,
477                                                 prefix_length)
478
479     @staticmethod
480     def vpp_add_ip_neighbor(node, iface_key, ip_addr, mac_address):
481         """Add IP neighbor on DUT node.
482
483         :param node: VPP node.
484         :param iface_key: Interface key.
485         :param ip_addr: IP address of the interface.
486         :param mac_address: MAC address of the interface.
487         :type node: dict
488         :type iface_key: str
489         :type ip_addr: str
490         :type mac_address: str
491         """
492         dst_ip = ip_address(ip_addr)
493
494         neighbor = dict(
495             sw_if_index=Topology.get_interface_sw_index(node, iface_key),
496             flags=0,
497             mac_address=str(mac_address),
498             ip_address=str(dst_ip)
499         )
500         cmd = u"ip_neighbor_add_del"
501         args = dict(
502             is_add=True,
503             neighbor=neighbor
504         )
505         err_msg = f"Failed to add IP neighbor on interface {iface_key}"
506
507         with PapiSocketExecutor(node) as papi_exec:
508             papi_exec.add(cmd, **args).get_reply(err_msg)
509
510     @staticmethod
511     def create_prefix_object(ip_addr, addr_len):
512         """Create prefix object.
513
514         :param ip_addr: IPv4 or IPv6 address.
515         :param addr_len: Length of IP address.
516         :type ip_addr: IPv4Address or IPv6Address
517         :type addr_len: int
518         :returns: Prefix object.
519         :rtype: dict
520         """
521         addr = IPAddress.create_ip_address_object(ip_addr)
522
523         return dict(
524             len=int(addr_len),
525             address=addr
526         )
527
528     @staticmethod
529     def compose_vpp_route_structure(node, network, prefix_len, **kwargs):
530         """Create route object for ip_route_add_del api call.
531
532         :param node: VPP node.
533         :param network: Route destination network address.
534         :param prefix_len: Route destination network prefix length.
535         :param kwargs: Optional key-value arguments:
536
537             gateway: Route gateway address. (str)
538             interface: Route interface. (str)
539             vrf: VRF table ID. (int)
540             count: number of IP addresses to add starting from network IP (int)
541             local: The route is local with same prefix (increment is 1).
542                 If None, then is not used. (bool)
543             lookup_vrf: VRF table ID for lookup. (int)
544             multipath: Enable multipath routing. (bool)
545             weight: Weight value for unequal cost multipath routing. (int)
546
547         :type node: dict
548         :type network: str
549         :type prefix_len: int
550         :type kwargs: dict
551         :returns: route parameter basic structure
552         :rtype: dict
553         """
554         interface = kwargs.get(u"interface", u"")
555         gateway = kwargs.get(u"gateway", u"")
556
557         net_addr = ip_address(network)
558
559         prefix = IPUtil.create_prefix_object(net_addr, prefix_len)
560
561         paths = list()
562         n_hop = dict(
563             address=IPAddress.union_addr(ip_address(gateway)) if gateway else 0,
564             via_label=MPLS_LABEL_INVALID,
565             obj_id=Constants.BITWISE_NON_ZERO
566         )
567         path = dict(
568             sw_if_index=InterfaceUtil.get_interface_index(node, interface)
569             if interface else Constants.BITWISE_NON_ZERO,
570             table_id=int(kwargs.get(u"lookup_vrf", 0)),
571             rpf_id=Constants.BITWISE_NON_ZERO,
572             weight=int(kwargs.get(u"weight", 1)),
573             preference=1,
574             type=getattr(
575                 FibPathType, u"FIB_PATH_TYPE_LOCAL"
576                 if kwargs.get(u"local", False)
577                 else u"FIB_PATH_TYPE_NORMAL"
578             ).value,
579             flags=getattr(FibPathFlags, u"FIB_PATH_FLAG_NONE").value,
580             proto=getattr(
581                 FibPathNhProto, u"FIB_PATH_NH_PROTO_IP6"
582                 if net_addr.version == 6
583                 else u"FIB_PATH_NH_PROTO_IP4"
584             ).value,
585             nh=n_hop,
586             n_labels=0,
587             label_stack=list(0 for _ in range(16))
588         )
589         paths.append(path)
590
591         route = dict(
592             table_id=int(kwargs.get(u"vrf", 0)),
593             prefix=prefix,
594             n_paths=len(paths),
595             paths=paths
596         )
597         return route
598
599     @staticmethod
600     def vpp_route_add(node, network, prefix_len, **kwargs):
601         """Add route to the VPP node.
602
603         :param node: VPP node.
604         :param network: Route destination network address.
605         :param prefix_len: Route destination network prefix length.
606         :param kwargs: Optional key-value arguments:
607
608             gateway: Route gateway address. (str)
609             interface: Route interface. (str)
610             vrf: VRF table ID. (int)
611             count: number of IP addresses to add starting from network IP (int)
612             local: The route is local with same prefix (increment is 1).
613                 If None, then is not used. (bool)
614             lookup_vrf: VRF table ID for lookup. (int)
615             multipath: Enable multipath routing. (bool)
616             weight: Weight value for unequal cost multipath routing. (int)
617
618         :type node: dict
619         :type network: str
620         :type prefix_len: int
621         :type kwargs: dict
622         """
623         count = kwargs.get(u"count", 1)
624
625         if count > 100:
626             gateway = kwargs.get(u"gateway", '')
627             interface = kwargs.get(u"interface", '')
628             vrf = kwargs.get(u"vrf", None)
629             multipath = kwargs.get(u"multipath", False)
630
631             with VatTerminal(node, json_param=False) as vat:
632
633                 vat.vat_terminal_exec_cmd_from_template(
634                     u"vpp_route_add.vat",
635                     network=network,
636                     prefix_length=prefix_len,
637                     via=f"via {gateway}" if gateway else u"",
638                     sw_if_index=f"sw_if_index "
639                     f"{InterfaceUtil.get_interface_index(node, interface)}"
640                     if interface else u"",
641                     vrf=f"vrf {vrf}" if vrf else u"",
642                     count=f"count {count}" if count else u"",
643                     multipath=u"multipath" if multipath else u""
644                 )
645             return
646
647         net_addr = ip_address(network)
648         cmd = u"ip_route_add_del"
649         args = dict(
650             is_add=True,
651             is_multipath=kwargs.get(u"multipath", False),
652             route=None
653         )
654         err_msg = f"Failed to add route(s) on host {node[u'host']}"
655
656         with PapiSocketExecutor(node) as papi_exec:
657             for i in range(kwargs.get(u"count", 1)):
658                 args[u"route"] = IPUtil.compose_vpp_route_structure(
659                     node, net_addr + i, prefix_len, **kwargs
660                 )
661                 history = bool(not 1 < i < kwargs.get(u"count", 1))
662                 papi_exec.add(cmd, history=history, **args)
663             papi_exec.get_replies(err_msg)
664
665     @staticmethod
666     def flush_ip_addresses(node, interface):
667         """Flush all IP addresses from specified interface.
668
669         :param node: VPP node.
670         :param interface: Interface name.
671         :type node: dict
672         :type interface: str
673         """
674         cmd = u"sw_interface_add_del_address"
675         args = dict(
676             sw_if_index=InterfaceUtil.get_interface_index(node, interface),
677             is_add=False,
678             del_all=True
679         )
680         err_msg = f"Failed to flush IP address on interface {interface}"
681
682         with PapiSocketExecutor(node) as papi_exec:
683             papi_exec.add(cmd, **args).get_reply(err_msg)
684
685     @staticmethod
686     def add_fib_table(node, table_id, ipv6=False):
687         """Create new FIB table according to ID.
688
689         :param node: Node to add FIB on.
690         :param table_id: FIB table ID.
691         :param ipv6: Is this an IPv6 table
692         :type node: dict
693         :type table_id: int
694         :type ipv6: bool
695         """
696         cmd = u"ip_table_add_del"
697         table = dict(
698             table_id=int(table_id),
699             is_ip6=ipv6
700         )
701         args = dict(
702             table=table,
703             is_add=True
704         )
705         err_msg = f"Failed to add FIB table on host {node[u'host']}"
706
707         with PapiSocketExecutor(node) as papi_exec:
708             papi_exec.add(cmd, **args).get_reply(err_msg)