X-Git-Url: https://gerrit.fd.io/r/gitweb?p=csit.git;a=blobdiff_plain;f=resources%2Flibraries%2Fpython%2FNATUtil.py;h=60e0e6d1a3f7fdbab1abef3eba2abf2f61a7b6f0;hp=aabcd36cdae92457bf2f5d52096c0b25a0dad426;hb=7829fea4a2c8936513fa95215b7d84997f814a69;hpb=d99951620507d354c4803eb1ee26609d992b70b3 diff --git a/resources/libraries/python/NATUtil.py b/resources/libraries/python/NATUtil.py index aabcd36cda..60e0e6d1a3 100644 --- a/resources/libraries/python/NATUtil.py +++ b/resources/libraries/python/NATUtil.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020 Cisco and/or its affiliates. +# Copyright (c) 2021 Cisco and/or its affiliates. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at: @@ -13,6 +13,7 @@ """NAT utilities library.""" +from math import log2, modf from pprint import pformat from enum import IntEnum @@ -26,7 +27,7 @@ from resources.libraries.python.PapiExecutor import PapiSocketExecutor class NatConfigFlags(IntEnum): - """Common NAT plugin APIs""" + """NAT plugin configuration flags""" NAT_IS_NONE = 0x00 NAT_IS_TWICE_NAT = 0x01 NAT_IS_SELF_TWICE_NAT = 0x02 @@ -38,6 +39,15 @@ class NatConfigFlags(IntEnum): NAT_IS_EXT_HOST_VALID = 0x80 +class Nat44ConfigFlags(IntEnum): + """NAT44 configuration flags""" + NAT44_IS_ENDPOINT_INDEPENDENT = 0x00 + NAT44_IS_ENDPOINT_DEPENDENT = 0x01 + NAT44_IS_STATIC_MAPPING_ONLY = 0x02 + NAT44_IS_CONNECTION_TRACKING = 0x04 + NAT44_IS_OUT2IN_DPO = 0x08 + + class NatAddrPortAllocAlg(IntEnum): """NAT Address and port assignment algorithms.""" NAT_ALLOC_ALG_DEFAULT = 0 @@ -51,6 +61,60 @@ class NATUtil: def __init__(self): pass + @staticmethod + def enable_nat44_plugin( + node, inside_vrf=0, outside_vrf=0, users=0, user_memory=0, + sessions=0, session_memory=0, user_sessions=0, mode=u""): + """Enable NAT44 plugin. + + :param node: DUT node. + :param inside_vrf: Inside VRF ID. + :param outside_vrf: Outside VRF ID. + :param users: Maximum number of users. Used only in endpoint-independent + mode. + :param user_memory: User memory size - overwrite auto calculated hash + allocation parameter if non-zero. + :param sessions: Maximum number of sessions. + :param session_memory: Session memory size - overwrite auto calculated + hash allocation parameter if non-zero. + :param user_sessions: Maximum number of sessions per user. Used only in + endpoint-independent mode. + :param mode: NAT44 mode. Valid values: + - endpoint-independent + - endpoint-dependent + - static-mapping-only + - connection-tracking + - out2in-dpo + :type node: dict + :type inside_vrf: str or int + :type outside_vrf: str or int + :type users: str or int + :type user_memory: str or int + :type sessions: str or int + :type session_memory: str or int + :type user_sessions: str or int + :type mode: str + """ + cmd = u"nat44_plugin_enable_disable" + err_msg = f"Failed to enable NAT44 plugin on the host {node[u'host']}!" + args_in = dict( + enable=True, + inside_vrf=int(inside_vrf), + outside_vrf=int(outside_vrf), + users=int(users), + user_memory=int(user_memory), + sessions=int(sessions), + session_memory=int(session_memory), + user_sessions=int(user_sessions), + flags=getattr( + Nat44ConfigFlags, + f"NAT44_IS_{mode.replace(u'-', u'_').upper()}" + ).value + ) + + with PapiSocketExecutor(node) as papi_exec: + papi_exec.add(cmd, **args_in).get_reply(err_msg) + @staticmethod def set_nat44_interface(node, interface, flag): """Set inside and outside interfaces for NAT44. @@ -95,6 +159,10 @@ class NATUtil: flag=u"NAT_IS_NONE"): """Set NAT44 address range. + The return value is a callable (zero argument Python function) + which can be used to reset NAT state, so repeated trial measurements + hit the same slow path. + :param node: DUT node. :param start_ip: IP range start. :param end_ip: IP range end. @@ -105,6 +173,8 @@ class NATUtil: :type end_ip: str :type vrf_id: int :type flag: str + :returns: Resetter of the NAT state. + :rtype: Callable[[], None] """ cmd = u"nat44_add_del_address_range" err_msg = f"Failed to set NAT44 address range on host {node[u'host']}" @@ -119,20 +189,38 @@ class NATUtil: with PapiSocketExecutor(node) as papi_exec: papi_exec.add(cmd, **args_in).get_reply(err_msg) + # A closure, accessing the variables above. + def resetter(): + """Delete and re-add the NAT range setting.""" + with PapiSocketExecutor(node) as papi_exec: + args_in[u"is_add"] = False + papi_exec.add(cmd, **args_in) + args_in[u"is_add"] = True + papi_exec.add(cmd, **args_in) + papi_exec.get_replies(err_msg) + + return resetter + @staticmethod - def show_nat_config(node): - """Show the NAT configuration. + def show_nat44_config(node): + """Show the NAT44 plugin running configuration. :param node: DUT node. :type node: dict """ - cmd = u"nat_show_config" - err_msg = f"Failed to get NAT configuration on host {node[u'host']}" + cmd = u"nat44_show_running_config" + err_msg = f"Failed to get NAT44 configuration on host {node[u'host']}" - with PapiSocketExecutor(node) as papi_exec: - reply = papi_exec.add(cmd).get_reply(err_msg) + try: + with PapiSocketExecutor(node) as papi_exec: + reply = papi_exec.add(cmd).get_reply(err_msg) + except AssertionError: + # Perhaps VPP is an older version + old_cmd = u"nat_show_config" + with PapiSocketExecutor(node) as papi_exec: + reply = papi_exec.add(old_cmd).get_reply(err_msg) - logger.debug(f"NAT Configuration:\n{pformat(reply)}") + logger.debug(f"NAT44 Configuration:\n{pformat(reply)}") @staticmethod def show_nat44_summary(node): @@ -140,8 +228,10 @@ class NATUtil: :param node: Topology node. :type node: dict + :returns: NAT44 summary data. + :rtype: str """ - PapiSocketExecutor.run_cli_cmd(node, u"show nat44 summary") + return PapiSocketExecutor.run_cli_cmd(node, u"show nat44 summary") @staticmethod def show_nat_base_data(node): @@ -197,10 +287,36 @@ class NATUtil: :returns: Value of max_translations_per_thread NAT44 parameter. :rtype: int """ - from math import log2, modf + # vpp-device tests have not dedicated physical core so + # ${thr_count_int} == 0 but we need to use one thread + threads = 1 if not int(threads) else int(threads) rest, mult = modf(log2(sessions/(10*threads))) return 2 ** (int(mult) + (1 if rest else 0)) * 10 + @staticmethod + def get_nat44_sessions_number(node, proto): + """Get number of established NAT44 sessions from actual NAT44 mapping + data. + + :param node: DUT node. + :param proto: Required protocol - TCP/UDP/ICMP. + :type node: dict + :type proto: str + :returns: Number of established NAT44 sessions. + :rtype: int + :raises ValueError: If not supported protocol. + """ + nat44_data = dict() + if proto in [u"UDP", u"TCP", u"ICMP"]: + for line in NATUtil.show_nat44_summary(node).splitlines(): + sum_k, sum_v = line.split(u":") if u":" in line \ + else (line, None) + nat44_data[sum_k] = sum_v.strip() if isinstance(sum_v, str) \ + else sum_v + else: + raise ValueError(f"Unsupported protocol: {proto}!") + return nat44_data.get(f"total {proto.lower()} sessions", 0) + # DET44 PAPI calls # DET44 means deterministic mode of NAT44 @staticmethod @@ -253,6 +369,10 @@ class NATUtil: def set_det44_mapping(node, ip_in, subnet_in, ip_out, subnet_out): """Set DET44 mapping. + The return value is a callable (zero argument Python function) + which can be used to reset NAT state, so repeated trial measurements + hit the same slow path. + :param node: DUT node. :param ip_in: Inside IP. :param subnet_in: Inside IP subnet. @@ -277,6 +397,18 @@ class NATUtil: with PapiSocketExecutor(node) as papi_exec: papi_exec.add(cmd, **args_in).get_reply(err_msg) + # A closure, accessing the variables above. + def resetter(): + """Delete and re-add the deterministic NAT mapping.""" + with PapiSocketExecutor(node) as papi_exec: + args_in[u"is_add"] = False + papi_exec.add(cmd, **args_in) + args_in[u"is_add"] = True + papi_exec.add(cmd, **args_in) + papi_exec.get_replies(err_msg) + + return resetter + @staticmethod def get_det44_mapping(node): """Get DET44 mapping data. @@ -299,14 +431,12 @@ class NATUtil: def get_det44_sessions_number(node): """Get number of established DET44 sessions from actual DET44 mapping data. - :param node: DUT node. :type node: dict :returns: Number of established DET44 sessions. :rtype: int """ det44_data = NATUtil.get_det44_mapping(node) - return det44_data.get(u"ses_num", 0) @staticmethod