diff --git a/src/azure-cli/azure/cli/command_modules/acs/_consts.py b/src/azure-cli/azure/cli/command_modules/acs/_consts.py index 6900914982c..e366de894c7 100644 --- a/src/azure-cli/azure/cli/command_modules/acs/_consts.py +++ b/src/azure-cli/azure/cli/command_modules/acs/_consts.py @@ -234,6 +234,8 @@ CONST_AZURE_SERVICE_MESH_UPGRADE_COMMAND_START = "Start" CONST_AZURE_SERVICE_MESH_UPGRADE_COMMAND_COMPLETE = "Complete" CONST_AZURE_SERVICE_MESH_UPGRADE_COMMAND_ROLLBACK = "Rollback" +CONST_AZURE_SERVICE_MESH_DEFAULT_EGRESS_NAMESPACE = "aks-istio-egress" +CONST_AZURE_SERVICE_MESH_MAX_EGRESS_NAME_LENGTH = 63 # Dns zone contributor role CONST_PRIVATE_DNS_ZONE_CONTRIBUTOR_ROLE = "Private DNS Zone Contributor" diff --git a/src/azure-cli/azure/cli/command_modules/acs/_help.py b/src/azure-cli/azure/cli/command_modules/acs/_help.py index f3bdbd5df6d..cbab7294d2a 100644 --- a/src/azure-cli/azure/cli/command_modules/acs/_help.py +++ b/src/azure-cli/azure/cli/command_modules/acs/_help.py @@ -2564,6 +2564,46 @@ text: az aks mesh disable-ingress-gateway --resource-group MyResourceGroup --name MyManagedCluster --ingress-gateway-type Internal """ +helps['aks mesh enable-egress-gateway'] = """ + type: command + short-summary: Enable an Azure Service Mesh egress gateway. + long-summary: This command enables an Azure Service Mesh egress gateway in given cluster. + parameters: + - name: --istio-eg-gtw-name --istio-egressgateway-name + type: string + short-summary: Specify the name of the Istio egress gateway. + long-summary: This required field specifies the name of the Istio egress gateway. Must be between 1 and 253 characters, must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character. + - name: --istio-eg-gtw-ns --istio-egressgateway-namespace + type: string + short-summary: Specify the namespace of the Istio egress gateway. + long-summary: This optional field specifies the namespace of the Istio egress gateway. Defaults to "aks-istio-egress" if unspecified. + - name: --gateway-configuration-name --gtw-config-name + type: string + short-summary: Specify the name of the StaticGatewayConfiguration resource. + long-summary: This required field specifies the name of the StaticGatewayConfiguration resource for the Istio egress gateway. See https://aka.ms/aks-static-egress-gateway on how to create and configure a Static Egress Gateway agentpool. + examples: + - name: Enable an Istio egress gateway. Static egress gateway must be enabled prior to creating an Istio egress gateway. See https://aka.ms/aks-static-egress-gateway on how to create and configure a Static Egress Gateway agentpool. + text: az aks mesh enable-egress-gateway --resource-group MyResourceGroup --name MyManagedCluster --istio-egressgateway-name my-istio-egress-1 --istio-egressgateway-namespace my-namespace-1 --gateway-configuration-name sgc-istio-egress-1 +""" + +helps['aks mesh disable-egress-gateway'] = """ + type: command + short-summary: Disable an Azure Service Mesh egress gateway. + long-summary: This command disables an Azure Service Mesh egress gateway in given cluster. + parameters: + - name: --istio-eg-gtw-name --istio-egressgateway-name + type: string + short-summary: Specify the name of the Istio egress gateway. + long-summary: This required field specifies the name of the Istio egress gateway. Must be between 1 and 253 characters, must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character. + - name: --istio-eg-gtw-ns --istio-egressgateway-namespace + type: string + short-summary: Specify the namespace of the Istio egress gateway. + long-summary: This optional field specifies the namespace of the Istio egress gateway. Defaults to "aks-istio-egress" if unspecified. + examples: + - name: Disable an Istio egress gateway. + text: az aks mesh disable-egress-gateway --resource-group MyResourceGroup --name MyManagedCluster --istio-egressgateway-name my-istio-egress-1 --istio-egressgateway-namespace my-namespace-1 +""" + helps["aks mesh get-revisions"] = """ type: command short-summary: Discover available Azure Service Mesh revisions and their compatibility. diff --git a/src/azure-cli/azure/cli/command_modules/acs/_params.py b/src/azure-cli/azure/cli/command_modules/acs/_params.py index d29006abba9..589c468fb76 100644 --- a/src/azure-cli/azure/cli/command_modules/acs/_params.py +++ b/src/azure-cli/azure/cli/command_modules/acs/_params.py @@ -54,6 +54,7 @@ CONST_LOAD_BALANCER_BACKEND_POOL_TYPE_NODE_IP_CONFIGURATION, CONST_AZURE_SERVICE_MESH_INGRESS_MODE_EXTERNAL, CONST_AZURE_SERVICE_MESH_INGRESS_MODE_INTERNAL, + CONST_AZURE_SERVICE_MESH_DEFAULT_EGRESS_NAMESPACE, CONST_NRG_LOCKDOWN_RESTRICTION_LEVEL_READONLY, CONST_NRG_LOCKDOWN_RESTRICTION_LEVEL_UNRESTRICTED, CONST_ARTIFACT_SOURCE_DIRECT, @@ -115,6 +116,7 @@ validate_allowed_host_ports, validate_application_security_groups, validate_node_public_ip_tags, validate_disable_windows_outbound_nat, + validate_asm_egress_name, validate_crg_id, validate_apiserver_subnet_id, validate_azure_service_mesh_revision, validate_message_of_the_day, @@ -1087,6 +1089,39 @@ def load_arguments(self, _): c.argument('ingress_gateway_type', arg_type=get_enum_type(ingress_gateway_types)) + with self.argument_context("aks mesh enable-egress-gateway") as c: + c.argument( + "istio_egressgateway_name", + validator=validate_asm_egress_name, + required=True, + options_list=["--istio-egressgateway-name", "--istio-eg-gtw-name"] + ) + c.argument( + "istio_egressgateway_namespace", + required=False, + default=CONST_AZURE_SERVICE_MESH_DEFAULT_EGRESS_NAMESPACE, + options_list=["--istio-egressgateway-namespace", "--istio-eg-gtw-ns"] + ) + c.argument( + "gateway_configuration_name", + required=True, + options_list=["--gateway-configuration-name", "--gtw-config-name"] + ) + + with self.argument_context("aks mesh disable-egress-gateway") as c: + c.argument( + "istio_egressgateway_name", + validator=validate_asm_egress_name, + required=True, + options_list=["--istio-egressgateway-name", "--istio-eg-gtw-name"] + ) + c.argument( + "istio_egressgateway_namespace", + required=False, + default=CONST_AZURE_SERVICE_MESH_DEFAULT_EGRESS_NAMESPACE, + options_list=["--istio-egressgateway-namespace", "--istio-eg-gtw-ns"] + ) + with self.argument_context('aks mesh enable') as c: c.argument('revision', validator=validate_azure_service_mesh_revision) c.argument('key_vault_id') diff --git a/src/azure-cli/azure/cli/command_modules/acs/_validators.py b/src/azure-cli/azure/cli/command_modules/acs/_validators.py index 7c1d9a23b95..cb9153bccfd 100644 --- a/src/azure-cli/azure/cli/command_modules/acs/_validators.py +++ b/src/azure-cli/azure/cli/command_modules/acs/_validators.py @@ -19,6 +19,7 @@ CONST_NETWORK_POD_IP_ALLOCATION_MODE_DYNAMIC_INDIVIDUAL, CONST_NETWORK_POD_IP_ALLOCATION_MODE_STATIC_BLOCK, CONST_NODEPOOL_MODE_GATEWAY, + CONST_AZURE_SERVICE_MESH_MAX_EGRESS_NAME_LENGTH, CONST_VIRTUAL_MACHINE_SCALE_SETS, CONST_AVAILABILITY_SET, CONST_VIRTUAL_MACHINES, @@ -137,6 +138,20 @@ def validate_agent_pool_name(namespace): _validate_nodepool_name(namespace.agent_pool_name) +def validate_asm_egress_name(namespace): + if namespace.istio_egressgateway_name is None: + return + name = namespace.istio_egressgateway_name + asm_egress_name_regex = re.compile(r'^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$') + match = asm_egress_name_regex.match(name) + if not match or len(name) > CONST_AZURE_SERVICE_MESH_MAX_EGRESS_NAME_LENGTH: + raise InvalidArgumentValueError( + f"Istio egress name {name} is invalid. Name must be between 1 and " + f"{CONST_AZURE_SERVICE_MESH_MAX_EGRESS_NAME_LENGTH} characters, must consist of lower case alphanumeric " + "characters, '-' or '.', and must start and end with an alphanumeric character." + ) + + def validate_kubectl_version(namespace): """Validates a string as a possible Kubernetes version.""" k8s_release_regex = re.compile(r'^[v|V]?(\d+\.\d+\.\d+.*|latest)$') diff --git a/src/azure-cli/azure/cli/command_modules/acs/commands.py b/src/azure-cli/azure/cli/command_modules/acs/commands.py index 8971e0a8200..022545b6f5e 100644 --- a/src/azure-cli/azure/cli/command_modules/acs/commands.py +++ b/src/azure-cli/azure/cli/command_modules/acs/commands.py @@ -254,11 +254,20 @@ def load_command_table(self, _): 'enable-ingress-gateway', 'aks_mesh_enable_ingress_gateway', supports_no_wait=True) + g.custom_command( + "enable-egress-gateway", + "aks_mesh_enable_egress_gateway", + supports_no_wait=True) g.custom_command( 'disable-ingress-gateway', 'aks_mesh_disable_ingress_gateway', supports_no_wait=True, confirmation=True) + g.custom_command( + "disable-egress-gateway", + "aks_mesh_disable_egress_gateway", + supports_no_wait=True, + confirmation=True) g.custom_command( 'get-revisions', 'aks_mesh_get_revisions', diff --git a/src/azure-cli/azure/cli/command_modules/acs/custom.py b/src/azure-cli/azure/cli/command_modules/acs/custom.py index 8ee7614ffc7..7d43d45afd8 100644 --- a/src/azure-cli/azure/cli/command_modules/acs/custom.py +++ b/src/azure-cli/azure/cli/command_modules/acs/custom.py @@ -3412,6 +3412,44 @@ def aks_mesh_disable_ingress_gateway( ingress_gateway_type=ingress_gateway_type) +def aks_mesh_enable_egress_gateway( + cmd, + client, + resource_group_name, + name, + istio_egressgateway_name, + istio_egressgateway_namespace, + gateway_configuration_name, +): + return _aks_mesh_update( + cmd, + client, + resource_group_name, + name, + enable_egress_gateway=True, + istio_egressgateway_name=istio_egressgateway_name, + istio_egressgateway_namespace=istio_egressgateway_namespace, + gateway_configuration_name=gateway_configuration_name) + + +def aks_mesh_disable_egress_gateway( + cmd, + client, + resource_group_name, + name, + istio_egressgateway_name, + istio_egressgateway_namespace, +): + return _aks_mesh_update( + cmd, + client, + resource_group_name, + name, + istio_egressgateway_name=istio_egressgateway_name, + istio_egressgateway_namespace=istio_egressgateway_namespace, + disable_egress_gateway=True) + + def aks_mesh_get_revisions( cmd, client, @@ -3534,6 +3572,11 @@ def _aks_mesh_update( enable_ingress_gateway=None, disable_ingress_gateway=None, ingress_gateway_type=None, + enable_egress_gateway=None, + disable_egress_gateway=None, + istio_egressgateway_name=None, + istio_egressgateway_namespace=None, + gateway_configuration_name=None, revision=None, yes=False, mesh_upgrade_command=None, diff --git a/src/azure-cli/azure/cli/command_modules/acs/managed_cluster_decorator.py b/src/azure-cli/azure/cli/command_modules/acs/managed_cluster_decorator.py index 862a711ed3b..56c6a008b57 100644 --- a/src/azure-cli/azure/cli/command_modules/acs/managed_cluster_decorator.py +++ b/src/azure-cli/azure/cli/command_modules/acs/managed_cluster_decorator.py @@ -41,6 +41,7 @@ CONST_AZURE_SERVICE_MESH_UPGRADE_COMMAND_START, CONST_AZURE_SERVICE_MESH_UPGRADE_COMMAND_COMPLETE, CONST_AZURE_SERVICE_MESH_UPGRADE_COMMAND_ROLLBACK, + CONST_AZURE_SERVICE_MESH_DEFAULT_EGRESS_NAMESPACE, CONST_PRIVATE_DNS_ZONE_CONTRIBUTOR_ROLE, CONST_DNS_ZONE_CONTRIBUTOR_ROLE, CONST_ARTIFACT_SOURCE_CACHE, @@ -4658,7 +4659,7 @@ def _handle_ingress_gateways_asm(self, new_profile: ServiceMeshProfile) -> Tuple disable_ingress_gateway = self.raw_param.get("disable_ingress_gateway", False) ingress_gateway_type = self.raw_param.get("ingress_gateway_type", None) - # disallow disable ingress gateway on a cluser with no asm enabled + # disallow disable ingress gateway on a cluster with no asm enabled if disable_ingress_gateway: if new_profile is None or new_profile.mode == CONST_AZURE_SERVICE_MESH_MODE_DISABLED: raise ArgumentUsageError( @@ -4712,6 +4713,97 @@ def _handle_ingress_gateways_asm(self, new_profile: ServiceMeshProfile) -> Tuple return new_profile, updated + def _handle_egress_gateways_asm(self, new_profile: ServiceMeshProfile) -> Tuple[ServiceMeshProfile, bool]: + updated = False + enable_egress_gateway = self.raw_param.get("enable_egress_gateway", False) + disable_egress_gateway = self.raw_param.get("disable_egress_gateway", False) + istio_egressgateway_name = self.raw_param.get("istio_egressgateway_name", None) + istio_egressgateway_namespace = self.raw_param.get( + "istio_egressgateway_namespace", + CONST_AZURE_SERVICE_MESH_DEFAULT_EGRESS_NAMESPACE + ) + gateway_configuration_name = self.raw_param.get("gateway_configuration_name", None) + + # disallow disable egress gateway on a cluster with no asm enabled + if disable_egress_gateway: + if new_profile is None or new_profile.mode == CONST_AZURE_SERVICE_MESH_MODE_DISABLED: + raise ArgumentUsageError( + "Istio has not been enabled for this cluster, please refer to https://aka.ms/asm-aks-addon-docs " + "for more details on enabling Azure Service Mesh." + ) + # deal with egress gateways + if enable_egress_gateway and disable_egress_gateway: + raise MutuallyExclusiveArgumentError( + "Cannot both enable and disable azure service mesh egress gateway at the same time.", + ) + if enable_egress_gateway or disable_egress_gateway: + # if a gateway is enabled, enable the mesh + if enable_egress_gateway: + + new_profile.mode = CONST_AZURE_SERVICE_MESH_MODE_ISTIO + if new_profile.istio is None: + new_profile.istio = self.models.IstioServiceMesh() # pylint: disable=no-member + updated = True + + # Gateway configuration name is required for Istio egress gateway enablement + if not gateway_configuration_name: + raise RequiredArgumentMissingError("--gateway-configuration-name is required.") + + if not istio_egressgateway_name: + raise RequiredArgumentMissingError("--istio-egressgateway-name is required.") + + # ensure necessary fields + if new_profile.istio.components is None: + new_profile.istio.components = self.models.IstioComponents() # pylint: disable=no-member + updated = True + if new_profile.istio.components.egress_gateways is None: + new_profile.istio.components.egress_gateways = [] + updated = True + # make update if the egress gateway already exists + egress_gateway_exists = False + for egress in new_profile.istio.components.egress_gateways: + if egress.name == istio_egressgateway_name and egress.namespace == istio_egressgateway_namespace: + if not egress.enabled and disable_egress_gateway: + raise ArgumentUsageError( + f'Egress gateway {istio_egressgateway_name} ' + f'in namespace {istio_egressgateway_namespace} is already disabled.' + ) + if egress.enabled and enable_egress_gateway: + if egress.gateway_configuration_name == gateway_configuration_name: + raise ArgumentUsageError( + f'Egress gateway {istio_egressgateway_name} ' + f'in namespace {istio_egressgateway_namespace} is already enabled ' + f'with gateway configuration name {gateway_configuration_name}.' + ) + egress.enabled = enable_egress_gateway + # only update gateway configuration name for enabled egress gateways + if enable_egress_gateway: + egress.gateway_configuration_name = gateway_configuration_name + egress_gateway_exists = True + updated = True + break + + # egress gateway doesn't exist, append + if not egress_gateway_exists: + if enable_egress_gateway: + new_profile.istio.components.egress_gateways.append( + self.models.IstioEgressGateway( # pylint: disable=no-member + enabled=enable_egress_gateway, + name=istio_egressgateway_name, + namespace=istio_egressgateway_namespace, + gateway_configuration_name=gateway_configuration_name, + ) + ) + elif disable_egress_gateway: + raise ArgumentUsageError( + f'Egress gateway {istio_egressgateway_name} ' + f'in namespace {istio_egressgateway_namespace} does not exist, cannot disable.' + ) + + updated = True + + return new_profile, updated + def _handle_enable_disable_asm(self, new_profile: ServiceMeshProfile) -> Tuple[ServiceMeshProfile, bool]: updated = False # enable/disable @@ -4773,6 +4865,9 @@ def update_azure_service_mesh_profile(self) -> ServiceMeshProfile: new_profile, updated_ingress_gateways_asm = self._handle_ingress_gateways_asm(new_profile) updated |= updated_ingress_gateways_asm + new_profile, updated_egress_gateways_asm = self._handle_egress_gateways_asm(new_profile) + updated |= updated_egress_gateways_asm + new_profile, updated_pluginca_asm = self._handle_pluginca_asm(new_profile) updated |= updated_pluginca_asm diff --git a/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_aks_commands.py b/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_aks_commands.py index 2287d32ddf3..45eb3b31737 100644 --- a/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_aks_commands.py +++ b/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_aks_commands.py @@ -194,12 +194,15 @@ def _get_test_identity_object_id(self) -> str: os.environ["AZURE_CLI_LIVE_TEST_IDENTITY_OBJECT_ID"] = test_identity_object_id return test_identity_object_id - def _get_asm_supported_revision(self, location): + def _get_asm_supported_revision(self, location, secondLatest=False): mesh_revisions_cmd = f"aks mesh get-revisions -l {location}" mesh_revisions = self.cmd(mesh_revisions_cmd).get_output_in_json() assert len(mesh_revisions["meshRevisions"]) > 0 revisions = [r["revision"] for r in mesh_revisions["meshRevisions"]] sorted_revisons = sort_asm_revisions(revisions) + lenRevisions = len(sorted_revisons) + if secondLatest and lenRevisions > 1: + return sorted_revisons[lenRevisions - 2] # Return the second latest revision return sorted_revisons[0] def _get_asm_upgrade_version(self, resource_group, name): @@ -2132,7 +2135,7 @@ def test_aks_azure_service_mesh_enable_disable(self, resource_group, resource_gr 'name': aks_name, 'location': resource_group_location, 'ssh_key_value': self.generate_ssh_keys(), - 'revision': self._get_asm_supported_revision(resource_group_location), + 'revision': self._get_asm_supported_revision(resource_group_location, False), }) # create cluster without --enable-azure-service-mesh @@ -2161,6 +2164,217 @@ def test_aks_azure_service_mesh_enable_disable(self, resource_group, resource_gr self.is_empty(), ]) + # live only due to installing kubectl binary + @live_only() + @AllowLargeResponse() + @AKSCustomResourceGroupPreparer( + random_name_length=17, name_prefix="clitest", location="eastus2euap" + ) + def test_aks_azure_service_mesh_with_egress_gateway( + self, resource_group, resource_group_location + ): + """This test case exercises enabling and disabling an Istio egress gateway. + + It creates a cluster with azure service mesh profile and Static Egress Gateway enabled. + After that, we create a gateway nodepool and a staticgatewayconfiguration resource. + Then, we create an Istio egress gateway, and then delete it. + """ + + # reset the count so in replay mode the random names will start with 0 + self.test_resources_count = 0 + # kwargs for string formatting + aks_name = self.create_random_name("cliakstest", 16) + self.kwargs.update( + { + "resource_group": resource_group, + "name": aks_name, + "location": resource_group_location, + "ssh_key_value": self.generate_ssh_keys(), + "revision": self._get_asm_supported_revision(resource_group_location, True), + } + ) + + # create cluster with --enable-azure-service-mesh and --enable-static-egress-gateway + # Static Egress Gateway is required for Istio Egress Gateway + create_cmd = ( + "aks create --resource-group={resource_group} --name={name} --location={location} " + "--aks-custom-headers=AKSHTTPCustomFeatures=Microsoft.ContainerService/AzureServiceMeshPreview,AKSHTTPCustomFeatures=Microsoft.ContainerService/StaticEgressGatewayPreview " + "--ssh-key-value={ssh_key_value} " + "--enable-static-egress-gateway " + "--enable-azure-service-mesh --revision={revision} --output=json" + ) + self.cmd( + create_cmd, + checks=[ + self.check("provisioningState", "Succeeded"), + self.check("serviceMeshProfile.mode", "Istio"), + self.check("networkProfile.staticEgressGatewayProfile.enabled", True) + ], + ) + + gwNodepoolName = "istiogtw" + + self.kwargs.update( + { + "gwNodepoolName": gwNodepoolName + } + ) + + # add Gateway-mode agentpool + self.cmd( + "aks nodepool add " + "--resource-group={resource_group} " + "--cluster-name={name} " + "--name={gwNodepoolName} " + "--mode=Gateway " + "--node-count=2 " + "--gateway-prefix-size=31 " + "--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/StaticEgressGatewayPreview", + checks=[ + self.check("provisioningState", "Succeeded"), + self.check("gatewayProfile.publicIpPrefixSize", 31), + ], + ) + + istio_egress_name = "istio-egress-1" + istio_egress_namespace = 'istio-ns-1' + istio_sgc_name = "istio-sgc-1" + + self.kwargs.update( + { + "istio_egress_namespace": istio_egress_namespace, + "istio_egress_name": istio_egress_name, + "istio_sgc_name": istio_sgc_name + } + ) + # install kubectl + try: + subprocess.call(["az", "aks", "install-cli"]) + except subprocess.CalledProcessError as err: + raise CLITestError("Failed to install kubectl with error: '{}'!".format(err)) + + try: + # get credential + fd, browse_path = tempfile.mkstemp() + self.kwargs.update( + { + "browse_path": browse_path, + } + ) + try: + get_credential_cmd = "aks get-credentials -n {name} -g {resource_group} -f {browse_path}" + self.cmd(get_credential_cmd) + finally: + os.close(fd) + + sgcResource = f"""apiVersion: egressgateway.kubernetes.azure.com/v1alpha1 +kind: StaticGatewayConfiguration +metadata: + name: {istio_sgc_name} + namespace: {istio_egress_namespace} +spec: + gatewayNodepoolName: {gwNodepoolName} +""" + + sgc_fd, sgc_browse_path = tempfile.mkstemp() + + try: + with os.fdopen(sgc_fd, 'w') as temp_file: + temp_file.write(sgcResource) + + k_create_sgc_namespace_command = ["kubectl", "create", "namespace", istio_egress_namespace, "--kubeconfig", browse_path] + k_create_sgc_namespace_output = subprocess.check_output( + k_create_sgc_namespace_command, + universal_newlines=True, + stderr=subprocess.STDOUT, + ) + if not f"namespace/{istio_egress_namespace} created" in k_create_sgc_namespace_output: + raise CLITestError(f"failed to create istio egress gateway namespace: {istio_egress_namespace}") + + k_create_sgc_command = ["kubectl", "apply", "-f", sgc_browse_path, "--kubeconfig", browse_path] + k_create_sgc_output = subprocess.check_output( + k_create_sgc_command, + universal_newlines=True, + stderr=subprocess.STDOUT, + ) + if not f"staticgatewayconfiguration.egressgateway.kubernetes.azure.com/{istio_sgc_name} created" in k_create_sgc_output: + raise CLITestError("failed to create StaticGatewayConfiguration") + finally: + # Delete files + if os.path.exists(browse_path): + os.remove(browse_path) + + if os.path.exists(sgc_browse_path): + os.remove(sgc_browse_path) + + # enable Istio egress gateway + update_cmd = ( + "aks mesh enable-egress-gateway --resource-group={resource_group} --name={name} " + "--istio-egressgateway-name {istio_egress_name} --istio-egressgateway-namespace {istio_egress_namespace} " + "--gateway-configuration-name {istio_sgc_name}" + ) + self.cmd( + update_cmd, + checks=[ + self.check("serviceMeshProfile.mode", "Istio"), + self.check( + "serviceMeshProfile.istio.components.egressGateways[0].name", + istio_egress_name, + ), + self.check( + "serviceMeshProfile.istio.components.egressGateways[0].enabled", + True, + ), + self.check( + "serviceMeshProfile.istio.components.egressGateways[0].namespace", + istio_egress_namespace, + ), + self.check( + "serviceMeshProfile.istio.components.egressGateways[0].gatewayConfigurationName", + istio_sgc_name, + ), + ], + ) + + # disable the egress gateway + update_cmd = ( + "aks mesh disable-egress-gateway --resource-group={resource_group} --name={name} " + "--istio-egressgateway-name {istio_egress_name} --istio-egressgateway-namespace {istio_egress_namespace} --yes" + ) + self.cmd( + update_cmd, + checks=[ + self.check("serviceMeshProfile.mode", "Istio"), + self.check( + "serviceMeshProfile.istio.components.egressGateways[0].name", + istio_egress_name, + ), + self.check( + "serviceMeshProfile.istio.components.egressGateways[0].enabled", + None, + ), + self.check( + "serviceMeshProfile.istio.components.egressGateways[0].namespace", + istio_egress_namespace, + ), + self.check( + "serviceMeshProfile.istio.components.egressGateways[0].gatewayConfigurationName", + istio_sgc_name, + ), + ], + ) + finally: + # delete the cluster + delete_cmd = ( + "aks delete --resource-group={resource_group} --name={name} --yes --no-wait" + ) + self.cmd( + delete_cmd, + checks=[ + self.is_empty(), + ], + ) + @AllowLargeResponse() @AKSCustomResourceGroupPreparer(random_name_length=17, name_prefix='clitest', location='westus2') def test_aks_machine_cmds(self, resource_group, resource_group_location): @@ -2238,7 +2452,7 @@ def test_aks_azure_service_mesh_with_ingress_gateway(self, resource_group, resou 'name': aks_name, 'location': resource_group_location, 'ssh_key_value': self.generate_ssh_keys(), - 'revision': self._get_asm_supported_revision(resource_group_location), + 'revision': self._get_asm_supported_revision(resource_group_location, False), }) # create cluster with --enable-azure-service-mesh @@ -2291,7 +2505,7 @@ def test_aks_azure_service_mesh_canary_upgrade( self.test_resources_count = 0 # kwargs for string formatting aks_name = self.create_random_name("cliakstest", 16) - installed_revision = self._get_asm_supported_revision(resource_group_location) + installed_revision = self._get_asm_supported_revision(resource_group_location, False) self.kwargs.update( { "resource_group": resource_group, @@ -2412,7 +2626,7 @@ def test_aks_azure_service_mesh_with_pluginca(self, resource_group, resource_gro 'location': resource_group_location, 'ssh_key_value': self.generate_ssh_keys(), 'akv_resource_id': akv_resource_id, - 'revision': self._get_asm_supported_revision(resource_group_location), + 'revision': self._get_asm_supported_revision(resource_group_location, False), }) # create cluster @@ -2482,7 +2696,7 @@ def test_aks_azure_service_mesh_get_upgrades(self, resource_group, resource_grou 'name': aks_name, 'location': resource_group_location, 'ssh_key_value': self.generate_ssh_keys(), - 'revision': self._get_asm_supported_revision(resource_group_location), + 'revision': self._get_asm_supported_revision(resource_group_location, False), }) # create cluster diff --git a/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_managed_cluster_decorator.py b/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_managed_cluster_decorator.py index 1d502bd00d5..99e0cab01aa 100644 --- a/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_managed_cluster_decorator.py +++ b/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_managed_cluster_decorator.py @@ -49,6 +49,7 @@ CONST_AZURE_SERVICE_MESH_UPGRADE_COMMAND_COMPLETE, CONST_AZURE_SERVICE_MESH_UPGRADE_COMMAND_ROLLBACK, CONST_AZURE_SERVICE_MESH_MODE_DISABLED, + CONST_AZURE_SERVICE_MESH_DEFAULT_EGRESS_NAMESPACE, CONST_ARTIFACT_SOURCE_DIRECT, CONST_AVAILABILITY_SET, CONST_VIRTUAL_MACHINES, @@ -6085,6 +6086,63 @@ def test_handle_ingress_gateways_asm(self): ), )) + def test_handle_egress_gateways_asm(self): + ctx_0 = AKSManagedClusterContext( + self.cmd, + AKSManagedClusterParamDict( + { + "enable_azure_service_mesh": True, + "enable_egress_gateway": True, + "istio_egressgateway_name": "istio-egress-1", + "gateway_configuration_name": "istio-sgc-1", + } + ), + self.models, + decorator_mode=DecoratorMode.UPDATE, + ) + old_profile = self.models.ServiceMeshProfile( + mode=CONST_AZURE_SERVICE_MESH_MODE_DISABLED, + istio=self.models.IstioServiceMesh(), + ) + new_profile, updated = ctx_0._handle_egress_gateways_asm(old_profile) + self.assertEqual(updated, True) + self.assertEqual(new_profile, self.models.ServiceMeshProfile( + mode="Istio", + istio=self.models.IstioServiceMesh( + components=self.models.IstioComponents( + egress_gateways=[ + self.models.IstioEgressGateway( + enabled=True, + name="istio-egress-1", + namespace=CONST_AZURE_SERVICE_MESH_DEFAULT_EGRESS_NAMESPACE, + gateway_configuration_name="istio-sgc-1" + ) + ] + ) + ), + )) + # ASM was never enabled on the cluster + old_profile = self.models.ServiceMeshProfile( + mode=CONST_AZURE_SERVICE_MESH_MODE_DISABLED, + ) + new_profile, updated = ctx_0._handle_egress_gateways_asm(old_profile) + self.assertEqual(updated, True) + self.assertEqual(new_profile, self.models.ServiceMeshProfile( + mode="Istio", + istio=self.models.IstioServiceMesh( + components=self.models.IstioComponents( + egress_gateways=[ + self.models.IstioEgressGateway( + enabled=True, + name="istio-egress-1", + namespace=CONST_AZURE_SERVICE_MESH_DEFAULT_EGRESS_NAMESPACE, + gateway_configuration_name="istio-sgc-1" + ) + ] + ) + ), + )) + def test_handle_pluginca_asm(self): ctx_0 = AKSManagedClusterContext( self.cmd, @@ -12701,6 +12759,42 @@ def test_update_service_mesh_profile(self): ) self.assertEqual(dec_mc_3, ground_truth_mc_3) + dec_4 = AKSManagedClusterUpdateDecorator( + self.cmd, + self.client, + { + "enable_azure_service_mesh": True, + "enable_egress_gateway": True, + "istio_egressgateway_name": "istio-egress-1", + "gateway_configuration_name": "istio-sgc-1", + }, + ResourceType.MGMT_CONTAINERSERVICE, + ) + mc_4 = self.models.ManagedCluster( + location="test_location", + ) + dec_4.context.attach_mc(mc_4) + dec_mc_4 = dec_4.update_azure_service_mesh_profile(mc_4) + ground_truth_mc_4 = self.models.ManagedCluster( + location="test_location", + service_mesh_profile=self.models.ServiceMeshProfile( + mode="Istio", + istio=self.models.IstioServiceMesh( + components=self.models.IstioComponents( + egress_gateways=[ + self.models.IstioEgressGateway( + enabled=True, + name="istio-egress-1", + gateway_configuration_name="istio-sgc-1", + namespace=CONST_AZURE_SERVICE_MESH_DEFAULT_EGRESS_NAMESPACE, + ) + ] + ) + ), + ), + ) + self.assertEqual(dec_mc_4, ground_truth_mc_4) + # aks mesh upgrade start dec_5 = AKSManagedClusterUpdateDecorator( self.cmd, @@ -12907,6 +13001,38 @@ def test_update_service_mesh_profile(self): with self.assertRaises(ArgumentUsageError): dec_11.update_azure_service_mesh_profile(mc_11) + # az aks mesh disable-ingress-gateway - when azure service mesh was never enabled + dec_12 = AKSManagedClusterUpdateDecorator( + self.cmd, + self.client, + { + "disable_ingress_gateway": True, + }, + ResourceType.MGMT_CONTAINERSERVICE, + ) + mc_12 = self.models.ManagedCluster( + location="test_location", + ) + dec_12.context.attach_mc(mc_12) + with self.assertRaises(ArgumentUsageError): + dec_12.update_azure_service_mesh_profile(mc_12) + + # az aks mesh disable-egress-gateway - when azure service mesh was never enabled + dec_13 = AKSManagedClusterUpdateDecorator( + self.cmd, + self.client, + { + "disable_egress_gateway": True, + }, + ResourceType.MGMT_CONTAINERSERVICE, + ) + mc_13 = self.models.ManagedCluster( + location="test_location", + ) + dec_13.context.attach_mc(mc_13) + with self.assertRaises(ArgumentUsageError): + dec_13.update_azure_service_mesh_profile(mc_13) + def test_set_up_app_routing_profile(self): dec_1 = AKSManagedClusterCreateDecorator( self.cmd,