diff --git a/src/aks-preview/HISTORY.rst b/src/aks-preview/HISTORY.rst index 7416d3428ec..9faddc9e32a 100644 --- a/src/aks-preview/HISTORY.rst +++ b/src/aks-preview/HISTORY.rst @@ -12,6 +12,10 @@ To release a new version, please select a new version number (usually plus 1 to Pending +++++++ +22.0.0b4 ++++++++++ +* `az aks nodepool update`: Add preview `--zones`/`-z` support for migrating a regional node pool to automatic zone placement with `--zones auto`. Other availability zone changes are subject to service restrictions. Omitted zones remain unchanged, and the option can be combined with `--node-vm-size`. + 22.0.0b3 +++++++++ * `az aks update`: Relax the bring-your-own VNet subnet validation for converting non-HOBO to HOBO Automatic cluster. `--apiserver-subnet-id` is no longer required, and `--system-node-subnet-id` can be supplied on its own; omitted subnets keep their current networking. `--enable-hosted-system` is still required to request the conversion, and `--node-subnet-id` still requires `--system-node-subnet-id`. diff --git a/src/aks-preview/azext_aks_preview/_help.py b/src/aks-preview/azext_aks_preview/_help.py index 84fa526185e..6f442963b81 100644 --- a/src/aks-preview/azext_aks_preview/_help.py +++ b/src/aks-preview/azext_aks_preview/_help.py @@ -2853,6 +2853,9 @@ - name: --node-vm-size -s type: string short-summary: VM size for Kubernetes nodes. For VMSS pools, changing this triggers a rolling upgrade to replace nodes with the new size (preview). For VirtualMachines pools, only configurable when updating autoscale settings. + - name: --zones -z + type: string array + short-summary: Use `auto` to migrate a regional node pool to automatic zone placement. Other availability zone changes are subject to service restrictions. - name: --upgrade-strategy type: string short-summary: Upgrade strategy for the node pool. Allowed values are "Rolling" or "BlueGreen". Default is "Rolling". @@ -2892,6 +2895,8 @@ text: az aks nodepool update -g MyResourceGroup -n nodepool1 --cluster-name MyManagedCluster --update-cluster-autoscaler --node-vm-size "Standard_D2s_v3" --min-count 2 --max-count 4 - name: Resize VM size for a VMSS node pool (preview, requires AFEC registration) text: az aks nodepool update -g MyResourceGroup -n nodepool1 --cluster-name MyManagedCluster --node-vm-size Standard_D4s_v3 + - name: Migrate a regional node pool to automatic zone placement. + text: az aks nodepool update -g MyResourceGroup -n nodepool1 --cluster-name MyManagedCluster --zones auto - name: Update a node pool with blue-green upgrade settings text: az aks nodepool update -g MyResourceGroup -n nodepool1 --cluster-name MyManagedCluster --drain-batch-size 50% --drain-timeout-bg 5 --batch-soak-duration 10 --final-soak-duration 10 - name: Update a nodepool with a Capacity Reservation Group(CRG) ID. diff --git a/src/aks-preview/azext_aks_preview/_params.py b/src/aks-preview/azext_aks_preview/_params.py index e152dbff9ba..45a0f5f7077 100644 --- a/src/aks-preview/azext_aks_preview/_params.py +++ b/src/aks-preview/azext_aks_preview/_params.py @@ -2628,6 +2628,14 @@ def load_arguments(self, _): completer=get_vm_size_completion_list, is_preview=True, ) + c.argument( + "zones", + zones_type, + options_list=["--zones", "-z"], + is_preview=True, + help='Use "auto" to migrate a regional node pool to automatic zone placement. ' + 'Other availability zone changes are subject to service restrictions.', + ) c.argument( "gpu_driver", arg_type=get_enum_type(gpu_driver_install_modes) diff --git a/src/aks-preview/azext_aks_preview/agentpool_decorator.py b/src/aks-preview/azext_aks_preview/agentpool_decorator.py index 4dad7557b4d..c7d6c933096 100644 --- a/src/aks-preview/azext_aks_preview/agentpool_decorator.py +++ b/src/aks-preview/azext_aks_preview/agentpool_decorator.py @@ -2119,6 +2119,23 @@ def update_vm_size(self, agentpool: AgentPool) -> AgentPool: return agentpool + def update_zones(self, agentpool: AgentPool) -> AgentPool: + """Update availability zones for the AgentPool object when explicitly requested. + + The inherited context getter prefers the value from the fetched AgentPool over + the command-line value. Read the raw parameter here so an update can replace an + existing value while an omitted ``--zones`` leaves it untouched. + + :return: the AgentPool object + """ + self._ensure_agentpool(agentpool) + + zones = self.context.raw_param.get("zones") + if zones is not None: + agentpool.availability_zones = zones + + return agentpool + def update_localdns_profile(self, agentpool: AgentPool) -> AgentPool: """Update local DNS profile for the AgentPool object if provided via --localdns-config.""" self._ensure_agentpool(agentpool) @@ -2198,6 +2215,10 @@ def update_agentpool_profile_preview(self, agentpools: List[AgentPool] = None) - # update vm size for VMSS pools agentpool = self.update_vm_size(agentpool) + # Older CLI versions do not handle availability zones in the default update flow. + if not hasattr(AKSAgentPoolUpdateDecorator, "update_zones"): + agentpool = self.update_zones(agentpool) + # update local DNS profile agentpool = self.update_localdns_profile(agentpool) diff --git a/src/aks-preview/azext_aks_preview/custom.py b/src/aks-preview/azext_aks_preview/custom.py index 61965b5a603..cd54401f495 100644 --- a/src/aks-preview/azext_aks_preview/custom.py +++ b/src/aks-preview/azext_aks_preview/custom.py @@ -2388,6 +2388,7 @@ def aks_agentpool_update( # local DNS localdns_config=None, node_vm_size=None, + zones=None, gpu_driver=None, gpu_mig_strategy=None, # crg diff --git a/src/aks-preview/azext_aks_preview/tests/latest/recordings/test_aks_approuting_enable_with_keyvault_secrets_provider_addon_and_keyvault_id.yaml b/src/aks-preview/azext_aks_preview/tests/latest/recordings/test_aks_approuting_enable_with_keyvault_secrets_provider_addon_and_keyvault_id.yaml index d9ede469cbf..c9506ff2c46 100755 --- a/src/aks-preview/azext_aks_preview/tests/latest/recordings/test_aks_approuting_enable_with_keyvault_secrets_provider_addon_and_keyvault_id.yaml +++ b/src/aks-preview/azext_aks_preview/tests/latest/recordings/test_aks_approuting_enable_with_keyvault_secrets_provider_addon_and_keyvault_id.yaml @@ -15,7 +15,7 @@ interactions: User-Agent: - AZURECLI/2.58.0 azsdk-python-core/1.28.0 Python/3.8.10 (Linux-6.2.0-1019-azure-x86_64-with-glibc2.29) method: GET - uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest000001/providers/Microsoft.KeyVault/vaults/cliakstestkv000002?api-version=2025-05-01 + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest000001/providers/Microsoft.KeyVault/vaults/cliakstestkv000002?api-version=2026-02-01 response: body: string: '{"error":{"code":"ResourceNotFound","message":"The Resource ''Microsoft.KeyVault/vaults/cliakstestkv000002'' @@ -70,7 +70,7 @@ interactions: User-Agent: - AZURECLI/2.58.0 azsdk-python-core/1.28.0 Python/3.8.10 (Linux-6.2.0-1019-azure-x86_64-with-glibc2.29) method: PUT - uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest000001/providers/Microsoft.KeyVault/vaults/cliakstestkv000002?api-version=2025-05-01 + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest000001/providers/Microsoft.KeyVault/vaults/cliakstestkv000002?api-version=2026-02-01 response: body: string: '{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest000001/providers/Microsoft.KeyVault/vaults/cliakstestkv000002","name":"cliakstestkv000002","type":"Microsoft.KeyVault/vaults","location":"eastus","tags":{},"systemData":{"createdBy":"3fac8b4e-cd90-4baa-a5d2-66d52bc8349d","createdByType":"Application","createdAt":"2024-03-11T07:38:10.965Z","lastModifiedBy":"3fac8b4e-cd90-4baa-a5d2-66d52bc8349d","lastModifiedByType":"Application","lastModifiedAt":"2024-03-11T07:38:10.965Z"},"properties":{"sku":{"family":"A","name":"standard"},"tenantId":"72f988bf-86f1-41af-91ab-2d7cd011db47","accessPolicies":[],"enabledForDeployment":false,"enableSoftDelete":true,"softDeleteRetentionInDays":90,"enableRbacAuthorization":true,"vaultUri":"https://cliakstestkv000002.vault.azure.net","provisioningState":"RegisteringDns","publicNetworkAccess":"Enabled"}}' @@ -120,7 +120,7 @@ interactions: User-Agent: - AZURECLI/2.58.0 azsdk-python-core/1.28.0 Python/3.8.10 (Linux-6.2.0-1019-azure-x86_64-with-glibc2.29) method: GET - uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest000001/providers/Microsoft.KeyVault/vaults/cliakstestkv000002?api-version=2025-05-01 + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest000001/providers/Microsoft.KeyVault/vaults/cliakstestkv000002?api-version=2026-02-01 response: body: string: '{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest000001/providers/Microsoft.KeyVault/vaults/cliakstestkv000002","name":"cliakstestkv000002","type":"Microsoft.KeyVault/vaults","location":"eastus","tags":{},"systemData":{"createdBy":"3fac8b4e-cd90-4baa-a5d2-66d52bc8349d","createdByType":"Application","createdAt":"2024-03-11T07:38:10.965Z","lastModifiedBy":"3fac8b4e-cd90-4baa-a5d2-66d52bc8349d","lastModifiedByType":"Application","lastModifiedAt":"2024-03-11T07:38:10.965Z"},"properties":{"sku":{"family":"A","name":"standard"},"tenantId":"72f988bf-86f1-41af-91ab-2d7cd011db47","accessPolicies":[],"enabledForDeployment":false,"enableSoftDelete":true,"softDeleteRetentionInDays":90,"enableRbacAuthorization":true,"vaultUri":"https://cliakstestkv000002.vault.azure.net/","provisioningState":"RegisteringDns","publicNetworkAccess":"Enabled"}}' @@ -168,7 +168,7 @@ interactions: User-Agent: - AZURECLI/2.58.0 azsdk-python-core/1.28.0 Python/3.8.10 (Linux-6.2.0-1019-azure-x86_64-with-glibc2.29) method: GET - uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest000001/providers/Microsoft.KeyVault/vaults/cliakstestkv000002?api-version=2025-05-01 + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest000001/providers/Microsoft.KeyVault/vaults/cliakstestkv000002?api-version=2026-02-01 response: body: string: '{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest000001/providers/Microsoft.KeyVault/vaults/cliakstestkv000002","name":"cliakstestkv000002","type":"Microsoft.KeyVault/vaults","location":"eastus","tags":{},"systemData":{"createdBy":"3fac8b4e-cd90-4baa-a5d2-66d52bc8349d","createdByType":"Application","createdAt":"2024-03-11T07:38:10.965Z","lastModifiedBy":"3fac8b4e-cd90-4baa-a5d2-66d52bc8349d","lastModifiedByType":"Application","lastModifiedAt":"2024-03-11T07:38:10.965Z"},"properties":{"sku":{"family":"A","name":"standard"},"tenantId":"72f988bf-86f1-41af-91ab-2d7cd011db47","accessPolicies":[],"enabledForDeployment":false,"enableSoftDelete":true,"softDeleteRetentionInDays":90,"enableRbacAuthorization":true,"vaultUri":"https://cliakstestkv000002.vault.azure.net/","provisioningState":"Succeeded","publicNetworkAccess":"Enabled"}}' @@ -1551,7 +1551,7 @@ interactions: User-Agent: - AZURECLI/2.58.0 azsdk-python-core/1.28.0 Python/3.8.10 (Linux-6.2.0-1019-azure-x86_64-with-glibc2.29) method: GET - uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest000001/providers/Microsoft.KeyVault/vaults/cliakstestkv000002?api-version=2025-05-01 + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest000001/providers/Microsoft.KeyVault/vaults/cliakstestkv000002?api-version=2026-02-01 response: body: string: '{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest000001/providers/Microsoft.KeyVault/vaults/cliakstestkv000002","name":"cliakstestkv000002","type":"Microsoft.KeyVault/vaults","location":"eastus","tags":{},"systemData":{"createdBy":"3fac8b4e-cd90-4baa-a5d2-66d52bc8349d","createdByType":"Application","createdAt":"2024-03-11T07:38:10.965Z","lastModifiedBy":"3fac8b4e-cd90-4baa-a5d2-66d52bc8349d","lastModifiedByType":"Application","lastModifiedAt":"2024-03-11T07:38:10.965Z"},"properties":{"sku":{"family":"A","name":"standard"},"tenantId":"72f988bf-86f1-41af-91ab-2d7cd011db47","accessPolicies":[],"enabledForDeployment":false,"enableSoftDelete":true,"softDeleteRetentionInDays":90,"enableRbacAuthorization":true,"vaultUri":"https://cliakstestkv000002.vault.azure.net/","provisioningState":"Succeeded","publicNetworkAccess":"Enabled"}}' @@ -1700,7 +1700,7 @@ interactions: User-Agent: - AZURECLI/2.58.0 azsdk-python-core/1.28.0 Python/3.8.10 (Linux-6.2.0-1019-azure-x86_64-with-glibc2.29) method: GET - uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest000001/providers/Microsoft.KeyVault/vaults/cliakstestkv000002?api-version=2025-05-01 + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest000001/providers/Microsoft.KeyVault/vaults/cliakstestkv000002?api-version=2026-02-01 response: body: string: '{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest000001/providers/Microsoft.KeyVault/vaults/cliakstestkv000002","name":"cliakstestkv000002","type":"Microsoft.KeyVault/vaults","location":"eastus","tags":{},"systemData":{"createdBy":"3fac8b4e-cd90-4baa-a5d2-66d52bc8349d","createdByType":"Application","createdAt":"2024-03-11T07:38:10.965Z","lastModifiedBy":"3fac8b4e-cd90-4baa-a5d2-66d52bc8349d","lastModifiedByType":"Application","lastModifiedAt":"2024-03-11T07:38:10.965Z"},"properties":{"sku":{"family":"A","name":"standard"},"tenantId":"72f988bf-86f1-41af-91ab-2d7cd011db47","accessPolicies":[],"enabledForDeployment":false,"enableSoftDelete":true,"softDeleteRetentionInDays":90,"enableRbacAuthorization":true,"vaultUri":"https://cliakstestkv000002.vault.azure.net/","provisioningState":"Succeeded","publicNetworkAccess":"Enabled"}}' diff --git a/src/aks-preview/azext_aks_preview/tests/latest/recordings/test_aks_check_network.yaml b/src/aks-preview/azext_aks_preview/tests/latest/recordings/test_aks_check_network.yaml index d578a52deb6..5b6d999ab2e 100644 --- a/src/aks-preview/azext_aks_preview/tests/latest/recordings/test_aks_check_network.yaml +++ b/src/aks-preview/azext_aks_preview/tests/latest/recordings/test_aks_check_network.yaml @@ -2414,7 +2414,7 @@ interactions: User-Agent: - AZURECLI/2.87.0 azsdk-python-core/1.39.0 Python/3.12.3 (Linux-6.17.0-1018-azure-x86_64-with-glibc2.39) method: GET - uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MC_clitest000001_cliakstest000001_eastus2/providers/Microsoft.Compute/virtualMachineScaleSets/aks-nodepool1-14233614-vmss/virtualMachines/1?api-version=2025-04-01 + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MC_clitest000001_cliakstest000001_eastus2/providers/Microsoft.Compute/virtualMachineScaleSets/aks-nodepool1-14233614-vmss/virtualMachines/1?api-version=2026-04-01 response: body: string: "{\r\n \"name\": \"aks-nodepool1-14233614-vmss_1\",\r\n \"id\": \"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MC_clitest000001_cliakstest000001_eastus2/providers/Microsoft.Compute/virtualMachineScaleSets/aks-nodepool1-14233614-vmss/virtualMachines/1\",\r\n diff --git a/src/aks-preview/azext_aks_preview/tests/latest/test_agentpool_decorator.py b/src/aks-preview/azext_aks_preview/tests/latest/test_agentpool_decorator.py index 123a1eaf20a..116d76045c6 100644 --- a/src/aks-preview/azext_aks_preview/tests/latest/test_agentpool_decorator.py +++ b/src/aks-preview/azext_aks_preview/tests/latest/test_agentpool_decorator.py @@ -3181,6 +3181,74 @@ def common_update_vm_size(self): # vm_size should remain unchanged for VMs pools self.assertEqual(dec_agentpool_3.vm_size, "Standard_D2s_v3") + def common_update_zones(self): + # No zones provided: preserve both the value and list instance from the fetched pool. + dec_1 = AKSPreviewAgentPoolUpdateDecorator( + self.cmd, + self.client, + {"zones": None}, + self.resource_type, + self.agentpool_decorator_mode, + ) + with self.assertRaises(CLIInternalError): + dec_1.update_zones(None) + + existing_zones = ["1", "2"] + agentpool_1 = self.create_initialized_agentpool_instance( + availability_zones=existing_zones + ) + stored_zones = agentpool_1.availability_zones + dec_1.context.attach_agentpool(agentpool_1) + dec_agentpool_1 = dec_1.update_zones(agentpool_1) + self.assertIs(dec_agentpool_1.availability_zones, stored_zones) + + # The automatic-zone token is passed through as a one-element list. + dec_2 = AKSPreviewAgentPoolUpdateDecorator( + self.cmd, + self.client, + {"zones": ["auto"]}, + self.resource_type, + self.agentpool_decorator_mode, + ) + agentpool_2 = self.create_initialized_agentpool_instance( + availability_zones=None + ) + dec_2.context.attach_agentpool(agentpool_2) + dec_agentpool_2 = dec_2.update_zones(agentpool_2) + self.assertEqual(dec_agentpool_2.availability_zones, ["auto"]) + + payload = dec_agentpool_2.as_dict() + if self.agentpool_decorator_mode == AgentPoolDecoratorMode.STANDALONE: + payload = payload["properties"] + self.assertEqual(payload["availabilityZones"], ["auto"]) + + # Explicit zone lists compose with a VM-size update on the same payload. + dec_3 = AKSPreviewAgentPoolUpdateDecorator( + self.cmd, + self.client, + { + "zones": ["1", "2", "3"], + "node_vm_size": "Standard_D4s_v3", + }, + self.resource_type, + self.agentpool_decorator_mode, + ) + agentpool_3 = self.create_initialized_agentpool_instance( + availability_zones=None, + vm_size="Standard_D2s_v3", + ) + dec_3.context.attach_agentpool(agentpool_3) + dec_agentpool_3 = dec_3.update_vm_size(agentpool_3) + dec_agentpool_3 = dec_3.update_zones(dec_agentpool_3) + self.assertEqual(dec_agentpool_3.availability_zones, ["1", "2", "3"]) + self.assertEqual(dec_agentpool_3.vm_size, "Standard_D4s_v3") + + payload = dec_agentpool_3.as_dict() + if self.agentpool_decorator_mode == AgentPoolDecoratorMode.STANDALONE: + payload = payload["properties"] + self.assertEqual(payload["availabilityZones"], ["1", "2", "3"]) + self.assertEqual(payload["vmSize"], "Standard_D4s_v3") + def common_update_upgrade_strategy(self): # Test case 1: No upgrade strategy provided (should not change agentpool) dec_1 = AKSPreviewAgentPoolUpdateDecorator( @@ -3524,6 +3592,9 @@ def test_update_fips_image(self): def test_update_vm_size(self): self.common_update_vm_size() + def test_update_zones(self): + self.common_update_zones() + def test_update_upgrade_strategy(self): self.common_update_upgrade_strategy() @@ -3559,6 +3630,8 @@ def test_update_agentpool_profile_preview(self): "nodepool_name", ] self.assertEqual(positional_params, ground_truth_positional_params) + self.assertIn("zones", optional_params) + self.assertIsNone(optional_params["zones"]) # prepare a dictionary of default parameters raw_param_dict = { @@ -3623,6 +3696,9 @@ def test_update_fips_image(self): def test_update_vm_size(self): self.common_update_vm_size() + def test_update_zones(self): + self.common_update_zones() + def test_update_upgrade_strategy(self): self.common_update_upgrade_strategy() diff --git a/src/aks-preview/azext_aks_preview/tests/latest/test_update_agentpool_profile_preview.py b/src/aks-preview/azext_aks_preview/tests/latest/test_update_agentpool_profile_preview.py index 00840329776..74886ad5554 100644 --- a/src/aks-preview/azext_aks_preview/tests/latest/test_update_agentpool_profile_preview.py +++ b/src/aks-preview/azext_aks_preview/tests/latest/test_update_agentpool_profile_preview.py @@ -41,6 +41,7 @@ class TestUpdateAgentPoolProfilePreview(unittest.TestCase): base_handles_vms_autoscaler = hasattr( AKSAgentPoolUpdateDecorator, "update_auto_scaler_properties_vms" ) + base_handles_zones = hasattr(AKSAgentPoolUpdateDecorator, "update_zones") def _assert_vms_autoscaler_update(self, update_method, agentpool, expected): if expected and not self.base_handles_vms_autoscaler: @@ -48,6 +49,12 @@ def _assert_vms_autoscaler_update(self, update_method, agentpool, expected): else: update_method.assert_not_called() + def _assert_zones_update(self, update_method, agentpool, expected): + if expected and not self.base_handles_zones: + update_method.assert_called_once_with(agentpool) + else: + update_method.assert_not_called() + def setUp(self): """Set up test fixtures.""" # manually register CUSTOM_MGMT_AKS_PREVIEW @@ -143,6 +150,7 @@ def test_update_agentpool_profile_preview_default_behavior(self): decorator.update_fips_image = Mock(return_value=agentpool) decorator.update_ssh_access = Mock(return_value=agentpool) decorator.update_vm_size = Mock(return_value=agentpool) + decorator.update_zones = Mock(return_value=agentpool) decorator.update_localdns_profile = Mock(return_value=agentpool) decorator.update_auto_scaler_properties_vms = Mock(return_value=agentpool) decorator.update_upgrade_strategy = Mock(return_value=agentpool) @@ -171,6 +179,7 @@ def test_update_agentpool_profile_preview_default_behavior(self): decorator.update_fips_image.assert_called_once_with(agentpool) decorator.update_ssh_access.assert_called_once_with(agentpool) decorator.update_vm_size.assert_called_once_with(agentpool) + self._assert_zones_update(decorator.update_zones, agentpool, expected=True) decorator.update_localdns_profile.assert_called_once_with(agentpool) self._assert_vms_autoscaler_update( decorator.update_auto_scaler_properties_vms, agentpool, expected=True @@ -182,6 +191,62 @@ def test_update_agentpool_profile_preview_default_behavior(self): decorator.update_crg.assert_called_once_with(agentpool) decorator.update_prepared_image_specification.assert_called_once_with(agentpool) + def test_update_zones_is_not_applied_twice_when_base_handles_it(self): + """A newer CLI base invokes the preview override from its default update flow.""" + raw_param_dict = { + "resource_group_name": "test_rg", + "cluster_name": "test_cluster", + "nodepool_name": "test_nodepool", + "zones": ["auto"], + } + decorator = AKSPreviewAgentPoolUpdateDecorator( + self.cmd, + self.client, + raw_param_dict, + self.resource_type, + self.agentpool_decorator_mode, + ) + agentpool = self._create_initialized_agentpool_instance( + nodepool_name="test_nodepool", + availability_zones=None, + ) + decorator.context.attach_agentpool(agentpool) + + original_update_zones = decorator.update_zones + decorator.update_zones = Mock(wraps=original_update_zones) + + # Simulate a future base update flow calling the dynamically-dispatched + # preview override before returning the fetched AgentPool. + decorator.update_agentpool_profile_default = Mock( + side_effect=lambda _: decorator.update_zones(agentpool) + ) + for method_name in [ + "update_network_profile", + "update_artifact_streaming", + "update_managed_gpu", + "update_secure_boot", + "update_vtpm", + "update_os_sku", + "update_fips_image", + "update_ssh_access", + "update_vm_size", + "update_localdns_profile", + "update_auto_scaler_properties_vms", + "update_upgrade_strategy", + "update_blue_green_upgrade_settings", + "update_gpu_profile", + "update_gpu_mig_strategy", + "update_crg", + "update_prepared_image_specification", + ]: + setattr(decorator, method_name, Mock(return_value=agentpool)) + + with patch.object(AKSAgentPoolUpdateDecorator, "update_zones", create=True): + result = decorator.update_agentpool_profile_preview() + + decorator.update_zones.assert_called_once_with(agentpool) + self.assertEqual(result.availability_zones, ["auto"]) + def test_update_agentpool_profile_preview_with_agentpools_parameter(self): """Test update_agentpool_profile_preview with agentpools parameter.""" # Arrange @@ -219,6 +284,7 @@ def test_update_agentpool_profile_preview_with_agentpools_parameter(self): decorator.update_fips_image = Mock(return_value=agentpool) decorator.update_ssh_access = Mock(return_value=agentpool) decorator.update_vm_size = Mock(return_value=agentpool) + decorator.update_zones = Mock(return_value=agentpool) decorator.update_localdns_profile = Mock(return_value=agentpool) decorator.update_auto_scaler_properties_vms = Mock(return_value=agentpool) decorator.update_upgrade_strategy = Mock(return_value=agentpool) @@ -390,6 +456,7 @@ def test_update_agentpool_profile_preview_system_mode_regular_flow(self): decorator.update_fips_image = Mock(return_value=agentpool) decorator.update_ssh_access = Mock(return_value=agentpool) decorator.update_vm_size = Mock(return_value=agentpool) + decorator.update_zones = Mock(return_value=agentpool) decorator.update_localdns_profile = Mock(return_value=agentpool) decorator.update_auto_scaler_properties_vms = Mock(return_value=agentpool) decorator.update_upgrade_strategy = Mock(return_value=agentpool) @@ -416,6 +483,7 @@ def test_update_agentpool_profile_preview_system_mode_regular_flow(self): decorator.update_fips_image.assert_called_once_with(agentpool) decorator.update_ssh_access.assert_called_once_with(agentpool) decorator.update_vm_size.assert_called_once_with(agentpool) + self._assert_zones_update(decorator.update_zones, agentpool, expected=True) decorator.update_localdns_profile.assert_called_once_with(agentpool) self._assert_vms_autoscaler_update( decorator.update_auto_scaler_properties_vms, agentpool, expected=True @@ -469,6 +537,7 @@ def mock_method(pool): decorator.update_fips_image = create_mock_update_method("update_fips_image") decorator.update_ssh_access = create_mock_update_method("update_ssh_access") decorator.update_vm_size = create_mock_update_method("update_vm_size") + decorator.update_zones = create_mock_update_method("update_zones") decorator.update_localdns_profile = create_mock_update_method("update_localdns_profile") decorator.update_auto_scaler_properties_vms = create_mock_update_method("update_auto_scaler_properties_vms") decorator.update_upgrade_strategy = create_mock_update_method("update_upgrade_strategy") @@ -502,6 +571,8 @@ def mock_method(pool): ] if not self.base_handles_vms_autoscaler: expected_order.insert(10, "update_auto_scaler_properties_vms") + if not self.base_handles_zones: + expected_order.insert(9, "update_zones") self.assertEqual(call_order, expected_order) def test_update_agentpool_profile_preview_preserves_agentpool_reference(self): @@ -546,6 +617,7 @@ def track_and_return(pool): decorator.update_fips_image = create_tracking_mock("update_fips_image") decorator.update_ssh_access = create_tracking_mock("update_ssh_access") decorator.update_vm_size = create_tracking_mock("update_vm_size") + decorator.update_zones = create_tracking_mock("update_zones") decorator.update_localdns_profile = create_tracking_mock("update_localdns_profile") decorator.update_auto_scaler_properties_vms = create_tracking_mock("update_auto_scaler_properties_vms") decorator.update_upgrade_strategy = create_tracking_mock("update_upgrade_strategy") @@ -619,6 +691,8 @@ def test_update_agentpool_profile_preview_mixed_modes_scenario(self): 'update_upgrade_strategy', 'update_blue_green_upgrade_settings', 'update_gpu_profile', 'update_gpu_mig_strategy', 'update_crg', 'update_prepared_image_specification' ] + if not self.base_handles_zones: + update_methods.insert(9, 'update_zones') for method_name in update_methods: setattr(decorator, method_name, Mock(return_value=agentpool)) @@ -772,6 +846,7 @@ def test_update_agentpool_profile_preview_managed_cluster_mode(self): decorator.update_fips_image = Mock(return_value=agentpool) decorator.update_ssh_access = Mock(return_value=agentpool) decorator.update_vm_size = Mock(return_value=agentpool) + decorator.update_zones = Mock(return_value=agentpool) decorator.update_localdns_profile = Mock(return_value=agentpool) decorator.update_auto_scaler_properties_vms = Mock(return_value=agentpool) decorator.update_upgrade_strategy = Mock(return_value=agentpool) diff --git a/src/aks-preview/setup.py b/src/aks-preview/setup.py index 8e7c3d36a83..e2526fbe99f 100644 --- a/src/aks-preview/setup.py +++ b/src/aks-preview/setup.py @@ -9,7 +9,7 @@ from setuptools import find_packages, setup -VERSION = "22.0.0b3" +VERSION = "22.0.0b4" CLASSIFIERS = [ "Development Status :: 4 - Beta",