From db8f1cc141e64b21fa994b8dee3e2d7693b0bad0 Mon Sep 17 00:00:00 2001 From: Neha Arora Date: Fri, 5 Sep 2025 13:59:35 -0700 Subject: [PATCH 1/7] AKS: Change --enable-azure-container-storage --disable-azure-container-storage behavior and add --container-storage-version --- src/aks-preview/azext_aks_preview/_params.py | 94 +- .../azurecontainerstorage/_consts.py | 8 +- .../azurecontainerstorage/_helpers.py | 40 +- .../azurecontainerstorage/_validators.py | 160 ++- .../azurecontainerstorage/acstor_ops.py | 132 +- src/aks-preview/azext_aks_preview/custom.py | 2 + .../managed_cluster_decorator.py | 1113 ++++++++++------- .../tests/latest/test_aks_commands.py | 336 ++++- .../tests/latest/test_validators.py | 388 ++++-- src/aks-preview/linter_exclusions.yml | 6 + 10 files changed, 1678 insertions(+), 601 deletions(-) diff --git a/src/aks-preview/azext_aks_preview/_params.py b/src/aks-preview/azext_aks_preview/_params.py index 8e4e871eda4..321f24032df 100644 --- a/src/aks-preview/azext_aks_preview/_params.py +++ b/src/aks-preview/azext_aks_preview/_params.py @@ -441,6 +441,12 @@ ] # azure container storage +# azure container storage +container_storage_versions = [ + "1", + "2" +] + storage_pool_types = [ CONST_STORAGE_POOL_TYPE_AZURE_DISK, CONST_STORAGE_POOL_TYPE_EPHEMERAL_DISK, @@ -1028,8 +1034,14 @@ def load_arguments(self, _): # azure container storage c.argument( "enable_azure_container_storage", - arg_type=get_enum_type(storage_pool_types), - help="enable azure container storage and define storage pool type", + arg_type=_get_enable_azure_container_storage_type(), + help="enable azure container storage. Can be used as a flag (defaults to True) or with a" + " storage pool type value: (azureDisk, ephemeralDisk, elasticSan)", + ) + c.argument( + "container_storage_version", + arg_type=get_enum_type(container_storage_versions), + help="set azure container storage version, the latest version will be installed by default", ) c.argument( "storage_pool_name", @@ -1488,13 +1500,21 @@ def load_arguments(self, _): # azure container storage c.argument( "enable_azure_container_storage", - arg_type=get_enum_type(storage_pool_types), - help="enable azure container storage and define storage pool type", + arg_type=_get_enable_azure_container_storage_type(), + help="enable azure container storage. Can be used as a flag (defaults to True) or with a" + " storage pool type value: (azureDisk, ephemeralDisk, elasticSan)", ) c.argument( "disable_azure_container_storage", - arg_type=get_enum_type(disable_storage_pool_types), - help="disable azure container storage or any one of the storage pool types", + arg_type=_get_disable_azure_container_storage_type(), + help="disable azure container storage or any one of the storage pool types." + " Can be used as a flag (defaults to True) or with a storagepool type value:" + " azureDisk, ephemeralDisk, elasticSan, all (to disable all storage pools).", + ) + c.argument( + "container_storage_version", + arg_type=get_enum_type(container_storage_versions), + help="set azure container storage version, the latest version will be installed by default", ) c.argument( "storage_pool_name", @@ -2922,3 +2942,65 @@ def _get_default_install_location(exe_name): else: install_location = None return install_location + + +def _get_enable_azure_container_storage_type(): + """Custom argument type that accepts both None and enum values""" + import argparse + from azure.cli.core.azclierror import InvalidArgumentValueError + + class AzureContainerStorageAction(argparse.Action): + def __call__(self, parser, namespace, values, option_string=None): + if values is None: + # When used as a flag without value, set as True + setattr(namespace, self.dest, True) + return + + if isinstance(values, str): + # Handle enum values (case insensitive) + for storage_type in storage_pool_types: + if values.lower() == storage_type.lower(): + setattr(namespace, self.dest, storage_type) + return + + # Invalid value + valid_values = storage_pool_types + raise InvalidArgumentValueError( + f"Invalid value '{values}'. Valid values are: {', '.join(valid_values)}" + ) + + return CLIArgumentType( + nargs='?', # Optional argument + action=AzureContainerStorageAction, + ) + + +def _get_disable_azure_container_storage_type(): + """Custom argument type that accepts both None and enum values""" + import argparse + from azure.cli.core.azclierror import InvalidArgumentValueError + + class AzureContainerStorageAction(argparse.Action): + def __call__(self, parser, namespace, values, option_string=None): + if values is None: + # When used as a flag without value, set as True + setattr(namespace, self.dest, True) + return + + if isinstance(values, str): + # Handle enum values (case insensitive) + for storage_type in disable_storage_pool_types: + if values.lower() == storage_type.lower(): + setattr(namespace, self.dest, storage_type) + return + + # Invalid value + valid_values = disable_storage_pool_types + raise InvalidArgumentValueError( + f"Invalid value '{values}'. Valid values are: {', '.join(valid_values)}" + ) + + return CLIArgumentType( + nargs='?', # Optional argument + action=AzureContainerStorageAction, + ) diff --git a/src/aks-preview/azext_aks_preview/azurecontainerstorage/_consts.py b/src/aks-preview/azext_aks_preview/azurecontainerstorage/_consts.py index b428abff7d5..3019cdbd640 100644 --- a/src/aks-preview/azext_aks_preview/azurecontainerstorage/_consts.py +++ b/src/aks-preview/azext_aks_preview/azurecontainerstorage/_consts.py @@ -6,13 +6,17 @@ CONST_ACSTOR_ALL = "all" CONST_ACSTOR_IO_ENGINE_LABEL_KEY = "acstor.azure.com/io-engine" CONST_ACSTOR_IO_ENGINE_LABEL_VAL = "acstor" -CONST_ACSTOR_K8S_EXTENSION_NAME = "microsoft.azurecontainerstorage" +CONST_ACSTOR_V1_K8S_EXTENSION_NAME = "microsoft.azurecontainerstorage" +CONST_ACSTOR_V1_EXT_INSTALLATION_NAME = "azurecontainerstorage" +CONST_ACSTOR_VERSION_V1 = "1" CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY = "EphemeralVolumeOnly" CONST_DISK_TYPE_PV_WITH_ANNOTATION = "PersistentVolumeWithAnnotation" CONST_EPHEMERAL_NVME_PERF_TIER_BASIC = "Basic" CONST_EPHEMERAL_NVME_PERF_TIER_PREMIUM = "Premium" CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD = "Standard" -CONST_EXT_INSTALLATION_NAME = "azurecontainerstorage" +CONST_ACSTOR_EXT_INSTALLATION_NAME = "acstor" +CONST_ACSTOR_EXT_INSTALLATION_NAMESPACE = "kube-system" +CONST_ACSTOR_K8S_EXTENSION_NAME = "microsoft.azurecontainerstoragev2" CONST_K8S_EXTENSION_CLIENT_FACTORY_MOD_NAME = "azext_k8s_extension._client_factory" CONST_K8S_EXTENSION_CUSTOM_MOD_NAME = "azext_k8s_extension.custom" CONST_K8S_EXTENSION_NAME = "k8s-extension" diff --git a/src/aks-preview/azext_aks_preview/azurecontainerstorage/_helpers.py b/src/aks-preview/azext_aks_preview/azurecontainerstorage/_helpers.py index 4bed9c333ca..6f7bec95f22 100644 --- a/src/aks-preview/azext_aks_preview/azurecontainerstorage/_helpers.py +++ b/src/aks-preview/azext_aks_preview/azurecontainerstorage/_helpers.py @@ -14,7 +14,6 @@ CONST_EPHEMERAL_NVME_PERF_TIER_BASIC, CONST_EPHEMERAL_NVME_PERF_TIER_PREMIUM, CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD, - CONST_EXT_INSTALLATION_NAME, CONST_K8S_EXTENSION_CLIENT_FACTORY_MOD_NAME, CONST_K8S_EXTENSION_CUSTOM_MOD_NAME, CONST_K8S_EXTENSION_NAME, @@ -23,13 +22,15 @@ CONST_STORAGE_POOL_TYPE_AZURE_DISK, CONST_STORAGE_POOL_TYPE_ELASTIC_SAN, CONST_STORAGE_POOL_TYPE_EPHEMERAL_DISK, + CONST_ACSTOR_V1_K8S_EXTENSION_NAME, + CONST_ACSTOR_V1_EXT_INSTALLATION_NAME, ) from azure.cli.command_modules.acs._roleassignments import ( add_role_assignment, build_role_scope, delete_role_assignments, ) -from azure.cli.core.azclierror import UnknownError +from azure.cli.core.azclierror import ResourceNotFoundError, UnknownError from knack.log import get_logger logger = get_logger(__name__) @@ -133,12 +134,12 @@ def check_if_extension_is_installed(cmd, resource_group, cluster_name) -> bool: client, resource_group, cluster_name, - CONST_EXT_INSTALLATION_NAME, + CONST_ACSTOR_V1_EXT_INSTALLATION_NAME, "managedClusters", ) extension_type = extension.extension_type.lower() - if extension_type != CONST_ACSTOR_K8S_EXTENSION_NAME: + if extension_type != CONST_ACSTOR_V1_K8S_EXTENSION_NAME: return_val = False except: # pylint: disable=bare-except return_val = False @@ -177,7 +178,7 @@ def get_extension_installed_and_cluster_configs( client, resource_group, cluster_name, - CONST_EXT_INSTALLATION_NAME, + CONST_ACSTOR_V1_EXT_INSTALLATION_NAME, "managedClusters", ) @@ -252,6 +253,35 @@ def get_extension_installed_and_cluster_configs( ) +def get_container_storage_extension_installed( + cmd, + resource_group, + cluster_name, + extension_name, +) -> Tuple[bool, str]: + + client_factory = get_k8s_extension_module(CONST_K8S_EXTENSION_CLIENT_FACTORY_MOD_NAME) + client = client_factory.cf_k8s_extension_operation(cmd.cli_ctx) + k8s_extension_custom_mod = get_k8s_extension_module(CONST_K8S_EXTENSION_CUSTOM_MOD_NAME) + is_extension_installed = False + extension_version = "" + + try: + extension = k8s_extension_custom_mod.show_k8s_extension( + client, + resource_group, + cluster_name, + extension_name, + "managedClusters", + ) + is_extension_installed = True + extension_version = extension.current_version + except ResourceNotFoundError: + # Extension not found, which is expected if not installed. + is_extension_installed = False + return is_extension_installed, extension_version + + def get_initial_resource_value_args( storage_pool_type, storage_pool_option, diff --git a/src/aks-preview/azext_aks_preview/azurecontainerstorage/_validators.py b/src/aks-preview/azext_aks_preview/azurecontainerstorage/_validators.py index 29e7433b7df..143b6f56d25 100644 --- a/src/aks-preview/azext_aks_preview/azurecontainerstorage/_validators.py +++ b/src/aks-preview/azext_aks_preview/azurecontainerstorage/_validators.py @@ -10,6 +10,7 @@ ) from azext_aks_preview.azurecontainerstorage._consts import ( CONST_ACSTOR_ALL, + CONST_ACSTOR_VERSION_V1, CONST_ACSTOR_IO_ENGINE_LABEL_KEY, CONST_ACSTOR_IO_ENGINE_LABEL_VAL, CONST_STORAGE_POOL_OPTION_NVME, @@ -42,7 +43,7 @@ logger = get_logger(__name__) -def validate_disable_azure_container_storage_params( # pylint: disable=too-many-branches +def validate_disable_azure_container_storage_params_v1( # pylint: disable=too-many-branches storage_pool_type, storage_pool_name, storage_pool_sku, @@ -63,6 +64,13 @@ def validate_disable_azure_container_storage_params( # pylint: disable=too-many 'Azure Container Storage is not enabled in the cluster.' ) + if not isinstance(storage_pool_type, str): + raise InvalidArgumentValueError( + f'Azure Container Storage has version {CONST_ACSTOR_VERSION_V1} installed. ' + 'A pool type value must be specified when using --disable-azure-container-storage. ' + " Allowed values are: all, azureDisk, elasticSan, ephemeralDisk" + ) + if storage_pool_name is not None: raise MutuallyExclusiveArgumentError( 'Conflicting flags. Cannot define --storage-pool-name value ' @@ -173,7 +181,7 @@ def validate_disable_azure_container_storage_params( # pylint: disable=too-many ) -def validate_enable_azure_container_storage_params( # pylint: disable=too-many-locals,too-many-branches +def validate_enable_azure_container_storage_v1_params( # pylint: disable=too-many-locals,too-many-branches storage_pool_type, storage_pool_name, storage_pool_sku, @@ -181,7 +189,9 @@ def validate_enable_azure_container_storage_params( # pylint: disable=too-many- storage_pool_size, nodepool_list, agentpool_details, - is_extension_installed, + is_v1_extension_installed, + is_v2_extension_installed, + v2_extension_version, is_azureDisk_enabled, is_elasticSan_enabled, is_ephemeralDisk_localssd_enabled, @@ -191,6 +201,31 @@ def validate_enable_azure_container_storage_params( # pylint: disable=too-many- existing_ephemeral_disk_volume_type, existing_ephemeral_disk_nvme_perf_tier, ): + + if is_v2_extension_installed: + # Hardcoding this error message to version 1 because this is only valid for v1 + raise InvalidArgumentValueError( + 'Failed to enable Azure Container Storage version 1 as Azure Container Storage version ' + f'{v2_extension_version} is already installed on the cluster. Try enabling this version on another ' + 'cluster. You can also enable this version by first disabling the existing installation of ' + 'Azure Container Storage by running --disable-azure-container-storage. ' + 'Note that disabling can impact existing workloads that depend on Azure Container Storage.' + ) + + if storage_pool_type not in [ + CONST_STORAGE_POOL_TYPE_AZURE_DISK, + CONST_STORAGE_POOL_TYPE_ELASTIC_SAN, + CONST_STORAGE_POOL_TYPE_EPHEMERAL_DISK + ]: + raise InvalidArgumentValueError( + f'Storage pool type value must be specified for --enable-azure-container-storage ' + 'when enabling Azure Container Storage v1. ' + 'Supported values are ' + f'{CONST_STORAGE_POOL_TYPE_AZURE_DISK}, ' + f'{CONST_STORAGE_POOL_TYPE_ELASTIC_SAN}, ' + f'{CONST_STORAGE_POOL_TYPE_EPHEMERAL_DISK}.' + ) + if storage_pool_name is not None: pattern = r'[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*' is_pool_name_valid = re.fullmatch(pattern, storage_pool_name) @@ -228,11 +263,11 @@ def validate_enable_azure_container_storage_params( # pylint: disable=too-many- required_type_installed_for_nvme_perf_tier = False if ephemeral_disk_volume_type is not None: - required_type_installed_for_disk_vol_type = is_extension_installed and \ + required_type_installed_for_disk_vol_type = is_v1_extension_installed and \ (is_ephemeralDisk_localssd_enabled or is_ephemeralDisk_nvme_enabled) if ephemeral_disk_nvme_perf_tier is not None: - required_type_installed_for_nvme_perf_tier = is_extension_installed and \ + required_type_installed_for_nvme_perf_tier = is_v1_extension_installed and \ is_ephemeralDisk_nvme_enabled if storage_pool_option is None: @@ -364,10 +399,10 @@ def validate_enable_azure_container_storage_params( # pylint: disable=too-many- storage_pool_type, storage_pool_option, storage_pool_sku, - is_extension_installed + is_v1_extension_installed ) - if is_extension_installed: + if is_v1_extension_installed: if (is_azureDisk_enabled and storage_pool_type == CONST_STORAGE_POOL_TYPE_AZURE_DISK) or \ (is_elasticSan_enabled and @@ -396,6 +431,117 @@ def validate_enable_azure_container_storage_params( # pylint: disable=too-many- ) +def validate_enable_azure_container_storage_params( + is_extension_installed, + is_v1_extension_installed, + v1_extension_version, + storage_pool_type, + storage_pool_name, + storage_pool_sku, + storage_pool_option, + storage_pool_size +): + if is_v1_extension_installed: + raise InvalidArgumentValueError( + f'Failed to enable the latest version of Azure Container Storage as version {v1_extension_version} ' + 'is already installed on the cluster. Try enabling Azure Container Storage in another cluster. ' + 'You can also enable the latest version by first disabling the existing installation using ' + '--disable-azure-container-storage all. Note that disabling can impact existing workloads ' + 'that depend on Azure Container Storage.' + ) + + if is_extension_installed: + raise InvalidArgumentValueError( + 'Cannot enable Azure Container Storage as it is already enabled on the cluster.' + ) + + # Todo: Remove this for the 2.1.0 release + if storage_pool_type is not None and not isinstance(storage_pool_type, bool): + raise InvalidArgumentValueError( + 'The latest version of Azure Container Storage only supports ephemeral nvme storage and does not ' + 'require or support a storage-pool-type value for --enable-azure-container-storage parameter. ' + f'Please remove {storage_pool_type} from the command and try again.' + ) + + if storage_pool_name is not None: + raise InvalidArgumentValueError( + 'The latest version of Azure Container Storage does not ' + 'require or support a --storage-pool-name value. ' + f'Please remove --storage-pool-name {storage_pool_name} from the command and try again.' + ) + + if storage_pool_sku is not None: + raise InvalidArgumentValueError( + 'The latest version of Azure Container Storage does not ' + 'require or support a --storage-pool-sku value. ' + f'Please remove --storage-pool-sku {storage_pool_sku} from the command and try again.' + ) + + if storage_pool_option is not None: + raise InvalidArgumentValueError( + 'The latest version of Azure Container Storage does not ' + 'require or support a --storage-pool-option value. ' + f'Please remove --storage-pool-option {storage_pool_option} from the command and try again.' + ) + + if storage_pool_size is not None: + raise InvalidArgumentValueError( + 'The latest version of Azure Container Storage does not ' + 'require or support a --storage-pool-size value. ' + f'Please remove --storage-pool-size {storage_pool_size} from the command and try again.' + ) + + +def validate_disable_azure_container_storage_params( + is_extension_installed, + storage_pool_type, + storage_pool_name, + storage_pool_sku, + storage_pool_option, + storage_pool_size +): + if not is_extension_installed: + raise InvalidArgumentValueError( + 'Cannot disable Azure Container Storage as it is not enabled on the cluster.' + ) + + # Todo: Remove this for the 2.1.0 release + if storage_pool_type is not None and not isinstance(storage_pool_type, bool): + raise InvalidArgumentValueError( + 'The latest version of Azure Container Storage only supports ephemeral nvme storage and does not ' + 'require or support a storage-pool-type value for --disable-azure-container-storage parameter. ' + f'Please remove {storage_pool_type} from the command and try again.' + ) + + if storage_pool_name is not None: + raise InvalidArgumentValueError( + 'The latest version of Azure Container Storage does not ' + 'require or support a --storage-pool-name value. ' + f'Please remove --storage-pool-name {storage_pool_name} from the command and try again.' + ) + + if storage_pool_sku is not None: + raise InvalidArgumentValueError( + 'The latest version of Azure Container Storage does not ' + 'require or support a --storage-pool-sku value. ' + f'Please remove --storage-pool-sku {storage_pool_sku} from the command and try again.' + ) + + if storage_pool_option is not None: + raise InvalidArgumentValueError( + 'The latest version of Azure Container Storage does not ' + 'require or support a --storage-pool-option value. ' + f'Please remove --storage-pool-option {storage_pool_option} from the command and try again.' + ) + + if storage_pool_size is not None: + raise InvalidArgumentValueError( + 'The latest version of Azure Container Storage does not ' + 'require or support a --storage-pool-size value. ' + f'Please remove --storage-pool-size {storage_pool_size} from the command and try again.' + ) + + # _Validate_storage_pool_size validates that the storage_pool_size is # string of a combination of a float number immediately followed by # Ti or Gi e.g. 2Ti, 512Gi, 1.5Ti. The function also validates that the diff --git a/src/aks-preview/azext_aks_preview/azurecontainerstorage/acstor_ops.py b/src/aks-preview/azext_aks_preview/azurecontainerstorage/acstor_ops.py index b2be006331e..981d8010fde 100644 --- a/src/aks-preview/azext_aks_preview/azurecontainerstorage/acstor_ops.py +++ b/src/aks-preview/azext_aks_preview/azurecontainerstorage/acstor_ops.py @@ -7,11 +7,14 @@ from azure.cli.core.commands import LongRunningOperation from azext_aks_preview.azurecontainerstorage._consts import ( CONST_ACSTOR_ALL, + CONST_ACSTOR_V1_K8S_EXTENSION_NAME, + CONST_ACSTOR_V1_EXT_INSTALLATION_NAME, + CONST_ACSTOR_EXT_INSTALLATION_NAME, + CONST_ACSTOR_EXT_INSTALLATION_NAMESPACE, CONST_ACSTOR_K8S_EXTENSION_NAME, CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, CONST_DISK_TYPE_PV_WITH_ANNOTATION, CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD, - CONST_EXT_INSTALLATION_NAME, CONST_K8S_EXTENSION_CLIENT_FACTORY_MOD_NAME, CONST_K8S_EXTENSION_CUSTOM_MOD_NAME, CONST_STORAGE_POOL_DEFAULT_SIZE, @@ -37,7 +40,7 @@ logger = get_logger(__name__) -def perform_enable_azure_container_storage( # pylint: disable=too-many-statements,too-many-locals,too-many-branches +def perform_enable_azure_container_storage_v1( # pylint: disable=too-many-statements,too-many-locals,too-many-branches cmd, subscription_id, resource_group, @@ -226,7 +229,7 @@ def perform_enable_azure_container_storage( # pylint: disable=too-many-statemen client, resource_group, cluster_name, - CONST_EXT_INSTALLATION_NAME, + CONST_ACSTOR_V1_EXT_INSTALLATION_NAME, "managedClusters", configuration_settings=config_settings, yes=True, @@ -239,9 +242,9 @@ def perform_enable_azure_container_storage( # pylint: disable=too-many-statemen client, resource_group, cluster_name, - CONST_EXT_INSTALLATION_NAME, + CONST_ACSTOR_V1_EXT_INSTALLATION_NAME, "managedClusters", - CONST_ACSTOR_K8S_EXTENSION_NAME, + CONST_ACSTOR_V1_K8S_EXTENSION_NAME, auto_upgrade_minor_version=True, release_train="stable", scope="cluster", @@ -320,7 +323,7 @@ def perform_enable_azure_container_storage( # pylint: disable=too-many-statemen client, resource_group, cluster_name, - CONST_EXT_INSTALLATION_NAME, + CONST_ACSTOR_V1_EXT_INSTALLATION_NAME, "managedClusters", yes=True, no_wait=True, @@ -331,7 +334,7 @@ def perform_enable_azure_container_storage( # pylint: disable=too-many-statemen client, resource_group, cluster_name, - CONST_EXT_INSTALLATION_NAME, + CONST_ACSTOR_V1_EXT_INSTALLATION_NAME, "managedClusters", configuration_settings=update_settings, yes=True, @@ -339,7 +342,7 @@ def perform_enable_azure_container_storage( # pylint: disable=too-many-statemen ) -def perform_disable_azure_container_storage( # pylint: disable=too-many-statements,too-many-locals,too-many-branches +def perform_disable_azure_container_storage_v1( # pylint: disable=too-many-statements,too-many-locals,too-many-branches cmd, subscription_id, resource_group, @@ -418,7 +421,7 @@ def perform_disable_azure_container_storage( # pylint: disable=too-many-stateme client, resource_group, cluster_name, - CONST_EXT_INSTALLATION_NAME, + CONST_ACSTOR_V1_EXT_INSTALLATION_NAME, "managedClusters", configuration_settings=config_settings, yes=True, @@ -471,7 +474,7 @@ def perform_disable_azure_container_storage( # pylint: disable=too-many-stateme client, resource_group, cluster_name, - CONST_EXT_INSTALLATION_NAME, + CONST_ACSTOR_V1_EXT_INSTALLATION_NAME, "managedClusters", configuration_settings=config_settings, yes=True, @@ -497,7 +500,7 @@ def perform_disable_azure_container_storage( # pylint: disable=too-many-stateme client, resource_group, cluster_name, - CONST_EXT_INSTALLATION_NAME, + CONST_ACSTOR_V1_EXT_INSTALLATION_NAME, "managedClusters", yes=True, no_wait=no_wait_delete_op, @@ -605,7 +608,7 @@ def perform_disable_azure_container_storage( # pylint: disable=too-many-stateme client, resource_group, cluster_name, - CONST_EXT_INSTALLATION_NAME, + CONST_ACSTOR_V1_EXT_INSTALLATION_NAME, "managedClusters", configuration_settings=config_settings, yes=True, @@ -679,7 +682,7 @@ def perform_disable_azure_container_storage( # pylint: disable=too-many-stateme client, resource_group, cluster_name, - CONST_EXT_INSTALLATION_NAME, + CONST_ACSTOR_V1_EXT_INSTALLATION_NAME, "managedClusters", configuration_settings=update_settings, yes=True, @@ -688,3 +691,106 @@ def perform_disable_azure_container_storage( # pylint: disable=too-many-stateme if not disable_op_failure: logger.warning("Azure Container Storage storagepool type %s has been disabled.", storage_pool_type) + + +def perform_enable_azure_container_storage( + cmd, + resource_group, + cluster_name, + is_called_from_extension=False, +): + # This will be set true only when aks-preview extension is used + # and we want the aks-preview ManagedClusterDecorator to call the + # perform_disable_azure_container_storage function. + if not is_called_from_extension: + return + + client_factory = get_k8s_extension_module(CONST_K8S_EXTENSION_CLIENT_FACTORY_MOD_NAME) + client = client_factory.cf_k8s_extension_operation(cmd.cli_ctx) + + k8s_extension_custom_mod = get_k8s_extension_module(CONST_K8S_EXTENSION_CUSTOM_MOD_NAME) + config_settings = [] + delete_extension = False + + try: + result = k8s_extension_custom_mod.create_k8s_extension( + cmd, + client, + resource_group, + cluster_name, + CONST_ACSTOR_EXT_INSTALLATION_NAME, + "managedClusters", + CONST_ACSTOR_K8S_EXTENSION_NAME, + auto_upgrade_minor_version=True, + release_train="stable", + scope="cluster", + release_namespace=CONST_ACSTOR_EXT_INSTALLATION_NAMESPACE, + configuration_settings=config_settings, + ) + op_text = "Azure Container Storage successfully installed" + long_op_result = LongRunningOperation(cmd.cli_ctx)(result) + if long_op_result.provisioning_state == "Succeeded": + logger.warning(op_text) + except Exception as ex: # pylint: disable=broad-except + logger.error("Azure Container Storage failed to install.\nError: %s", ex) + delete_extension = True + + if delete_extension: + logger.warning("Cleaning up the cluster by disabling Azure Container Storage") + try: + delete_op_result = k8s_extension_custom_mod.delete_k8s_extension( + cmd, + client, + resource_group, + cluster_name, + CONST_ACSTOR_EXT_INSTALLATION_NAME, + "managedClusters", + yes=True, + ) + + LongRunningOperation(cmd.cli_ctx)(delete_op_result) + logger.warning("Azure Container Storage has been disabled.") + logger.warning( + "Please retry enabling Azure Container Storage by running " + "`az aks update` along with `--enable-azure-container-storage`" + ) + except Exception as delete_ex: + raise UnknownError( + "Failed to disable Azure Container Storage with error: %s" % delete_ex + ) from delete_ex + + +def perform_disable_azure_container_storage( + cmd, + resource_group, + cluster_name, + is_called_from_extension=False, +): + # This will be set true only when aks-preview extension is used + # and we want the aks-preview ManagedClusterDecorator to call the + # perform_disable_azure_container_storage function. + if not is_called_from_extension: + return + + client_factory = get_k8s_extension_module(CONST_K8S_EXTENSION_CLIENT_FACTORY_MOD_NAME) + client = client_factory.cf_k8s_extension_operation(cmd.cli_ctx) + k8s_extension_custom_mod = get_k8s_extension_module(CONST_K8S_EXTENSION_CUSTOM_MOD_NAME) + + try: + delete_op_result = k8s_extension_custom_mod.delete_k8s_extension( + cmd, + client, + resource_group, + cluster_name, + CONST_ACSTOR_EXT_INSTALLATION_NAME, + "managedClusters", + yes=True, + no_wait=False, + ) + + LongRunningOperation(cmd.cli_ctx)(delete_op_result) + logger.warning("Azure Container Storage has been disabled.") + except Exception as delete_ex: + raise UnknownError( + "Failed to disable Azure Container Storage with error: %s" % delete_ex + ) from delete_ex diff --git a/src/aks-preview/azext_aks_preview/custom.py b/src/aks-preview/azext_aks_preview/custom.py index dbb9b810507..93784563a51 100644 --- a/src/aks-preview/azext_aks_preview/custom.py +++ b/src/aks-preview/azext_aks_preview/custom.py @@ -732,6 +732,7 @@ def aks_create( enable_ai_toolchain_operator=False, # azure container storage enable_azure_container_storage=None, + container_storage_version=None, storage_pool_name=None, storage_pool_size=None, storage_pool_sku=None, @@ -953,6 +954,7 @@ def aks_update( # azure container storage enable_azure_container_storage=None, disable_azure_container_storage=None, + container_storage_version=None, storage_pool_name=None, storage_pool_size=None, storage_pool_sku=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 3885e992b00..bedb41b54e2 100644 --- a/src/aks-preview/azext_aks_preview/managed_cluster_decorator.py +++ b/src/aks-preview/azext_aks_preview/managed_cluster_decorator.py @@ -46,6 +46,11 @@ CONST_ACNS_DATAPATH_ACCELERATION_MODE_BPFVETH, CONST_ACNS_DATAPATH_ACCELERATION_MODE_NONE ) +from azext_aks_preview.azurecontainerstorage._consts import ( + CONST_ACSTOR_EXT_INSTALLATION_NAME, + CONST_ACSTOR_V1_EXT_INSTALLATION_NAME, + CONST_ACSTOR_VERSION_V1, +) from azext_aks_preview._helpers import ( check_is_apiserver_vnet_integration_cluster, check_is_azure_cli_core_editable_installed, @@ -75,6 +80,8 @@ AKSPreviewAgentPoolUpdateDecorator, ) from azext_aks_preview.azurecontainerstorage.acstor_ops import ( + perform_disable_azure_container_storage_v1, + perform_enable_azure_container_storage_v1, perform_disable_azure_container_storage, perform_enable_azure_container_storage, ) @@ -207,6 +214,12 @@ def external_functions(self) -> SimpleNamespace: external_functions["add_role_assignment"] = add_role_assignment external_functions["_add_role_assignment_executor_new"] = _add_role_assignment_executor_new # azure container storage functions + external_functions[ + "perform_enable_azure_container_storage_v1" + ] = perform_enable_azure_container_storage_v1 + external_functions[ + "perform_disable_azure_container_storage_v1" + ] = perform_disable_azure_container_storage_v1 external_functions["perform_enable_azure_container_storage"] = perform_enable_azure_container_storage external_functions["perform_disable_azure_container_storage"] = perform_disable_azure_container_storage self.__external_functions = SimpleNamespace(**external_functions) @@ -3538,106 +3551,134 @@ def set_up_azure_container_storage(self, mc: ManagedCluster) -> ManagedCluster: :return: ManagedCluster """ self._ensure_mc(mc) - # read the azure container storage values passed - pool_type = self.context.raw_param.get("enable_azure_container_storage") - enable_azure_container_storage = pool_type is not None - ephemeral_disk_volume_type = self.context.raw_param.get("ephemeral_disk_volume_type") - ephemeral_disk_nvme_perf_tier = self.context.raw_param.get("ephemeral_disk_nvme_perf_tier") - if (ephemeral_disk_volume_type is not None or ephemeral_disk_nvme_perf_tier is not None) and \ - not enable_azure_container_storage: - params_defined_arr = [] - if ephemeral_disk_volume_type is not None: - params_defined_arr.append('--ephemeral-disk-volume-type') - if ephemeral_disk_nvme_perf_tier is not None: - params_defined_arr.append('--ephemeral-disk-nvme-perf-tier') - - params_defined = 'and '.join(params_defined_arr) - raise RequiredArgumentMissingError( - f'Cannot set {params_defined} without the parameter --enable-azure-container-storage.' - ) - if enable_azure_container_storage: - pool_name = self.context.raw_param.get("storage_pool_name") - pool_option = self.context.raw_param.get("storage_pool_option") - pool_sku = self.context.raw_param.get("storage_pool_sku") - pool_size = self.context.raw_param.get("storage_pool_size") - if not mc.agent_pool_profiles: - raise UnknownError("Encountered an unexpected error while getting the agent pools from the cluster.") - agentpool = mc.agent_pool_profiles[0] - agentpool_details = {} - pool_details = {} - pool_details["vm_size"] = agentpool.vm_size - pool_details["count"] = agentpool.count - pool_details["os_type"] = agentpool.os_type - pool_details["mode"] = agentpool.mode - pool_details["node_taints"] = agentpool.node_taints - pool_details["zoned"] = agentpool.availability_zones is not None - agentpool_details[agentpool.name] = pool_details - # Marking the only agentpool name as the valid nodepool for - # installing Azure Container Storage during `az aks create` - nodepool_list = agentpool.name - - from azext_aks_preview.azurecontainerstorage._validators import ( - validate_enable_azure_container_storage_params - ) - from azext_aks_preview.azurecontainerstorage._consts import ( - CONST_ACSTOR_IO_ENGINE_LABEL_KEY, - CONST_ACSTOR_IO_ENGINE_LABEL_VAL, - CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, - CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD, - ) + if self.context.raw_param.get("enable_azure_container_storage") is not None: + self.context.set_intermediate("enable_azure_container_storage", True, overwrite_exists=True) + container_storage_version = self.context.raw_param.get("container_storage_version") + + if container_storage_version is not None and container_storage_version == CONST_ACSTOR_VERSION_V1: + # read the azure container storage values passed + pool_type = self.context.raw_param.get("enable_azure_container_storage") + enable_azure_container_storage = pool_type is not None + ephemeral_disk_volume_type = self.context.raw_param.get("ephemeral_disk_volume_type") + ephemeral_disk_nvme_perf_tier = self.context.raw_param.get("ephemeral_disk_nvme_perf_tier") + if (ephemeral_disk_volume_type is not None or ephemeral_disk_nvme_perf_tier is not None) and \ + not enable_azure_container_storage: + params_defined_arr = [] + if ephemeral_disk_volume_type is not None: + params_defined_arr.append('--ephemeral-disk-volume-type') + if ephemeral_disk_nvme_perf_tier is not None: + params_defined_arr.append('--ephemeral-disk-nvme-perf-tier') + + params_defined = 'and '.join(params_defined_arr) + raise RequiredArgumentMissingError( + f'Cannot set {params_defined} without the parameter --enable-azure-container-storage.' + ) - vm_cache_generated = self.context.get_intermediate( - "vm_cache_generated", - default_value=False, - ) + if enable_azure_container_storage: + pool_name = self.context.raw_param.get("storage_pool_name") + pool_option = self.context.raw_param.get("storage_pool_option") + pool_sku = self.context.raw_param.get("storage_pool_sku") + pool_size = self.context.raw_param.get("storage_pool_size") + if not mc.agent_pool_profiles: + raise UnknownError( + "Encountered an unexpected error while getting the agent pools from the cluster." + ) + agentpool = mc.agent_pool_profiles[0] + agentpool_details = {} + pool_details = {} + pool_details["vm_size"] = agentpool.vm_size + pool_details["count"] = agentpool.count + pool_details["os_type"] = agentpool.os_type + pool_details["mode"] = agentpool.mode + pool_details["node_taints"] = agentpool.node_taints + pool_details["zoned"] = agentpool.availability_zones is not None + agentpool_details[agentpool.name] = pool_details + # Marking the only agentpool name as the valid nodepool for + # installing Azure Container Storage during `az aks create` + nodepool_list = agentpool.name - if not vm_cache_generated: - from azext_aks_preview.azurecontainerstorage._helpers import generate_vm_sku_cache_for_region - generate_vm_sku_cache_for_region(self.cmd.cli_ctx, self.context.get_location()) - self.context.set_intermediate("vm_cache_generated", True, overwrite_exists=True) - - default_ephemeral_disk_volume_type = CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY - default_ephemeral_disk_nvme_perf_tier = CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD - validate_enable_azure_container_storage_params( - pool_type, - pool_name, - pool_sku, - pool_option, - pool_size, - nodepool_list, - agentpool_details, - False, - False, - False, - False, - False, - ephemeral_disk_volume_type, - ephemeral_disk_nvme_perf_tier, - default_ephemeral_disk_volume_type, - default_ephemeral_disk_nvme_perf_tier, - ) + from azure.cli.command_modules.acs.azurecontainerstorage._validators import ( + validate_enable_azure_container_storage_v1_params + ) + from azure.cli.command_modules.acs.azurecontainerstorage._consts import ( + CONST_ACSTOR_IO_ENGINE_LABEL_KEY, + CONST_ACSTOR_IO_ENGINE_LABEL_VAL, + CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, + CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD, + ) + from azure.cli.command_modules.acs.azurecontainerstorage._helpers import ( + generate_vm_sku_cache_for_region + ) + generate_vm_sku_cache_for_region(self.cmd.cli_ctx, self.context.get_location()) + + default_ephemeral_disk_volume_type = CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY + default_ephemeral_disk_nvme_perf_tier = CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD + validate_enable_azure_container_storage_v1_params( + pool_type, + pool_name, + pool_sku, + pool_option, + pool_size, + nodepool_list, + agentpool_details, + False, + False, + "", + False, + False, + False, + False, + ephemeral_disk_volume_type, + ephemeral_disk_nvme_perf_tier, + default_ephemeral_disk_volume_type, + default_ephemeral_disk_nvme_perf_tier, + ) - # Setup Azure Container Storage labels on the nodepool - nodepool_labels = agentpool.node_labels - if nodepool_labels is None: - nodepool_labels = {} - nodepool_labels[CONST_ACSTOR_IO_ENGINE_LABEL_KEY] = CONST_ACSTOR_IO_ENGINE_LABEL_VAL - agentpool.node_labels = nodepool_labels + # Setup Azure Container Storage labels on the nodepool + nodepool_labels = agentpool.node_labels + if nodepool_labels is None: + nodepool_labels = {} + nodepool_labels[CONST_ACSTOR_IO_ENGINE_LABEL_KEY] = CONST_ACSTOR_IO_ENGINE_LABEL_VAL + agentpool.node_labels = nodepool_labels - # set intermediates - self.context.set_intermediate("enable_azure_container_storage", True, overwrite_exists=True) - self.context.set_intermediate("azure_container_storage_nodepools", nodepool_list, overwrite_exists=True) - self.context.set_intermediate( - "current_ephemeral_nvme_perf_tier", - default_ephemeral_disk_nvme_perf_tier, - overwrite_exists=True - ) - self.context.set_intermediate( - "existing_ephemeral_disk_volume_type", - default_ephemeral_disk_volume_type, - overwrite_exists=True - ) + # set intermediates + self.context.set_intermediate( + "container_storage_version", container_storage_version, overwrite_exists=True + ) + self.context.set_intermediate( + "azure_container_storage_nodepools", nodepool_list, overwrite_exists=True + ) + self.context.set_intermediate( + "current_ephemeral_nvme_perf_tier", + default_ephemeral_disk_nvme_perf_tier, + overwrite_exists=True + ) + self.context.set_intermediate( + "existing_ephemeral_disk_volume_type", + default_ephemeral_disk_volume_type, + overwrite_exists=True + ) + else: + enable_azure_container_storage = self.context.raw_param.get("enable_azure_container_storage") + storage_pool_name = self.context.raw_param.get("storage_pool_name") + pool_sku = self.context.raw_param.get("storage_pool_sku") + pool_option = self.context.raw_param.get("storage_pool_option") + pool_size = self.context.raw_param.get("storage_pool_size") + + from azure.cli.command_modules.acs.azurecontainerstorage._validators import ( + validate_enable_azure_container_storage_params, + ) + validate_enable_azure_container_storage_params( + False, + False, + "", + enable_azure_container_storage, + storage_pool_name, + pool_sku, + pool_option, + pool_size, + ) return mc @@ -4103,76 +4144,97 @@ def postprocessing_after_mc_created(self, cluster: ManagedCluster) -> None: # enable azure container storage enable_azure_container_storage = self.context.get_intermediate("enable_azure_container_storage") + container_storage_version = self.context.get_intermediate("container_storage_version") if enable_azure_container_storage: - if cluster.identity_profile is None or cluster.identity_profile["kubeletidentity"] is None: - logger.warning( - "Unexpected error getting kubelet's identity for the cluster. " - "Unable to perform the azure container storage operation." - ) - return + if container_storage_version is None and container_storage_version == CONST_ACSTOR_VERSION_V1: + if cluster.identity_profile is None or cluster.identity_profile["kubeletidentity"] is None: + logger.warning( + "Unexpected error getting kubelet's identity for the cluster. " + "Unable to perform the azure container storage operation." + ) + return + + # Get the node_resource_group from the cluster object since + # `mc` in `context` still doesn't have the updated node_resource_group. + if cluster.node_resource_group is None: + logger.warning( + "Unexpected error getting cluster's node resource group. " + "Unable to perform the azure container storage operation." + ) + return - # Get the node_resource_group from the cluster object since - # `mc` in `context` still doesn't have the updated node_resource_group. - if cluster.node_resource_group is None: - logger.warning( - "Unexpected error getting cluster's node resource group. " - "Unable to perform the azure container storage operation." + pool_name = self.context.raw_param.get("storage_pool_name") + pool_type = self.context.raw_param.get( + "enable_azure_container_storage" + ) + pool_option = self.context.raw_param.get("storage_pool_option") + pool_sku = self.context.raw_param.get("storage_pool_sku") + pool_size = self.context.raw_param.get("storage_pool_size") + ephemeral_disk_volume_type = self.context.raw_param.get("ephemeral_disk_volume_type") + ephemeral_disk_nvme_perf_tier = self.context.raw_param.get("ephemeral_disk_nvme_perf_tier") + existing_ephemeral_disk_volume_type = self.context.get_intermediate( + "existing_ephemeral_disk_volume_type" + ) + existing_ephemeral_nvme_perf_tier = self.context.get_intermediate( + "current_ephemeral_nvme_perf_tier" + ) + kubelet_identity_object_id = cluster.identity_profile["kubeletidentity"].object_id + node_resource_group = cluster.node_resource_group + agent_pool_vm_sizes = [] + if len(cluster.agent_pool_profiles) > 0: + # Cluster creation has only 1 agentpool + agentpool_profile = cluster.agent_pool_profiles[0] + agent_pool_vm_sizes.append(agentpool_profile.vm_size) + + self.context.external_functions.perform_enable_azure_container_storage_v1( + self.cmd, + self.context.get_subscription_id(), + self.context.get_resource_group_name(), + self.context.get_name(), + node_resource_group, + kubelet_identity_object_id, + pool_name, + pool_type, + pool_size, + pool_sku, + pool_option, + agent_pool_vm_sizes, + ephemeral_disk_volume_type, + ephemeral_disk_nvme_perf_tier, + True, + existing_ephemeral_disk_volume_type, + existing_ephemeral_nvme_perf_tier, + is_called_from_extension=True, + ) + else: + self.context.external_functions.perform_enable_azure_container_storage( + self.cmd, + self.context.get_resource_group_name(), + self.context.get_name(), + is_called_from_extension=True, ) - return - - pool_name = self.context.raw_param.get("storage_pool_name") - pool_type = self.context.raw_param.get("enable_azure_container_storage") - pool_option = self.context.raw_param.get("storage_pool_option") - pool_sku = self.context.raw_param.get("storage_pool_sku") - pool_size = self.context.raw_param.get("storage_pool_size") - ephemeral_disk_volume_type = self.context.raw_param.get("ephemeral_disk_volume_type") - ephemeral_disk_nvme_perf_tier = self.context.raw_param.get("ephemeral_disk_nvme_perf_tier") - existing_ephemeral_disk_volume_type = self.context.get_intermediate("existing_ephemeral_disk_volume_type") - existing_ephemeral_nvme_perf_tier = self.context.get_intermediate("current_ephemeral_nvme_perf_tier") - kubelet_identity_object_id = cluster.identity_profile["kubeletidentity"].object_id - node_resource_group = cluster.node_resource_group - agent_pool_vm_sizes = [] - if len(cluster.agent_pool_profiles) > 0: - # Cluster creation has only 1 agentpool - agentpool_profile = cluster.agent_pool_profiles[0] - agent_pool_vm_sizes.append(agentpool_profile.vm_size) + # Add role assignments for automatic sku + if cluster.sku is not None and cluster.sku.name == "Automatic": + try: + user = get_graph_client(self.cmd.cli_ctx).signed_in_user_get() + except Exception as e: # pylint: disable=broad-except + logger.warning("Could not get signed in user: %s", str(e)) + else: + self.context.external_functions._add_role_assignment_executor_new( # type: ignore # pylint: disable=protected-access + self.cmd, + "Azure Kubernetes Service RBAC Cluster Admin", + user["id"], + scope=cluster.id, + resolve_assignee=False, + ) + else: self.context.external_functions.perform_enable_azure_container_storage( self.cmd, - self.context.get_subscription_id(), self.context.get_resource_group_name(), self.context.get_name(), - node_resource_group, - kubelet_identity_object_id, - pool_name, - pool_type, - pool_size, - pool_sku, - pool_option, - agent_pool_vm_sizes, - ephemeral_disk_volume_type, - ephemeral_disk_nvme_perf_tier, - True, - existing_ephemeral_disk_volume_type, - existing_ephemeral_nvme_perf_tier, - is_called_from_extension=True, ) - # Add role assignments for automatic sku - if cluster.sku is not None and cluster.sku.name == "Automatic": - try: - user = get_graph_client(self.cmd.cli_ctx).signed_in_user_get() - except Exception as e: # pylint: disable=broad-except - logger.warning("Could not get signed in user: %s", str(e)) - else: - self.context.external_functions._add_role_assignment_executor_new( # type: ignore # pylint: disable=protected-access - self.cmd, - "Azure Kubernetes Service RBAC Cluster Admin", - user["id"], - scope=cluster.id, - resolve_assignee=False, - ) - class AKSPreviewManagedClusterUpdateDecorator(AKSManagedClusterUpdateDecorator): def __init__( @@ -4404,50 +4466,92 @@ def update_azure_container_storage(self, mc: ManagedCluster) -> ManagedCluster: :return: ManagedCluster """ self._ensure_mc(mc) - # read the azure container storage values passed - enable_pool_type = self.context.raw_param.get("enable_azure_container_storage") - disable_pool_type = self.context.raw_param.get("disable_azure_container_storage") - enable_azure_container_storage = enable_pool_type is not None - disable_azure_container_storage = disable_pool_type is not None - nodepool_list = self.context.raw_param.get("azure_container_storage_nodepools") - ephemeral_disk_volume_type = self.context.raw_param.get("ephemeral_disk_volume_type") - ephemeral_disk_nvme_perf_tier = self.context.raw_param.get("ephemeral_disk_nvme_perf_tier") - if enable_azure_container_storage and disable_azure_container_storage: - raise MutuallyExclusiveArgumentError( - 'Conflicting flags. Cannot set --enable-azure-container-storage ' - 'and --disable-azure-container-storage together.' + + # check if we are trying to enable container storage v1 + enable_azure_container_storage_param = self.context.raw_param.get("enable_azure_container_storage") + disable_azure_container_storage_param = self.context.raw_param.get("disable_azure_container_storage") + container_storage_version = self.context.raw_param.get("container_storage_version") + + if disable_azure_container_storage_param is not None and container_storage_version is not None: + raise InvalidArgumentValueError( + 'The --container-storage-version parameter is not required when disabling Azure Container Storage.' + ' Please remove this parameter and try again.' ) + # pylint: disable=too-many-nested-blocks + if enable_azure_container_storage_param is not None or disable_azure_container_storage_param is not None: + self.context.set_intermediate("container_storage_version", container_storage_version, overwrite_exists=True) - if (ephemeral_disk_volume_type is not None or ephemeral_disk_nvme_perf_tier is not None) and \ - not enable_azure_container_storage: - params_defined_arr = [] - if ephemeral_disk_volume_type is not None: - params_defined_arr.append('--ephemeral-disk-volume-type') - if ephemeral_disk_nvme_perf_tier is not None: - params_defined_arr.append('--ephemeral-disk-nvme-perf-tier') - - params_defined = 'and '.join(params_defined_arr) - raise RequiredArgumentMissingError( - f'Cannot set {params_defined} without the parameter --enable-azure-container-storage.' + enable_azure_container_storage_v1 = ( + enable_azure_container_storage_param is not None + and container_storage_version == CONST_ACSTOR_VERSION_V1 ) - # pylint: disable=too-many-nested-blocks - if enable_azure_container_storage or disable_azure_container_storage: - # Require the agent pool profiles for azure container storage - # operations. Raise exception if not found. - if not mc.agent_pool_profiles: - raise UnknownError( - "Encounter an unexpected error while getting agent pool profiles from the cluster " - "in the process of updating agentpool profile." + # Check if we are trying to disable container storage v1 + from azure.cli.command_modules.acs.azurecontainerstorage._helpers import ( + get_container_storage_extension_installed + ) + try: + is_container_storage_v1_extension_installed, _ = get_container_storage_extension_installed( + self.cmd, + self.context.get_resource_group_name(), + self.context.get_name(), + CONST_ACSTOR_V1_EXT_INSTALLATION_NAME, ) - storagepool_name = self.context.raw_param.get("storage_pool_name") - pool_option = self.context.raw_param.get("storage_pool_option") - pool_sku = self.context.raw_param.get("storage_pool_sku") - pool_size = self.context.raw_param.get("storage_pool_size") - agentpool_details = {} - from azext_aks_preview.azurecontainerstorage._helpers import get_extension_installed_and_cluster_configs + except Exception as ex: + raise UnknownError( + f"An error occurred while checking the version of Azure Container Storage" + f"extension installed on the cluster: {str(ex)}" + ) from ex - try: + disable_azure_container_storage_v1 = ( + disable_azure_container_storage_param is not None + and is_container_storage_v1_extension_installed + ) + # pylint: disable=too-many-nested-blocks + if enable_azure_container_storage_v1 or disable_azure_container_storage_v1: + + # read the azure container storage values passed + enable_pool_type = self.context.raw_param.get("enable_azure_container_storage") + disable_pool_type = self.context.raw_param.get("disable_azure_container_storage") + enable_azure_container_storage = enable_pool_type is not None + disable_azure_container_storage = disable_pool_type is not None + nodepool_list = self.context.raw_param.get("azure_container_storage_nodepools") + ephemeral_disk_volume_type = self.context.raw_param.get("ephemeral_disk_volume_type") + ephemeral_disk_nvme_perf_tier = self.context.raw_param.get("ephemeral_disk_nvme_perf_tier") + if enable_azure_container_storage and disable_azure_container_storage: + raise MutuallyExclusiveArgumentError( + 'Conflicting flags. Cannot set --enable-azure-container-storage ' + 'and --disable-azure-container-storage together.' + ) + + if (ephemeral_disk_volume_type is not None or ephemeral_disk_nvme_perf_tier is not None) and \ + not enable_azure_container_storage: + params_defined_arr = [] + if ephemeral_disk_volume_type is not None: + params_defined_arr.append('--ephemeral-disk-volume-type') + if ephemeral_disk_nvme_perf_tier is not None: + params_defined_arr.append('--ephemeral-disk-nvme-perf-tier') + + params_defined = 'and '.join(params_defined_arr) + raise RequiredArgumentMissingError( + f'Cannot set {params_defined} without the parameter --enable-azure-container-storage.' + ) + + # Require the agent pool profiles for azure container storage + # operations. Raise exception if not found. + if not mc.agent_pool_profiles: + raise UnknownError( + "Encounter an unexpected error while getting agent pool profiles from the cluster " + "in the process of updating agentpool profile." + ) + storagepool_name = self.context.raw_param.get("storage_pool_name") + pool_option = self.context.raw_param.get("storage_pool_option") + pool_sku = self.context.raw_param.get("storage_pool_sku") + pool_size = self.context.raw_param.get("storage_pool_size") + agentpool_details = {} + from azure.cli.command_modules.acs.azurecontainerstorage._helpers import ( + get_extension_installed_and_cluster_configs + ) ( is_extension_installed, is_azureDisk_enabled, @@ -4463,219 +4567,324 @@ def update_azure_container_storage(self, mc: ManagedCluster) -> ManagedCluster: self.context.get_name(), mc.agent_pool_profiles, ) - except UnknownError as e: - logger.error("\nError fetching installed extension and cluster config: %s", e) - return mc - except Exception as ex: # pylint: disable=broad-except - logger.error("Exception fetching installed extension and cluster config: %s", ex) - return mc - - vm_cache_generated = self.context.get_intermediate( - "vm_cache_generated", - default_value=False, - ) - - if not vm_cache_generated: - from azext_aks_preview.azurecontainerstorage._helpers import generate_vm_sku_cache_for_region - generate_vm_sku_cache_for_region(self.cmd.cli_ctx, self.context.get_location()) - self.context.set_intermediate("vm_cache_generated", True, overwrite_exists=True) - if enable_azure_container_storage: - from azext_aks_preview.azurecontainerstorage._consts import ( - CONST_ACSTOR_IO_ENGINE_LABEL_KEY, - CONST_ACSTOR_IO_ENGINE_LABEL_VAL - ) - labelled_nodepool_arr = [] - for agentpool in mc.agent_pool_profiles: - pool_details = {} - nodepool_name = agentpool.name - pool_details["vm_size"] = agentpool.vm_size - pool_details["count"] = agentpool.count - pool_details["os_type"] = agentpool.os_type - pool_details["mode"] = agentpool.mode - pool_details["node_taints"] = agentpool.node_taints - pool_details["zoned"] = agentpool.availability_zones is not None - if agentpool.node_labels is not None: - node_labels = agentpool.node_labels - if node_labels is not None and \ - node_labels.get(CONST_ACSTOR_IO_ENGINE_LABEL_KEY) is not None and \ - nodepool_name is not None: - labelled_nodepool_arr.append(nodepool_name) - pool_details["node_labels"] = node_labels - agentpool_details[nodepool_name] = pool_details - - # Incase of a new installation, if the nodepool list is not defined - # then check for all the nodepools which are marked with acstor io-engine - # labels and include them for installation. If none of the nodepools are - # labelled, either pick nodepool1 as default, or if only - # one nodepool exists, choose the only nodepool by default. - if not is_extension_installed: - if nodepool_list is None: - nodepool_list = "" - if len(labelled_nodepool_arr) > 0: - nodepool_list = ','.join(labelled_nodepool_arr) - elif len(agentpool_details) == 1: - nodepool_list = ','.join(agentpool_details.keys()) - - from azext_aks_preview.azurecontainerstorage._validators import ( - validate_enable_azure_container_storage_params - ) - validate_enable_azure_container_storage_params( - enable_pool_type, - storagepool_name, - pool_sku, - pool_option, - pool_size, - nodepool_list, - agentpool_details, - is_extension_installed, - is_azureDisk_enabled, - is_elasticSan_enabled, - is_ephemeralDisk_localssd_enabled, - is_ephemeralDisk_nvme_enabled, - ephemeral_disk_volume_type, - ephemeral_disk_nvme_perf_tier, - existing_ephemeral_disk_volume_type, - existing_perf_tier, + from azure.cli.command_modules.acs.azurecontainerstorage._helpers import ( + generate_vm_sku_cache_for_region ) + generate_vm_sku_cache_for_region(self.cmd.cli_ctx, self.context.get_location()) - if is_ephemeralDisk_nvme_enabled and ephemeral_disk_nvme_perf_tier is not None: - # Adding this intermediate and check to ensure that the below - # message prompt doesn't appear twice when aks-preview extension - # is called from both update_mc_profile_preview and update_mc_profile_default. - is_azure_container_storage_perf_tier_op_set = self.context.get_intermediate( - "azure_container_storage_perf_tier_op_set", - default_value="default", + if enable_azure_container_storage: + from azure.cli.command_modules.acs.azurecontainerstorage._helpers import ( + get_container_storage_extension_installed + ) + try: + is_container_storage_v2_extension_installed, version_v2 = ( + get_container_storage_extension_installed( + self.cmd, + self.context.get_resource_group_name(), + self.context.get_name(), + CONST_ACSTOR_EXT_INSTALLATION_NAME, + ) + ) + except Exception as ex: + raise UnknownError( + f"An error occurred while checking if Azure Container Storage" + f"extension is installed on the cluster: {str(ex)}" + ) from ex + + from azure.cli.command_modules.acs.azurecontainerstorage._consts import ( + CONST_ACSTOR_IO_ENGINE_LABEL_KEY, + CONST_ACSTOR_IO_ENGINE_LABEL_VAL + ) + labelled_nodepool_arr = [] + for agentpool in mc.agent_pool_profiles: + pool_details = {} + nodepool_name = agentpool.name + pool_details["vm_size"] = agentpool.vm_size + pool_details["count"] = agentpool.count + pool_details["os_type"] = agentpool.os_type + pool_details["mode"] = agentpool.mode + pool_details["node_taints"] = agentpool.node_taints + pool_details["zoned"] = agentpool.availability_zones is not None + if agentpool.node_labels is not None: + node_labels = agentpool.node_labels + if node_labels is not None and \ + node_labels.get(CONST_ACSTOR_IO_ENGINE_LABEL_KEY) is not None and \ + nodepool_name is not None: + labelled_nodepool_arr.append(nodepool_name) + pool_details["node_labels"] = node_labels + agentpool_details[nodepool_name] = pool_details + + # Incase of a new installation, if the nodepool list is not defined + # then check for all the nodepools which are marked with acstor io-engine + # labels and include them for installation. If none of the nodepools are + # labelled, either pick nodepool1 as default, or if only + # one nodepool exists, choose the only nodepool by default. + if not is_extension_installed: + if nodepool_list is None: + nodepool_list = "" + if len(labelled_nodepool_arr) > 0: + nodepool_list = ','.join(labelled_nodepool_arr) + elif len(agentpool_details) == 1: + nodepool_list = ','.join(agentpool_details.keys()) + + from azure.cli.command_modules.acs.azurecontainerstorage._validators import ( + validate_enable_azure_container_storage_v1_params + ) + validate_enable_azure_container_storage_v1_params( + enable_pool_type, + storagepool_name, + pool_sku, + pool_option, + pool_size, + nodepool_list, + agentpool_details, + is_extension_installed, + is_container_storage_v2_extension_installed, + version_v2, + is_azureDisk_enabled, + is_elasticSan_enabled, + is_ephemeralDisk_localssd_enabled, + is_ephemeralDisk_nvme_enabled, + ephemeral_disk_volume_type, + ephemeral_disk_nvme_perf_tier, + existing_ephemeral_disk_volume_type, + existing_perf_tier, ) - if is_azure_container_storage_perf_tier_op_set == "default": + if is_ephemeralDisk_nvme_enabled and ephemeral_disk_nvme_perf_tier is not None: msg = ( "Changing ephemeralDisk NVMe performance tier may result in a temporary " "interruption to the applications using Azure Container Storage. Do you " "want to continue with this operation?" ) - if not (self.context.get_yes() or prompt_y_n(msg, default="n")): raise DecoratorEarlyExitException() + # If the extension is already installed, + # we expect that the Azure Container Storage + # nodes are already labelled. Use those label + # to generate the nodepool_list. + if is_extension_installed: + nodepool_list = ','.join(labelled_nodepool_arr) + else: + # Set Azure Container Storage labels on the required nodepools. + nodepool_list_arr = nodepool_list.split(',') + for agentpool in mc.agent_pool_profiles: + labels = agentpool.node_labels + if agentpool.name in nodepool_list_arr: + if labels is None: + labels = {} + labels[CONST_ACSTOR_IO_ENGINE_LABEL_KEY] = CONST_ACSTOR_IO_ENGINE_LABEL_VAL + else: + # Remove residual Azure Container Storage labels + # from any other nodepools where its not intended + if labels is not None: + labels.pop(CONST_ACSTOR_IO_ENGINE_LABEL_KEY, None) + agentpool.node_labels = labels + + # set intermediates + self.context.set_intermediate( + "azure_container_storage_nodepools", + nodepool_list, + overwrite_exists=True + ) + self.context.set_intermediate("enable_azure_container_storage", True, overwrite_exists=True) + if disable_azure_container_storage: + from azure.cli.command_modules.acs.azurecontainerstorage._validators import ( + validate_disable_azure_container_storage_params_v1 + ) + validate_disable_azure_container_storage_params_v1( + disable_pool_type, + storagepool_name, + pool_sku, + pool_option, + pool_size, + nodepool_list, + is_extension_installed, + is_azureDisk_enabled, + is_elasticSan_enabled, + is_ephemeralDisk_localssd_enabled, + is_ephemeralDisk_nvme_enabled, + ephemeral_disk_volume_type, + ephemeral_disk_nvme_perf_tier, + ) + + is_pre_disable_validate_set = self.context.get_intermediate( + "pre_disable_validate_azure_container_storage", + default_value="default", + ) + + # pre_disable_validate_azure_container_storage will be set to + # True or False if the updated version az aks cli is being used. + # If that is the case, we will skip showing the prompt. + if is_pre_disable_validate_set == "default": + + pre_disable_validate = False + + msg = ( + "Disabling Azure Container Storage will forcefully delete all the storage pools in the" + " cluster and affect the applications using these storage pools. Forceful deletion of" + " storage pools can also lead to leaking of storage resources which are being consumed." + " Do you want to validate whether any of the storage pools are being used" + " before disabling Azure Container Storage?" + ) + + from azure.cli.command_modules.acs.azurecontainerstorage._consts import ( + CONST_ACSTOR_ALL, + ) + if disable_pool_type != CONST_ACSTOR_ALL: + msg = ( + f"Disabling Azure Container Storage for storage pool type {disable_pool_type} " + "will forcefully delete all the storage pools of the same type and affect the " + "applications using these storage pools. Forceful deletion of storage pools can " + "also lead to leaking of storage resources which are being consumed. Do you want to " + f"validate whether any of the storage pools of type {disable_pool_type} are being used " + "before disabling Azure Container Storage?" + ) + if self.context.get_yes() or prompt_y_n(msg, default="y"): + pre_disable_validate = True + + # set intermediate self.context.set_intermediate( - "azure_container_storage_perf_tier_op_set", - True, + "pre_disable_validate_azure_container_storage", + pre_disable_validate, overwrite_exists=True ) - # If the extension is already installed, - # we expect that the Azure Container Storage - # nodes are already labelled. Use those label - # to generate the nodepool_list. - if is_extension_installed: - nodepool_list = ','.join(labelled_nodepool_arr) - else: - # Set Azure Container Storage labels on the required nodepools. - nodepool_list_arr = nodepool_list.split(',') - for agentpool in mc.agent_pool_profiles: - labels = agentpool.node_labels - if agentpool.name in nodepool_list_arr: - if labels is None: - labels = {} - labels[CONST_ACSTOR_IO_ENGINE_LABEL_KEY] = CONST_ACSTOR_IO_ENGINE_LABEL_VAL - else: - # Remove residual Azure Container Storage labels - # from any other nodepools where its not intended - if labels is not None: - labels.pop(CONST_ACSTOR_IO_ENGINE_LABEL_KEY, None) - agentpool.node_labels = labels - - # set intermediates - self.context.set_intermediate("azure_container_storage_nodepools", nodepool_list, overwrite_exists=True) - self.context.set_intermediate("enable_azure_container_storage", True, overwrite_exists=True) - - if disable_azure_container_storage: - from azext_aks_preview.azurecontainerstorage._validators import ( - validate_disable_azure_container_storage_params + # Set intermediates + self.context.set_intermediate("disable_azure_container_storage", True, overwrite_exists=True) + self.context.set_intermediate( + "container_storage_version", + CONST_ACSTOR_VERSION_V1, + overwrite_exists=True ) - validate_disable_azure_container_storage_params( - disable_pool_type, - storagepool_name, - pool_sku, - pool_option, - pool_size, - nodepool_list, - is_extension_installed, - is_azureDisk_enabled, - is_elasticSan_enabled, - is_ephemeralDisk_localssd_enabled, + self.context.set_intermediate("is_extension_installed", is_extension_installed, overwrite_exists=True) + self.context.set_intermediate("is_azureDisk_enabled", is_azureDisk_enabled, overwrite_exists=True) + self.context.set_intermediate("is_elasticSan_enabled", is_elasticSan_enabled, overwrite_exists=True) + self.context.set_intermediate("current_core_value", current_core_value, overwrite_exists=True) + self.context.set_intermediate( + "current_ephemeral_nvme_perf_tier", + existing_perf_tier, + overwrite_exists=True + ) + self.context.set_intermediate( + "existing_ephemeral_disk_volume_type", + existing_ephemeral_disk_volume_type, + overwrite_exists=True + ) + self.context.set_intermediate( + "is_ephemeralDisk_nvme_enabled", is_ephemeralDisk_nvme_enabled, - ephemeral_disk_volume_type, - ephemeral_disk_nvme_perf_tier, + overwrite_exists=True + ) + self.context.set_intermediate( + "is_ephemeralDisk_localssd_enabled", + is_ephemeralDisk_localssd_enabled, + overwrite_exists=True ) + self.context.set_intermediate("current_core_value", current_core_value, overwrite_exists=True) + + else: + storage_pool_name = self.context.raw_param.get("storage_pool_name") + pool_sku = self.context.raw_param.get("storage_pool_sku") + pool_option = self.context.raw_param.get("storage_pool_option") + pool_size = self.context.raw_param.get("storage_pool_size") + + if enable_azure_container_storage_param and disable_azure_container_storage_param: + raise MutuallyExclusiveArgumentError( + 'Conflicting flags. Cannot set --enable-azure-container-storage ' + 'and --disable-azure-container-storage together.' + ) - is_pre_disable_validate_set = self.context.get_intermediate( - "pre_disable_validate_azure_container_storage", - default_value="default", + from azure.cli.command_modules.acs.azurecontainerstorage._helpers import ( + get_container_storage_extension_installed + ) + is_extension_installed, _ = get_container_storage_extension_installed( + self.cmd, + self.context.get_resource_group_name(), + self.context.get_name(), + CONST_ACSTOR_EXT_INSTALLATION_NAME, ) - # pre_disable_validate_azure_container_storage will be set to - # True or False if the updated version az aks cli is being used. - # If that is the case, we will skip showing the prompt. - if is_pre_disable_validate_set == "default": - pre_disable_validate = False - - msg = ( - "Disabling Azure Container Storage will forcefully delete all the storage pools in the cluster " - "and affect the applications using these storage pools. Forceful deletion of storage pools can " - "also lead to leaking of storage resources which are being consumed. Do you want to validate " - "whether any of the storage pools are being used before disabling Azure Container Storage?" + from azure.cli.command_modules.acs.azurecontainerstorage._helpers import ( + get_container_storage_extension_installed + ) + try: + is_containerstorage_v1_installed, v1_extension_version = get_container_storage_extension_installed( + self.cmd, + self.context.get_resource_group_name(), + self.context.get_name(), + CONST_ACSTOR_V1_EXT_INSTALLATION_NAME, + ) + except Exception as ex: + raise UnknownError( + f"An error occurred while checking if Azure Container Storage " + f"extension is installed on the cluster: {str(ex)}" + ) from ex + + if enable_azure_container_storage_param: + from azure.cli.command_modules.acs.azurecontainerstorage._validators import ( + validate_enable_azure_container_storage_params, + ) + validate_enable_azure_container_storage_params( + is_extension_installed, + is_containerstorage_v1_installed, + v1_extension_version, + enable_azure_container_storage_param, + storage_pool_name, + pool_sku, + pool_option, + pool_size, ) - from azext_aks_preview.azurecontainerstorage._consts import ( - CONST_ACSTOR_ALL, + if disable_azure_container_storage_param: + is_pre_disable_validate_set = self.context.get_intermediate( + "pre_disable_validate_azure_container_storage", + default_value="default", ) - if disable_pool_type != CONST_ACSTOR_ALL: + + # pre_disable_validate_azure_container_storage will be set to + # True or False if the updated version az aks cli is being used. + # If that is the case, we will skip showing the prompt. + if is_pre_disable_validate_set == "default": + pre_disable_validate = False + msg = ( - f"Disabling Azure Container Storage for storage pool type {disable_pool_type} " - "will forcefully delete all the storage pools of the same type and affect the " - "applications using these storage pools. Forceful deletion of storage pools can " - "also lead to leaking of storage resources which are being consumed. Do you want to " - f"validate whether any of the storage pools of type {disable_pool_type} are being used " - "before disabling Azure Container Storage?" + "Please make sure there are no existing PVs and PVCs that are provisioned by Azure" + " Container Storage before disabling. If Azure Container Storage is disabled with" + " remaining PVs and PVCs, any data associated with those PVs and PVCs will not be erased" + " and the nodes will be left in an unclean state. The PVs and PVCs can only be cleaned up" + " after re-enabling it by running 'az aks update --enable-azure-container-storage'." + " Would you like to proceed with the disabling?" ) - if self.context.get_yes() or prompt_y_n(msg, default="y"): - pre_disable_validate = True + if not self.context.get_yes() and not prompt_y_n(msg, default="n"): + pre_disable_validate = True + raise DecoratorEarlyExitException() - # set intermediate - self.context.set_intermediate("disable_azure_container_storage", True, overwrite_exists=True) - self.context.set_intermediate( - "pre_disable_validate_azure_container_storage", - pre_disable_validate, - overwrite_exists=True - ) + from azure.cli.command_modules.acs.azurecontainerstorage._validators import ( + validate_disable_azure_container_storage_params + ) + validate_disable_azure_container_storage_params( + is_extension_installed, + disable_azure_container_storage_param, + storage_pool_name, + pool_sku, + pool_option, + pool_size + ) - # Set intermediates - self.context.set_intermediate("is_extension_installed", is_extension_installed, overwrite_exists=True) - self.context.set_intermediate("is_azureDisk_enabled", is_azureDisk_enabled, overwrite_exists=True) - self.context.set_intermediate("is_elasticSan_enabled", is_elasticSan_enabled, overwrite_exists=True) - self.context.set_intermediate("current_core_value", current_core_value, overwrite_exists=True) - self.context.set_intermediate( - "current_ephemeral_nvme_perf_tier", - existing_perf_tier, - overwrite_exists=True - ) - self.context.set_intermediate( - "existing_ephemeral_disk_volume_type", - existing_ephemeral_disk_volume_type, - overwrite_exists=True - ) - self.context.set_intermediate( - "is_ephemeralDisk_nvme_enabled", - is_ephemeralDisk_nvme_enabled, - overwrite_exists=True - ) - self.context.set_intermediate( - "is_ephemeralDisk_localssd_enabled", - is_ephemeralDisk_localssd_enabled, - overwrite_exists=True - ) + self.context.set_intermediate( + "pre_disable_validate_azure_container_storage", + pre_disable_validate, + overwrite_exists=True + ) + + if enable_azure_container_storage_param: + self.context.set_intermediate("enable_azure_container_storage", True) + + if disable_azure_container_storage_param: + self.context.set_intermediate("disable_azure_container_storage", True) return mc @@ -5816,6 +6025,8 @@ def postprocessing_after_mc_created(self, cluster: ManagedCluster) -> None: super().postprocessing_after_mc_created(cluster) enable_azure_container_storage = self.context.get_intermediate("enable_azure_container_storage") disable_azure_container_storage = self.context.get_intermediate("disable_azure_container_storage") + container_storage_version = self.context.get_intermediate("container_storage_version") + is_extension_installed = self.context.get_intermediate("is_extension_installed") is_azureDisk_enabled = self.context.get_intermediate("is_azureDisk_enabled") is_elasticSan_enabled = self.context.get_intermediate("is_elasticSan_enabled") @@ -5828,76 +6039,94 @@ def postprocessing_after_mc_created(self, cluster: ManagedCluster) -> None: # enable azure container storage if enable_azure_container_storage: - if cluster.identity_profile is None or cluster.identity_profile["kubeletidentity"] is None: - logger.warning( - "Unexpected error getting kubelet's identity for the cluster." - "Unable to perform azure container storage operation." - ) - return - pool_name = self.context.raw_param.get("storage_pool_name") - pool_type = self.context.raw_param.get("enable_azure_container_storage") - pool_sku = self.context.raw_param.get("storage_pool_sku") - pool_size = self.context.raw_param.get("storage_pool_size") - nodepool_list = self.context.get_intermediate("azure_container_storage_nodepools") - ephemeral_disk_volume_type = self.context.raw_param.get("ephemeral_disk_volume_type") - ephemeral_disk_nvme_perf_tier = self.context.raw_param.get("ephemeral_disk_nvme_perf_tier") - kubelet_identity_object_id = cluster.identity_profile["kubeletidentity"].object_id - acstor_nodepool_skus = [] - for agentpool_profile in cluster.agent_pool_profiles: - if agentpool_profile.name in nodepool_list: - acstor_nodepool_skus.append(agentpool_profile.vm_size) + if container_storage_version is not None and container_storage_version == CONST_ACSTOR_VERSION_V1: + if cluster.identity_profile is None or cluster.identity_profile["kubeletidentity"] is None: + logger.warning( + "Unexpected error getting kubelet's identity for the cluster." + "Unable to perform azure container storage operation." + ) + return + pool_name = self.context.raw_param.get("storage_pool_name") + pool_type = self.context.raw_param.get("enable_azure_container_storage") + pool_sku = self.context.raw_param.get("storage_pool_sku") + pool_size = self.context.raw_param.get("storage_pool_size") + nodepool_list = self.context.get_intermediate("azure_container_storage_nodepools") + ephemeral_disk_volume_type = self.context.raw_param.get("ephemeral_disk_volume_type") + ephemeral_disk_nvme_perf_tier = self.context.raw_param.get("ephemeral_disk_nvme_perf_tier") + kubelet_identity_object_id = cluster.identity_profile["kubeletidentity"].object_id + acstor_nodepool_skus = [] + for agentpool_profile in cluster.agent_pool_profiles: + if agentpool_profile.name in nodepool_list: + acstor_nodepool_skus.append(agentpool_profile.vm_size) - self.context.external_functions.perform_enable_azure_container_storage( - self.cmd, - self.context.get_subscription_id(), - self.context.get_resource_group_name(), - self.context.get_name(), - self.context.get_node_resource_group(), - kubelet_identity_object_id, - pool_name, - pool_type, - pool_size, - pool_sku, - pool_option, - acstor_nodepool_skus, - ephemeral_disk_volume_type, - ephemeral_disk_nvme_perf_tier, - False, - existing_ephemeral_disk_volume_type, - existing_ephemeral_nvme_perf_tier, - is_extension_installed, - is_azureDisk_enabled, - is_elasticSan_enabled, - is_ephemeralDisk_localssd_enabled, - is_ephemeralDisk_nvme_enabled, - current_core_value, - is_called_from_extension=True, - ) + self.context.external_functions.perform_enable_azure_container_storage_v1( + self.cmd, + self.context.get_subscription_id(), + self.context.get_resource_group_name(), + self.context.get_name(), + self.context.get_node_resource_group(), + kubelet_identity_object_id, + pool_name, + pool_type, + pool_size, + pool_sku, + pool_option, + acstor_nodepool_skus, + ephemeral_disk_volume_type, + ephemeral_disk_nvme_perf_tier, + False, + existing_ephemeral_disk_volume_type, + existing_ephemeral_nvme_perf_tier, + is_extension_installed, + is_azureDisk_enabled, + is_elasticSan_enabled, + is_ephemeralDisk_localssd_enabled, + is_ephemeralDisk_nvme_enabled, + current_core_value, + is_called_from_extension=True, + ) + else: + self.context.external_functions.perform_enable_azure_container_storage( + self.cmd, + self.context.get_resource_group_name(), + self.context.get_name(), + is_called_from_extension=True, + ) # disable azure container storage if disable_azure_container_storage: - pool_type = self.context.raw_param.get("disable_azure_container_storage") - kubelet_identity_object_id = cluster.identity_profile["kubeletidentity"].object_id - pre_disable_validate = self.context.get_intermediate("pre_disable_validate_azure_container_storage") - self.context.external_functions.perform_disable_azure_container_storage( - self.cmd, - self.context.get_subscription_id(), - self.context.get_resource_group_name(), - self.context.get_name(), - self.context.get_node_resource_group(), - kubelet_identity_object_id, - pre_disable_validate, - pool_type, - pool_option, - is_elasticSan_enabled, - is_azureDisk_enabled, - is_ephemeralDisk_localssd_enabled, - is_ephemeralDisk_nvme_enabled, - current_core_value, - existing_ephemeral_disk_volume_type, - existing_ephemeral_nvme_perf_tier, - is_called_from_extension=True, - ) + if container_storage_version is not None: + if container_storage_version == CONST_ACSTOR_VERSION_V1: + pool_type = self.context.raw_param.get("disable_azure_container_storage") + kubelet_identity_object_id = cluster.identity_profile["kubeletidentity"].object_id + pre_disable_validate = self.context.get_intermediate("pre_disable_validate_azure_container_storage") + self.context.external_functions.perform_disable_azure_container_storage_v1( + self.cmd, + self.context.get_subscription_id(), + self.context.get_resource_group_name(), + self.context.get_name(), + self.context.get_node_resource_group(), + kubelet_identity_object_id, + pre_disable_validate, + pool_type, + pool_option, + is_elasticSan_enabled, + is_azureDisk_enabled, + is_ephemeralDisk_localssd_enabled, + is_ephemeralDisk_nvme_enabled, + current_core_value, + existing_ephemeral_disk_volume_type, + existing_ephemeral_nvme_perf_tier, + is_called_from_extension=True, + ) + + else: + self.context.external_functions.perform_disable_azure_container_storage( + self.cmd, + self.context.get_resource_group_name(), + self.context.get_name(), + is_called_from_extension=True, + ) # attach keyvault to app routing addon from azure.cli.command_modules.keyvault.custom import set_policy 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 c364cd1908b..6b6032b26f9 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 @@ -27,6 +27,7 @@ from azure.cli.testsdk.scenario_tests import AllowLargeResponse from azure.core.exceptions import HttpResponseError from knack.util import CLIError +from azext_aks_preview.azurecontainerstorage._consts import (CONST_ACSTOR_EXT_INSTALLATION_NAME, CONST_ACSTOR_K8S_EXTENSION_NAME) from .test_localdns_profile import assert_dns_overrides_equal, vnetDnsOverridesExpected, kubeDnsOverridesExpected @@ -11996,11 +11997,11 @@ def test_aks_create_with_azuremonitorappmonitoring( # live only due to downloading k8s-extension extension @live_only() - @AllowLargeResponse(99999) + @AllowLargeResponse(999999) @AKSCustomResourceGroupPreparer( - random_name_length=17, name_prefix="clitest", location="westus2" + random_name_length=17, name_prefix="clitest", location="eastus" ) - def test_aks_create_with_azurecontainerstorage( + def test_aks_create_with_azurecontainerstorage_v1( self, resource_group, resource_group_location ): # reset the count so in replay mode the random names will start with 0 @@ -12025,7 +12026,7 @@ def test_aks_create_with_azurecontainerstorage( create_cmd = ( "aks create --resource-group={resource_group} --name={name} --location={location} --ssh-key-value={ssh_key_value} --node-vm-size={node_vm_size} " - "--node-count 3 --enable-managed-identity --enable-azure-container-storage azureDisk --output=json" + "--node-count 3 --enable-managed-identity --container-storage-version 1 --enable-azure-container-storage azureDisk --output=json" ) # enabling azurecontainerstorage will not affect any field in the cluster. @@ -12050,11 +12051,11 @@ def test_aks_create_with_azurecontainerstorage( # live only due to downloading k8s-extension extension @live_only() - @AllowLargeResponse(99999) + @AllowLargeResponse(999999) @AKSCustomResourceGroupPreparer( - random_name_length=17, name_prefix="clitest", location="westus2" + random_name_length=17, name_prefix="clitest", location="australiaeast" ) - def test_aks_create_with_azurecontainerstorage_with_ephemeral_disk_parameters( + def test_aks_create_with_azurecontainerstorage_v1_with_ephemeral_disk_parameters( self, resource_group, resource_group_location ): # reset the count so in replay mode the random names will start with 0 @@ -12079,7 +12080,7 @@ def test_aks_create_with_azurecontainerstorage_with_ephemeral_disk_parameters( create_cmd = ( "aks create --resource-group={resource_group} --name={name} --location={location} --ssh-key-value={ssh_key_value} --node-vm-size={node_vm_size} " - "--node-count 3 --enable-managed-identity --enable-azure-container-storage ephemeralDisk --storage-pool-option NVMe " + "--node-count 3 --enable-managed-identity --container-storage-version 1 --enable-azure-container-storage ephemeralDisk --storage-pool-option NVMe " "--ephemeral-disk-volume-type EphemeralVolumeOnly --ephemeral-disk-nvme-perf-tier Premium --output=json" ) @@ -12105,11 +12106,11 @@ def test_aks_create_with_azurecontainerstorage_with_ephemeral_disk_parameters( # live only due to downloading k8s-extension extension @live_only() - @AllowLargeResponse(99999) + @AllowLargeResponse(999999) @AKSCustomResourceGroupPreparer( - random_name_length=17, name_prefix="clitest", location="westus2" + random_name_length=17, name_prefix="clitest", location="swedencentral" ) - def test_aks_create_with_azurecontainerstorage_with_nodepool_name( + def test_aks_create_with_azurecontainerstorage_v1_with_nodepool_name( self, resource_group, resource_group_location ): # reset the count so in replay mode the random names will start with 0 @@ -12136,7 +12137,7 @@ def test_aks_create_with_azurecontainerstorage_with_nodepool_name( create_cmd = ( "aks create --resource-group={resource_group} --name={name} --location={location} --ssh-key-value={ssh_key_value} --node-vm-size={node_vm_size} " - "--node-count 3 --nodepool-name {nodepool_name} --enable-managed-identity --enable-azure-container-storage azureDisk --output=json" + "--node-count 3 --nodepool-name {nodepool_name} --enable-managed-identity --container-storage-version 1 --enable-azure-container-storage azureDisk --output=json" ) # enabling azurecontainerstorage will not affect any field in the cluster. @@ -12160,6 +12161,153 @@ def test_aks_create_with_azurecontainerstorage_with_nodepool_name( ], ) + # live only due to downloading k8s-extension extension + @live_only() + @AllowLargeResponse(99999) + @AKSCustomResourceGroupPreparer( + random_name_length=17, name_prefix="clitest", location="uksouth" + ) + def test_aks_create_with_azurecontainerstorage(self, resource_group, resource_group_location): + + # 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) + + node_vm_size = "Standard_L8s_v3" + self.kwargs.update( + { + "resource_group": resource_group, + "name": aks_name, + "location": resource_group_location, + "resource_type": "Microsoft.ContainerService/ManagedClusters", + "ssh_key_value": self.generate_ssh_keys(), + "node_vm_size": node_vm_size, + } + ) + + # add k8s-extension extension for azurecontainerstorage operations. + self.cmd("extension add --name k8s-extension") + + create_cmd = ( + "aks create --resource-group={resource_group} --name={name} --location={location} " + "--ssh-key-value={ssh_key_value} --node-vm-size={node_vm_size} " + "--node-count 3 --enable-managed-identity --enable-azure-container-storage --output=json" + ) + + self.cmd( + create_cmd, + checks=[ + self.check("provisioningState", "Succeeded"), + ], + ) + + # Verify that the azure-container-storage extension is installed + extension_list_cmd = "k8s-extension list --resource-group={resource_group} --cluster-name={name} --cluster-type managedClusters" + extensions = self.cmd(extension_list_cmd).get_output_in_json() + + # Check if azure-container-storage extension exists + acs_extension_found = False + for extension in extensions: + if extension.get("name") == CONST_ACSTOR_EXT_INSTALLATION_NAME : + acs_extension_found = True + # Additional checks on the extension properties + assert extension.get("provisioningState") == "Succeeded", "Extension provisioning failed" + assert extension.get("extensionType") == CONST_ACSTOR_K8S_EXTENSION_NAME, "Wrong extension type" + break + + assert acs_extension_found, "Azure Container Storage not found" + + # delete + cmd = ( + "aks delete --resource-group={resource_group} --name={name} --yes --no-wait" + ) + self.cmd( + cmd, + checks=[ + self.is_empty(), + ], + ) + + @live_only() + @AllowLargeResponse(99999) + @AKSCustomResourceGroupPreparer(random_name_length=17, name_prefix='clitest', location='ukwest') + def test_aks_update_with_azurecontainerstorage(self, resource_group, resource_group_location): + + aks_name = self.create_random_name('cliakstest', 16) + node_vm_size = 'standard_l8s_v3' + self.kwargs.update({ + 'resource_group': resource_group, + 'name': aks_name, + 'location': resource_group_location, + 'ssh_key_value': self.generate_ssh_keys(), + 'node_vm_size': node_vm_size, + }) + + # add k8s-extension extension for azurecontainerstorage operations. + # self.cmd('extension add --name k8s-extension') + + # create: without enable-azure-container-storage + create_cmd = 'aks create --resource-group={resource_group} --name={name} --location={location} --ssh-key-value={ssh_key_value} --node-vm-size={node_vm_size} --node-count 3 --enable-managed-identity --output=json' + self.cmd(create_cmd, checks=[ + self.check('provisioningState', 'Succeeded'), + ]) + + # update: enable-azure-container-storage + update_cmd = 'aks update --resource-group={resource_group} --name={name} --yes --output=json ' \ + '--enable-azure-container-storage' + + self.cmd(update_cmd, checks=[ + self.check('provisioningState', 'Succeeded'), + ]) + + # Verify that the azure-container-storage extension is installed + extension_list_cmd = "k8s-extension list --resource-group={resource_group} --cluster-name={name} --cluster-type managedClusters" + extensions = self.cmd(extension_list_cmd).get_output_in_json() + + # Check if azure-container-storage extension exists + acs_extension_found = False + for extension in extensions: + if extension.get("name") == CONST_ACSTOR_EXT_INSTALLATION_NAME : + acs_extension_found = True + # Additional checks on the extension properties + assert extension.get("provisioningState") == "Succeeded", "Extension provisioning failed" + assert extension.get("extensionType") == CONST_ACSTOR_K8S_EXTENSION_NAME, "Wrong extension type" + break + + assert acs_extension_found, "Azure Container Storage not found" + + # Sleep for 5 mins before next operation, + # since update operations take + # some time to finish. + time.sleep(10 * 60) + + # update: disable-azure-container-storage + update_cmd = 'aks update --resource-group={resource_group} --name={name} --yes --output=json ' \ + '--disable-azure-container-storage' + self.cmd(update_cmd, checks=[ + self.check('provisioningState', 'Succeeded'), + ]) + + # Verify that the azure-container-storage extension doesn't exist anymore + extension_list_cmd = "k8s-extension list --resource-group={resource_group} --cluster-name={name} --cluster-type managedClusters" + extensions = self.cmd(extension_list_cmd).get_output_in_json() + + # Check if azure-container-storage extension sill exists + acs_extension_found = False + for extension in extensions: + if extension.get("name") == CONST_ACSTOR_EXT_INSTALLATION_NAME : + acs_extension_found = True + break + + assert not acs_extension_found, "Azure Container Storage v2 still exists after disable operation" + + # delete + cmd = 'aks delete --resource-group={resource_group} --name={name} --yes --no-wait' + self.cmd(cmd, checks=[ + self.is_empty(), + ]) + @live_only() @AllowLargeResponse() @AKSCustomResourceGroupPreparer( @@ -12282,9 +12430,9 @@ def test_aks_update_with_azuremonitorappmonitoring(self, resource_group, resourc ]) @live_only() - @AllowLargeResponse(99999) - @AKSCustomResourceGroupPreparer(random_name_length=17, name_prefix='clitest', location='westus2') - def test_aks_update_with_azurecontainerstorage(self, resource_group, resource_group_location): + @AllowLargeResponse(999999) + @AKSCustomResourceGroupPreparer(random_name_length=17, name_prefix='clitest', location='swedencentral') + def test_aks_update_with_azurecontainerstorage_v1(self, resource_group, resource_group_location): aks_name = self.create_random_name('cliakstest', 16) node_vm_size = 'standard_d4s_v3' self.kwargs.update({ @@ -12309,7 +12457,7 @@ def test_aks_update_with_azurecontainerstorage(self, resource_group, resource_gr # update: enable-azure-container-storage update_cmd = 'aks update --resource-group={resource_group} --name={name} --yes --output=json ' \ - '--enable-azure-container-storage azureDisk' + '--container-storage-version 1 --enable-azure-container-storage azureDisk' self.cmd(update_cmd, checks=[ self.check('provisioningState', 'Succeeded'), @@ -12334,9 +12482,9 @@ def test_aks_update_with_azurecontainerstorage(self, resource_group, resource_gr ]) @live_only() - @AllowLargeResponse(99999) - @AKSCustomResourceGroupPreparer(random_name_length=17, name_prefix='clitest', location='westus2') - def test_aks_update_with_azurecontainerstorage_with_ephemeral_disk_parameters(self, resource_group, resource_group_location): + @AllowLargeResponse(999999) + @AKSCustomResourceGroupPreparer(random_name_length=17, name_prefix='clitest', location='westeurope') + def test_aks_update_with_azurecontainerstorage_v1_with_ephemeral_disk_parameters(self, resource_group, resource_group_location): aks_name = self.create_random_name('cliakstest', 16) node_vm_size = 'standard_l8s_v3' self.kwargs.update({ @@ -12363,7 +12511,7 @@ def test_aks_update_with_azurecontainerstorage_with_ephemeral_disk_parameters(se update_cmd = 'aks update --resource-group={resource_group} --name={name} --yes --output=json ' \ '--enable-azure-container-storage ephemeralDisk --storage-pool-option NVMe ' \ '--ephemeral-disk-volume-type PersistentVolumeWithAnnotation ' \ - '--ephemeral-disk-nvme-perf-tier Standard' + '--ephemeral-disk-nvme-perf-tier Standard --container-storage-version 1' self.cmd(update_cmd, checks=[ self.check('provisioningState', 'Succeeded'), @@ -12387,6 +12535,154 @@ def test_aks_update_with_azurecontainerstorage_with_ephemeral_disk_parameters(se self.is_empty(), ]) + + # live only due to downloading k8s-extension extension + @live_only() + @AllowLargeResponse(99999) + @AKSCustomResourceGroupPreparer( + random_name_length=17, name_prefix="clitest", location="uksouth" + ) + def test_aks_create_with_azurecontainerstorage(self, resource_group, resource_group_location): + + # 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) + + node_vm_size = "Standard_L8s_v3" + self.kwargs.update( + { + "resource_group": resource_group, + "name": aks_name, + "location": resource_group_location, + "resource_type": "Microsoft.ContainerService/ManagedClusters", + "ssh_key_value": self.generate_ssh_keys(), + "node_vm_size": node_vm_size, + } + ) + + # add k8s-extension extension for azurecontainerstorage operations. + self.cmd("extension add --name k8s-extension") + + create_cmd = ( + "aks create --resource-group={resource_group} --name={name} --location={location} " + "--ssh-key-value={ssh_key_value} --node-vm-size={node_vm_size} " + "--node-count 3 --enable-managed-identity --enable-azure-container-storage --output=json" + ) + + self.cmd( + create_cmd, + checks=[ + self.check("provisioningState", "Succeeded"), + ], + ) + + # Verify that the azure-container-storage extension is installed + extension_list_cmd = "k8s-extension list --resource-group={resource_group} --cluster-name={name} --cluster-type managedClusters" + extensions = self.cmd(extension_list_cmd).get_output_in_json() + + # Check if azure-container-storage extension exists + acs_extension_found = False + for extension in extensions: + if extension.get("name") == CONST_ACSTOR_EXT_INSTALLATION_NAME : + acs_extension_found = True + # Additional checks on the extension properties + assert extension.get("provisioningState") == "Succeeded", "Extension provisioning failed" + assert extension.get("extensionType") == CONST_ACSTOR_K8S_EXTENSION_NAME, "Wrong extension type" + break + + assert acs_extension_found, "Azure Container Storage not found" + + # delete + cmd = ( + "aks delete --resource-group={resource_group} --name={name} --yes --no-wait" + ) + self.cmd( + cmd, + checks=[ + self.is_empty(), + ], + ) + + @live_only() + @AllowLargeResponse(99999) + @AKSCustomResourceGroupPreparer(random_name_length=17, name_prefix='clitest', location='uksouth') + def test_aks_update_with_azurecontainerstorage(self, resource_group, resource_group_location): + + aks_name = self.create_random_name('cliakstest', 16) + node_vm_size = 'standard_l8s_v3' + self.kwargs.update({ + 'resource_group': resource_group, + 'name': aks_name, + 'location': resource_group_location, + 'ssh_key_value': self.generate_ssh_keys(), + 'node_vm_size': node_vm_size, + }) + + # add k8s-extension extension for azurecontainerstorage operations. + # self.cmd('extension add --name k8s-extension') + + # create: without enable-azure-container-storage + create_cmd = 'aks create --resource-group={resource_group} --name={name} --location={location} --ssh-key-value={ssh_key_value} --node-vm-size={node_vm_size} --node-count 3 --enable-managed-identity --output=json' + self.cmd(create_cmd, checks=[ + self.check('provisioningState', 'Succeeded'), + ]) + + # update: enable-azure-container-storage + update_cmd = 'aks update --resource-group={resource_group} --name={name} --yes --output=json ' \ + '--enable-azure-container-storage' + + self.cmd(update_cmd, checks=[ + self.check('provisioningState', 'Succeeded'), + ]) + + # Verify that the azure-container-storage extension is installed + extension_list_cmd = "k8s-extension list --resource-group={resource_group} --cluster-name={name} --cluster-type managedClusters" + extensions = self.cmd(extension_list_cmd).get_output_in_json() + + # Check if azure-container-storage extension exists + acs_extension_found = False + for extension in extensions: + if extension.get("name") == CONST_ACSTOR_EXT_INSTALLATION_NAME : + acs_extension_found = True + # Additional checks on the extension properties + assert extension.get("provisioningState") == "Succeeded", "Extension provisioning failed" + assert extension.get("extensionType") == CONST_ACSTOR_K8S_EXTENSION_NAME, "Wrong extension type" + break + + assert acs_extension_found, "Azure Container Storage not found" + + # Sleep for 5 mins before next operation, + # since update operations take + # some time to finish. + time.sleep(10 * 60) + + # update: disable-azure-container-storage + update_cmd = 'aks update --resource-group={resource_group} --name={name} --yes --output=json ' \ + '--disable-azure-container-storage' + self.cmd(update_cmd, checks=[ + self.check('provisioningState', 'Succeeded'), + ]) + + # Verify that the azure-container-storage extension doesn't exist anymore + extension_list_cmd = "k8s-extension list --resource-group={resource_group} --cluster-name={name} --cluster-type managedClusters" + extensions = self.cmd(extension_list_cmd).get_output_in_json() + + # Check if azure-container-storage extension sill exists + acs_extension_found = False + for extension in extensions: + if extension.get("name") == CONST_ACSTOR_EXT_INSTALLATION_NAME : + acs_extension_found = True + break + + assert not acs_extension_found, "Azure Container Storage v2 still exists after disable operation" + + # delete + cmd = 'aks delete --resource-group={resource_group} --name={name} --yes --no-wait' + self.cmd(cmd, checks=[ + self.is_empty(), + ]) + # live only due to workspace is not mocked correctly @AllowLargeResponse() @AKSCustomResourceGroupPreparer( diff --git a/src/aks-preview/azext_aks_preview/tests/latest/test_validators.py b/src/aks-preview/azext_aks_preview/tests/latest/test_validators.py index 819a1e9e921..2bed8b22478 100644 --- a/src/aks-preview/azext_aks_preview/tests/latest/test_validators.py +++ b/src/aks-preview/azext_aks_preview/tests/latest/test_validators.py @@ -929,8 +929,7 @@ def test_valid_resource_quotas(self): namespace = ManagedNamespace(cpu_request="500m", cpu_limit="800m", memory_request="1Gi", memory_limit="2Gi") validators.validate_resource_quota(namespace) - -class TestValidateDisableAzureContainerStorage(unittest.TestCase): +class TestValidateDisableAzureContainerStorageV1(unittest.TestCase): def test_disable_when_extension_not_installed(self): is_extension_installed = False err = ( @@ -938,7 +937,7 @@ def test_disable_when_extension_not_installed(self): "Azure Container Storage is not enabled in the cluster." ) with self.assertRaises(InvalidArgumentValueError) as cm: - acstor_validator.validate_disable_azure_container_storage_params( + acstor_validator.validate_disable_azure_container_storage_params_v1( None, None, None, None, None, None, is_extension_installed, False, False, False, False, None, None ) self.assertEqual(str(cm.exception), err) @@ -950,8 +949,8 @@ def test_disable_flag_with_storage_pool_name(self): "when --disable-azure-container-storage is set." ) with self.assertRaises(MutuallyExclusiveArgumentError) as cm: - acstor_validator.validate_disable_azure_container_storage_params( - None, storage_pool_name, None, None, None, None, True, False, False, False, False, None, None + acstor_validator.validate_disable_azure_container_storage_params_v1( + acstor_consts.CONST_STORAGE_POOL_TYPE_AZURE_DISK, storage_pool_name, None, None, None, None, True, False, False, False, False, None, None ) self.assertEqual(str(cm.exception), err) @@ -962,8 +961,8 @@ def test_disable_flag_with_storage_pool_sku(self): "when --disable-azure-container-storage is set." ) with self.assertRaises(MutuallyExclusiveArgumentError) as cm: - acstor_validator.validate_disable_azure_container_storage_params( - None, None, storage_pool_sku, None, None, None, True, False, False, False, False, None, None + acstor_validator.validate_disable_azure_container_storage_params_v1( + acstor_consts.CONST_STORAGE_POOL_TYPE_EPHEMERAL_DISK, None, storage_pool_sku, None, None, None, True, False, False, False, False, None, None ) self.assertEqual(str(cm.exception), err) @@ -974,8 +973,8 @@ def test_disable_flag_with_storage_pool_size(self): "when --disable-azure-container-storage is set." ) with self.assertRaises(MutuallyExclusiveArgumentError) as cm: - acstor_validator.validate_disable_azure_container_storage_params( - None, None, None, None, storage_pool_size, None, True, False, False, False, False, None, None + acstor_validator.validate_disable_azure_container_storage_params_v1( + acstor_consts.CONST_STORAGE_POOL_TYPE_AZURE_DISK, None, None, None, storage_pool_size, None, True, False, False, False, False, None, None ) self.assertEqual(str(cm.exception), err) @@ -987,8 +986,8 @@ def test_disable_flag_with_ephemeral_disk_volume_type(self): "when --disable-azure-container-storage is set." ) with self.assertRaises(MutuallyExclusiveArgumentError) as cm: - acstor_validator.validate_disable_azure_container_storage_params( - None, None, None, None, None, None, True, False, False, False, False, ephemeral_disk_volume_type, None + acstor_validator.validate_disable_azure_container_storage_params_v1( + acstor_consts.CONST_STORAGE_POOL_TYPE_EPHEMERAL_DISK, None, None, None, None, None, True, False, False, False, False, ephemeral_disk_volume_type, None ) self.assertEqual(str(cm.exception), err) @@ -1001,8 +1000,8 @@ def test_disable_flag_with_ephemeral_disk_nvme_perf_tier(self): "when --disable-azure-container-storage is set." ) with self.assertRaises(MutuallyExclusiveArgumentError) as cm: - acstor_validator.validate_disable_azure_container_storage_params( - None, None, None, None, None, None, True, False, False, False, False, None, perf_tier + acstor_validator.validate_disable_azure_container_storage_params_v1( + acstor_consts.CONST_STORAGE_POOL_TYPE_EPHEMERAL_DISK, None, None, None, None, None, True, False, False, False, False, None, perf_tier ) self.assertEqual(str(cm.exception), err) @@ -1014,7 +1013,7 @@ def test_disable_flag_with_storage_pool_option_not_ephemeralDisk(self): "--disable-azure-container-storage is not set to ephemeralDisk." ) with self.assertRaises(ArgumentUsageError) as cm: - acstor_validator.validate_disable_azure_container_storage_params( + acstor_validator.validate_disable_azure_container_storage_params_v1( storage_pool_type, None, None, storage_pool_option, None, None, True, False, False, False, False, None, None ) self.assertEqual(str(cm.exception), err) @@ -1026,7 +1025,7 @@ def test_disable_flag_with_storage_pool_option_not_set_both_ephemeralDisk_enable "the types: NVMe and Temp are enabled in the cluster." ) with self.assertRaises(RequiredArgumentMissingError) as cm: - acstor_validator.validate_disable_azure_container_storage_params( + acstor_validator.validate_disable_azure_container_storage_params_v1( storage_pool_type, None, None, None, None, None, True, False, False, True, True, None, None ) self.assertEqual(str(cm.exception), err) @@ -1039,7 +1038,7 @@ def test_disable_flag_with_nodepool_list(self): "when --disable-azure-container-storage is set." ) with self.assertRaises(MutuallyExclusiveArgumentError) as cm: - acstor_validator.validate_disable_azure_container_storage_params( + acstor_validator.validate_disable_azure_container_storage_params_v1( storage_pool_type, None, None, None, None, nodepool_list, True, False, False, False, False, None, None ) self.assertEqual(str(cm.exception), err) @@ -1053,7 +1052,7 @@ def test_disable_type_when_not_enabled(self): "type {0} in the cluster.".format(pool_type) ) with self.assertRaises(ArgumentUsageError) as cm: - acstor_validator.validate_disable_azure_container_storage_params( + acstor_validator.validate_disable_azure_container_storage_params_v1( pool_type, None, None, None, None, None, True, is_azureDisk_enabled, False, False, False, None, None ) self.assertEqual(str(cm.exception), err) @@ -1066,7 +1065,7 @@ def test_disable_only_storage_pool_installed(self): "To disable Azure Container Storage, set --disable-azure-container-storage to all." ) with self.assertRaises(ArgumentUsageError) as cm: - acstor_validator.validate_disable_azure_container_storage_params( + acstor_validator.validate_disable_azure_container_storage_params_v1( pool_type, None, None, None, None, None, True, True, False, False, False, None, None ) self.assertEqual(str(cm.exception), err) @@ -1080,19 +1079,19 @@ def test_disable_only_storagepool_type_enabled(self): "To disable Azure Container Storage, set --disable-azure-container-storage to all." ) with self.assertRaises(ArgumentUsageError) as cm: - acstor_validator.validate_disable_azure_container_storage_params( + acstor_validator.validate_disable_azure_container_storage_params_v1( pool_type, None, None, None, None, None, True, is_azureDisk_enabled, False, False, False, None, None ) self.assertEqual(str(cm.exception), err) def test_valid_disable(self): pool_type = acstor_consts.CONST_STORAGE_POOL_TYPE_ELASTIC_SAN - acstor_validator.validate_disable_azure_container_storage_params( + acstor_validator.validate_disable_azure_container_storage_params_v1( pool_type, None, None, None, None, None, True, False, True, True, False, None, None ) -class TestValidateEnableAzureContainerStorage(unittest.TestCase): +class TestValidateEnableAzureContainerStorageV1(unittest.TestCase): @classmethod def setUpClass(cls): def side_effect_fn(sku_name): @@ -1120,6 +1119,21 @@ def tearDownClass(cls): # Stop the patcher cls.patcher.stop() + def test_enable_when_latest_enabled(self): + v2_extension_version = "2.0.0" + err = ( + 'Failed to enable Azure Container Storage version 1 as Azure Container Storage version ' + f'{v2_extension_version} is already installed on the cluster. Try enabling this version on another ' + 'cluster. You can also enable this version by first disabling the existing installation of ' + 'Azure Container Storage by running --disable-azure-container-storage. ' + 'Note that disabling can impact existing workloads that depend on Azure Container Storage.' + ) + with self.assertRaises(InvalidArgumentValueError) as cm: + acstor_validator.validate_enable_azure_container_storage_v1_params( + acstor_consts.CONST_STORAGE_POOL_TYPE_AZURE_DISK, None, None, None, None, None, None, False, True, v2_extension_version, False, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD + ) + self.assertEqual(str(cm.exception), err) + def test_enable_with_invalid_storage_pool_name(self): storage_pool_name = "my_test_pool" err = ( @@ -1128,8 +1142,8 @@ def test_enable_with_invalid_storage_pool_name(self): "'-' or '.', and must start and end with an alphanumeric character." ) with self.assertRaises(InvalidArgumentValueError) as cm: - acstor_validator.validate_enable_azure_container_storage_params( - None, storage_pool_name, None, None, None, None, None, False, False, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD + acstor_validator.validate_enable_azure_container_storage_v1_params( + acstor_consts.CONST_STORAGE_POOL_TYPE_AZURE_DISK, storage_pool_name, None, None, None, None, None, False, False, "", False, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD ) self.assertEqual(str(cm.exception), err) @@ -1139,8 +1153,8 @@ def test_enable_with_sku_and_ephemeral_disk_pool(self): storage_pool_type = acstor_consts.CONST_STORAGE_POOL_TYPE_EPHEMERAL_DISK err = "Cannot set --storage-pool-sku when --enable-azure-container-storage is ephemeralDisk." with self.assertRaises(ArgumentUsageError) as cm: - acstor_validator.validate_enable_azure_container_storage_params( - storage_pool_type, storage_pool_name, storage_pool_sku, None, None, None, None, False, False, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD + acstor_validator.validate_enable_azure_container_storage_v1_params( + storage_pool_type, storage_pool_name, storage_pool_sku, None, None, None, None, False, False, "", False, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD ) self.assertEqual(str(cm.exception), err) @@ -1161,8 +1175,8 @@ def test_enable_with_sku_and_elastic_san_pool(self): ) ) with self.assertRaises(ArgumentUsageError) as cm: - acstor_validator.validate_enable_azure_container_storage_params( - storage_pool_type, storage_pool_name, storage_pool_sku, None, None, None, None, False, False, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD + acstor_validator.validate_enable_azure_container_storage_v1_params( + storage_pool_type, storage_pool_name, storage_pool_sku, None, None, None, None, False,False, "", False, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD ) self.assertEqual(str(cm.exception), err) @@ -1180,8 +1194,8 @@ def test_enable_with_premiumv2_sku_and_azure_disk(self): ) ) with self.assertRaises(ArgumentUsageError) as cm: - acstor_validator.validate_enable_azure_container_storage_params( - storage_pool_type, storage_pool_name, storage_pool_sku, None, None, nodepool_list, agentpools, False, False, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD + acstor_validator.validate_enable_azure_container_storage_v1_params( + storage_pool_type, storage_pool_name, storage_pool_sku, None, None, nodepool_list, agentpools, False, False, "", False, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD ) self.assertEqual(str(cm.exception), err) @@ -1197,8 +1211,8 @@ def test_enable_with_insufficient_cores_1(self): "which is assigned for Azure Container Storage has nodes with 2 cores." ) with self.assertRaises(InvalidArgumentValueError) as cm: - acstor_validator.validate_enable_azure_container_storage_params( - storage_pool_type, storage_pool_name, storage_pool_sku, None, None, nodepool_list, agentpools, False, False, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD + acstor_validator.validate_enable_azure_container_storage_v1_params( + storage_pool_type, storage_pool_name, storage_pool_sku, None, None, nodepool_list, agentpools, False, False, "", False, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD ) self.assertEqual(str(cm.exception), err) @@ -1214,8 +1228,8 @@ def test_enable_with_insufficient_cores_2(self): "which is assigned for Azure Container Storage has nodes with 2 cores." ) with self.assertRaises(InvalidArgumentValueError) as cm: - acstor_validator.validate_enable_azure_container_storage_params( - storage_pool_type, storage_pool_name, storage_pool_sku, None, None, nodepool_list, agentpools, False, False, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD + acstor_validator.validate_enable_azure_container_storage_v1_params( + storage_pool_type, storage_pool_name, storage_pool_sku, None, None, nodepool_list, agentpools, False, False, "", False, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD ) self.assertEqual(str(cm.exception), err) @@ -1231,8 +1245,8 @@ def test_enable_with_insufficient_cores_3(self): "which is assigned for Azure Container Storage has nodes with 1 cores." ) with self.assertRaises(InvalidArgumentValueError) as cm: - acstor_validator.validate_enable_azure_container_storage_params( - storage_pool_type, storage_pool_name, storage_pool_sku, None, None, nodepool_list, agentpools, False, False, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD + acstor_validator.validate_enable_azure_container_storage_v1_params( + storage_pool_type, storage_pool_name, storage_pool_sku, None, None, nodepool_list, agentpools, False, False, "", False, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD ) self.assertEqual(str(cm.exception), err) @@ -1248,8 +1262,8 @@ def test_enable_with_insufficient_cores_4(self): "which is assigned for Azure Container Storage has nodes with 2 cores." ) with self.assertRaises(InvalidArgumentValueError) as cm: - acstor_validator.validate_enable_azure_container_storage_params( - storage_pool_type, storage_pool_name, storage_pool_sku, None, None, nodepool_list, agentpools, False, False, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD + acstor_validator.validate_enable_azure_container_storage_v1_params( + storage_pool_type, storage_pool_name, storage_pool_sku, None, None, nodepool_list, agentpools, False, False, "", False, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD ) self.assertEqual(str(cm.exception), err) @@ -1265,8 +1279,8 @@ def test_enable_with_insufficient_cores_5(self): "which is assigned for Azure Container Storage has nodes with 2 cores." ) with self.assertRaises(InvalidArgumentValueError) as cm: - acstor_validator.validate_enable_azure_container_storage_params( - storage_pool_type, storage_pool_name, storage_pool_sku, None, None, nodepool_list, agentpools, False, False, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD + acstor_validator.validate_enable_azure_container_storage_v1_params( + storage_pool_type, storage_pool_name, storage_pool_sku, None, None, nodepool_list, agentpools, False, False, "", False, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD ) self.assertEqual(str(cm.exception), err) @@ -1276,8 +1290,8 @@ def test_enable_with_option_and_non_ephemeral_disk_pool(self): storage_pool_type = acstor_consts.CONST_STORAGE_POOL_TYPE_AZURE_DISK err = "Cannot set --storage-pool-option when --enable-azure-container-storage is not ephemeralDisk." with self.assertRaises(ArgumentUsageError) as cm: - acstor_validator.validate_enable_azure_container_storage_params( - storage_pool_type, storage_pool_name, None, storage_pool_option, None, None, None, False, False, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD + acstor_validator.validate_enable_azure_container_storage_v1_params( + storage_pool_type, storage_pool_name, None, storage_pool_option, None, None, None, False, False, "", False, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD ) self.assertEqual(str(cm.exception), err) @@ -1287,8 +1301,8 @@ def test_enable_with_ephemeral_disk_volume_type_and_non_ephemeral_disk_pool(self storage_pool_type = acstor_consts.CONST_STORAGE_POOL_TYPE_AZURE_DISK err = "Cannot set --ephemeral-disk-volume-type when --enable-azure-container-storage is not ephemeralDisk." with self.assertRaises(ArgumentUsageError) as cm: - acstor_validator.validate_enable_azure_container_storage_params( - storage_pool_type, storage_pool_name, None, None, None, None, None, False, False, False, False, False, ephemeral_disk_volume_type, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD + acstor_validator.validate_enable_azure_container_storage_v1_params( + storage_pool_type, storage_pool_name, None, None, None, None, None, False, False, "", False, False, False, False, ephemeral_disk_volume_type, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD ) self.assertEqual(str(cm.exception), err) @@ -1300,8 +1314,8 @@ def test_enable_with_ephemeral_disk_nvme_perf_tier_and_non_ephemeral_disk_pool(s "Cannot set --ephemeral-disk-nvme-perf-tier when --enable-azure-container-storage is not ephemeralDisk." ) with self.assertRaises(ArgumentUsageError) as cm: - acstor_validator.validate_enable_azure_container_storage_params( - storage_pool_type, storage_pool_name, None, None, None, None, None, False, False, False, False, False, None, perf_tier, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD + acstor_validator.validate_enable_azure_container_storage_v1_params( + storage_pool_type, storage_pool_name, None, None, None, None, None, False, False, "", False, False, False, False, None, perf_tier, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD ) self.assertEqual(str(cm.exception), err) @@ -1316,8 +1330,8 @@ def test_enable_with_ephemeral_disk_nvme_perf_tier_and_ephemeral_temp_disk_pool( "Enable the option using --storage-pool-option." ) with self.assertRaises(ArgumentUsageError) as cm: - acstor_validator.validate_enable_azure_container_storage_params( - storage_pool_type, storage_pool_name, None, storage_pool_option, None, None, None, False, False, False, False, False, None, perf_tier, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD + acstor_validator.validate_enable_azure_container_storage_v1_params( + storage_pool_type, storage_pool_name, None, storage_pool_option, None, None, None, False, False, "", False, False, False, False, None, perf_tier, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD ) self.assertEqual(str(cm.exception), err) @@ -1325,12 +1339,12 @@ def test_enable_with_same_ephemeral_disk_nvme_perf_tier_already_set(self): perf_tier = acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_PREMIUM storage_pool_type = acstor_consts.CONST_STORAGE_POOL_TYPE_EPHEMERAL_DISK err = ( - "Azure Container Storage is already configured with --ephemeral-disk-nvme-perf-tier " - f"value set to {perf_tier}." + "Azure Container Storage is already configured with --ephemeral-disk-nvme-perf-tier " + f"value set to {perf_tier}." ) with self.assertRaises(InvalidArgumentValueError) as cm: - acstor_validator.validate_enable_azure_container_storage_params( - storage_pool_type, None, None, None, None, None, None, True, False, False, False, True, None, perf_tier, acstor_consts.CONST_DISK_TYPE_PV_WITH_ANNOTATION, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_PREMIUM + acstor_validator.validate_enable_azure_container_storage_v1_params( + storage_pool_type, None, None, None, None, None, None, True, False, "", False, False, False, True, None, perf_tier, acstor_consts.CONST_DISK_TYPE_PV_WITH_ANNOTATION, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_PREMIUM ) self.assertEqual(str(cm.exception), err) @@ -1338,12 +1352,12 @@ def test_enable_with_same_ephemeral_disk_volume_type_already_set(self): disk_vol_type = acstor_consts.CONST_DISK_TYPE_PV_WITH_ANNOTATION storage_pool_type = acstor_consts.CONST_STORAGE_POOL_TYPE_EPHEMERAL_DISK err = ( - "Azure Container Storage is already configured with --ephemeral-disk-volume-type " - f"value set to {disk_vol_type}." + "Azure Container Storage is already configured with --ephemeral-disk-volume-type " + f"value set to {disk_vol_type}." ) with self.assertRaises(InvalidArgumentValueError) as cm: - acstor_validator.validate_enable_azure_container_storage_params( - storage_pool_type, None, None, None, None, None, None, True, False, False, False, True, disk_vol_type, None, acstor_consts.CONST_DISK_TYPE_PV_WITH_ANNOTATION, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_PREMIUM + acstor_validator.validate_enable_azure_container_storage_v1_params( + storage_pool_type, None, None, None, None, None, None, True, False, "", False, False, False, True, disk_vol_type, None, acstor_consts.CONST_DISK_TYPE_PV_WITH_ANNOTATION, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_PREMIUM ) self.assertEqual(str(cm.exception), err) @@ -1352,13 +1366,13 @@ def test_enable_with_same_ephemeral_disk_nvme_perf_tier_and_ephemeral_temp_disk_ disk_vol_type = acstor_consts.CONST_DISK_TYPE_PV_WITH_ANNOTATION storage_pool_type = acstor_consts.CONST_STORAGE_POOL_TYPE_EPHEMERAL_DISK err = ( - "Azure Container Storage is already configured with --ephemeral-disk-volume-type " - f"value set to {disk_vol_type} and --ephemeral-disk-nvme-perf-tier " - f"value set to {perf_tier}." + "Azure Container Storage is already configured with --ephemeral-disk-volume-type " + f"value set to {disk_vol_type} and --ephemeral-disk-nvme-perf-tier " + f"value set to {perf_tier}." ) with self.assertRaises(InvalidArgumentValueError) as cm: - acstor_validator.validate_enable_azure_container_storage_params( - storage_pool_type, None, None, None, None, None, None, True, False, False, False, True, disk_vol_type, perf_tier, acstor_consts.CONST_DISK_TYPE_PV_WITH_ANNOTATION, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD + acstor_validator.validate_enable_azure_container_storage_v1_params( + storage_pool_type, None, None, None, None, None, None, True, False, "", False, False, False, True, disk_vol_type, perf_tier, acstor_consts.CONST_DISK_TYPE_PV_WITH_ANNOTATION, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD ) self.assertEqual(str(cm.exception), err) @@ -1368,8 +1382,8 @@ def test_enable_with_option_all_and_ephemeral_disk_pool(self): storage_pool_type = acstor_consts.CONST_STORAGE_POOL_TYPE_EPHEMERAL_DISK err = "Cannot set --storage-pool-option value as all when --enable-azure-container-storage is set." with self.assertRaises(InvalidArgumentValueError) as cm: - acstor_validator.validate_enable_azure_container_storage_params( - storage_pool_type, storage_pool_name, None, storage_pool_option, None, None, None, False, False, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD + acstor_validator.validate_enable_azure_container_storage_v1_params( + storage_pool_type, storage_pool_name, None, storage_pool_option, None, False, "", None, None, False, False, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD ) self.assertEqual(str(cm.exception), err) @@ -1379,8 +1393,8 @@ def test_enable_with_invalid_storage_pool_size(self): storage_pool_type = acstor_consts.CONST_STORAGE_POOL_TYPE_AZURE_DISK err = "Value for --storage-pool-size should be defined with size followed by Gi or Ti e.g. 512Gi or 2Ti." with self.assertRaises(ArgumentUsageError) as cm: - acstor_validator.validate_enable_azure_container_storage_params( - storage_pool_type, storage_pool_name, None, None, storage_pool_size, None, None, False, False, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD + acstor_validator.validate_enable_azure_container_storage_v1_params( + storage_pool_type, storage_pool_name, None, None, storage_pool_size, None, None, False, False, "", False, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD ) self.assertEqual(str(cm.exception), err) @@ -1390,8 +1404,8 @@ def test_enable_with_invalid_size_for_esan_storage_pool(self): storage_pool_type = acstor_consts.CONST_STORAGE_POOL_TYPE_ELASTIC_SAN err = "Value for --storage-pool-size must be at least 1Ti when --enable-azure-container-storage is elasticSan." with self.assertRaises(ArgumentUsageError) as cm: - acstor_validator.validate_enable_azure_container_storage_params( - storage_pool_type, storage_pool_name, None, None, storage_pool_size, None, None, False, False, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD + acstor_validator.validate_enable_azure_container_storage_v1_params( + storage_pool_type, storage_pool_name, None, None, storage_pool_size, None, None, False, False, "", False, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD ) self.assertEqual(str(cm.exception), err) @@ -1407,8 +1421,8 @@ def test_invalid_comma_separated_nodepool_list(self): "alphanumeric characters and must begin with a lowercase letter." ) with self.assertRaises(InvalidArgumentValueError) as cm: - acstor_validator.validate_enable_azure_container_storage_params( - storage_pool_type, storage_pool_name, None, None, storage_pool_size, nodepool_list, None, False, False, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD + acstor_validator.validate_enable_azure_container_storage_v1_params( + storage_pool_type, storage_pool_name, None, None, storage_pool_size, nodepool_list, None, False, False, "", False, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD ) self.assertEqual(str(cm.exception), err) @@ -1426,8 +1440,8 @@ def test_missing_nodepool_from_cluster_nodepool_list_single(self): "\nAborting installation of Azure Container Storage." ) with self.assertRaises(InvalidArgumentValueError) as cm: - acstor_validator.validate_enable_azure_container_storage_params( - storage_pool_type, storage_pool_name, None, storage_pool_option, storage_pool_size, nodepool_list, agentpools, False, False, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD + acstor_validator.validate_enable_azure_container_storage_v1_params( + storage_pool_type, storage_pool_name, None, storage_pool_option, storage_pool_size, nodepool_list, agentpools, False, False, "", False, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD ) self.assertEqual(str(cm.exception), err) @@ -1437,7 +1451,7 @@ def test_missing_nodepool_from_cluster_nodepool_list_multiple(self): storage_pool_type = acstor_consts.CONST_STORAGE_POOL_TYPE_EPHEMERAL_DISK storage_pool_option = acstor_consts.CONST_STORAGE_POOL_OPTION_SSD nodepool_list = "pool1,pool2" - agentpools = {"nodepool1": {}, "nodepool2": {}} + agentpools = {"nodepool1": {}, "nodepool2":{}} err = ( "Node pool: pool1 not found. Please provide a comma separated " "string of existing node pool names in --azure-container-storage-nodepools." @@ -1445,8 +1459,8 @@ def test_missing_nodepool_from_cluster_nodepool_list_multiple(self): "\nAborting installation of Azure Container Storage." ) with self.assertRaises(InvalidArgumentValueError) as cm: - acstor_validator.validate_enable_azure_container_storage_params( - storage_pool_type, storage_pool_name, None, storage_pool_option, storage_pool_size, nodepool_list, agentpools, False, False, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD + acstor_validator.validate_enable_azure_container_storage_v1_params( + storage_pool_type, storage_pool_name, None, storage_pool_option, storage_pool_size, nodepool_list, agentpools, False, False, "", False, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD ) self.assertEqual(str(cm.exception), err) @@ -1456,16 +1470,15 @@ def test_system_nodepool_with_taint(self): storage_pool_type = acstor_consts.CONST_STORAGE_POOL_TYPE_EPHEMERAL_DISK storage_pool_option = acstor_consts.CONST_STORAGE_POOL_OPTION_SSD nodepool_list = "nodepool1" - agentpools = {"nodepool1": {"mode": "System", "node_taints": [ - "CriticalAddonsOnly=true:NoSchedule"]}, "nodepool2": {"count": 1}} + agentpools = {"nodepool1": {"mode": "System", "node_taints": ["CriticalAddonsOnly=true:NoSchedule"]}, "nodepool2": {"count": 1}} err = ( 'Unable to install Azure Container Storage on system nodepool: nodepool1 ' 'since it has a taint CriticalAddonsOnly=true:NoSchedule. Remove the taint from the node pool ' 'and retry the Azure Container Storage operation.' ) with self.assertRaises(InvalidArgumentValueError) as cm: - acstor_validator.validate_enable_azure_container_storage_params( - storage_pool_type, storage_pool_name, None, storage_pool_option, storage_pool_size, nodepool_list, agentpools, False, False, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD + acstor_validator.validate_enable_azure_container_storage_v1_params( + storage_pool_type, storage_pool_name, None, storage_pool_option, storage_pool_size, nodepool_list, agentpools, False, False, "", False, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD ) self.assertEqual(str(cm.exception), err) @@ -1480,8 +1493,8 @@ def test_nodepool_from_cluster_nodepool_list_with_insufficient_count(self): "Insufficient nodes present. Azure Container Storage requires atleast 3 nodes to be enabled." ) with self.assertRaises(UnknownError) as cm: - acstor_validator.validate_enable_azure_container_storage_params( - storage_pool_type, storage_pool_name, None, storage_pool_option, storage_pool_size, nodepool_list, agentpools, False, False, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD + acstor_validator.validate_enable_azure_container_storage_v1_params( + storage_pool_type, storage_pool_name, None, storage_pool_option, storage_pool_size, nodepool_list, agentpools, False, False, "", False, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD ) self.assertEqual(str(cm.exception), err) @@ -1492,8 +1505,8 @@ def test_valid_enable_for_azure_disk_pool(self): storage_pool_sku = acstor_consts.CONST_STORAGE_POOL_SKU_PREMIUM_LRS nodepool_list = "nodepool1,nodepool2" agentpools = {"nodepool1": {"mode": "User", "count": 2}, "nodepool2": {"mode": "System", "count": 1}} - acstor_validator.validate_enable_azure_container_storage_params( - storage_pool_type, storage_pool_name, storage_pool_sku, None, storage_pool_size, nodepool_list, agentpools, False, False, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD + acstor_validator.validate_enable_azure_container_storage_v1_params( + storage_pool_type, storage_pool_name, storage_pool_sku, None, storage_pool_size, nodepool_list, agentpools, False, False, "", False, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD ) def test_valid_enable_for_ephemeral_disk_pool(self): @@ -1502,10 +1515,9 @@ def test_valid_enable_for_ephemeral_disk_pool(self): storage_pool_type = acstor_consts.CONST_STORAGE_POOL_TYPE_EPHEMERAL_DISK storage_pool_option = acstor_consts.CONST_STORAGE_POOL_OPTION_NVME nodepool_list = "nodepool1" - agentpools = {"nodepool1": {"vm_size": "Standard_L8s_v3", "mode": "System", - "count": 5}, "nodepool2": {"vm_size": "Standard_L8s_v3"}} - acstor_validator.validate_enable_azure_container_storage_params( - storage_pool_type, storage_pool_name, None, storage_pool_option, storage_pool_size, nodepool_list, agentpools, False, False, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD + agentpools = {"nodepool1": {"vm_size": "Standard_L8s_v3", "mode": "System", "count": 5}, "nodepool2": {"vm_size": "Standard_L8s_v3"}} + acstor_validator.validate_enable_azure_container_storage_v1_params( + storage_pool_type, storage_pool_name, None, storage_pool_option, storage_pool_size, nodepool_list, agentpools, False, False, "", False, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD ) def test_valid_enable_for_ephemeral_disk_pool_with_ephemeral_disk_volume_type(self): @@ -1515,18 +1527,17 @@ def test_valid_enable_for_ephemeral_disk_pool_with_ephemeral_disk_volume_type(se storage_pool_option = acstor_consts.CONST_STORAGE_POOL_OPTION_NVME nodepool_list = "nodepool1" ephemeral_disk_volume_type = acstor_consts.CONST_DISK_TYPE_PV_WITH_ANNOTATION - agentpools = {"nodepool1": {"vm_size": "Standard_L8s_v3", "mode": "System", - "count": 3}, "nodepool2": {"vm_size": "Standard_L8s_v3"}} - acstor_validator.validate_enable_azure_container_storage_params( - storage_pool_type, storage_pool_name, None, storage_pool_option, storage_pool_size, nodepool_list, agentpools, False, False, False, False, False, ephemeral_disk_volume_type, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD + agentpools = {"nodepool1": {"vm_size": "Standard_L8s_v3", "mode": "System", "count": 3}, "nodepool2": {"vm_size": "Standard_L8s_v3"}} + acstor_validator.validate_enable_azure_container_storage_v1_params( + storage_pool_type, storage_pool_name, None, storage_pool_option, storage_pool_size, nodepool_list, agentpools, False, False, "", False, False, False, False, ephemeral_disk_volume_type, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD ) def test_valid_enable_for_ephemeral_disk_pool_with_ephemeral_disk_volume_type_already_installed(self): storage_pool_type = acstor_consts.CONST_STORAGE_POOL_TYPE_EPHEMERAL_DISK ephemeral_disk_volume_type = acstor_consts.CONST_DISK_TYPE_PV_WITH_ANNOTATION agentpools = {"nodepool1": {"node_labels": {"acstor.azure.com/io-engine": "acstor"}, "count": 3}, "nodepool2": {}} - acstor_validator.validate_enable_azure_container_storage_params( - storage_pool_type, None, None, None, None, None, agentpools, True, False, False, True, False, ephemeral_disk_volume_type, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD + acstor_validator.validate_enable_azure_container_storage_v1_params( + storage_pool_type, None, None, None, None, None, agentpools, True, False, "", False, False, True, False, ephemeral_disk_volume_type, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD ) def test_valid_enable_for_ephemeral_disk_pool_with_ephemeral_disk_nvme_perf_tier(self): @@ -1536,18 +1547,17 @@ def test_valid_enable_for_ephemeral_disk_pool_with_ephemeral_disk_nvme_perf_tier storage_pool_option = acstor_consts.CONST_STORAGE_POOL_OPTION_NVME nodepool_list = "nodepool1" perf_tier = acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_PREMIUM - agentpools = {"nodepool1": {"vm_size": "Standard_L8s_v3", "count": 4}, - "nodepool2": {"vm_size": "Standard_L8s_v3"}} - acstor_validator.validate_enable_azure_container_storage_params( - storage_pool_type, storage_pool_name, None, storage_pool_option, storage_pool_size, nodepool_list, agentpools, False, False, False, False, False, None, perf_tier, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD + agentpools = {"nodepool1": {"vm_size": "Standard_L8s_v3", "count": 4}, "nodepool2": {"vm_size": "Standard_L8s_v3"}} + acstor_validator.validate_enable_azure_container_storage_v1_params( + storage_pool_type, storage_pool_name, None, storage_pool_option, storage_pool_size, nodepool_list, agentpools, False, False, "", False, False, False, False, None, perf_tier, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD ) def test_valid_enable_for_ephemeral_disk_pool_with_azure_container_storage_per_tier_nvme_already_installed(self): storage_pool_type = acstor_consts.CONST_STORAGE_POOL_TYPE_EPHEMERAL_DISK perf_tier = acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_PREMIUM agentpools = {"nodepool1": {"node_labels": {"acstor.azure.com/io-engine": "acstor"}, "count": 3}, "nodepool2": {}} - acstor_validator.validate_enable_azure_container_storage_params( - storage_pool_type, None, None, None, None, None, agentpools, True, False, False, False, True, None, perf_tier, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD + acstor_validator.validate_enable_azure_container_storage_v1_params( + storage_pool_type, None, None, None, None, None, agentpools, True, False, "", False, False, False, True, None, perf_tier, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD ) def test_extension_installed_nodepool_list_defined(self): @@ -1560,8 +1570,8 @@ def test_extension_installed_nodepool_list_defined(self): "Use 'az aks nodepool' to label the node pool instead." ) with self.assertRaises(ArgumentUsageError) as cm: - acstor_validator.validate_enable_azure_container_storage_params( - storage_pool_type, None, None, None, None, nodepool_list, None, True, False, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD + acstor_validator.validate_enable_azure_container_storage_v1_params( + storage_pool_type, None, None, None, None, nodepool_list, None, True, False, "", False, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD ) self.assertEqual(str(cm.exception), err) @@ -1570,15 +1580,15 @@ def test_extension_installed_storagepool_type_installed(self): storage_pool_size = "5Ti" storage_pool_type = acstor_consts.CONST_STORAGE_POOL_TYPE_AZURE_DISK storage_pool_sku = acstor_consts.CONST_STORAGE_POOL_SKU_PREMIUM_LRS - agentpools = {"nodepool1": {"node_labels": {"acstor.azure.com/io-engine": "acstor"}, "count": 3}, "nodepool2": {}} + agentpools = {"nodepool1": {"node_labels": {"acstor.azure.com/io-engine": "acstor"}, "count": 3}, "nodepool2" :{}} err = ( "Invalid --enable-azure-container-storage value. " "Azure Container Storage is already enabled for storage pool type " "{0} in the cluster.".format(storage_pool_type) ) with self.assertRaises(ArgumentUsageError) as cm: - acstor_validator.validate_enable_azure_container_storage_params( - storage_pool_type, storage_pool_name, storage_pool_sku, None, storage_pool_size, None, agentpools, True, True, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD + acstor_validator.validate_enable_azure_container_storage_v1_params( + storage_pool_type, storage_pool_name, storage_pool_sku, None, storage_pool_size, None, agentpools, True, False, "", True, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD ) self.assertEqual(str(cm.exception), err) @@ -1587,11 +1597,177 @@ def test_valid_cluster_update(self): storage_pool_size = "5Ti" storage_pool_type = acstor_consts.CONST_STORAGE_POOL_TYPE_AZURE_DISK storage_pool_sku = acstor_consts.CONST_STORAGE_POOL_SKU_PREMIUM_LRS - agentpools = {"nodepool1": {"node_labels": {"acstor.azure.com/io-engine": "acstor"}, - "mode": "User", "count": 3}, "nodepool2": {}} - acstor_validator.validate_enable_azure_container_storage_params( - storage_pool_type, storage_pool_name, storage_pool_sku, None, storage_pool_size, None, agentpools, True, False, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD + agentpools = {"nodepool1": {"node_labels": {"acstor.azure.com/io-engine": "acstor"}, "mode": "User", "count": 3}, "nodepool2": {}} + acstor_validator.validate_enable_azure_container_storage_v1_params( + storage_pool_type, storage_pool_name, storage_pool_sku, None, storage_pool_size, None, agentpools, True, False, "", False, False, False, False, None, None, acstor_consts.CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD + ) + +class TestValidateDisableAzureContainerStorage(unittest.TestCase): + def test_disable_with_storagepool_type_params(self): + storage_pool_type = acstor_consts.CONST_STORAGE_POOL_TYPE_AZURE_DISK + with self.assertRaises(InvalidArgumentValueError) as cm: + acstor_validator.validate_disable_azure_container_storage_params( + True, storage_pool_type, "", "", "", "", + ) + err = ( + 'The latest version of Azure Container Storage only supports ephemeral nvme storage and does not ' + 'require or support a storage-pool-type value for --disable-azure-container-storage parameter. ' + f'Please remove {storage_pool_type} from the command and try again.' + ) + self.assertEqual(str(cm.exception), err) + + def test_disable_when_acstor_not_enabled(self): + with self.assertRaises(InvalidArgumentValueError) as cm: + acstor_validator.validate_disable_azure_container_storage_params( + False, "", "", "", "", "", + ) + err = ( + 'Cannot disable Azure Container Storage as it is not enabled on the cluster.' + ) + self.assertEqual(str(cm.exception), err) + + def test_disable_with_storage_pool_name(self): + storage_pool_name = "valid-name" + with self.assertRaises(InvalidArgumentValueError) as cm: + acstor_validator.validate_disable_azure_container_storage_params( + True, True, storage_pool_name, "", "", "", + ) + err = ( + 'The latest version of Azure Container Storage does not ' + 'require or support a --storage-pool-name value. ' + f'Please remove --storage-pool-name {storage_pool_name} from the command and try again.' + ) + self.assertEqual(str(cm.exception), err) + + def test_disable_with_storage_pool_sku(self): + storage_pool_sku = "valid-sku" + with self.assertRaises(InvalidArgumentValueError) as cm: + acstor_validator.validate_disable_azure_container_storage_params( + True, True, None, storage_pool_sku, "", "", + ) + err = ( + 'The latest version of Azure Container Storage does not ' + 'require or support a --storage-pool-sku value. ' + f'Please remove --storage-pool-sku {storage_pool_sku} from the command and try again.' + ) + self.assertEqual(str(cm.exception), err) + + def test_disable_with_storage_pool_option(self): + storage_pool_option = "valid-option" + with self.assertRaises(InvalidArgumentValueError) as cm: + acstor_validator.validate_disable_azure_container_storage_params( + True, True, None, None, storage_pool_option, "", + ) + err = ( + 'The latest version of Azure Container Storage does not ' + 'require or support a --storage-pool-option value. ' + f'Please remove --storage-pool-option {storage_pool_option} from the command and try again.' + ) + self.assertEqual(str(cm.exception), err) + + def test_disable_with_storage_pool_size(self): + storage_pool_size = "valid-size" + with self.assertRaises(InvalidArgumentValueError) as cm: + acstor_validator.validate_disable_azure_container_storage_params( + True, True, None, None, None, storage_pool_size + ) + err = ( + 'The latest version of Azure Container Storage does not ' + 'require or support a --storage-pool-size value. ' + f'Please remove --storage-pool-size {storage_pool_size} from the command and try again.' + ) + self.assertEqual(str(cm.exception), err) + +class TestValidateEnableAzureContainerStorage(unittest.TestCase): + def test_enable_when_already_enabled(self): + with self.assertRaises(InvalidArgumentValueError) as cm: + acstor_validator.validate_enable_azure_container_storage_params( + True, "", "", "", "", "", "", "", + ) + err = ( + 'Cannot enable Azure Container Storage as it is already enabled on the cluster.' + ) + self.assertEqual(str(cm.exception), err) + + def test_enable_when_v1_installed(self): + v1_extension_version = "1.3.0" + with self.assertRaises(InvalidArgumentValueError) as cm: + acstor_validator.validate_enable_azure_container_storage_params( + False, True, v1_extension_version, "", "", "", "", "", + ) + err = ( + f'Failed to enable the latest version of Azure Container Storage as version {v1_extension_version} ' + 'is already installed on the cluster. Try enabling Azure Container Storage in another cluster. ' + 'You can also enable the latest version by first disabling the existing installation using ' + '--disable-azure-container-storage all. Note that disabling can impact existing workloads ' + 'that depend on Azure Container Storage.' + ) + self.assertEqual(str(cm.exception), err) + + def test_enable_with_storagepool_type(self): + storage_pool_type = acstor_consts.CONST_STORAGE_POOL_TYPE_AZURE_DISK + with self.assertRaises(InvalidArgumentValueError) as cm: + acstor_validator.validate_enable_azure_container_storage_params( + False, False, "", storage_pool_type, None, None, None, None, + ) + err = ( + 'The latest version of Azure Container Storage only supports ephemeral nvme storage and does not ' + 'require or support a storage-pool-type value for --enable-azure-container-storage parameter. ' + f'Please remove {storage_pool_type} from the command and try again.' + ) + self.assertEqual(str(cm.exception), err) + + def test_enable_with_storage_pool_name(self): + storage_pool_name = "valid-name" + with self.assertRaises(InvalidArgumentValueError) as cm: + acstor_validator.validate_enable_azure_container_storage_params( + False, False, "", None, storage_pool_name, None, None, None, + ) + err = ( + 'The latest version of Azure Container Storage does not ' + 'require or support a --storage-pool-name value. ' + f'Please remove --storage-pool-name {storage_pool_name} from the command and try again.' + ) + self.assertEqual(str(cm.exception), err) + + def test_enable_with_storage_pool_sku(self): + storage_pool_sku = "valid-sku" + with self.assertRaises(InvalidArgumentValueError) as cm: + acstor_validator.validate_enable_azure_container_storage_params( + False, False, "", None, None, storage_pool_sku, None, None, + ) + err = ( + 'The latest version of Azure Container Storage does not ' + 'require or support a --storage-pool-sku value. ' + f'Please remove --storage-pool-sku {storage_pool_sku} from the command and try again.' + ) + self.assertEqual(str(cm.exception), err) + + def test_enable_with_storage_pool_option(self): + storage_pool_option = "valid-option" + with self.assertRaises(InvalidArgumentValueError) as cm: + acstor_validator.validate_enable_azure_container_storage_params( + False, False, "", None, None, None, storage_pool_option, None, + ) + err = ( + 'The latest version of Azure Container Storage does not ' + 'require or support a --storage-pool-option value. ' + f'Please remove --storage-pool-option {storage_pool_option} from the command and try again.' ) + self.assertEqual(str(cm.exception), err) + + def test_enable_with_storagepool_size(self): + storage_pool_size = "valid-size" + with self.assertRaises(InvalidArgumentValueError) as cm: + acstor_validator.validate_enable_azure_container_storage_params( + False, False, "", None, None, None, None, storage_pool_size, + ) + err = ( + 'The latest version of Azure Container Storage does not ' + 'require or support a --storage-pool-size value. ' + f'Please remove --storage-pool-size {storage_pool_size} from the command and try again.' + ) + self.assertEqual(str(cm.exception), err) class GatewayPrefixSizeSpace: diff --git a/src/aks-preview/linter_exclusions.yml b/src/aks-preview/linter_exclusions.yml index 9e9e1260603..d1bb0efc247 100644 --- a/src/aks-preview/linter_exclusions.yml +++ b/src/aks-preview/linter_exclusions.yml @@ -63,6 +63,9 @@ aks create: enable_azure_container_storage: rule_exclusions: - option_length_too_long + container_storage_version: + rule_exclusions: + - option_length_too_long ephemeral_disk_volume_type: rule_exclusions: - option_length_too_long @@ -203,6 +206,9 @@ aks update: disable_azure_container_storage: rule_exclusions: - option_length_too_long + container_storage_version: + rule_exclusions: + - option_length_too_long azure_container_storage_nodepools: rule_exclusions: - option_length_too_long From 49f6a160bd5b9baf43bc48de62a2e55ab8d9bd37 Mon Sep 17 00:00:00 2001 From: Neha Arora Date: Fri, 5 Sep 2025 19:33:48 -0700 Subject: [PATCH 2/7] Fix some issues and add History notes --- src/aks-preview/HISTORY.rst | 8 ++++++++ .../azext_aks_preview/managed_cluster_decorator.py | 8 +------- .../azext_aks_preview/tests/latest/test_validators.py | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/aks-preview/HISTORY.rst b/src/aks-preview/HISTORY.rst index cc523c3d42a..440476773e1 100644 --- a/src/aks-preview/HISTORY.rst +++ b/src/aks-preview/HISTORY.rst @@ -30,6 +30,14 @@ Pending * Enable autoscaler support for the VirtualMachines agent pool type. * Add `--node-vm-size` to `az aks update` to allow changing the VM size for VirtualMachines node pools. +18.0.0b37 ++++++++ +* `az aks create`: Add new parameter `--container-storage-version` to enable the given version of Azure Container Storage. +* `az aks update`: Add new parameter `--container-storage-version` to enable the given version of Azure Container Storage. +* `az aks create`: Change behavior of `--enable-azure-container-storage` to enable latest Azure Container Storage by default. +* `az aks update`: Change behavior of `--enable-azure-container-storage` to enable latest Azure Container Storage by default. +* `az aks update`: Change behavior of `--disable-azure-container-storage` to handle disable based on the installed version. + 18.0.0b36 +++++++ * Add option `AzureLinux3` to `--os-sku` for `az aks nodepool add` and `az aks nodepool update`. 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 bedb41b54e2..a002bf7a2c9 100644 --- a/src/aks-preview/azext_aks_preview/managed_cluster_decorator.py +++ b/src/aks-preview/azext_aks_preview/managed_cluster_decorator.py @@ -4146,7 +4146,7 @@ def postprocessing_after_mc_created(self, cluster: ManagedCluster) -> None: enable_azure_container_storage = self.context.get_intermediate("enable_azure_container_storage") container_storage_version = self.context.get_intermediate("container_storage_version") if enable_azure_container_storage: - if container_storage_version is None and container_storage_version == CONST_ACSTOR_VERSION_V1: + if container_storage_version is not None and container_storage_version == CONST_ACSTOR_VERSION_V1: if cluster.identity_profile is None or cluster.identity_profile["kubeletidentity"] is None: logger.warning( "Unexpected error getting kubelet's identity for the cluster. " @@ -4228,12 +4228,6 @@ def postprocessing_after_mc_created(self, cluster: ManagedCluster) -> None: scope=cluster.id, resolve_assignee=False, ) - else: - self.context.external_functions.perform_enable_azure_container_storage( - self.cmd, - self.context.get_resource_group_name(), - self.context.get_name(), - ) class AKSPreviewManagedClusterUpdateDecorator(AKSManagedClusterUpdateDecorator): diff --git a/src/aks-preview/azext_aks_preview/tests/latest/test_validators.py b/src/aks-preview/azext_aks_preview/tests/latest/test_validators.py index 2bed8b22478..de4322d7765 100644 --- a/src/aks-preview/azext_aks_preview/tests/latest/test_validators.py +++ b/src/aks-preview/azext_aks_preview/tests/latest/test_validators.py @@ -1580,7 +1580,7 @@ def test_extension_installed_storagepool_type_installed(self): storage_pool_size = "5Ti" storage_pool_type = acstor_consts.CONST_STORAGE_POOL_TYPE_AZURE_DISK storage_pool_sku = acstor_consts.CONST_STORAGE_POOL_SKU_PREMIUM_LRS - agentpools = {"nodepool1": {"node_labels": {"acstor.azure.com/io-engine": "acstor"}, "count": 3}, "nodepool2" :{}} + agentpools = {"nodepool1": {"node_labels": {"acstor.azure.com/io-engine": "acstor"}, "count": 3}, "nodepool2": {}} err = ( "Invalid --enable-azure-container-storage value. " "Azure Container Storage is already enabled for storage pool type " From bda7f7b0a12966442100907de35c1043c03d8993 Mon Sep 17 00:00:00 2001 From: Neha Arora Date: Fri, 5 Sep 2025 22:17:53 -0700 Subject: [PATCH 3/7] Minor fixes --- .../azurecontainerstorage/_helpers.py | 3 +- .../managed_cluster_decorator.py | 118 +++++++++----- .../tests/latest/test_aks_commands.py | 148 ------------------ .../tests/latest/test_validators.py | 16 +- 4 files changed, 84 insertions(+), 201 deletions(-) diff --git a/src/aks-preview/azext_aks_preview/azurecontainerstorage/_helpers.py b/src/aks-preview/azext_aks_preview/azurecontainerstorage/_helpers.py index 6f7bec95f22..05862767521 100644 --- a/src/aks-preview/azext_aks_preview/azurecontainerstorage/_helpers.py +++ b/src/aks-preview/azext_aks_preview/azurecontainerstorage/_helpers.py @@ -8,7 +8,6 @@ from azext_aks_preview.azurecontainerstorage._consts import ( CONST_ACSTOR_ALL, CONST_ACSTOR_IO_ENGINE_LABEL_KEY, - CONST_ACSTOR_K8S_EXTENSION_NAME, CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, CONST_DISK_TYPE_PV_WITH_ANNOTATION, CONST_EPHEMERAL_NVME_PERF_TIER_BASIC, @@ -182,7 +181,7 @@ def get_extension_installed_and_cluster_configs( "managedClusters", ) - is_extension_installed = extension.extension_type.lower() == CONST_ACSTOR_K8S_EXTENSION_NAME + is_extension_installed = extension.extension_type.lower() == CONST_ACSTOR_V1_K8S_EXTENSION_NAME config_settings = extension.configuration_settings if is_extension_installed and config_settings is not 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 a002bf7a2c9..0630336513d 100644 --- a/src/aks-preview/azext_aks_preview/managed_cluster_decorator.py +++ b/src/aks-preview/azext_aks_preview/managed_cluster_decorator.py @@ -3607,10 +3607,16 @@ def set_up_azure_container_storage(self, mc: ManagedCluster) -> ManagedCluster: CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD, ) - from azure.cli.command_modules.acs.azurecontainerstorage._helpers import ( - generate_vm_sku_cache_for_region + + vm_cache_generated = self.context.get_intermediate( + "vm_cache_generated", + default_value=False, ) - generate_vm_sku_cache_for_region(self.cmd.cli_ctx, self.context.get_location()) + + if not vm_cache_generated: + from azext_aks_preview.azurecontainerstorage._helpers import generate_vm_sku_cache_for_region + generate_vm_sku_cache_for_region(self.cmd.cli_ctx, self.context.get_location()) + self.context.set_intermediate("vm_cache_generated", True, overwrite_exists=True) default_ephemeral_disk_volume_type = CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY default_ephemeral_disk_nvme_perf_tier = CONST_EPHEMERAL_NVME_PERF_TIER_STANDARD @@ -4214,20 +4220,20 @@ def postprocessing_after_mc_created(self, cluster: ManagedCluster) -> None: is_called_from_extension=True, ) - # Add role assignments for automatic sku - if cluster.sku is not None and cluster.sku.name == "Automatic": - try: - user = get_graph_client(self.cmd.cli_ctx).signed_in_user_get() - except Exception as e: # pylint: disable=broad-except - logger.warning("Could not get signed in user: %s", str(e)) - else: - self.context.external_functions._add_role_assignment_executor_new( # type: ignore # pylint: disable=protected-access - self.cmd, - "Azure Kubernetes Service RBAC Cluster Admin", - user["id"], - scope=cluster.id, - resolve_assignee=False, - ) + # Add role assignments for automatic sku + if cluster.sku is not None and cluster.sku.name == "Automatic": + try: + user = get_graph_client(self.cmd.cli_ctx).signed_in_user_get() + except Exception as e: # pylint: disable=broad-except + logger.warning("Could not get signed in user: %s", str(e)) + else: + self.context.external_functions._add_role_assignment_executor_new( # type: ignore # pylint: disable=protected-access + self.cmd, + "Azure Kubernetes Service RBAC Cluster Admin", + user["id"], + scope=cluster.id, + resolve_assignee=False, + ) class AKSPreviewManagedClusterUpdateDecorator(AKSManagedClusterUpdateDecorator): @@ -4546,26 +4552,38 @@ def update_azure_container_storage(self, mc: ManagedCluster) -> ManagedCluster: from azure.cli.command_modules.acs.azurecontainerstorage._helpers import ( get_extension_installed_and_cluster_configs ) - ( - is_extension_installed, - is_azureDisk_enabled, - is_elasticSan_enabled, - is_ephemeralDisk_localssd_enabled, - is_ephemeralDisk_nvme_enabled, - current_core_value, - existing_ephemeral_disk_volume_type, - existing_perf_tier, - ) = get_extension_installed_and_cluster_configs( - self.cmd, - self.context.get_resource_group_name(), - self.context.get_name(), - mc.agent_pool_profiles, + try: + ( + is_extension_installed, + is_azureDisk_enabled, + is_elasticSan_enabled, + is_ephemeralDisk_localssd_enabled, + is_ephemeralDisk_nvme_enabled, + current_core_value, + existing_ephemeral_disk_volume_type, + existing_perf_tier, + ) = get_extension_installed_and_cluster_configs( + self.cmd, + self.context.get_resource_group_name(), + self.context.get_name(), + mc.agent_pool_profiles, + ) + except UnknownError as e: + logger.error("\nError fetching installed extension and cluster config: %s", e) + return mc + except Exception as ex: # pylint: disable=broad-except + logger.error("Exception fetching installed extension and cluster config: %s", ex) + return mc + + vm_cache_generated = self.context.get_intermediate( + "vm_cache_generated", + default_value=False, ) - from azure.cli.command_modules.acs.azurecontainerstorage._helpers import ( - generate_vm_sku_cache_for_region - ) - generate_vm_sku_cache_for_region(self.cmd.cli_ctx, self.context.get_location()) + if not vm_cache_generated: + from azext_aks_preview.azurecontainerstorage._helpers import generate_vm_sku_cache_for_region + generate_vm_sku_cache_for_region(self.cmd.cli_ctx, self.context.get_location()) + self.context.set_intermediate("vm_cache_generated", True, overwrite_exists=True) if enable_azure_container_storage: from azure.cli.command_modules.acs.azurecontainerstorage._helpers import ( @@ -4647,13 +4665,29 @@ def update_azure_container_storage(self, mc: ManagedCluster) -> ManagedCluster: ) if is_ephemeralDisk_nvme_enabled and ephemeral_disk_nvme_perf_tier is not None: - msg = ( - "Changing ephemeralDisk NVMe performance tier may result in a temporary " - "interruption to the applications using Azure Container Storage. Do you " - "want to continue with this operation?" + # Adding this intermediate and check to ensure that the below + # message prompt doesn't appear twice when aks-preview extension + # is called from both update_mc_profile_preview and update_mc_profile_default. + is_azure_container_storage_perf_tier_op_set = self.context.get_intermediate( + "azure_container_storage_perf_tier_op_set", + default_value="default", ) - if not (self.context.get_yes() or prompt_y_n(msg, default="n")): - raise DecoratorEarlyExitException() + + if is_azure_container_storage_perf_tier_op_set == "default": + msg = ( + "Changing ephemeralDisk NVMe performance tier may result in a temporary " + "interruption to the applications using Azure Container Storage. Do you " + "want to continue with this operation?" + ) + if not (self.context.get_yes() or prompt_y_n(msg, default="n")): + raise DecoratorEarlyExitException() + + self.context.set_intermediate( + "azure_container_storage_perf_tier_op_set", + True, + overwrite_exists=True + ) + # If the extension is already installed, # we expect that the Azure Container Storage # nodes are already labelled. Use those label @@ -4778,7 +4812,6 @@ def update_azure_container_storage(self, mc: ManagedCluster) -> ManagedCluster: overwrite_exists=True ) self.context.set_intermediate("current_core_value", current_core_value, overwrite_exists=True) - else: storage_pool_name = self.context.raw_param.get("storage_pool_name") pool_sku = self.context.raw_param.get("storage_pool_sku") @@ -6113,7 +6146,6 @@ def postprocessing_after_mc_created(self, cluster: ManagedCluster) -> None: existing_ephemeral_nvme_perf_tier, is_called_from_extension=True, ) - else: self.context.external_functions.perform_disable_azure_container_storage( self.cmd, 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 6b6032b26f9..ecf0654d922 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 @@ -12535,154 +12535,6 @@ def test_aks_update_with_azurecontainerstorage_v1_with_ephemeral_disk_parameters self.is_empty(), ]) - - # live only due to downloading k8s-extension extension - @live_only() - @AllowLargeResponse(99999) - @AKSCustomResourceGroupPreparer( - random_name_length=17, name_prefix="clitest", location="uksouth" - ) - def test_aks_create_with_azurecontainerstorage(self, resource_group, resource_group_location): - - # 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) - - node_vm_size = "Standard_L8s_v3" - self.kwargs.update( - { - "resource_group": resource_group, - "name": aks_name, - "location": resource_group_location, - "resource_type": "Microsoft.ContainerService/ManagedClusters", - "ssh_key_value": self.generate_ssh_keys(), - "node_vm_size": node_vm_size, - } - ) - - # add k8s-extension extension for azurecontainerstorage operations. - self.cmd("extension add --name k8s-extension") - - create_cmd = ( - "aks create --resource-group={resource_group} --name={name} --location={location} " - "--ssh-key-value={ssh_key_value} --node-vm-size={node_vm_size} " - "--node-count 3 --enable-managed-identity --enable-azure-container-storage --output=json" - ) - - self.cmd( - create_cmd, - checks=[ - self.check("provisioningState", "Succeeded"), - ], - ) - - # Verify that the azure-container-storage extension is installed - extension_list_cmd = "k8s-extension list --resource-group={resource_group} --cluster-name={name} --cluster-type managedClusters" - extensions = self.cmd(extension_list_cmd).get_output_in_json() - - # Check if azure-container-storage extension exists - acs_extension_found = False - for extension in extensions: - if extension.get("name") == CONST_ACSTOR_EXT_INSTALLATION_NAME : - acs_extension_found = True - # Additional checks on the extension properties - assert extension.get("provisioningState") == "Succeeded", "Extension provisioning failed" - assert extension.get("extensionType") == CONST_ACSTOR_K8S_EXTENSION_NAME, "Wrong extension type" - break - - assert acs_extension_found, "Azure Container Storage not found" - - # delete - cmd = ( - "aks delete --resource-group={resource_group} --name={name} --yes --no-wait" - ) - self.cmd( - cmd, - checks=[ - self.is_empty(), - ], - ) - - @live_only() - @AllowLargeResponse(99999) - @AKSCustomResourceGroupPreparer(random_name_length=17, name_prefix='clitest', location='uksouth') - def test_aks_update_with_azurecontainerstorage(self, resource_group, resource_group_location): - - aks_name = self.create_random_name('cliakstest', 16) - node_vm_size = 'standard_l8s_v3' - self.kwargs.update({ - 'resource_group': resource_group, - 'name': aks_name, - 'location': resource_group_location, - 'ssh_key_value': self.generate_ssh_keys(), - 'node_vm_size': node_vm_size, - }) - - # add k8s-extension extension for azurecontainerstorage operations. - # self.cmd('extension add --name k8s-extension') - - # create: without enable-azure-container-storage - create_cmd = 'aks create --resource-group={resource_group} --name={name} --location={location} --ssh-key-value={ssh_key_value} --node-vm-size={node_vm_size} --node-count 3 --enable-managed-identity --output=json' - self.cmd(create_cmd, checks=[ - self.check('provisioningState', 'Succeeded'), - ]) - - # update: enable-azure-container-storage - update_cmd = 'aks update --resource-group={resource_group} --name={name} --yes --output=json ' \ - '--enable-azure-container-storage' - - self.cmd(update_cmd, checks=[ - self.check('provisioningState', 'Succeeded'), - ]) - - # Verify that the azure-container-storage extension is installed - extension_list_cmd = "k8s-extension list --resource-group={resource_group} --cluster-name={name} --cluster-type managedClusters" - extensions = self.cmd(extension_list_cmd).get_output_in_json() - - # Check if azure-container-storage extension exists - acs_extension_found = False - for extension in extensions: - if extension.get("name") == CONST_ACSTOR_EXT_INSTALLATION_NAME : - acs_extension_found = True - # Additional checks on the extension properties - assert extension.get("provisioningState") == "Succeeded", "Extension provisioning failed" - assert extension.get("extensionType") == CONST_ACSTOR_K8S_EXTENSION_NAME, "Wrong extension type" - break - - assert acs_extension_found, "Azure Container Storage not found" - - # Sleep for 5 mins before next operation, - # since update operations take - # some time to finish. - time.sleep(10 * 60) - - # update: disable-azure-container-storage - update_cmd = 'aks update --resource-group={resource_group} --name={name} --yes --output=json ' \ - '--disable-azure-container-storage' - self.cmd(update_cmd, checks=[ - self.check('provisioningState', 'Succeeded'), - ]) - - # Verify that the azure-container-storage extension doesn't exist anymore - extension_list_cmd = "k8s-extension list --resource-group={resource_group} --cluster-name={name} --cluster-type managedClusters" - extensions = self.cmd(extension_list_cmd).get_output_in_json() - - # Check if azure-container-storage extension sill exists - acs_extension_found = False - for extension in extensions: - if extension.get("name") == CONST_ACSTOR_EXT_INSTALLATION_NAME : - acs_extension_found = True - break - - assert not acs_extension_found, "Azure Container Storage v2 still exists after disable operation" - - # delete - cmd = 'aks delete --resource-group={resource_group} --name={name} --yes --no-wait' - self.cmd(cmd, checks=[ - self.is_empty(), - ]) - # live only due to workspace is not mocked correctly @AllowLargeResponse() @AKSCustomResourceGroupPreparer( diff --git a/src/aks-preview/azext_aks_preview/tests/latest/test_validators.py b/src/aks-preview/azext_aks_preview/tests/latest/test_validators.py index de4322d7765..8629e3c7540 100644 --- a/src/aks-preview/azext_aks_preview/tests/latest/test_validators.py +++ b/src/aks-preview/azext_aks_preview/tests/latest/test_validators.py @@ -1339,8 +1339,8 @@ def test_enable_with_same_ephemeral_disk_nvme_perf_tier_already_set(self): perf_tier = acstor_consts.CONST_EPHEMERAL_NVME_PERF_TIER_PREMIUM storage_pool_type = acstor_consts.CONST_STORAGE_POOL_TYPE_EPHEMERAL_DISK err = ( - "Azure Container Storage is already configured with --ephemeral-disk-nvme-perf-tier " - f"value set to {perf_tier}." + "Azure Container Storage is already configured with --ephemeral-disk-nvme-perf-tier " + f"value set to {perf_tier}." ) with self.assertRaises(InvalidArgumentValueError) as cm: acstor_validator.validate_enable_azure_container_storage_v1_params( @@ -1352,8 +1352,8 @@ def test_enable_with_same_ephemeral_disk_volume_type_already_set(self): disk_vol_type = acstor_consts.CONST_DISK_TYPE_PV_WITH_ANNOTATION storage_pool_type = acstor_consts.CONST_STORAGE_POOL_TYPE_EPHEMERAL_DISK err = ( - "Azure Container Storage is already configured with --ephemeral-disk-volume-type " - f"value set to {disk_vol_type}." + "Azure Container Storage is already configured with --ephemeral-disk-volume-type " + f"value set to {disk_vol_type}." ) with self.assertRaises(InvalidArgumentValueError) as cm: acstor_validator.validate_enable_azure_container_storage_v1_params( @@ -1366,9 +1366,9 @@ def test_enable_with_same_ephemeral_disk_nvme_perf_tier_and_ephemeral_temp_disk_ disk_vol_type = acstor_consts.CONST_DISK_TYPE_PV_WITH_ANNOTATION storage_pool_type = acstor_consts.CONST_STORAGE_POOL_TYPE_EPHEMERAL_DISK err = ( - "Azure Container Storage is already configured with --ephemeral-disk-volume-type " - f"value set to {disk_vol_type} and --ephemeral-disk-nvme-perf-tier " - f"value set to {perf_tier}." + "Azure Container Storage is already configured with --ephemeral-disk-volume-type " + f"value set to {disk_vol_type} and --ephemeral-disk-nvme-perf-tier " + f"value set to {perf_tier}." ) with self.assertRaises(InvalidArgumentValueError) as cm: acstor_validator.validate_enable_azure_container_storage_v1_params( @@ -1451,7 +1451,7 @@ def test_missing_nodepool_from_cluster_nodepool_list_multiple(self): storage_pool_type = acstor_consts.CONST_STORAGE_POOL_TYPE_EPHEMERAL_DISK storage_pool_option = acstor_consts.CONST_STORAGE_POOL_OPTION_SSD nodepool_list = "pool1,pool2" - agentpools = {"nodepool1": {}, "nodepool2":{}} + agentpools = {"nodepool1": {}, "nodepool2": {}} err = ( "Node pool: pool1 not found. Please provide a comma separated " "string of existing node pool names in --azure-container-storage-nodepools." From 342b3657f980b7698dc4c9eb467bd4174824c954 Mon Sep 17 00:00:00 2001 From: Neha Arora Date: Thu, 11 Sep 2025 17:12:00 -0700 Subject: [PATCH 4/7] fix live tests --- .../azurecontainerstorage/_validators.py | 2 +- .../managed_cluster_decorator.py | 43 ++++++++++--------- .../tests/latest/test_aks_commands.py | 6 +-- 3 files changed, 25 insertions(+), 26 deletions(-) diff --git a/src/aks-preview/azext_aks_preview/azurecontainerstorage/_validators.py b/src/aks-preview/azext_aks_preview/azurecontainerstorage/_validators.py index 143b6f56d25..cc5625ffd68 100644 --- a/src/aks-preview/azext_aks_preview/azurecontainerstorage/_validators.py +++ b/src/aks-preview/azext_aks_preview/azurecontainerstorage/_validators.py @@ -661,7 +661,7 @@ def _validate_nodepools( # pylint: disable=too-many-branches,too-many-locals 'and retry the Azure Container Storage operation.' ) vm_size = agentpool.get("vm_size") - if vm_size is not None: + if vm_size is not None and vm_size != "": cpu_value, nvme_enabled = get_vm_sku_details(vm_size.lower()) if cpu_value is None or nvme_enabled is None: raise UnknownError( 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 0630336513d..87e64bfc520 100644 --- a/src/aks-preview/azext_aks_preview/managed_cluster_decorator.py +++ b/src/aks-preview/azext_aks_preview/managed_cluster_decorator.py @@ -3598,10 +3598,10 @@ def set_up_azure_container_storage(self, mc: ManagedCluster) -> ManagedCluster: # installing Azure Container Storage during `az aks create` nodepool_list = agentpool.name - from azure.cli.command_modules.acs.azurecontainerstorage._validators import ( + from azext_aks_preview.azurecontainerstorage._validators import ( validate_enable_azure_container_storage_v1_params ) - from azure.cli.command_modules.acs.azurecontainerstorage._consts import ( + from azext_aks_preview.azurecontainerstorage._consts import ( CONST_ACSTOR_IO_ENGINE_LABEL_KEY, CONST_ACSTOR_IO_ENGINE_LABEL_VAL, CONST_DISK_TYPE_EPHEMERAL_VOLUME_ONLY, @@ -3672,7 +3672,7 @@ def set_up_azure_container_storage(self, mc: ManagedCluster) -> ManagedCluster: pool_option = self.context.raw_param.get("storage_pool_option") pool_size = self.context.raw_param.get("storage_pool_size") - from azure.cli.command_modules.acs.azurecontainerstorage._validators import ( + from azext_aks_preview.azurecontainerstorage._validators import ( validate_enable_azure_container_storage_params, ) validate_enable_azure_container_storage_params( @@ -4487,7 +4487,7 @@ def update_azure_container_storage(self, mc: ManagedCluster) -> ManagedCluster: ) # Check if we are trying to disable container storage v1 - from azure.cli.command_modules.acs.azurecontainerstorage._helpers import ( + from azext_aks_preview.azurecontainerstorage._helpers import ( get_container_storage_extension_installed ) try: @@ -4549,7 +4549,7 @@ def update_azure_container_storage(self, mc: ManagedCluster) -> ManagedCluster: pool_sku = self.context.raw_param.get("storage_pool_sku") pool_size = self.context.raw_param.get("storage_pool_size") agentpool_details = {} - from azure.cli.command_modules.acs.azurecontainerstorage._helpers import ( + from azext_aks_preview.azurecontainerstorage._helpers import ( get_extension_installed_and_cluster_configs ) try: @@ -4586,7 +4586,7 @@ def update_azure_container_storage(self, mc: ManagedCluster) -> ManagedCluster: self.context.set_intermediate("vm_cache_generated", True, overwrite_exists=True) if enable_azure_container_storage: - from azure.cli.command_modules.acs.azurecontainerstorage._helpers import ( + from azext_aks_preview.azurecontainerstorage._helpers import ( get_container_storage_extension_installed ) try: @@ -4604,7 +4604,7 @@ def update_azure_container_storage(self, mc: ManagedCluster) -> ManagedCluster: f"extension is installed on the cluster: {str(ex)}" ) from ex - from azure.cli.command_modules.acs.azurecontainerstorage._consts import ( + from azext_aks_preview.azurecontainerstorage._consts import ( CONST_ACSTOR_IO_ENGINE_LABEL_KEY, CONST_ACSTOR_IO_ENGINE_LABEL_VAL ) @@ -4640,7 +4640,7 @@ def update_azure_container_storage(self, mc: ManagedCluster) -> ManagedCluster: elif len(agentpool_details) == 1: nodepool_list = ','.join(agentpool_details.keys()) - from azure.cli.command_modules.acs.azurecontainerstorage._validators import ( + from azext_aks_preview.azurecontainerstorage._validators import ( validate_enable_azure_container_storage_v1_params ) validate_enable_azure_container_storage_v1_params( @@ -4719,7 +4719,7 @@ def update_azure_container_storage(self, mc: ManagedCluster) -> ManagedCluster: self.context.set_intermediate("enable_azure_container_storage", True, overwrite_exists=True) if disable_azure_container_storage: - from azure.cli.command_modules.acs.azurecontainerstorage._validators import ( + from azext_aks_preview.azurecontainerstorage._validators import ( validate_disable_azure_container_storage_params_v1 ) validate_disable_azure_container_storage_params_v1( @@ -4758,7 +4758,7 @@ def update_azure_container_storage(self, mc: ManagedCluster) -> ManagedCluster: " before disabling Azure Container Storage?" ) - from azure.cli.command_modules.acs.azurecontainerstorage._consts import ( + from azext_aks_preview.azurecontainerstorage._consts import ( CONST_ACSTOR_ALL, ) if disable_pool_type != CONST_ACSTOR_ALL: @@ -4780,13 +4780,14 @@ def update_azure_container_storage(self, mc: ManagedCluster) -> ManagedCluster: overwrite_exists=True ) - # Set intermediates - self.context.set_intermediate("disable_azure_container_storage", True, overwrite_exists=True) - self.context.set_intermediate( - "container_storage_version", - CONST_ACSTOR_VERSION_V1, - overwrite_exists=True - ) + # Set intermediates + self.context.set_intermediate("disable_azure_container_storage", True, overwrite_exists=True) + self.context.set_intermediate( + "container_storage_version", + CONST_ACSTOR_VERSION_V1, + overwrite_exists=True + ) + self.context.set_intermediate("is_extension_installed", is_extension_installed, overwrite_exists=True) self.context.set_intermediate("is_azureDisk_enabled", is_azureDisk_enabled, overwrite_exists=True) self.context.set_intermediate("is_elasticSan_enabled", is_elasticSan_enabled, overwrite_exists=True) @@ -4824,7 +4825,7 @@ def update_azure_container_storage(self, mc: ManagedCluster) -> ManagedCluster: 'and --disable-azure-container-storage together.' ) - from azure.cli.command_modules.acs.azurecontainerstorage._helpers import ( + from azext_aks_preview.azurecontainerstorage._helpers import ( get_container_storage_extension_installed ) is_extension_installed, _ = get_container_storage_extension_installed( @@ -4834,7 +4835,7 @@ def update_azure_container_storage(self, mc: ManagedCluster) -> ManagedCluster: CONST_ACSTOR_EXT_INSTALLATION_NAME, ) - from azure.cli.command_modules.acs.azurecontainerstorage._helpers import ( + from azext_aks_preview.azurecontainerstorage._helpers import ( get_container_storage_extension_installed ) try: @@ -4851,7 +4852,7 @@ def update_azure_container_storage(self, mc: ManagedCluster) -> ManagedCluster: ) from ex if enable_azure_container_storage_param: - from azure.cli.command_modules.acs.azurecontainerstorage._validators import ( + from azext_aks_preview.azurecontainerstorage._validators import ( validate_enable_azure_container_storage_params, ) validate_enable_azure_container_storage_params( @@ -4889,7 +4890,7 @@ def update_azure_container_storage(self, mc: ManagedCluster) -> ManagedCluster: pre_disable_validate = True raise DecoratorEarlyExitException() - from azure.cli.command_modules.acs.azurecontainerstorage._validators import ( + from azext_aks_preview.azurecontainerstorage._validators import ( validate_disable_azure_container_storage_params ) validate_disable_azure_container_storage_params( 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 ecf0654d922..1453e824243 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 @@ -12461,8 +12461,7 @@ def test_aks_update_with_azurecontainerstorage_v1(self, resource_group, resource self.cmd(update_cmd, checks=[ self.check('provisioningState', 'Succeeded'), - ]) - + ]) # Sleep for 10 mins before next operation, # since azure container storage operations take # some time to post process. @@ -12515,8 +12514,7 @@ def test_aks_update_with_azurecontainerstorage_v1_with_ephemeral_disk_parameters self.cmd(update_cmd, checks=[ self.check('provisioningState', 'Succeeded'), - ]) - + ]) # Sleep for 10 mins before next operation, # since azure container storage operations take # some time to post process. From a09e23f1f2448af8b502e849f3c0ff550fb37de3 Mon Sep 17 00:00:00 2001 From: Neha Arora Date: Wed, 17 Sep 2025 18:24:19 -0700 Subject: [PATCH 5/7] update history notes --- src/aks-preview/HISTORY.rst | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/aks-preview/HISTORY.rst b/src/aks-preview/HISTORY.rst index 440476773e1..fd676f60535 100644 --- a/src/aks-preview/HISTORY.rst +++ b/src/aks-preview/HISTORY.rst @@ -13,6 +13,14 @@ Pending +++++++ * Add option `Windows2025` to `--os-sku` for `az aks nodepool add`. +18.0.0b40 ++++++++ +* `az aks create`: Add new parameter `--container-storage-version` to enable the given version of Azure Container Storage. +* `az aks update`: Add new parameter `--container-storage-version` to enable the given version of Azure Container Storage. +* `az aks create`: Change behavior of `--enable-azure-container-storage` to enable latest Azure Container Storage by default. +* `az aks update`: Change behavior of `--enable-azure-container-storage` to enable latest Azure Container Storage by default. +* `az aks update`: Change behavior of `--disable-azure-container-storage` to handle disable based on the installed version. + 18.0.0b39 +++++++ * Add option `AzureLinuxOSGuard` and `AzureLinux3OSGuard` to `--os-sku` for `az aks nodepool add` and `az aks nodepool update`. @@ -30,14 +38,6 @@ Pending * Enable autoscaler support for the VirtualMachines agent pool type. * Add `--node-vm-size` to `az aks update` to allow changing the VM size for VirtualMachines node pools. -18.0.0b37 -+++++++ -* `az aks create`: Add new parameter `--container-storage-version` to enable the given version of Azure Container Storage. -* `az aks update`: Add new parameter `--container-storage-version` to enable the given version of Azure Container Storage. -* `az aks create`: Change behavior of `--enable-azure-container-storage` to enable latest Azure Container Storage by default. -* `az aks update`: Change behavior of `--enable-azure-container-storage` to enable latest Azure Container Storage by default. -* `az aks update`: Change behavior of `--disable-azure-container-storage` to handle disable based on the installed version. - 18.0.0b36 +++++++ * Add option `AzureLinux3` to `--os-sku` for `az aks nodepool add` and `az aks nodepool update`. From 24862e6585c4963dbd98232e21ee628b2353ec01 Mon Sep 17 00:00:00 2001 From: Neha Arora Date: Wed, 24 Sep 2025 09:58:01 -0700 Subject: [PATCH 6/7] update setup.py --- src/aks-preview/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aks-preview/setup.py b/src/aks-preview/setup.py index bc8c9863fc3..3fc6651cc8a 100644 --- a/src/aks-preview/setup.py +++ b/src/aks-preview/setup.py @@ -9,7 +9,7 @@ from setuptools import find_packages, setup -VERSION = "18.0.0b39" +VERSION = "18.0.0b40" CLASSIFIERS = [ "Development Status :: 4 - Beta", From 1dc15142974c5e648084cb1e3532c60295b78311 Mon Sep 17 00:00:00 2001 From: FumingZhang <81607949+FumingZhang@users.noreply.github.com> Date: Fri, 26 Sep 2025 13:06:08 +1000 Subject: [PATCH 7/7] Update src/aks-preview/HISTORY.rst --- src/aks-preview/HISTORY.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aks-preview/HISTORY.rst b/src/aks-preview/HISTORY.rst index fd676f60535..39c93b3c34f 100644 --- a/src/aks-preview/HISTORY.rst +++ b/src/aks-preview/HISTORY.rst @@ -11,10 +11,10 @@ To release a new version, please select a new version number (usually plus 1 to Pending +++++++ -* Add option `Windows2025` to `--os-sku` for `az aks nodepool add`. 18.0.0b40 +++++++ +* Add option `Windows2025` to `--os-sku` for `az aks nodepool add`. * `az aks create`: Add new parameter `--container-storage-version` to enable the given version of Azure Container Storage. * `az aks update`: Add new parameter `--container-storage-version` to enable the given version of Azure Container Storage. * `az aks create`: Change behavior of `--enable-azure-container-storage` to enable latest Azure Container Storage by default.