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