From 324fbf8144d52146d31b1bdb3836a69536ef33ab Mon Sep 17 00:00:00 2001 From: Fuming Zhang Date: Thu, 6 Aug 2026 03:42:24 +0000 Subject: [PATCH 1/5] {AKS} Fix remaining CLI Runner test failures Avoid retry formatting of inline JSON, initialize Container Insights workspaces before AKS creation, wait between sequential VMS pool operations, and skip live scenarios whose service toggles are unavailable in the runner subscription. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../acs/tests/latest/test_aks_commands.py | 23 ++++++++++++-- .../latest/test_aks_provisioning_retry.py | 30 +++++++++++++++++++ 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_aks_commands.py b/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_aks_commands.py index 3def6ae241b..b3f0411a897 100644 --- a/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_aks_commands.py +++ b/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_aks_commands.py @@ -338,9 +338,10 @@ def _create_container_insights_workspace(self, resource_group, location): "promotionCode": "", }, } + self.kwargs["container_insights_solution"] = json.dumps(solution) self.cmd( f"resource create --id {solution_id} --api-version 2015-11-01-preview " - f"--is-full-object --properties '{json.dumps(solution)}'" + "--is-full-object --properties '{container_insights_solution}'" ) return workspace_id @@ -4550,6 +4551,7 @@ def test_aks_nodepool_add_with_disable_windows_outbound_nat( # the availability of features is controlled by a toggle and cannot be fully tested yet, # however, existing test results show that the client side works as expected, so exclude it at this moment + @unittest.skip("Artifact streaming is not enabled in the live-test subscription") @live_only() @AllowLargeResponse() @AKSCustomResourceGroupPreparer( @@ -4608,6 +4610,7 @@ def test_aks_nodepool_add_with_artifact_streaming( # the availability of features is controlled by a toggle and cannot be fully tested yet, # however, existing test results show that the client side works as expected, so exclude it at this moment + @unittest.skip("Artifact streaming is not enabled in the live-test subscription") @live_only() @AllowLargeResponse() @AKSCustomResourceGroupPreparer( @@ -5255,19 +5258,23 @@ def test_aks_create_default_service_with_skip_role_assignment_msi(self, resource def test_aks_create_default_service_with_monitoring_addon_msi(self, resource_group, resource_group_location): # kwargs for string formatting aks_name = self.create_random_name('cliakstest', 16) + workspace_id = self._create_container_insights_workspace( + resource_group, resource_group_location + ) self.kwargs.update({ 'resource_group': resource_group, 'name': aks_name, 'dns_name_prefix': self.create_random_name('cliaksdns', 16), 'ssh_key_value': self.generate_ssh_keys(), 'location': resource_group_location, - 'resource_type': 'Microsoft.ContainerService/ManagedClusters' + 'resource_type': 'Microsoft.ContainerService/ManagedClusters', + 'workspace_id': workspace_id, }) # create cluster with monitoring-addon create_cmd = 'aks create --resource-group={resource_group} --name={name} --location={location} ' \ '--dns-name-prefix={dns_name_prefix} --node-count=1 --ssh-key-value={ssh_key_value} ' \ - '--enable-addons monitoring' + '--enable-addons monitoring --workspace-resource-id={workspace_id}' self.cmd(create_cmd, checks=[ self.exists('fqdn'), self.exists('nodeResourceGroup'), @@ -8671,6 +8678,7 @@ def test_aks_update_with_azuremonitormetrics(self, resource_group, resource_grou self.is_empty(), ]) + @unittest.skip("Control plane metrics is not enabled in the live-test subscription") @live_only() @AllowLargeResponse() @AKSCustomResourceGroupPreparer( @@ -8721,6 +8729,7 @@ def test_aks_create_with_control_plane_metrics(self, resource_group, resource_gr self.cmd('aks delete --resource-group={resource_group} --name={name} --yes --no-wait', checks=[self.is_empty()]) + @unittest.skip("Control plane metrics is not enabled in the live-test subscription") @live_only() @AllowLargeResponse() @AKSCustomResourceGroupPreparer( @@ -15544,6 +15553,8 @@ def test_aks_create_autoscaler_then_update_vms_pool(self, resource_group, resour self.check('agentPoolProfiles[0].virtualMachinesProfile.scale.autoscale[0].minCount', 1), self.check('agentPoolProfiles[0].virtualMachinesProfile.scale.autoscale[0].maxCount', 3), ]) + wait_cmd = 'aks wait --resource-group={resource_group} --name={name} --updated --interval 30 --timeout 1800' + self.cmd(wait_cmd, checks=[self.is_empty()]) # add another vms nodepool with autoscaler enabled add_nodepool_cmd = 'aks nodepool add -g {resource_group} --cluster-name {name} -n {nodepool_name} ' \ @@ -15557,6 +15568,7 @@ def test_aks_create_autoscaler_then_update_vms_pool(self, resource_group, resour self.check('virtualMachinesProfile.scale.autoscale[0].minCount', 0), self.check('virtualMachinesProfile.scale.autoscale[0].maxCount', 3), ]) + self.cmd(wait_cmd, checks=[self.is_empty()]) # update an existing autoscale profile using auto-scale update update_autoscale_cmd = 'aks nodepool auto-scale update -g {resource_group} --cluster-name {name} -n {nodepool_name} ' \ @@ -15569,6 +15581,7 @@ def test_aks_create_autoscaler_then_update_vms_pool(self, resource_group, resour self.check('virtualMachinesProfile.scale.autoscale[0].minCount', 1), self.check('virtualMachinesProfile.scale.autoscale[0].maxCount', 5), ]) + self.cmd(wait_cmd, checks=[self.is_empty()]) # add a second autoscale profile add_autoscale_cmd = 'aks nodepool auto-scale add -g {resource_group} --cluster-name {name} -n {nodepool_name} ' \ @@ -15580,12 +15593,14 @@ def test_aks_create_autoscaler_then_update_vms_pool(self, resource_group, resour self.check('virtualMachinesProfile.scale.autoscale[1].minCount', 1), self.check('virtualMachinesProfile.scale.autoscale[1].maxCount', 3), ]) + self.cmd(wait_cmd, checks=[self.is_empty()]) # delete the second autoscale profile delete_autoscale_cmd = 'aks nodepool auto-scale delete -g {resource_group} --cluster-name {name} -n {nodepool_name} ' \ '--current-node-vm-size {node_vm_size1} --yes' np = self.cmd(delete_autoscale_cmd).get_output_in_json() assert len(np["virtualMachinesProfile"]["scale"]["autoscale"]) == 1 + self.cmd(wait_cmd, checks=[self.is_empty()]) # disable autoscaler (auto to manual) disable_autoscaler_cmd = 'aks nodepool update -g {resource_group} --cluster-name {name} -n {nodepool_name} ' \ @@ -15594,6 +15609,7 @@ def test_aks_create_autoscaler_then_update_vms_pool(self, resource_group, resour self.check('provisioningState', 'Succeeded'), self.check('virtualMachinesProfile.scale.manual[0].size', 'standard_d4s_v3'), ]) + self.cmd(wait_cmd, checks=[self.is_empty()]) # enable autoscaler (manual to auto) enable_autoscaler_cmd = 'aks nodepool update -g {resource_group} --cluster-name {name} -n {nodepool_name} ' \ @@ -15604,6 +15620,7 @@ def test_aks_create_autoscaler_then_update_vms_pool(self, resource_group, resour self.check('virtualMachinesProfile.scale.autoscale[0].minCount', 1), self.check('virtualMachinesProfile.scale.autoscale[0].maxCount', 3), ]) + self.cmd(wait_cmd, checks=[self.is_empty()]) # delete self.cmd('aks delete -g {resource_group} -n {name} --yes --no-wait', checks=[self.is_empty()]) diff --git a/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_aks_provisioning_retry.py b/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_aks_provisioning_retry.py index 246c72c272e..337cf899c59 100644 --- a/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_aks_provisioning_retry.py +++ b/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_aks_provisioning_retry.py @@ -100,6 +100,36 @@ def test_live_command_without_checks_uses_retry_path(self): instance._cmd_with_retry.assert_called_once_with('aks delete', [], False) +class TestCreateContainerInsightsWorkspace(unittest.TestCase): + + def test_solution_payload_is_passed_as_registered_kwarg(self): + from azure.cli.command_modules.acs.tests.latest.test_aks_commands import ( + AzureKubernetesServiceScenarioTest, + ) + instance = object.__new__(AzureKubernetesServiceScenarioTest) + instance.kwargs = {} + instance.create_random_name = MagicMock(return_value='workspace') + workspace_result = MockExecutionResult({ + 'id': ( + '/subscriptions/sub/resourceGroups/rg/providers/' + 'Microsoft.OperationalInsights/workspaces/workspace' + ) + }) + solution_result = MockExecutionResult({}) + instance.cmd = MagicMock(side_effect=[workspace_result, solution_result]) + + workspace_id = instance._create_container_insights_workspace('rg', 'westus2') + + self.assertEqual(workspace_result.get_output_in_json()['id'], workspace_id) + self.assertEqual( + json.loads(instance.kwargs['container_insights_solution'])['location'], + 'westus2', + ) + solution_command = instance.cmd.call_args_list[1].args[0] + self.assertIn("'{container_insights_solution}'", solution_command) + self.assertNotIn('{"location"', solution_command) + + class TestCmdWithRetry(unittest.TestCase): def _make_instance(self): From 3a8bda8b1ae318c53cacd7e10a399afa92893c8f Mon Sep 17 00:00:00 2001 From: Fuming Zhang Date: Thu, 6 Aug 2026 05:48:33 +0000 Subject: [PATCH 2/5] {AKS} Keep GA feature scenarios enabled Artifact Streaming is GA and Control Plane Metrics is on the GA rollout path, so retain their live coverage instead of masking API-version and toggle-rollout failures. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../cli/command_modules/acs/tests/latest/test_aks_commands.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_aks_commands.py b/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_aks_commands.py index b3f0411a897..6682fe29e70 100644 --- a/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_aks_commands.py +++ b/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_aks_commands.py @@ -4551,7 +4551,6 @@ def test_aks_nodepool_add_with_disable_windows_outbound_nat( # the availability of features is controlled by a toggle and cannot be fully tested yet, # however, existing test results show that the client side works as expected, so exclude it at this moment - @unittest.skip("Artifact streaming is not enabled in the live-test subscription") @live_only() @AllowLargeResponse() @AKSCustomResourceGroupPreparer( @@ -4610,7 +4609,6 @@ def test_aks_nodepool_add_with_artifact_streaming( # the availability of features is controlled by a toggle and cannot be fully tested yet, # however, existing test results show that the client side works as expected, so exclude it at this moment - @unittest.skip("Artifact streaming is not enabled in the live-test subscription") @live_only() @AllowLargeResponse() @AKSCustomResourceGroupPreparer( @@ -8678,7 +8676,6 @@ def test_aks_update_with_azuremonitormetrics(self, resource_group, resource_grou self.is_empty(), ]) - @unittest.skip("Control plane metrics is not enabled in the live-test subscription") @live_only() @AllowLargeResponse() @AKSCustomResourceGroupPreparer( @@ -8729,7 +8726,6 @@ def test_aks_create_with_control_plane_metrics(self, resource_group, resource_gr self.cmd('aks delete --resource-group={resource_group} --name={name} --yes --no-wait', checks=[self.is_empty()]) - @unittest.skip("Control plane metrics is not enabled in the live-test subscription") @live_only() @AllowLargeResponse() @AKSCustomResourceGroupPreparer( From 852e6e73081084001cd912fff10681dc26b65284 Mon Sep 17 00:00:00 2001 From: Fuming Zhang Date: Thu, 6 Aug 2026 07:46:40 +0000 Subject: [PATCH 3/5] {AKS} Keep live settle waits out of replay Gate the VMS autoscaler settle checks to live and recording runs so existing replay cassettes do not issue unrecorded GET requests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../acs/tests/latest/test_aks_commands.py | 23 ++++++++----- .../latest/test_aks_provisioning_retry.py | 33 +++++++++++++++++++ 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_aks_commands.py b/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_aks_commands.py index 6682fe29e70..8d56697e893 100644 --- a/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_aks_commands.py +++ b/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_aks_commands.py @@ -345,6 +345,14 @@ def _create_container_insights_workspace(self, resource_group, location): ) return workspace_id + def _wait_for_cluster_update(self): + if self.is_live or self.in_recording: + self.cmd( + 'aks wait --resource-group={resource_group} --name={name} ' + '--updated --interval 30 --timeout 1800', + checks=[self.is_empty()], + ) + def _get_lower_lts_version(self, location, version): """Return the highest LTS version that is lower than the given version.""" lts_versions = self._get_lts_versions(location) @@ -15549,8 +15557,7 @@ def test_aks_create_autoscaler_then_update_vms_pool(self, resource_group, resour self.check('agentPoolProfiles[0].virtualMachinesProfile.scale.autoscale[0].minCount', 1), self.check('agentPoolProfiles[0].virtualMachinesProfile.scale.autoscale[0].maxCount', 3), ]) - wait_cmd = 'aks wait --resource-group={resource_group} --name={name} --updated --interval 30 --timeout 1800' - self.cmd(wait_cmd, checks=[self.is_empty()]) + self._wait_for_cluster_update() # add another vms nodepool with autoscaler enabled add_nodepool_cmd = 'aks nodepool add -g {resource_group} --cluster-name {name} -n {nodepool_name} ' \ @@ -15564,7 +15571,7 @@ def test_aks_create_autoscaler_then_update_vms_pool(self, resource_group, resour self.check('virtualMachinesProfile.scale.autoscale[0].minCount', 0), self.check('virtualMachinesProfile.scale.autoscale[0].maxCount', 3), ]) - self.cmd(wait_cmd, checks=[self.is_empty()]) + self._wait_for_cluster_update() # update an existing autoscale profile using auto-scale update update_autoscale_cmd = 'aks nodepool auto-scale update -g {resource_group} --cluster-name {name} -n {nodepool_name} ' \ @@ -15577,7 +15584,7 @@ def test_aks_create_autoscaler_then_update_vms_pool(self, resource_group, resour self.check('virtualMachinesProfile.scale.autoscale[0].minCount', 1), self.check('virtualMachinesProfile.scale.autoscale[0].maxCount', 5), ]) - self.cmd(wait_cmd, checks=[self.is_empty()]) + self._wait_for_cluster_update() # add a second autoscale profile add_autoscale_cmd = 'aks nodepool auto-scale add -g {resource_group} --cluster-name {name} -n {nodepool_name} ' \ @@ -15589,14 +15596,14 @@ def test_aks_create_autoscaler_then_update_vms_pool(self, resource_group, resour self.check('virtualMachinesProfile.scale.autoscale[1].minCount', 1), self.check('virtualMachinesProfile.scale.autoscale[1].maxCount', 3), ]) - self.cmd(wait_cmd, checks=[self.is_empty()]) + self._wait_for_cluster_update() # delete the second autoscale profile delete_autoscale_cmd = 'aks nodepool auto-scale delete -g {resource_group} --cluster-name {name} -n {nodepool_name} ' \ '--current-node-vm-size {node_vm_size1} --yes' np = self.cmd(delete_autoscale_cmd).get_output_in_json() assert len(np["virtualMachinesProfile"]["scale"]["autoscale"]) == 1 - self.cmd(wait_cmd, checks=[self.is_empty()]) + self._wait_for_cluster_update() # disable autoscaler (auto to manual) disable_autoscaler_cmd = 'aks nodepool update -g {resource_group} --cluster-name {name} -n {nodepool_name} ' \ @@ -15605,7 +15612,7 @@ def test_aks_create_autoscaler_then_update_vms_pool(self, resource_group, resour self.check('provisioningState', 'Succeeded'), self.check('virtualMachinesProfile.scale.manual[0].size', 'standard_d4s_v3'), ]) - self.cmd(wait_cmd, checks=[self.is_empty()]) + self._wait_for_cluster_update() # enable autoscaler (manual to auto) enable_autoscaler_cmd = 'aks nodepool update -g {resource_group} --cluster-name {name} -n {nodepool_name} ' \ @@ -15616,7 +15623,7 @@ def test_aks_create_autoscaler_then_update_vms_pool(self, resource_group, resour self.check('virtualMachinesProfile.scale.autoscale[0].minCount', 1), self.check('virtualMachinesProfile.scale.autoscale[0].maxCount', 3), ]) - self.cmd(wait_cmd, checks=[self.is_empty()]) + self._wait_for_cluster_update() # delete self.cmd('aks delete -g {resource_group} -n {name} --yes --no-wait', checks=[self.is_empty()]) diff --git a/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_aks_provisioning_retry.py b/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_aks_provisioning_retry.py index 337cf899c59..713479b660c 100644 --- a/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_aks_provisioning_retry.py +++ b/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_aks_provisioning_retry.py @@ -130,6 +130,39 @@ def test_solution_payload_is_passed_as_registered_kwarg(self): self.assertNotIn('{"location"', solution_command) +class TestWaitForClusterUpdate(unittest.TestCase): + + @staticmethod + def _make_instance(is_live=False, in_recording=False): + from azure.cli.command_modules.acs.tests.latest.test_aks_commands import ( + AzureKubernetesServiceScenarioTest, + ) + instance = object.__new__(AzureKubernetesServiceScenarioTest) + instance.is_live = is_live + instance.in_recording = in_recording + instance.cmd = MagicMock() + instance.is_empty = MagicMock(return_value='empty-check') + return instance + + def test_replay_does_not_issue_wait_request(self): + instance = self._make_instance() + + instance._wait_for_cluster_update() + + instance.cmd.assert_not_called() + + def test_live_run_waits_for_cluster_update(self): + instance = self._make_instance(is_live=True) + + instance._wait_for_cluster_update() + + instance.cmd.assert_called_once_with( + 'aks wait --resource-group={resource_group} --name={name} ' + '--updated --interval 30 --timeout 1800', + checks=['empty-check'], + ) + + class TestCmdWithRetry(unittest.TestCase): def _make_instance(self): From 295029529a5c21f9d092ce32cb9716cb37ed655f Mon Sep 17 00:00:00 2001 From: Fuming Zhang Date: Thu, 13 Aug 2026 00:41:38 +0000 Subject: [PATCH 4/5] {AKS} Default IPv4 managed outbound IP count Preserve the historical IPv4 managed outbound IP default when only the IPv6 count is supplied. Keep explicit and existing IPv4 values unchanged, and cover create and update behavior with focused tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../cli/command_modules/acs/_loadbalancer.py | 7 ++- .../acs/tests/latest/test_aks_commands.py | 3 +- .../acs/tests/latest/test_loadbalancer.py | 48 +++++++++++++++++++ 3 files changed, 54 insertions(+), 4 deletions(-) diff --git a/src/azure-cli/azure/cli/command_modules/acs/_loadbalancer.py b/src/azure-cli/azure/cli/command_modules/acs/_loadbalancer.py index 6db5e36e63d..6eeb78b45cf 100644 --- a/src/azure-cli/azure/cli/command_modules/acs/_loadbalancer.py +++ b/src/azure-cli/azure/cli/command_modules/acs/_loadbalancer.py @@ -104,8 +104,11 @@ def configure_load_balancer_profile(managed_outbound_ip_count, managed_outbound_ "ManagedClusterLoadBalancerProfileManagedOutboundIPs" ) profile.managed_outbound_i_ps = ManagedClusterLoadBalancerProfileManagedOutboundIPs() - if managed_outbound_ip_count is not None: - profile.managed_outbound_i_ps.count = managed_outbound_ip_count + profile.managed_outbound_i_ps.count = ( + managed_outbound_ip_count + if managed_outbound_ip_count is not None + else profile.managed_outbound_i_ps.count or 1 + ) if managed_outbound_ipv6_count is not None: profile.managed_outbound_i_ps.count_ipv6 = managed_outbound_ipv6_count elif profile.managed_outbound_i_ps is not None: diff --git a/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_aks_commands.py b/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_aks_commands.py index 8d56697e893..22302e96141 100644 --- a/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_aks_commands.py +++ b/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_aks_commands.py @@ -11416,8 +11416,7 @@ def test_aks_create_and_update_ipv6_count(self, resource_group, resource_group_l create_cmd = 'aks create --resource-group={resource_group} --name={name} --location={location} ' \ '--pod-cidr 172.126.0.0/16 --service-cidr 172.56.0.0/16 --dns-service-ip 172.56.0.10 ' \ '--pod-cidrs 172.126.0.0/16,2001:abcd:1234::/64 --service-cidrs 172.56.0.0/16,2001:ffff::/108 ' \ - '--ip-families IPv4,IPv6 --load-balancer-managed-outbound-ip-count 1 ' \ - '--load-balancer-managed-outbound-ipv6-count 2 ' \ + '--ip-families IPv4,IPv6 --load-balancer-managed-outbound-ipv6-count 2 ' \ '--network-plugin kubenet --ssh-key-value={ssh_key_value} --kubernetes-version {k8s_version} ' \ '--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/AKS-EnableDualStack' self.cmd(create_cmd, checks=[ diff --git a/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_loadbalancer.py b/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_loadbalancer.py index 4bb62ad47d2..1753849add8 100644 --- a/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_loadbalancer.py +++ b/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_loadbalancer.py @@ -71,6 +71,54 @@ def test_configure_load_balancer_profile(self): self.assertEqual(p.idle_timeout_in_minutes, 3600) self.assertEqual(p.backend_pool_type, "nodeIP") + def test_create_load_balancer_profile_defaults_ipv4_count_with_ipv6(self): + cmd = MockCmd(MockCLI()) + load_balancer_models = AKSManagedClusterModels( + cmd, ResourceType.MGMT_CONTAINERSERVICE + ).load_balancer_models + + profile = loadbalancer.create_load_balancer_profile( + None, + 2, + None, + None, + None, + None, + None, + load_balancer_models, + ) + + self.assertEqual(profile.managed_outbound_i_ps.count, 1) + self.assertEqual(profile.managed_outbound_i_ps.count_ipv6, 2) + + def test_update_load_balancer_profile_preserves_ipv4_count_with_ipv6(self): + cmd = MockCmd(MockCLI()) + load_balancer_models = AKSManagedClusterModels( + cmd, ResourceType.MGMT_CONTAINERSERVICE + ).load_balancer_models + profile = load_balancer_models.ManagedClusterLoadBalancerProfile( + managed_outbound_i_ps=( + load_balancer_models.ManagedClusterLoadBalancerProfileManagedOutboundIPs( + count=3 + ) + ) + ) + + profile = loadbalancer.update_load_balancer_profile( + None, + 2, + None, + None, + None, + None, + None, + profile, + load_balancer_models, + ) + + self.assertEqual(profile.managed_outbound_i_ps.count, 3) + self.assertEqual(profile.managed_outbound_i_ps.count_ipv6, 2) + def test_update_load_balancer_profile(self): cmd = MockCmd(MockCLI()) managed_outbound_ip_count = None From 2407b10df5725da77f550462bbf025084bf8d877 Mon Sep 17 00:00:00 2001 From: Fuming Zhang Date: Thu, 13 Aug 2026 06:32:19 +0000 Subject: [PATCH 5/5] {AKS} Prevent saving retry-enabled recordings Retry polling and refetch requests cannot replay through the normal scenario path. Disable recording for these live runs and discard any temporary cassette traffic captured while preparers override the recording flag.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../acs/tests/latest/test_aks_commands.py | 19 +++++++-- .../latest/test_aks_provisioning_retry.py | 39 ++++++++++++++++++- 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_aks_commands.py b/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_aks_commands.py index 22302e96141..a271b1c64af 100644 --- a/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_aks_commands.py +++ b/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_aks_commands.py @@ -50,15 +50,28 @@ def __init__(self, method_name): super(AzureKubernetesServiceScenarioTest, self).__init__( method_name, recording_processors=[KeyReplacer()] ) + self._retry_live_without_recording = ( + self.is_live and + os.environ.get('AZURE_CLI_TEST_RETRY_PROVISIONING_CHECK') == 'true' + ) + if self._retry_live_without_recording: + # Poll/refetch requests are incompatible with normal replay. + self.disable_recording = True + + def _save_recording_file(self, *args): + if self._retry_live_without_recording: + # Preparers can temporarily override disable_recording. + self.cassette.dirty = False + if os.path.exists(self.temp_recording_file): + os.remove(self.temp_recording_file) + return + return super()._save_recording_file(*args) def cmd(self, command, checks=None, expect_failure=False): # Live-only retry adapter: when AZURE_CLI_TEST_RETRY_PROVISIONING_CHECK # is set during a live run, retry AKS operation conflicts and poll # provisioningState until terminal so asynchronous service operations # can't fail the test on a transient conflict or stale 'Updating' body. - # Recordings made with the flag enabled must NOT be committed; the - # replay pipeline runs with the flag off and would assert against the - # initial pre-poll response. if (self.is_live and os.environ.get('AZURE_CLI_TEST_RETRY_PROVISIONING_CHECK') == 'true'): if checks is None: diff --git a/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_aks_provisioning_retry.py b/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_aks_provisioning_retry.py index 713479b660c..259066f94ab 100644 --- a/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_aks_provisioning_retry.py +++ b/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_aks_provisioning_retry.py @@ -5,8 +5,9 @@ import json import os +import tempfile import unittest -from unittest.mock import MagicMock, patch +from unittest.mock import MagicMock, patch, PropertyMock from azure.cli.testsdk.checkers import JMESPathCheck from knack.util import CLIError @@ -86,6 +87,42 @@ def test_list_response_returns_false(self): class TestCmdRetryDispatch(unittest.TestCase): + @patch.dict(os.environ, {'AZURE_CLI_TEST_RETRY_PROVISIONING_CHECK': 'true'}) + @patch( + 'azure.cli.testsdk.scenario_tests.config.TestConfig.record_mode', + new_callable=PropertyMock, + return_value=True, + ) + def test_retry_enabled_live_instance_disables_recording(self, _record_mode): + from azure.cli.command_modules.acs.tests.latest.test_aks_commands import ( + AzureKubernetesServiceScenarioTest, + ) + instance = AzureKubernetesServiceScenarioTest('runTest') + + self.assertTrue(instance.disable_recording) + + @patch.dict(os.environ, {'AZURE_CLI_TEST_RETRY_PROVISIONING_CHECK': 'true'}) + @patch( + 'azure.cli.testsdk.scenario_tests.config.TestConfig.record_mode', + new_callable=PropertyMock, + return_value=True, + ) + def test_retry_enabled_live_instance_never_saves_cassette(self, _record_mode): + from azure.cli.command_modules.acs.tests.latest.test_aks_commands import ( + AzureKubernetesServiceScenarioTest, + ) + instance = AzureKubernetesServiceScenarioTest('runTest') + instance.cassette = MagicMock() + instance.cassette.dirty = True + fd, temp_recording_file = tempfile.mkstemp() + os.close(fd) + instance.temp_recording_file = temp_recording_file + + instance._save_recording_file() + + self.assertFalse(instance.cassette.dirty) + self.assertFalse(os.path.exists(temp_recording_file)) + @patch.dict(os.environ, {'AZURE_CLI_TEST_RETRY_PROVISIONING_CHECK': 'true'}) def test_live_command_without_checks_uses_retry_path(self): from azure.cli.command_modules.acs.tests.latest.test_aks_commands import (