Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -338,12 +351,21 @@ 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

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)
Expand Down Expand Up @@ -5255,19 +5277,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'),
Expand Down Expand Up @@ -11403,8 +11429,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=[
Expand Down Expand Up @@ -15544,6 +15569,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),
])
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} ' \
Expand All @@ -15557,6 +15583,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._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} ' \
Expand All @@ -15569,6 +15596,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._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} ' \
Expand All @@ -15580,12 +15608,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._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._wait_for_cluster_update()

# disable autoscaler (auto to manual)
disable_autoscaler_cmd = 'aks nodepool update -g {resource_group} --cluster-name {name} -n {nodepool_name} ' \
Expand All @@ -15594,6 +15624,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._wait_for_cluster_update()

# enable autoscaler (manual to auto)
enable_autoscaler_cmd = 'aks nodepool update -g {resource_group} --cluster-name {name} -n {nodepool_name} ' \
Expand All @@ -15604,6 +15635,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._wait_for_cluster_update()

# delete
self.cmd('aks delete -g {resource_group} -n {name} --yes --no-wait', checks=[self.is_empty()])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 (
Expand All @@ -100,6 +137,69 @@ 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 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):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading