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 @@ -20,12 +20,12 @@
<retry count="{retry_count}" interval="0" first-fast-retry="true" condition="@(context.Response.StatusCode == 429 || context.Response.StatusCode == 503)">
<forward-request timeout="60" buffer-request-body="true" />
<choose>
<when condition="@((context.Response.StatusCode == 429 || context.Response.StatusCode == 503) &amp;&amp; context.Response.Headers.ContainsKey('Retry-After'))">
<when condition='@((context.Response.StatusCode == 429 || context.Response.StatusCode == 503) &amp;&amp; context.Response.Headers.ContainsKey("Retry-After"))'>
<cache-lookup-value key="lb-retry-min-{cache_key}" variable-name="cachedRetryEpoch" />
<set-variable name="updatedRetryEpoch" value="@{{
<set-variable name="updatedRetryEpoch" value='@{{
// Parse the Retry-After header value as an integer number of seconds
int seconds;
var raHeader = context.Response.Headers.GetValueOrDefault('Retry-After', '0');
var raHeader = context.Response.Headers.GetValueOrDefault("Retry-After", "0");
if (!int.TryParse(raHeader, out seconds)) {{ seconds = 0; }}

// Calculate the candidate epoch time based on the Retry-After header
Expand All @@ -38,10 +38,10 @@
// epoch permanently in the past and the on-error block always emits Retry-After: 0,
// even when the pool is actually exhausted again.
long cachedEpoch;
var cached = context.Variables.ContainsKey('cachedRetryEpoch') ? context.Variables['cachedRetryEpoch'] as string : null;
var cached = context.Variables.ContainsKey("cachedRetryEpoch") ? context.Variables["cachedRetryEpoch"] as string : null;
return (long.TryParse(cached, out cachedEpoch) &amp;&amp; cachedEpoch &gt; nowEpoch &amp;&amp; cachedEpoch &lt; candidateEpoch) ? cachedEpoch.ToString() : candidateEpoch.ToString();
}}" />
<cache-store-value key="lb-retry-min-{cache_key}" value="@((string)context.Variables['updatedRetryEpoch'])" duration="300" />
}}' />
<cache-store-value key="lb-retry-min-{cache_key}" value='@((string)context.Variables["updatedRetryEpoch"])' duration="300" />
</when>
</choose>
</retry>
Expand Down
8 changes: 4 additions & 4 deletions shared/python/azure_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
# cleanups reliable.
_AZ_CLI_LOCK = threading.Lock()
_NESTED_DEPLOYMENT_RESOURCE_TYPE = 'microsoft.resources/deployments'
_LEGACY_APIM_DIAGNOSTIC_SETTING_PATTERN = re.compile(r'^apim-(?:costing-diagnostics|inference-failover)-\d+$')
_LEGACY_APIM_DIAGNOSTIC_SETTING_PATTERN = re.compile(r'^apim-(?:diag|costing-diagnostics-\d+|inference-failover-\d+)$')


def _strip_ansi(text: str) -> str:
Expand Down Expand Up @@ -227,9 +227,6 @@ def _extract_arm_error_details(error_payload: Any) -> tuple[str, str]:
code = error_payload.get('code') if isinstance(error_payload.get('code'), str) else ''
message = error_payload.get('message') if isinstance(error_payload.get('message'), str) else ''

if message:
return code, message

details = error_payload.get('details')
if isinstance(details, list):
for detail in details:
Expand All @@ -243,6 +240,9 @@ def _extract_arm_error_details(error_payload: Any) -> tuple[str, str]:
if nested_message:
return nested_code or code, nested_message

if message:
return code, message

return code, message


Expand Down
10 changes: 6 additions & 4 deletions tests/python/test_azure_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,14 +117,15 @@ def test_get_resource_group_location_empty():


def test_migrate_legacy_apim_diagnostic_settings_removes_only_repository_owned_settings():
"""Legacy sample settings using the infrastructure workspace should be removed."""
"""Legacy repository settings using the infrastructure workspace should be removed."""
apim_id = '/subscriptions/sub/resourceGroups/rg/providers/Microsoft.ApiManagement/service/apim-test'
workspace_id = '/subscriptions/sub/resourceGroups/rg/providers/Microsoft.OperationalInsights/workspaces/log-test'
outputs = []
for json_data in (
[apim_id],
[workspace_id],
[
{'name': 'apim-diag', 'workspaceId': workspace_id},
{'name': 'apim-costing-diagnostics-1', 'workspaceId': workspace_id.upper()},
{'name': 'apim-inference-failover-60', 'workspaceId': workspace_id},
{'name': 'customer-diagnostics', 'workspaceId': workspace_id},
Expand All @@ -135,13 +136,14 @@ def test_migrate_legacy_apim_diagnostic_settings_removes_only_repository_owned_s
output = Output(True, json.dumps(json_data))
output.json_data = json_data
outputs.append(output)
outputs.extend((Output(True, ''), Output(True, '')))
outputs.extend((Output(True, ''), Output(True, ''), Output(True, '')))

with patch('azure_resources.run', side_effect=outputs) as mock_run:
removed = az.migrate_legacy_apim_diagnostic_settings('rg')

assert removed == ['apim-costing-diagnostics-1', 'apim-inference-failover-60']
assert mock_run.call_args_list[-2:] == [
assert removed == ['apim-diag', 'apim-costing-diagnostics-1', 'apim-inference-failover-60']
assert mock_run.call_args_list[-3:] == [
call(f'az monitor diagnostic-settings delete --name apim-diag --resource "{apim_id}"'),
call(f'az monitor diagnostic-settings delete --name apim-costing-diagnostics-1 --resource "{apim_id}"'),
call(f'az monitor diagnostic-settings delete --name apim-inference-failover-60 --resource "{apim_id}"'),
]
Expand Down
1 change: 1 addition & 0 deletions tests/python/test_azure_resources_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ def test_extract_group_deployment_context_returns_none_for_non_matching_commands
def test_extract_arm_error_details_prefers_nested_detail_message() -> None:
payload = {
'code': 'TopLevel',
'message': 'One or more fields contain incorrect values:',
'details': [
{
'code': 'NestedCode',
Expand Down
14 changes: 14 additions & 0 deletions tests/python/test_load_balancing_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import json
import sys
import xml.etree.ElementTree as ET
from pathlib import Path
from unittest.mock import MagicMock

Expand Down Expand Up @@ -49,6 +50,19 @@ def test_load_balancing_policies_use_bounded_retry_count() -> None:

assert expected_assignment in code_source
assert '<retry count="2"' in rendered_policy
ET.fromstring(rendered_policy)


@pytest.mark.unit
def test_retry_tracking_policy_uses_csharp_string_literals() -> None:
"""Prevent multi-character C# values from being emitted as character literals."""
policy = (LOAD_BALANCING_DIR / 'apim-policies' / 'aca-backend-pool-load-balancing-with-retry-tracked.xml').read_text(encoding='utf-8')

assert 'ContainsKey("Retry-After")' in policy
assert 'GetValueOrDefault("Retry-After", "0")' in policy
assert 'ContainsKey("cachedRetryEpoch")' in policy
assert "ContainsKey('" not in policy
assert "GetValueOrDefault('" not in policy


def _create_runner(*, responses=None, sleep=None, clock=None):
Expand Down