From 79a6d06f10393ede1d88c3d5b76728b745d467a5 Mon Sep 17 00:00:00 2001 From: cdossa Date: Wed, 11 Mar 2026 17:01:50 -0700 Subject: [PATCH] aks-preview: Add managedNATGatewayV2 outbound type support Add support for the managedNATGatewayV2 outbound type which uses Azure NAT Gateway Standard V2 SKU. New CLI parameters: - --nat-gateway-managed-outbound-ipv6-count: IPv6 managed IPs (1-16, dual-stack) - --nat-gateway-outbound-ip-ids: User-provided public IP resource IDs - --nat-gateway-outbound-ip-prefix-ids: User-provided IP prefix resource IDs These are valid only with --outbound-type managedNATGatewayV2. --- src/aks-preview/HISTORY.rst | 1 + .../configs/ext_matrix_default.json | 3 + src/aks-preview/azext_aks_preview/_consts.py | 1 + src/aks-preview/azext_aks_preview/_help.py | 12 +- .../azext_aks_preview/_natgateway.py | 88 ++++++++++++--- src/aks-preview/azext_aks_preview/_params.py | 80 +++++++++++++- .../azext_aks_preview/_validators.py | 29 +++++ src/aks-preview/azext_aks_preview/custom.py | 6 + .../managed_cluster_decorator.py | 81 +++++++++++++- .../tests/latest/test_aks_commands.py | 71 ++++++++++++ .../tests/latest/test_natgateway.py | 103 ++++++++++++++++++ 11 files changed, 452 insertions(+), 23 deletions(-) diff --git a/src/aks-preview/HISTORY.rst b/src/aks-preview/HISTORY.rst index 79b399496a8..b6909a31f9d 100644 --- a/src/aks-preview/HISTORY.rst +++ b/src/aks-preview/HISTORY.rst @@ -11,6 +11,7 @@ To release a new version, please select a new version number (usually plus 1 to Pending +++++++ +* `az aks create/update`: Add `--outbound-type managedNATGatewayV2` support using Azure NAT Gateway Standard V2 SKU with IPv6, user-provided IPs, and IP prefixes. 19.0.0b28 +++++++ diff --git a/src/aks-preview/azcli_aks_live_test/configs/ext_matrix_default.json b/src/aks-preview/azcli_aks_live_test/configs/ext_matrix_default.json index e3e54bb1540..a9138382806 100644 --- a/src/aks-preview/azcli_aks_live_test/configs/ext_matrix_default.json +++ b/src/aks-preview/azcli_aks_live_test/configs/ext_matrix_default.json @@ -29,6 +29,9 @@ "pod ip allocation mode static block, missing feature registration": [ "test_aks_create_with_pod_ip_allocation_mode_static_block" ], + "managed nat gateway v2, missing feature registration": [ + "test_aks_create_and_update_with_managed_nat_gateway_v2" + ], "[deprecated] workload runtime, missing feature registration": [ "test_aks_nodepool_add_with_workload_runtime" ], diff --git a/src/aks-preview/azext_aks_preview/_consts.py b/src/aks-preview/azext_aks_preview/_consts.py index 10c430a9ce6..ad3f5e6ec43 100644 --- a/src/aks-preview/azext_aks_preview/_consts.py +++ b/src/aks-preview/azext_aks_preview/_consts.py @@ -386,6 +386,7 @@ CONST_OUTBOUND_TYPE_NONE = "none" CONST_OUTBOUND_TYPE_BLOCK = "block" +CONST_OUTBOUND_TYPE_MANAGED_NAT_GATEWAY_V2 = "managedNATGatewayV2" # IMDS restriction consts CONST_IMDS_RESTRICTION_ENABLED = "None" diff --git a/src/aks-preview/azext_aks_preview/_help.py b/src/aks-preview/azext_aks_preview/_help.py index cac2a156f5c..f71fc9ed92d 100644 --- a/src/aks-preview/azext_aks_preview/_help.py +++ b/src/aks-preview/azext_aks_preview/_help.py @@ -158,15 +158,15 @@ - name: --nat-gateway-managed-outbound-ip-count type: int short-summary: NAT gateway managed outbound IP count. - long-summary: Desired number of managed outbound IPs for NAT gateway outbound connection. Please specify a value in the range of [1, 16]. Valid for Standard SKU load balancer cluster with managedNATGateway outbound type only. + long-summary: Desired number of managed outbound IPs for NAT gateway outbound connection. Please specify a value in the range of [1, 16]. Valid for Standard SKU load balancer cluster with managedNATGateway or managedNATGatewayV2 outbound type only. - name: --nat-gateway-idle-timeout type: int short-summary: NAT gateway idle timeout in minutes. - long-summary: Desired idle timeout for NAT gateway outbound flows, default is 4 minutes. Please specify a value in the range of [4, 120]. Valid for Standard SKU load balancer cluster with managedNATGateway outbound type only. + long-summary: Desired idle timeout for NAT gateway outbound flows, default is 4 minutes. Please specify a value in the range of [4, 120]. Valid for Standard SKU load balancer cluster with managedNATGateway or managedNATGatewayV2 outbound type only. - name: --outbound-type type: string short-summary: How outbound traffic will be configured for a cluster. - long-summary: Select between loadBalancer, userDefinedRouting, managedNATGateway, userAssignedNATGateway, none and block. If not set, defaults to type loadBalancer. Requires --vnet-subnet-id to be provided with a preconfigured route table and --load-balancer-sku to be Standard. + long-summary: Select between loadBalancer, userDefinedRouting, managedNATGateway, managedNATGatewayV2, userAssignedNATGateway, none and block. If not set, defaults to type loadBalancer. managedNATGatewayV2 uses Azure NAT Gateway Standard V2 SKU and supports IPv6, user-provided public IPs, and user-provided IP prefixes. - name: --enable-addons -a type: string short-summary: Enable the Kubernetes addons in a comma-separated list. @@ -960,15 +960,15 @@ - name: --nat-gateway-managed-outbound-ip-count type: int short-summary: NAT gateway managed outbound IP count. - long-summary: Desired number of managed outbound IPs for NAT gateway outbound connection. Please specify a value in the range of [1, 16]. Valid for Standard SKU load balancer cluster with managedNATGateway outbound type only. + long-summary: Desired number of managed outbound IPs for NAT gateway outbound connection. Please specify a value in the range of [1, 16]. Valid for Standard SKU load balancer cluster with managedNATGateway or managedNATGatewayV2 outbound type only. - name: --nat-gateway-idle-timeout type: int short-summary: NAT gateway idle timeout in minutes. - long-summary: Desired idle timeout for NAT gateway outbound flows, default is 4 minutes. Please specify a value in the range of [4, 120]. Valid for Standard SKU load balancer cluster with managedNATGateway outbound type only. + long-summary: Desired idle timeout for NAT gateway outbound flows, default is 4 minutes. Please specify a value in the range of [4, 120]. Valid for Standard SKU load balancer cluster with managedNATGateway or managedNATGatewayV2 outbound type only. - name: --outbound-type type: string short-summary: How outbound traffic will be configured for a cluster. - long-summary: This option will change the way how the outbound connections are managed in the AKS cluster. Available options are loadbalancer, managedNATGateway, userAssignedNATGateway, userDefinedRouting, none and block. For custom vnet, loadbalancer, userAssignedNATGateway and userDefinedRouting are supported. For aks managed vnet, loadbalancer, managedNATGateway and userDefinedRouting are supported. + long-summary: This option will change the way how the outbound connections are managed in the AKS cluster. Available options are loadbalancer, managedNATGateway, managedNATGatewayV2, userAssignedNATGateway, userDefinedRouting, none and block. For clusters using a custom virtual network, supported values are loadbalancer, userAssignedNATGateway and userDefinedRouting. For clusters using an AKS-managed virtual network, supported values are loadbalancer, managedNATGateway, managedNATGatewayV2 and userDefinedRouting. - name: --nrg-lockdown-restriction-level type: string short-summary: Restriction level on the managed node resource. diff --git a/src/aks-preview/azext_aks_preview/_natgateway.py b/src/aks-preview/azext_aks_preview/_natgateway.py index 7ab8e6f1f52..8d11f516df7 100644 --- a/src/aks-preview/azext_aks_preview/_natgateway.py +++ b/src/aks-preview/azext_aks_preview/_natgateway.py @@ -6,37 +6,99 @@ from types import SimpleNamespace -def create_nat_gateway_profile(managed_outbound_ip_count, idle_timeout, models: SimpleNamespace): +def create_nat_gateway_profile( + managed_outbound_ip_count, + idle_timeout, + models: SimpleNamespace, + managed_outbound_ipv6_count=None, + outbound_ip_ids=None, + outbound_ip_prefix_ids=None, +): """parse and build NAT gateway profile""" - if not is_nat_gateway_profile_provided(managed_outbound_ip_count, idle_timeout): + if not is_nat_gateway_profile_provided( + managed_outbound_ip_count, idle_timeout, + managed_outbound_ipv6_count, outbound_ip_ids, outbound_ip_prefix_ids, + ): return None profile = models.ManagedClusterNATGatewayProfile() - return configure_nat_gateway_profile(managed_outbound_ip_count, idle_timeout, profile, models) + return configure_nat_gateway_profile( + managed_outbound_ip_count, idle_timeout, profile, models, + managed_outbound_ipv6_count, outbound_ip_ids, outbound_ip_prefix_ids, + ) -def update_nat_gateway_profile(managed_outbound_ip_count, idle_timeout, profile, models: SimpleNamespace): +def update_nat_gateway_profile( + managed_outbound_ip_count, + idle_timeout, + profile, + models: SimpleNamespace, + managed_outbound_ipv6_count=None, + outbound_ip_ids=None, + outbound_ip_prefix_ids=None, +): """parse and update an existing NAT gateway profile""" - if not is_nat_gateway_profile_provided(managed_outbound_ip_count, idle_timeout): + if not is_nat_gateway_profile_provided( + managed_outbound_ip_count, idle_timeout, + managed_outbound_ipv6_count, outbound_ip_ids, outbound_ip_prefix_ids, + ): return profile if not profile: profile = models.ManagedClusterNATGatewayProfile() - return configure_nat_gateway_profile(managed_outbound_ip_count, idle_timeout, profile, models) + return configure_nat_gateway_profile( + managed_outbound_ip_count, idle_timeout, profile, models, + managed_outbound_ipv6_count, outbound_ip_ids, outbound_ip_prefix_ids, + ) -def is_nat_gateway_profile_provided(managed_outbound_ip_count, idle_timeout): - return any([managed_outbound_ip_count is not None, idle_timeout]) +def is_nat_gateway_profile_provided( + managed_outbound_ip_count, + idle_timeout, + managed_outbound_ipv6_count=None, + outbound_ip_ids=None, + outbound_ip_prefix_ids=None, +): + return any([ + managed_outbound_ip_count is not None, + idle_timeout, + managed_outbound_ipv6_count is not None, + outbound_ip_ids is not None, + outbound_ip_prefix_ids is not None, + ]) -def configure_nat_gateway_profile(managed_outbound_ip_count, idle_timeout, profile, models: SimpleNamespace): +def configure_nat_gateway_profile( + managed_outbound_ip_count, + idle_timeout, + profile, + models: SimpleNamespace, + managed_outbound_ipv6_count=None, + outbound_ip_ids=None, + outbound_ip_prefix_ids=None, +): """configure a NAT Gateway with customer supplied values""" - if managed_outbound_ip_count is not None: + if managed_outbound_ip_count is not None or managed_outbound_ipv6_count is not None: ManagedClusterManagedOutboundIPProfile = models.ManagedClusterManagedOutboundIPProfile - profile.managed_outbound_ip_profile = ManagedClusterManagedOutboundIPProfile( - count=managed_outbound_ip_count - ) + if not profile.managed_outbound_ip_profile: + profile.managed_outbound_ip_profile = ManagedClusterManagedOutboundIPProfile() + if managed_outbound_ip_count is not None: + profile.managed_outbound_ip_profile.count = managed_outbound_ip_count + if managed_outbound_ipv6_count is not None: + profile.managed_outbound_ip_profile.count_i_pv6 = managed_outbound_ipv6_count if idle_timeout: profile.idle_timeout_in_minutes = idle_timeout + if outbound_ip_ids is not None: + ManagedClusterNATGatewayProfileOutboundIPs = models.ManagedClusterNATGatewayProfileOutboundIPs + profile.outbound_i_ps = ManagedClusterNATGatewayProfileOutboundIPs( + public_i_ps=outbound_ip_ids + ) + + if outbound_ip_prefix_ids is not None: + ManagedClusterNATGatewayProfileOutboundIPPrefixes = models.ManagedClusterNATGatewayProfileOutboundIPPrefixes + profile.outbound_ip_prefixes = ManagedClusterNATGatewayProfileOutboundIPPrefixes( + public_ip_prefixes=outbound_ip_prefix_ids + ) + return profile diff --git a/src/aks-preview/azext_aks_preview/_params.py b/src/aks-preview/azext_aks_preview/_params.py index b1c272c44d9..22de352806d 100644 --- a/src/aks-preview/azext_aks_preview/_params.py +++ b/src/aks-preview/azext_aks_preview/_params.py @@ -33,6 +33,10 @@ tags_type, zones_type, ) +from azext_aks_preview._validators import ( + validate_nat_gateway_managed_outbound_ipv6_count, + validate_nat_gateway_v2_params, +) from azext_aks_preview._client_factory import CUSTOM_MGMT_AKS_PREVIEW from azext_aks_preview._completers import ( get_k8s_upgrades_completion_list, @@ -147,6 +151,7 @@ CONST_ARTIFACT_SOURCE_CACHE, CONST_OUTBOUND_TYPE_NONE, CONST_OUTBOUND_TYPE_BLOCK, + CONST_OUTBOUND_TYPE_MANAGED_NAT_GATEWAY_V2, CONST_APP_ROUTING_ANNOTATION_CONTROLLED_NGINX, CONST_APP_ROUTING_EXTERNAL_NGINX, CONST_APP_ROUTING_INTERNAL_NGINX, @@ -373,6 +378,7 @@ CONST_OUTBOUND_TYPE_LOAD_BALANCER, CONST_OUTBOUND_TYPE_USER_DEFINED_ROUTING, CONST_OUTBOUND_TYPE_MANAGED_NAT_GATEWAY, + CONST_OUTBOUND_TYPE_MANAGED_NAT_GATEWAY_V2, CONST_OUTBOUND_TYPE_USER_ASSIGNED_NAT_GATEWAY, CONST_OUTBOUND_TYPE_NONE, CONST_OUTBOUND_TYPE_BLOCK, @@ -661,7 +667,42 @@ def load_arguments(self, _): type=int, validator=validate_nat_gateway_idle_timeout, ) - c.argument("outbound_type", arg_type=get_enum_type(outbound_types)) + c.argument( + "nat_gateway_managed_outbound_ipv6_count", + options_list=[ + "--nat-gateway-managed-outbound-ipv6-count", + "--nat-gw-ipv6-count", + ], + type=int, + validator=validate_nat_gateway_managed_outbound_ipv6_count, + help="NAT gateway managed outbound IPv6 IP count. " + "Valid only with --outbound-type managedNATGatewayV2.", + ) + c.argument( + "nat_gateway_outbound_ip_ids", + options_list=[ + "--nat-gateway-outbound-ips", + "--nat-gw-ips", + ], + nargs="+", + help="Space-separated public IP resource IDs for the " + "cluster NAT gateway. V2 only.", + ) + c.argument( + "nat_gateway_outbound_ip_prefix_ids", + options_list=[ + "--nat-gateway-outbound-ip-prefixes", + "--nat-gw-prefixes", + ], + nargs="+", + help="Space-separated public IP prefix resource IDs " + "for the cluster NAT gateway. V2 only.", + ) + c.argument( + "outbound_type", + arg_type=get_enum_type(outbound_types), + validator=validate_nat_gateway_v2_params, + ) c.argument("network_plugin", arg_type=get_enum_type(network_plugins)) c.argument("network_plugin_mode", arg_type=get_enum_type(network_plugin_modes)) c.argument("network_policy") @@ -1264,6 +1305,37 @@ def load_arguments(self, _): type=int, validator=validate_nat_gateway_idle_timeout, ) + c.argument( + "nat_gateway_managed_outbound_ipv6_count", + options_list=[ + "--nat-gateway-managed-outbound-ipv6-count", + "--nat-gw-ipv6-count", + ], + type=int, + validator=validate_nat_gateway_managed_outbound_ipv6_count, + help="NAT gateway managed outbound IPv6 IP count. " + "Valid only with --outbound-type managedNATGatewayV2.", + ) + c.argument( + "nat_gateway_outbound_ip_ids", + options_list=[ + "--nat-gateway-outbound-ips", + "--nat-gw-ips", + ], + nargs="+", + help="Space-separated public IP resource IDs for the " + "cluster NAT gateway. V2 only.", + ) + c.argument( + "nat_gateway_outbound_ip_prefix_ids", + options_list=[ + "--nat-gateway-outbound-ip-prefixes", + "--nat-gw-prefixes", + ], + nargs="+", + help="Space-separated public IP prefix resource IDs " + "for the cluster NAT gateway. V2 only.", + ) c.argument("network_dataplane", arg_type=get_enum_type(network_dataplanes)) c.argument("network_policy") c.argument("network_plugin", arg_type=get_enum_type(network_plugins)) @@ -1442,7 +1514,11 @@ def load_arguments(self, _): validator=validate_ssh_key_for_update, ) c.argument("load_balancer_managed_outbound_ipv6_count", type=int) - c.argument("outbound_type", arg_type=get_enum_type(outbound_types)) + c.argument( + "outbound_type", + arg_type=get_enum_type(outbound_types), + validator=validate_nat_gateway_v2_params, + ) c.argument("enable_pod_identity", action="store_true") c.argument("enable_pod_identity_with_kubenet", action="store_true") c.argument("disable_pod_identity", action="store_true") diff --git a/src/aks-preview/azext_aks_preview/_validators.py b/src/aks-preview/azext_aks_preview/_validators.py index c5a2ec7d8f9..181f8b16f02 100644 --- a/src/aks-preview/azext_aks_preview/_validators.py +++ b/src/aks-preview/azext_aks_preview/_validators.py @@ -1144,3 +1144,32 @@ def validate_azure_monitor_logs_enable_disable(namespace): "Cannot specify both '--enable-azure-monitor-logs' and '--disable-azure-monitor-logs'. " "Use either '--enable-azure-monitor-logs' or '--disable-azure-monitor-logs'." ) + + +def validate_nat_gateway_managed_outbound_ipv6_count(namespace): + """validate NAT gateway profile managed outbound IPv6 count""" + if namespace.nat_gateway_managed_outbound_ipv6_count is not None: + if (namespace.nat_gateway_managed_outbound_ipv6_count < 1 or + namespace.nat_gateway_managed_outbound_ipv6_count > 16): + raise InvalidArgumentValueError( + "--nat-gateway-managed-outbound-ipv6-count " + "must be in the range [1,16]" + ) + + +def validate_nat_gateway_v2_params(namespace): + """Validate that V2-only NAT gateway params require managedNATGatewayV2.""" + v2_params = [ + getattr(namespace, 'nat_gateway_managed_outbound_ipv6_count', None), + getattr(namespace, 'nat_gateway_outbound_ip_ids', None), + getattr(namespace, 'nat_gateway_outbound_ip_prefix_ids', None), + ] + if any(p is not None for p in v2_params): + outbound_type = getattr(namespace, 'outbound_type', None) + if outbound_type != 'managedNATGatewayV2': + raise InvalidArgumentValueError( + "--nat-gateway-managed-outbound-ipv6-count, " + "--nat-gateway-outbound-ips, and " + "--nat-gateway-outbound-ip-prefixes are only " + "valid with --outbound-type managedNATGatewayV2." + ) diff --git a/src/aks-preview/azext_aks_preview/custom.py b/src/aks-preview/azext_aks_preview/custom.py index acc7311ccdf..ab273f365dd 100644 --- a/src/aks-preview/azext_aks_preview/custom.py +++ b/src/aks-preview/azext_aks_preview/custom.py @@ -955,6 +955,9 @@ def aks_create( load_balancer_backend_pool_type=None, nat_gateway_managed_outbound_ip_count=None, nat_gateway_idle_timeout=None, + nat_gateway_managed_outbound_ipv6_count=None, + nat_gateway_outbound_ip_ids=None, + nat_gateway_outbound_ip_prefix_ids=None, outbound_type=None, network_plugin=None, network_plugin_mode=None, @@ -1223,6 +1226,9 @@ def aks_update( load_balancer_backend_pool_type=None, nat_gateway_managed_outbound_ip_count=None, nat_gateway_idle_timeout=None, + nat_gateway_managed_outbound_ipv6_count=None, + nat_gateway_outbound_ip_ids=None, + nat_gateway_outbound_ip_prefix_ids=None, kube_proxy_config=None, auto_upgrade_channel=None, node_os_upgrade_channel=None, diff --git a/src/aks-preview/azext_aks_preview/managed_cluster_decorator.py b/src/aks-preview/azext_aks_preview/managed_cluster_decorator.py index af1d1ff1615..8d35fe19553 100644 --- a/src/aks-preview/azext_aks_preview/managed_cluster_decorator.py +++ b/src/aks-preview/azext_aks_preview/managed_cluster_decorator.py @@ -42,6 +42,7 @@ CONST_ARTIFACT_SOURCE_CACHE, CONST_OUTBOUND_TYPE_NONE, CONST_OUTBOUND_TYPE_BLOCK, + CONST_OUTBOUND_TYPE_MANAGED_NAT_GATEWAY_V2, CONST_IMDS_RESTRICTION_ENABLED, CONST_IMDS_RESTRICTION_DISABLED, CONST_AVAILABILITY_SET, @@ -181,6 +182,40 @@ def __init__(self, cmd: AzCommandsLoader, resource_type: ResourceType): super().__init__(cmd, resource_type) # holder for pod identity related models self.__pod_identity_models = None + # holder for nat gateway related models (overridden for V2 support) + self.__nat_gateway_models = None + + @property + def nat_gateway_models(self) -> SimpleNamespace: + """Get nat gateway related models, including V2 sub-models. + + Overridden from base class to add ManagedClusterNATGatewayProfileOutboundIPs + and ManagedClusterNATGatewayProfileOutboundIPPrefixes for V2 support. + + :return: SimpleNamespace + """ + if self.__nat_gateway_models is None: + nat_gateway_models = {} + nat_gateway_models["ManagedClusterNATGatewayProfile"] = ( + self.ManagedClusterNATGatewayProfile if hasattr(self, "ManagedClusterNATGatewayProfile") else None + ) + nat_gateway_models["ManagedClusterManagedOutboundIPProfile"] = ( + self.ManagedClusterManagedOutboundIPProfile + if hasattr(self, "ManagedClusterManagedOutboundIPProfile") + else None + ) + nat_gateway_models["ManagedClusterNATGatewayProfileOutboundIPs"] = ( + self.ManagedClusterNATGatewayProfileOutboundIPs + if hasattr(self, "ManagedClusterNATGatewayProfileOutboundIPs") + else None + ) + nat_gateway_models["ManagedClusterNATGatewayProfileOutboundIPPrefixes"] = ( + self.ManagedClusterNATGatewayProfileOutboundIPPrefixes + if hasattr(self, "ManagedClusterNATGatewayProfileOutboundIPPrefixes") + else None + ) + self.__nat_gateway_models = SimpleNamespace(**nat_gateway_models) + return self.__nat_gateway_models @property def pod_identity_models(self) -> SimpleNamespace: @@ -1042,6 +1077,33 @@ def get_load_balancer_outbound_ips(self) -> Union[str, List[ResourceReference], # read the original value passed by the command return self.raw_param.get("load_balancer_outbound_ips") + def get_nat_gateway_managed_outbound_ipv6_count(self) -> Union[int, None]: + """Obtain the value of nat_gateway_managed_outbound_ipv6_count. + + Only valid with managedNATGatewayV2 outbound type on dual-stack clusters. + + :return: int or None + """ + return self.raw_param.get("nat_gateway_managed_outbound_ipv6_count") + + def get_nat_gateway_outbound_ip_ids(self) -> Union[list, None]: + """Obtain the value of nat_gateway_outbound_ip_ids. + + Only valid with managedNATGatewayV2 outbound type. + + :return: list or None + """ + return self.raw_param.get("nat_gateway_outbound_ip_ids") + + def get_nat_gateway_outbound_ip_prefix_ids(self) -> Union[list, None]: + """Obtain the value of nat_gateway_outbound_ip_prefix_ids. + + Only valid with managedNATGatewayV2 outbound type. + + :return: list or None + """ + return self.raw_param.get("nat_gateway_outbound_ip_prefix_ids") + def get_load_balancer_outbound_ip_prefixes(self) -> Union[str, List[ResourceReference], None]: """Obtain the value of load_balancer_outbound_ip_prefixes. @@ -3973,11 +4035,17 @@ def set_up_network_profile(self, mc: ManagedCluster) -> ManagedCluster: models=self.models.load_balancer_models, ) - if self.context.get_nat_gateway_managed_outbound_ip_count() is not None: + if self.context.get_nat_gateway_managed_outbound_ip_count() is not None or \ + self.context.get_nat_gateway_managed_outbound_ipv6_count() is not None or \ + self.context.get_nat_gateway_outbound_ip_ids() is not None or \ + self.context.get_nat_gateway_outbound_ip_prefix_ids() is not None: network_profile.nat_gateway_profile = create_nat_gateway_profile( self.context.get_nat_gateway_managed_outbound_ip_count(), self.context.get_nat_gateway_idle_timeout(), models=self.models.nat_gateway_models, + managed_outbound_ipv6_count=self.context.get_nat_gateway_managed_outbound_ipv6_count(), + outbound_ip_ids=self.context.get_nat_gateway_outbound_ip_ids(), + outbound_ip_prefix_ids=self.context.get_nat_gateway_outbound_ip_prefix_ids(), ) network_profile.network_plugin_mode = self.context.get_network_plugin_mode() @@ -5532,6 +5600,9 @@ def get_special_parameter_default_value_pairs_list(self) -> List[Tuple[Any, Any] (self.context.get_load_balancer_managed_outbound_ipv6_count(), None), (self.context.get_load_balancer_outbound_ports(), None), (self.context.get_nat_gateway_managed_outbound_ip_count(), None), + (self.context.get_nat_gateway_managed_outbound_ipv6_count(), None), + (self.context.get_nat_gateway_outbound_ip_ids(), None), + (self.context.get_nat_gateway_outbound_ip_prefix_ids(), None), ] def check_raw_parameters(self): @@ -6266,7 +6337,10 @@ def update_nat_gateway_profile(self, mc: ManagedCluster) -> ManagedCluster: "Unexpectedly get an empty network profile in the process of updating nat gateway profile." ) outbound_type = self.context.get_outbound_type() - if outbound_type and outbound_type != CONST_OUTBOUND_TYPE_MANAGED_NAT_GATEWAY: + if outbound_type and outbound_type not in [ + CONST_OUTBOUND_TYPE_MANAGED_NAT_GATEWAY, + CONST_OUTBOUND_TYPE_MANAGED_NAT_GATEWAY_V2, + ]: mc.network_profile.nat_gateway_profile = None else: mc.network_profile.nat_gateway_profile = _update_nat_gateway_profile( @@ -6274,6 +6348,9 @@ def update_nat_gateway_profile(self, mc: ManagedCluster) -> ManagedCluster: self.context.get_nat_gateway_idle_timeout(), mc.network_profile.nat_gateway_profile, models=self.models.nat_gateway_models, + managed_outbound_ipv6_count=self.context.get_nat_gateway_managed_outbound_ipv6_count(), + outbound_ip_ids=self.context.get_nat_gateway_outbound_ip_ids(), + outbound_ip_prefix_ids=self.context.get_nat_gateway_outbound_ip_prefix_ids(), ) return mc diff --git a/src/aks-preview/azext_aks_preview/tests/latest/test_aks_commands.py b/src/aks-preview/azext_aks_preview/tests/latest/test_aks_commands.py index a21bbbed4aa..a0f8bf45967 100644 --- a/src/aks-preview/azext_aks_preview/tests/latest/test_aks_commands.py +++ b/src/aks-preview/azext_aks_preview/tests/latest/test_aks_commands.py @@ -279,6 +279,77 @@ def test_aks_create_and_update_with_managed_nat_gateway_outbound( ], ) + @AllowLargeResponse() + @AKSCustomResourceGroupPreparer( + random_name_length=17, + name_prefix="clitest", + location="eastus", + ) + # live_only: ManagedNATGatewayV2Preview feature flag must be registered on the + # test subscription and the RP-side feature toggle must be active. Recording-based + # tests will be added once the feature is enabled in public clouds. + @live_only() + def test_aks_create_and_update_with_managed_nat_gateway_v2( + self, resource_group, resource_group_location + ): + aks_name = self.create_random_name("cliakstest", 16) + self.kwargs.update( + { + "resource_group": resource_group, + "name": aks_name, + "ssh_key_value": self.generate_ssh_keys(), + } + ) + + create_cmd = ( + "aks create --resource-group={resource_group} --name={name} " + "--vm-set-type VirtualMachineScaleSets -c 1 " + "--outbound-type=managedNATGatewayV2 " + "--nat-gateway-managed-outbound-ip-count 2 " + "--ssh-key-value={ssh_key_value}" + ) + self.cmd( + create_cmd, + checks=[ + self.check("provisioningState", "Succeeded"), + self.check( + "networkProfile.outboundType", + "managedNATGatewayV2", + ), + self.check( + "networkProfile.natGatewayProfile" + ".managedOutboundIpProfile.count", + 2, + ), + ], + ) + + update_cmd = ( + "aks update --resource-group={resource_group} --name={name} " + "--nat-gateway-managed-outbound-ip-count 4 " + "--nat-gateway-idle-timeout 30 " + ) + self.cmd( + update_cmd, + checks=[ + self.check("provisioningState", "Succeeded"), + self.check( + "networkProfile.outboundType", + "managedNATGatewayV2", + ), + self.check( + "networkProfile.natGatewayProfile" + ".idleTimeoutInMinutes", + 30, + ), + self.check( + "networkProfile.natGatewayProfile" + ".managedOutboundIpProfile.count", + 4, + ), + ], + ) + @AllowLargeResponse() @AKSCustomResourceGroupPreparer( random_name_length=17, name_prefix="clitest", location="eastus2euap", preserve_default_location=True, diff --git a/src/aks-preview/azext_aks_preview/tests/latest/test_natgateway.py b/src/aks-preview/azext_aks_preview/tests/latest/test_natgateway.py index 314a68dcdc5..e7378b7ee4e 100644 --- a/src/aks-preview/azext_aks_preview/tests/latest/test_natgateway.py +++ b/src/aks-preview/azext_aks_preview/tests/latest/test_natgateway.py @@ -105,5 +105,108 @@ def test_nonempty_arguments(self): self.assertTrue(result) +class TestCreateNatGatewayV2Profile(unittest.TestCase): + def setUp(self): + register_aks_preview_resource_type() + self.cli_ctx = MockCLI() + self.cmd = MockCmd(self.cli_ctx) + self.nat_gateway_models = AKSPreviewManagedClusterModels(self.cmd, CUSTOM_MGMT_AKS_PREVIEW).nat_gateway_models + + def test_v2_with_managed_outbound_ipv6_count(self): + profile = natgateway.create_nat_gateway_profile( + 2, 30, models=self.nat_gateway_models, + managed_outbound_ipv6_count=4, + ) + self.assertEqual(profile.managed_outbound_ip_profile.count, 2) + self.assertEqual(profile.managed_outbound_ip_profile.count_i_pv6, 4) + self.assertEqual(profile.idle_timeout_in_minutes, 30) + + def test_v2_with_outbound_ip_ids(self): + ip_ids = ["/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Network/publicIPAddresses/ip1"] + profile = natgateway.create_nat_gateway_profile( + None, None, models=self.nat_gateway_models, + outbound_ip_ids=ip_ids, + ) + self.assertIsNotNone(profile) + self.assertEqual(len(profile.outbound_i_ps.public_i_ps), 1) + self.assertEqual(profile.outbound_i_ps.public_i_ps[0], ip_ids[0]) + + def test_v2_with_outbound_ip_prefix_ids(self): + prefix_ids = ["/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Network/publicIPPrefixes/prefix1"] + profile = natgateway.create_nat_gateway_profile( + None, None, models=self.nat_gateway_models, + outbound_ip_prefix_ids=prefix_ids, + ) + self.assertIsNotNone(profile) + self.assertEqual(len(profile.outbound_ip_prefixes.public_ip_prefixes), 1) + self.assertEqual(profile.outbound_ip_prefixes.public_ip_prefixes[0], prefix_ids[0]) + + def test_v2_only_ipv6_count(self): + profile = natgateway.create_nat_gateway_profile( + None, None, models=self.nat_gateway_models, + managed_outbound_ipv6_count=8, + ) + self.assertIsNotNone(profile) + self.assertEqual(profile.managed_outbound_ip_profile.count_i_pv6, 8) + + +class TestUpdateNatGatewayV2Profile(unittest.TestCase): + def setUp(self): + register_aks_preview_resource_type() + self.cli_ctx = MockCLI() + self.cmd = MockCmd(self.cli_ctx) + self.nat_gateway_models = AKSPreviewManagedClusterModels(self.cmd, CUSTOM_MGMT_AKS_PREVIEW).nat_gateway_models + + def test_v2_update_with_ipv6_count(self): + origin_profile = self.nat_gateway_models.ManagedClusterNATGatewayProfile( + managed_outbound_ip_profile=self.nat_gateway_models.ManagedClusterManagedOutboundIPProfile(count=1), + idle_timeout_in_minutes=4, + ) + profile = natgateway.update_nat_gateway_profile( + None, None, origin_profile, models=self.nat_gateway_models, + managed_outbound_ipv6_count=4, + ) + self.assertEqual(profile.managed_outbound_ip_profile.count, 1) + self.assertEqual(profile.managed_outbound_ip_profile.count_i_pv6, 4) + + def test_v2_update_with_outbound_ip_ids(self): + origin_profile = self.nat_gateway_models.ManagedClusterNATGatewayProfile(idle_timeout_in_minutes=4) + ip_ids = ["/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Network/publicIPAddresses/ip1"] + profile = natgateway.update_nat_gateway_profile( + None, None, origin_profile, models=self.nat_gateway_models, + outbound_ip_ids=ip_ids, + ) + self.assertEqual(len(profile.outbound_i_ps.public_i_ps), 1) + + def test_v2_empty_v2_params_returns_original(self): + origin_profile = self.nat_gateway_models.ManagedClusterNATGatewayProfile( + managed_outbound_ip_profile=self.nat_gateway_models.ManagedClusterManagedOutboundIPProfile(count=2), + idle_timeout_in_minutes=10, + ) + profile = natgateway.update_nat_gateway_profile( + None, None, origin_profile, models=self.nat_gateway_models, + ) + self.assertEqual(profile.managed_outbound_ip_profile.count, 2) + self.assertEqual(profile.idle_timeout_in_minutes, 10) + + +class TestIsNatGatewayV2ProfileProvided(unittest.TestCase): + def test_only_ipv6_count(self): + result = natgateway.is_nat_gateway_profile_provided(None, None, managed_outbound_ipv6_count=4) + self.assertTrue(result) + + def test_only_outbound_ip_ids(self): + result = natgateway.is_nat_gateway_profile_provided(None, None, outbound_ip_ids=["/sub/ip1"]) + self.assertTrue(result) + + def test_only_outbound_ip_prefix_ids(self): + result = natgateway.is_nat_gateway_profile_provided(None, None, outbound_ip_prefix_ids=["/sub/prefix1"]) + self.assertTrue(result) + + def test_all_none(self): + result = natgateway.is_nat_gateway_profile_provided(None, None, None, None, None) + self.assertFalse(result) + + if __name__ == '__main__': unittest.main()