Skip to content
Merged
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 @@ -6,6 +6,7 @@
import json
import os
import random
import re
import subprocess
import tempfile
import time
Expand Down Expand Up @@ -115,6 +116,28 @@ def _is_transient_operation_conflict(ex):
"ProvisioningState of extension: Updating" in message
)

@staticmethod
def _is_resource_already_exists_conflict(ex):
return "already exists" in str(ex).casefold()

@staticmethod
def _extract_cli_option(command, *option_names):
for option_name in option_names:
match = re.search(rf"{re.escape(option_name)}(?:=|\s+)(\S+)", command)
if match:
return match.group(1).strip("\"'")
return None

@classmethod
def _build_show_command_for_already_existing_resource(cls, command):
if not re.match(r"^aks\s+create\b", command.strip()):
return None
resource_group = cls._extract_cli_option(command, "--resource-group", "-g")
name = cls._extract_cli_option(command, "--name", "-n")
if not resource_group or not name:
return None
return f"aks show --resource-group {resource_group} --name {name}"

def _execute_with_transient_conflict_retry(self, command, expect_failure):
from azure.cli.testsdk.base import execute
import logging
Expand All @@ -127,6 +150,15 @@ def _execute_with_transient_conflict_retry(self, command, expect_failure):
try:
return execute(self.cli_ctx, command, expect_failure=expect_failure)
except (HttpResponseError, CLIError) as ex:
if (
not expect_failure and
attempt > 0 and
getattr(self, "_allow_retried_create_recovery", False) and
self._is_resource_already_exists_conflict(ex)
):
show_command = self._build_show_command_for_already_existing_resource(command)
if show_command:
return execute(self.cli_ctx, show_command, expect_failure=False)
if (
expect_failure or
not self._is_transient_operation_conflict(ex) or
Expand All @@ -144,6 +176,14 @@ def _execute_with_transient_conflict_retry(self, command, expect_failure):

raise AssertionError("unreachable")

def _cmd_with_retried_create_recovery(self, command, checks=None):
previous_value = getattr(self, "_allow_retried_create_recovery", False)
self._allow_retried_create_recovery = True
try:
return self.cmd(command, checks=checks)
finally:
self._allow_retried_create_recovery = previous_value

def _refetch_settled_aks_result(self, resource_id, fallback_result):
from azure.cli.testsdk.base import execute

Expand Down Expand Up @@ -383,6 +423,38 @@ def _wait_for_cluster_update(self):
checks=[self.is_empty()],
)

def _wait_for_cluster_property(self, query, expected, attempts=20, delay=30):
if not (self.is_live or self.in_recording):
return
last_value = None
for attempt in range(attempts):
last_value = self.cmd(
f'aks show --resource-group={{resource_group}} --name={{name}} '
f'--query "{query}" -o json'
).get_output_in_json()
if str(last_value).casefold() == str(expected).casefold():
return
if attempt < attempts - 1:
time.sleep(delay)
raise AssertionError(
f"Cluster property '{query}' did not reach {expected!r}; last value: {last_value!r}"
)

def _cmd_or_skip_if_artifact_streaming_unavailable(self, command, checks=None):
try:
return self.cmd(command, checks=checks)
except Exception as ex: # pylint: disable=broad-except
message = str(ex)
if (
"UnmarshalError" in message and
'unknown field "artifactStreamingProfile"' in message
):
self.skipTest(
"The stable AKS API used by Azure CLI does not currently expose "
"artifactStreamingProfile; coverage remains in aks-preview."
)
raise

# Substrings identifying an "unsupported/unavailable" condition (as opposed to e.g. a
# value/quota/permission validation error). On their own these are too generic to trigger a
# skip safely, since many unrelated errors (invalid VM size, bad SKU, etc.) also contain
Expand Down Expand Up @@ -1619,10 +1691,8 @@ def test_aks_create_default_service_with_monitoring_addon(self, resource_group,
])

# disable monitoring add-on
disable_addon_output = self.cmd('aks disable-addons -a monitoring -g {resource_group} -n {name}', checks=[
self.check('addonProfiles.omsagent.enabled', False),
]).get_output_in_json()
assert bool(disable_addon_output["addonProfiles"]["omsagent"]["config"]) == False
self.cmd('aks disable-addons -a monitoring -g {resource_group} -n {name}')
self._wait_for_cluster_property('addonProfiles.omsagent.enabled', False)

# show again
show_output = self.cmd('aks show -g {resource_group} -n {name}', checks=[
Expand Down Expand Up @@ -4697,7 +4767,7 @@ def test_aks_nodepool_add_with_artifact_streaming(
)

# nodepool add
self.cmd(
self._cmd_or_skip_if_artifact_streaming_unavailable(
"aks nodepool add --resource-group={resource_group} --cluster-name={name} --name={nodepool2_name} "
"--node-vm-size={node_vm_size} "
"--enable-artifact-streaming --aks-custom-headers=AKSHTTPCustomFeatures=Microsoft.ContainerService/ArtifactStreamingPreview",
Expand Down Expand Up @@ -4756,7 +4826,7 @@ def test_aks_nodepool_update_with_artifact_streaming(
)

# enable artifact streaming
self.cmd(
self._cmd_or_skip_if_artifact_streaming_unavailable(
"aks nodepool update "
"--resource-group={resource_group} "
"--cluster-name={name} "
Expand Down Expand Up @@ -8841,13 +8911,21 @@ def test_aks_create_with_control_plane_metrics(self, resource_group, resource_gr
# the final state via ``aks show`` after the cluster settles.
# Control Plane Metrics availability is still rolling out per-subscription/region; if the
# service reports the feature/toggle as unsupported here, skip rather than fail the test.
self._cmd_or_skip_if_unsupported(
create_cmd,
checks=[
self.check('provisioningState', 'Succeeded'),
],
skip_reason="Control Plane Metrics toggle is not yet available in this subscription/region",
)
try:
self._cmd_with_retried_create_recovery(
create_cmd,
checks=[self.check('provisioningState', 'Succeeded')],
)
except Exception as ex: # pylint: disable=broad-except
message = str(ex).casefold()
if (
any(marker in message for marker in self._CONTROL_PLANE_METRICS_CONTEXT_MARKERS) and
any(marker in message for marker in self._UNSUPPORTED_CONDITION_MARKERS)
):
self.skipTest(
"Control Plane Metrics toggle is not yet available in this subscription/region"
)
raise

wait_cmd = 'aks wait --resource-group={resource_group} --name={name} --created ' \
'--interval 60 --timeout 1800'
Expand Down Expand Up @@ -8893,9 +8971,10 @@ def test_aks_update_with_control_plane_metrics(self, resource_group, resource_gr
create_cmd = 'aks create --resource-group={resource_group} --name={name} --location={location} ' \
'--ssh-key-value={ssh_key_value} --node-vm-size={node_vm_size} --enable-managed-identity ' \
'--enable-azure-monitor-metrics --azure-monitor-workspace-resource-id={amw_id} --output=json'
self.cmd(create_cmd, checks=[
self.check('provisioningState', 'Succeeded'),
])
self._cmd_with_retried_create_recovery(
create_cmd,
checks=[self.check('provisioningState', 'Succeeded')],
)

# wait for AMW background setup to complete before issuing update
wait_cmd = 'aks wait --resource-group={resource_group} --name={name} --updated --timeout=1800'
Expand Down Expand Up @@ -10577,12 +10656,14 @@ def test_aks_kubenet_to_cni_overlay_migration(self, resource_group, resource_gro
'name': aks_name,
'location': cluster_location,
'k8s_version': create_version,
'node_vm_size': 'Standard_D2s_v3',
'ssh_key_value': self.generate_ssh_keys(),
})

# create
create_cmd = 'aks create --resource-group={resource_group} --name={name} --location={location} ' \
'--network-plugin kubenet --ssh-key-value={ssh_key_value} --kubernetes-version {k8s_version} ' \
'--node-vm-size {node_vm_size} ' \
'--service-cidr 172.56.0.0/16 --dns-service-ip 172.56.0.10 --pod-cidr 100.112.0.0/12 ' \
'--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/AzureOverlayPreview'
self.cmd(create_cmd, checks=[
Expand Down Expand Up @@ -14503,9 +14584,11 @@ def test_aks_create_acns_with_flow_logs(
checks=[
self.check("provisioningState", "Succeeded"),
self.check("addonProfiles.omsagent.enabled", True),
self.check("addonProfiles.omsagent.config.enableRetinaNetworkFlags", "False"),
],
)
self._wait_for_cluster_property(
"addonProfiles.omsagent.config.enableRetinaNetworkFlags", "False"
)

# update: enable high log scale mode independently via aks update
self.cmd(
Expand All @@ -14527,9 +14610,11 @@ def test_aks_create_acns_with_flow_logs(
checks=[
self.check("provisioningState", "Succeeded"),
self.check("addonProfiles.omsagent.enabled", True),
self.check("addonProfiles.omsagent.config.enableRetinaNetworkFlags", "True"),
],
)
self._wait_for_cluster_property(
"addonProfiles.omsagent.config.enableRetinaNetworkFlags", "True"
)

# delete
self.cmd(
Expand Down
Loading
Loading