From fedfa8fb60e1cd5a3a748ee26ec1961d755824e2 Mon Sep 17 00:00:00 2001 From: indusridhar Date: Mon, 26 Jan 2026 18:14:27 -0800 Subject: [PATCH 1/5] Add nodepool rollback and get-rollback-versions commands --- src/aks-preview/HISTORY.rst | 2 + src/aks-preview/azext_aks_preview/_format.py | 12 ++ src/aks-preview/azext_aks_preview/_help.py | 34 ++++++ src/aks-preview/azext_aks_preview/_params.py | 8 ++ src/aks-preview/azext_aks_preview/commands.py | 7 ++ src/aks-preview/azext_aks_preview/custom.py | 108 ++++++++++++++++++ .../tests/latest/test_aks_commands.py | 3 + 7 files changed, 174 insertions(+) diff --git a/src/aks-preview/HISTORY.rst b/src/aks-preview/HISTORY.rst index 9f7ae7ec985..d9b7d2f8829 100644 --- a/src/aks-preview/HISTORY.rst +++ b/src/aks-preview/HISTORY.rst @@ -11,6 +11,8 @@ To release a new version, please select a new version number (usually plus 1 to Pending +++++++ +* `az aks nodepool get-rollback-versions`: New command to get available rollback versions for an agent pool. +* `az aks nodepool rollback`: New command to rollback an agent pool to the most recently used configuration (N-1). 19.0.0b20 +++++++ diff --git a/src/aks-preview/azext_aks_preview/_format.py b/src/aks-preview/azext_aks_preview/_format.py index 6cae6f87a7f..c5dc034001c 100644 --- a/src/aks-preview/azext_aks_preview/_format.py +++ b/src/aks-preview/azext_aks_preview/_format.py @@ -153,6 +153,18 @@ def aks_agentpool_list_table_format(results): return [_aks_agentpool_table_format(r) for r in results] +def aks_agentpool_rollback_versions_table_format(results): + """Format rollback versions for display with "-o table".""" + if not results: + return [] + + def _format_rollback_version(result): + parsed = compile_jmes("""{\n kubernetesVersion: orchestrator_version,\n nodeImageVersion: node_image_version,\n timestamp: timestamp\n }""") + return parsed.search(result, Options(dict_cls=OrderedDict)) + + return [_format_rollback_version(r) for r in results] + + def aks_list_table_format(results): """"Format a list of managed clusters as summary results for display with "-o table".""" return [_aks_table_format(r) for r in results] diff --git a/src/aks-preview/azext_aks_preview/_help.py b/src/aks-preview/azext_aks_preview/_help.py index 47b538a092e..14e689dc26d 100644 --- a/src/aks-preview/azext_aks_preview/_help.py +++ b/src/aks-preview/azext_aks_preview/_help.py @@ -2466,6 +2466,40 @@ crafted: true """ +helps['aks nodepool get-rollback-versions'] = """ +type: command +short-summary: Get the available rollback versions for an agent pool of the managed Kubernetes cluster. +long-summary: | + Get the list of historically used Kubernetes and node image versions that can be used for rollback operations. +examples: + - name: Get the available rollback versions for an agent pool. + text: az aks nodepool get-rollback-versions --resource-group MyResourceGroup --cluster-name MyManagedCluster --nodepool-name MyNodePool + crafted: true +""" + +helps['aks nodepool rollback'] = """ +type: command +short-summary: Rollback an agent pool to the most recently used configuration (N-1). +long-summary: | + Rollback an agent pool to the most recently used version based on rollback history. + This will rollback both the Kubernetes version and node image version to their most recent previous state. + For downgrades to older versions (N-2 or earlier), use a separate downgrade operation. +parameters: + - name: --aks-custom-headers + type: string + short-summary: Send custom headers. When specified, format should be Key1=Value1,Key2=Value2. + - name: --if-match + type: string + short-summary: The revision of the resource being updated. This should match the current revision. + - name: --if-none-match + type: string + short-summary: Set to '*' to allow a new resource to be created, but to prevent updating an existing resource. +examples: + - name: Rollback a nodepool to the most recently used version. + text: az aks nodepool rollback --resource-group MyResourceGroup --cluster-name MyManagedCluster --nodepool-name MyNodePool + crafted: true +""" + helps['aks nodepool stop'] = """ type: command short-summary: Stop running agent pool in the managed Kubernetes cluster. diff --git a/src/aks-preview/azext_aks_preview/_params.py b/src/aks-preview/azext_aks_preview/_params.py index dd7d9524980..80a9e8ab833 100644 --- a/src/aks-preview/azext_aks_preview/_params.py +++ b/src/aks-preview/azext_aks_preview/_params.py @@ -2199,6 +2199,14 @@ def load_arguments(self, _): with self.argument_context("aks nodepool manual-scale delete") as c: c.argument("current_vm_sizes", is_preview=True) + with self.argument_context("aks nodepool get-rollback-versions") as c: + pass # Uses common nodepool parameters + + with self.argument_context("aks nodepool rollback") as c: + c.argument("aks_custom_headers", nargs="*") + c.argument("if_match") + c.argument("if_none_match") + with self.argument_context("aks machine") as c: c.argument("cluster_name", help="The cluster name.") c.argument( diff --git a/src/aks-preview/azext_aks_preview/commands.py b/src/aks-preview/azext_aks_preview/commands.py index 2805e8edb55..effdf1a0238 100644 --- a/src/aks-preview/azext_aks_preview/commands.py +++ b/src/aks-preview/azext_aks_preview/commands.py @@ -26,6 +26,7 @@ aks_addon_show_table_format, aks_agentpool_list_table_format, aks_agentpool_show_table_format, + aks_agentpool_rollback_versions_table_format, aks_machine_list_table_format, aks_machine_show_table_format, aks_operation_show_table_format, @@ -274,6 +275,12 @@ def load_command_table(self, _): g.custom_command("update", "aks_agentpool_update", supports_no_wait=True) g.custom_command("delete", "aks_agentpool_delete", supports_no_wait=True) g.custom_command("get-upgrades", "aks_agentpool_get_upgrade_profile") + g.custom_command( + "get-rollback-versions", + "aks_agentpool_get_rollback_versions", + table_transformer=aks_agentpool_rollback_versions_table_format + ) + g.custom_command("rollback", "aks_agentpool_rollback", supports_no_wait=True) g.custom_command("stop", "aks_agentpool_stop", supports_no_wait=True) g.custom_command("start", "aks_agentpool_start", supports_no_wait=True) g.custom_command( diff --git a/src/aks-preview/azext_aks_preview/custom.py b/src/aks-preview/azext_aks_preview/custom.py index 83a3d49e03e..8e6c2ef7850 100644 --- a/src/aks-preview/azext_aks_preview/custom.py +++ b/src/aks-preview/azext_aks_preview/custom.py @@ -2222,6 +2222,114 @@ def aks_agentpool_get_upgrade_profile(cmd, # pylint: disable=unused-argument return client.get_upgrade_profile(resource_group_name, cluster_name, nodepool_name) +def aks_agentpool_get_rollback_versions(cmd, # pylint: disable=unused-argument + client, + resource_group_name, + cluster_name, + nodepool_name): + """Get rollback versions for a nodepool.""" + upgrade_profile = client.get_upgrade_profile(resource_group_name, cluster_name, nodepool_name) + return upgrade_profile.recently_used_versions + + +def aks_agentpool_rollback(cmd, # pylint: disable=unused-argument + client, + resource_group_name, + cluster_name, + nodepool_name, + aks_custom_headers=None, + if_match=None, + if_none_match=None, + no_wait=False): + """Rollback a nodepool to the most recent previous version configuration.""" + from azext_aks_preview._client_factory import cf_managed_clusters + + # Warn users when auto-upgrade is enabled + if cmd and getattr(cmd, "cli_ctx", None): + try: + managed_clusters_client = cf_managed_clusters(cmd.cli_ctx) + managed_cluster = managed_clusters_client.get(resource_group_name, cluster_name) + auto_upgrade_profile = getattr(managed_cluster, "auto_upgrade_profile", None) + + upgrade_channel = getattr(auto_upgrade_profile, "upgrade_channel", None) if auto_upgrade_profile else None + node_os_upgrade_channel = ( + getattr(auto_upgrade_profile, "node_os_upgrade_channel", None) + if auto_upgrade_profile + else None + ) + + upgrade_channel_enabled = upgrade_channel and str(upgrade_channel).lower() != "none" + node_os_channel_enabled = node_os_upgrade_channel and str(node_os_upgrade_channel).lower() not in [ + "none", + "unmanaged", + ] + + if upgrade_channel_enabled or node_os_channel_enabled: + logger.warning( + "Auto-upgrade is enabled on cluster '%s' (upgradeChannel=%s, nodeOSUpgradeChannel=%s). " + "Rollback will not succeed until auto-upgrade is disabled. Please disable auto-upgrade to roll back the node pool.", + cluster_name, + upgrade_channel or "none", + node_os_upgrade_channel or "Unmanaged", + ) + except Exception as ex: # pylint: disable=broad-except + logger.debug("Unable to retrieve auto-upgrade configuration before rollback: %s", ex) + + logger.info("Fetching the most recent rollback version...") + + # Get upgrade profile to retrieve recently used versions + upgrade_profile = client.get_upgrade_profile(resource_group_name, cluster_name, nodepool_name) + + if not upgrade_profile.recently_used_versions or len(upgrade_profile.recently_used_versions) == 0: + raise CLIError( + "No rollback versions available. The nodepool must have been upgraded at least once " + "to have rollback history available." + ) + + # Sort by timestamp (most recent first) and get the most recent version + sorted_versions = sorted( + upgrade_profile.recently_used_versions, + key=lambda v: v.timestamp if v.timestamp else datetime.datetime.min, + reverse=True + ) + most_recent = sorted_versions[0] + + kubernetes_version = most_recent.orchestrator_version + node_image_version = most_recent.node_image_version + + logger.info( + "Rolling back to the most recent version: " + "Kubernetes version: %s, Node image version: %s (timestamp: %s)", + kubernetes_version, node_image_version, most_recent.timestamp + ) + + # Get the current agent pool + current_agentpool = client.get(resource_group_name, cluster_name, nodepool_name) + + # Update the agent pool configuration with rollback versions + current_agentpool.orchestrator_version = kubernetes_version + current_agentpool.node_image_version = node_image_version + + # Set custom headers if provided + headers = get_aks_custom_headers(aks_custom_headers) + if if_match: + headers['If-Match'] = if_match + if if_none_match: + headers['If-None-Match'] = if_none_match + + # Perform the rollback by updating the agent pool + # Server-side will validate the versions + return sdk_no_wait( + no_wait, + client.begin_create_or_update, + resource_group_name, + cluster_name, + nodepool_name, + current_agentpool, + headers=headers if headers else None + ) + + def aks_agentpool_stop(cmd, # pylint: disable=unused-argument client, resource_group_name, 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 878daa125a7..b538ca5bac6 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 @@ -21589,5 +21589,8 @@ def test_aks_create_and_update_with_gateway_api_and_azureservicemesh( ], ) + # TODO (indusridhar): Add tests for `test_aks_nodepool_get_rollback_versions` and `test_aks_nodepool_rollback` + # after AKS RP Jan 2026 release is complete and recently_used_versions field is populated in upgrade profile API + # TODO (zheweihu): add test `test_aks_create_and_update_with_gateway_api_without_azureservicemesh` # once https://msazure.visualstudio.com/CloudNativeCompute/_git/aks-rp/pullrequest/14404771 is rolled out From 9cf16a7ebe943fd52c9c05417b7822a490842df8 Mon Sep 17 00:00:00 2001 From: indusridhar Date: Mon, 26 Jan 2026 20:09:58 -0800 Subject: [PATCH 2/5] Add tests for live run --- .../tests/latest/test_aks_commands.py | 210 ++++++++++++++++++ 1 file changed, 210 insertions(+) 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 b538ca5bac6..65db7400558 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 @@ -21592,5 +21592,215 @@ def test_aks_create_and_update_with_gateway_api_and_azureservicemesh( # TODO (indusridhar): Add tests for `test_aks_nodepool_get_rollback_versions` and `test_aks_nodepool_rollback` # after AKS RP Jan 2026 release is complete and recently_used_versions field is populated in upgrade profile API + @AllowLargeResponse() + @AKSCustomResourceGroupPreparer( + random_name_length=17, name_prefix="clitest", location="westus2" + ) + def test_aks_nodepool_get_rollback_versions(self, resource_group, resource_group_location): + """Test az aks nodepool get-rollback-versions command""" + aks_name = self.create_random_name("cliakstest", 16) + node_pool_name = self.create_random_name("c", 6) + + self.kwargs.update( + { + "resource_group": resource_group, + "name": aks_name, + "node_pool_name": node_pool_name, + "ssh_key_value": self.generate_ssh_keys(), + } + ) + + # Create cluster with default Kubernetes version + create_cmd = ( + "aks create --resource-group={resource_group} --name={name} " + "--nodepool-name {node_pool_name} -c 1 " + "--ssh-key-value={ssh_key_value}" + ) + self.cmd( + create_cmd, + checks=[ + self.check("provisioningState", "Succeeded"), + ], + ) + + # Get the nodepool details before upgrade to capture the current versions + get_nodepool_cmd = ( + "aks nodepool show " + "--resource-group={resource_group} " + "--cluster-name={name} " + "--name={node_pool_name}" + ) + nodepool_before_upgrade = self.cmd(get_nodepool_cmd).get_output_in_json() + original_k8s_version = nodepool_before_upgrade["currentOrchestratorVersion"] + original_node_image_version = nodepool_before_upgrade["nodeImageVersion"] + + # Get valid upgrade versions from the API + get_upgrades_cmd = ( + "aks nodepool get-upgrades " + "--resource-group={resource_group} " + "--cluster-name={name} " + "--name={node_pool_name} " + "-o json" + ) + upgrades = self.cmd(get_upgrades_cmd).get_output_in_json() + + # Find a valid upgrade version from the available upgrades + upgrade_version = None + if upgrades and 'componentsByReleases' in upgrades and len(upgrades['componentsByReleases']) > 0: + # Get the first available kubernetes version from upgrades + first_release = upgrades['componentsByReleases'][0] + if 'kubernetesVersion' in first_release: + upgrade_version = first_release['kubernetesVersion'] + + # Skip test if no upgrade version is available + if not upgrade_version: + self.skipTest("No upgrade version available for testing") + + self.kwargs.update({"upgrade_version": upgrade_version}) + + # Upgrade the nodepool to populate recently used versions + upgrade_cmd = ( + "aks nodepool upgrade " + "--resource-group={resource_group} " + "--cluster-name={name} " + "--name={node_pool_name} " + "--kubernetes-version={upgrade_version} " + "--yes" + ) + self.cmd( + upgrade_cmd, + checks=[ + self.check("provisioningState", "Succeeded"), + ], + ) + + # Get rollback versions - should now have entries after the upgrade + get_rollback_cmd = ( + "aks nodepool get-rollback-versions " + "--resource-group={resource_group} " + "--cluster-name={name} " + "--nodepool-name={node_pool_name}" + ) + rollback_versions = self.cmd(get_rollback_cmd).get_output_in_json() + + # Verify that we have at least one rollback version available + assert len(rollback_versions) > 0, "Expected rollback versions after upgrade but found none" + + # Verify that the rollback versions contain the original versions from before the upgrade + assert rollback_versions[0]["kubernetesVersion"] == original_k8s_version + assert rollback_versions[0]["nodeImageVersion"] == original_node_image_version + + # Cleanup + self.cmd( + "aks delete -g {resource_group} -n {name} --yes --no-wait", + checks=[self.is_empty()], + ) + + @AllowLargeResponse() + @AKSCustomResourceGroupPreparer( + random_name_length=17, name_prefix="clitest", location="westus2" + ) + def test_aks_nodepool_rollback(self, resource_group, resource_group_location): + """Test az aks nodepool rollback command with various scenarios""" + aks_name = self.create_random_name("cliakstest", 16) + node_pool_name = self.create_random_name("c", 6) + + self.kwargs.update( + { + "resource_group": resource_group, + "name": aks_name, + "node_pool_name": node_pool_name, + "ssh_key_value": self.generate_ssh_keys(), + } + ) + + # Create cluster with default Kubernetes version + create_cmd = ( + "aks create --resource-group={resource_group} --name={name} " + "--nodepool-name {node_pool_name} -c 1 " + "--ssh-key-value={ssh_key_value}" + ) + self.cmd( + create_cmd, + checks=[ + self.check("provisioningState", "Succeeded"), + ], + ) + + # Get the nodepool details before upgrade to capture the current versions + get_nodepool_cmd = ( + "aks nodepool show " + "--resource-group={resource_group} " + "--cluster-name={name} " + "--name={node_pool_name}" + ) + nodepool_before_upgrade = self.cmd(get_nodepool_cmd).get_output_in_json() + original_k8s_version = nodepool_before_upgrade["currentOrchestratorVersion"] + original_node_image_version = nodepool_before_upgrade["nodeImageVersion"] + + # Get valid upgrade versions from the API + get_upgrades_cmd = ( + "aks nodepool get-upgrades " + "--resource-group={resource_group} " + "--cluster-name={name} " + "--name={node_pool_name} " + "-o json" + ) + upgrades = self.cmd(get_upgrades_cmd).get_output_in_json() + + # Find a valid upgrade version from the available upgrades + upgrade_version = None + if upgrades and 'componentsByReleases' in upgrades and len(upgrades['componentsByReleases']) > 0: + # Get the first available kubernetes version from upgrades + first_release = upgrades['componentsByReleases'][0] + if 'kubernetesVersion' in first_release: + upgrade_version = first_release['kubernetesVersion'] + + # Skip test if no upgrade version is available + if not upgrade_version: + self.skipTest("No upgrade version available for testing") + + self.kwargs.update({"upgrade_version": upgrade_version}) + + # Upgrade the nodepool to create rollback history + upgrade_cmd = ( + "aks nodepool upgrade " + "--resource-group={resource_group} " + "--cluster-name={name} " + "--name={node_pool_name} " + "--kubernetes-version={upgrade_version} " + "--yes" + ) + self.cmd( + upgrade_cmd, + checks=[ + self.check("provisioningState", "Succeeded"), + ], + ) + + # Rollback to most recent version (N-1) + rollback_cmd = ( + "aks nodepool rollback " + "--resource-group={resource_group} " + "--cluster-name={name} " + "--name={node_pool_name}" + ) + rollback_result = self.cmd( + rollback_cmd, + checks=[ + self.check("provisioningState", "Succeeded"), + ], + ).get_output_in_json() + + # Verify that both versions were rolled back to the most recent (original) versions + assert rollback_result["currentOrchestratorVersion"] == original_k8s_version + assert rollback_result["nodeImageVersion"] == original_node_image_version + + # Cleanup + self.cmd( + "aks delete -g {resource_group} -n {name} --yes --no-wait", + checks=[self.is_empty()], + ) + # TODO (zheweihu): add test `test_aks_create_and_update_with_gateway_api_without_azureservicemesh` # once https://msazure.visualstudio.com/CloudNativeCompute/_git/aks-rp/pullrequest/14404771 is rolled out From 00fa1c051e744f928a3bc02bea34896bb5795d2f Mon Sep 17 00:00:00 2001 From: indusridhar Date: Mon, 26 Jan 2026 20:50:23 -0800 Subject: [PATCH 3/5] Use Standard_DS2_v2 VM size for rollback tests --- .../azext_aks_preview/tests/latest/test_aks_commands.py | 2 ++ 1 file changed, 2 insertions(+) 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 65db7400558..ed3a5243bc9 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 @@ -21614,6 +21614,7 @@ def test_aks_nodepool_get_rollback_versions(self, resource_group, resource_group create_cmd = ( "aks create --resource-group={resource_group} --name={name} " "--nodepool-name {node_pool_name} -c 1 " + "--node-vm-size standard_d2s_v3 " "--ssh-key-value={ssh_key_value}" ) self.cmd( @@ -21718,6 +21719,7 @@ def test_aks_nodepool_rollback(self, resource_group, resource_group_location): create_cmd = ( "aks create --resource-group={resource_group} --name={name} " "--nodepool-name {node_pool_name} -c 1 " + "--node-vm-size standard_d2s_v3 " "--ssh-key-value={ssh_key_value}" ) self.cmd( From aa08812a820ab20727a17e0a374d5a0704a6223f Mon Sep 17 00:00:00 2001 From: indusridhar Date: Tue, 27 Jan 2026 12:41:44 -0800 Subject: [PATCH 4/5] Remove rollback tests, keep TODO for future implementation --- .../tests/latest/test_aks_commands.py | 212 ------------------ 1 file changed, 212 deletions(-) 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 ed3a5243bc9..b538ca5bac6 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 @@ -21592,217 +21592,5 @@ def test_aks_create_and_update_with_gateway_api_and_azureservicemesh( # TODO (indusridhar): Add tests for `test_aks_nodepool_get_rollback_versions` and `test_aks_nodepool_rollback` # after AKS RP Jan 2026 release is complete and recently_used_versions field is populated in upgrade profile API - @AllowLargeResponse() - @AKSCustomResourceGroupPreparer( - random_name_length=17, name_prefix="clitest", location="westus2" - ) - def test_aks_nodepool_get_rollback_versions(self, resource_group, resource_group_location): - """Test az aks nodepool get-rollback-versions command""" - aks_name = self.create_random_name("cliakstest", 16) - node_pool_name = self.create_random_name("c", 6) - - self.kwargs.update( - { - "resource_group": resource_group, - "name": aks_name, - "node_pool_name": node_pool_name, - "ssh_key_value": self.generate_ssh_keys(), - } - ) - - # Create cluster with default Kubernetes version - create_cmd = ( - "aks create --resource-group={resource_group} --name={name} " - "--nodepool-name {node_pool_name} -c 1 " - "--node-vm-size standard_d2s_v3 " - "--ssh-key-value={ssh_key_value}" - ) - self.cmd( - create_cmd, - checks=[ - self.check("provisioningState", "Succeeded"), - ], - ) - - # Get the nodepool details before upgrade to capture the current versions - get_nodepool_cmd = ( - "aks nodepool show " - "--resource-group={resource_group} " - "--cluster-name={name} " - "--name={node_pool_name}" - ) - nodepool_before_upgrade = self.cmd(get_nodepool_cmd).get_output_in_json() - original_k8s_version = nodepool_before_upgrade["currentOrchestratorVersion"] - original_node_image_version = nodepool_before_upgrade["nodeImageVersion"] - - # Get valid upgrade versions from the API - get_upgrades_cmd = ( - "aks nodepool get-upgrades " - "--resource-group={resource_group} " - "--cluster-name={name} " - "--name={node_pool_name} " - "-o json" - ) - upgrades = self.cmd(get_upgrades_cmd).get_output_in_json() - - # Find a valid upgrade version from the available upgrades - upgrade_version = None - if upgrades and 'componentsByReleases' in upgrades and len(upgrades['componentsByReleases']) > 0: - # Get the first available kubernetes version from upgrades - first_release = upgrades['componentsByReleases'][0] - if 'kubernetesVersion' in first_release: - upgrade_version = first_release['kubernetesVersion'] - - # Skip test if no upgrade version is available - if not upgrade_version: - self.skipTest("No upgrade version available for testing") - - self.kwargs.update({"upgrade_version": upgrade_version}) - - # Upgrade the nodepool to populate recently used versions - upgrade_cmd = ( - "aks nodepool upgrade " - "--resource-group={resource_group} " - "--cluster-name={name} " - "--name={node_pool_name} " - "--kubernetes-version={upgrade_version} " - "--yes" - ) - self.cmd( - upgrade_cmd, - checks=[ - self.check("provisioningState", "Succeeded"), - ], - ) - - # Get rollback versions - should now have entries after the upgrade - get_rollback_cmd = ( - "aks nodepool get-rollback-versions " - "--resource-group={resource_group} " - "--cluster-name={name} " - "--nodepool-name={node_pool_name}" - ) - rollback_versions = self.cmd(get_rollback_cmd).get_output_in_json() - - # Verify that we have at least one rollback version available - assert len(rollback_versions) > 0, "Expected rollback versions after upgrade but found none" - - # Verify that the rollback versions contain the original versions from before the upgrade - assert rollback_versions[0]["kubernetesVersion"] == original_k8s_version - assert rollback_versions[0]["nodeImageVersion"] == original_node_image_version - - # Cleanup - self.cmd( - "aks delete -g {resource_group} -n {name} --yes --no-wait", - checks=[self.is_empty()], - ) - - @AllowLargeResponse() - @AKSCustomResourceGroupPreparer( - random_name_length=17, name_prefix="clitest", location="westus2" - ) - def test_aks_nodepool_rollback(self, resource_group, resource_group_location): - """Test az aks nodepool rollback command with various scenarios""" - aks_name = self.create_random_name("cliakstest", 16) - node_pool_name = self.create_random_name("c", 6) - - self.kwargs.update( - { - "resource_group": resource_group, - "name": aks_name, - "node_pool_name": node_pool_name, - "ssh_key_value": self.generate_ssh_keys(), - } - ) - - # Create cluster with default Kubernetes version - create_cmd = ( - "aks create --resource-group={resource_group} --name={name} " - "--nodepool-name {node_pool_name} -c 1 " - "--node-vm-size standard_d2s_v3 " - "--ssh-key-value={ssh_key_value}" - ) - self.cmd( - create_cmd, - checks=[ - self.check("provisioningState", "Succeeded"), - ], - ) - - # Get the nodepool details before upgrade to capture the current versions - get_nodepool_cmd = ( - "aks nodepool show " - "--resource-group={resource_group} " - "--cluster-name={name} " - "--name={node_pool_name}" - ) - nodepool_before_upgrade = self.cmd(get_nodepool_cmd).get_output_in_json() - original_k8s_version = nodepool_before_upgrade["currentOrchestratorVersion"] - original_node_image_version = nodepool_before_upgrade["nodeImageVersion"] - - # Get valid upgrade versions from the API - get_upgrades_cmd = ( - "aks nodepool get-upgrades " - "--resource-group={resource_group} " - "--cluster-name={name} " - "--name={node_pool_name} " - "-o json" - ) - upgrades = self.cmd(get_upgrades_cmd).get_output_in_json() - - # Find a valid upgrade version from the available upgrades - upgrade_version = None - if upgrades and 'componentsByReleases' in upgrades and len(upgrades['componentsByReleases']) > 0: - # Get the first available kubernetes version from upgrades - first_release = upgrades['componentsByReleases'][0] - if 'kubernetesVersion' in first_release: - upgrade_version = first_release['kubernetesVersion'] - - # Skip test if no upgrade version is available - if not upgrade_version: - self.skipTest("No upgrade version available for testing") - - self.kwargs.update({"upgrade_version": upgrade_version}) - - # Upgrade the nodepool to create rollback history - upgrade_cmd = ( - "aks nodepool upgrade " - "--resource-group={resource_group} " - "--cluster-name={name} " - "--name={node_pool_name} " - "--kubernetes-version={upgrade_version} " - "--yes" - ) - self.cmd( - upgrade_cmd, - checks=[ - self.check("provisioningState", "Succeeded"), - ], - ) - - # Rollback to most recent version (N-1) - rollback_cmd = ( - "aks nodepool rollback " - "--resource-group={resource_group} " - "--cluster-name={name} " - "--name={node_pool_name}" - ) - rollback_result = self.cmd( - rollback_cmd, - checks=[ - self.check("provisioningState", "Succeeded"), - ], - ).get_output_in_json() - - # Verify that both versions were rolled back to the most recent (original) versions - assert rollback_result["currentOrchestratorVersion"] == original_k8s_version - assert rollback_result["nodeImageVersion"] == original_node_image_version - - # Cleanup - self.cmd( - "aks delete -g {resource_group} -n {name} --yes --no-wait", - checks=[self.is_empty()], - ) - # TODO (zheweihu): add test `test_aks_create_and_update_with_gateway_api_without_azureservicemesh` # once https://msazure.visualstudio.com/CloudNativeCompute/_git/aks-rp/pullrequest/14404771 is rolled out From e5d2b25be8ad229d92f4fa8b3ccd2ed44033a074 Mon Sep 17 00:00:00 2001 From: indusridhar Date: Tue, 27 Jan 2026 15:16:26 -0800 Subject: [PATCH 5/5] Fix line too long lint error in _format.py --- src/aks-preview/azext_aks_preview/_format.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/aks-preview/azext_aks_preview/_format.py b/src/aks-preview/azext_aks_preview/_format.py index c5dc034001c..2666cbde9bd 100644 --- a/src/aks-preview/azext_aks_preview/_format.py +++ b/src/aks-preview/azext_aks_preview/_format.py @@ -159,7 +159,11 @@ def aks_agentpool_rollback_versions_table_format(results): return [] def _format_rollback_version(result): - parsed = compile_jmes("""{\n kubernetesVersion: orchestrator_version,\n nodeImageVersion: node_image_version,\n timestamp: timestamp\n }""") + parsed = compile_jmes("""{ + kubernetesVersion: orchestrator_version, + nodeImageVersion: node_image_version, + timestamp: timestamp + }""") return parsed.search(result, Options(dict_cls=OrderedDict)) return [_format_rollback_version(r) for r in results]