Skip to content

Commit 7694969

Browse files
lubaihua33Copilot
andauthored
Improve VHD detail resolution via Resource Graph and harden storage lookup (#4617)
* azure: improve VHD detail resolution and storage account lookup resiliency * azure: fix flake8 E501 in storage account retry logs * azure: fix remaining E501 in storage account cache debug log * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Add a fix according to the comments --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent 35d3654 commit 7694969

2 files changed

Lines changed: 130 additions & 3 deletions

File tree

lisa/sut_orchestrator/azure/common.py

Lines changed: 129 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2637,7 +2637,114 @@ def find_by_name(resources: Any, type_name: str) -> Any:
26372637
return next(x for x in resources if x["type"] == type_name)
26382638

26392639

2640+
def get_vhd_details_by_resource_graph(
2641+
platform: "AzurePlatform", vhd_path: str
2642+
) -> Optional[Dict[str, str]]:
2643+
"""
2644+
Resolve VHD details by querying Azure Resource Graph first.
2645+
2646+
This avoids expensive per-subscription list operations when only a VHD URL is
2647+
provided and subscription/resource group information is needed.
2648+
"""
2649+
log = platform._log
2650+
matched = STORAGE_CONTAINER_BLOB_PATTERN.match(vhd_path)
2651+
assert matched, f"fail to get matched info from {vhd_path}"
2652+
sc_name = matched.group("sc")
2653+
container_name = matched.group("container")
2654+
blob_name = matched.group("blob")
2655+
2656+
try:
2657+
from azure.mgmt.resourcegraph import ResourceGraphClient
2658+
from azure.mgmt.resourcegraph.models import QueryRequest
2659+
except ImportError:
2660+
# Keep backward compatibility when resource graph dependency isn't present.
2661+
return None
2662+
2663+
try:
2664+
subscription_client = SubscriptionClient(platform.credential)
2665+
with global_credential_access_lock:
2666+
subscription_ids = [
2667+
x.subscription_id
2668+
for x in subscription_client.subscriptions.list()
2669+
if x.subscription_id
2670+
]
2671+
if not subscription_ids:
2672+
return None
2673+
2674+
rg_client = ResourceGraphClient(platform.credential)
2675+
escaped_name = sc_name.replace("'", "''")
2676+
query = (
2677+
"Resources "
2678+
"| where type =~ 'microsoft.storage/storageaccounts' "
2679+
f"| where name =~ '{escaped_name}' "
2680+
"| project subscriptionId, resourceGroup, name, location"
2681+
)
2682+
with global_credential_access_lock:
2683+
response = rg_client.resources(
2684+
QueryRequest(subscriptions=subscription_ids, query=query)
2685+
)
2686+
2687+
data = getattr(response, "data", None)
2688+
if not data:
2689+
return None
2690+
2691+
records: List[Dict[str, Any]] = []
2692+
if isinstance(data, list):
2693+
records = [x for x in data if isinstance(x, dict)]
2694+
elif isinstance(data, dict):
2695+
columns = data.get("columns")
2696+
rows = data.get("rows")
2697+
if isinstance(columns, list) and isinstance(rows, list):
2698+
column_names = [
2699+
str(x.get("name")) for x in columns if isinstance(x, dict)
2700+
]
2701+
for row in rows:
2702+
if isinstance(row, list) and len(row) == len(column_names):
2703+
records.append(dict(zip(column_names, row)))
2704+
2705+
for item in records:
2706+
if str(item.get("name", "")).lower() != sc_name.lower():
2707+
continue
2708+
2709+
subscription_id = str(item.get("subscriptionId", ""))
2710+
resource_group_name = str(item.get("resourceGroup", ""))
2711+
location = str(item.get("location", ""))
2712+
if not subscription_id or not resource_group_name or not location:
2713+
continue
2714+
2715+
log.debug(
2716+
"resolved vhd details via resource graph: "
2717+
f"storage_account={sc_name}, subscription={subscription_id}, "
2718+
f"resource_group={resource_group_name}, location={location}"
2719+
)
2720+
2721+
return {
2722+
"location": location,
2723+
"resource_group_name": resource_group_name,
2724+
"account_name": sc_name,
2725+
"container_name": container_name,
2726+
"blob_name": blob_name,
2727+
"subscription": subscription_id,
2728+
}
2729+
2730+
except Exception:
2731+
# Fall back to existing logic below if Resource Graph is unavailable
2732+
# transiently or blocked by permissions.
2733+
log.debug(
2734+
"resource graph lookup failed; falling back to storage account traversal",
2735+
exc_info=True,
2736+
)
2737+
return None
2738+
2739+
return None
2740+
2741+
26402742
def get_vhd_details(platform: "AzurePlatform", vhd_path: str) -> Any:
2743+
# Prefer Resource Graph lookup for faster and more reliable resolution.
2744+
rg_result = get_vhd_details_by_resource_graph(platform, vhd_path)
2745+
if rg_result:
2746+
return rg_result
2747+
26412748
matched = STORAGE_CONTAINER_BLOB_PATTERN.match(vhd_path)
26422749
assert matched, f"fail to get matched info from {vhd_path}"
26432750
sc_name = matched.group("sc")
@@ -2694,6 +2801,8 @@ def find_storage_account(
26942801
(an object) is not reliably hashable. Instead, we use a module-level
26952802
dict cache keyed by subscription_id.
26962803
"""
2804+
log = platform._log
2805+
26972806
# Check cache first
26982807
if subscription_id not in _storage_account_cache:
26992808
storage_client = get_storage_client(
@@ -2708,9 +2817,26 @@ def find_storage_account(
27082817
# triggers additional API calls during iteration. Frequent or concurrent
27092818
# iterations can exceed Azure's request limits, resulting in throttling
27102819
# errors. To mitigate this, we cache the full list of storage accounts.
2711-
_storage_account_cache[subscription_id] = list(
2712-
storage_client.storage_accounts.list()
2713-
)
2820+
for attempt in range(1, 4):
2821+
try:
2822+
sc_list = storage_client.storage_accounts.list()
2823+
_storage_account_cache[subscription_id] = list(sc_list)
2824+
break
2825+
except Exception as e:
2826+
log.exception(
2827+
"failed to load storage account cache for "
2828+
f"subscription '{subscription_id}' on "
2829+
f"attempt {attempt}/3",
2830+
exc_info=e,
2831+
)
2832+
if attempt < 3:
2833+
sleep(2 * attempt)
2834+
continue
2835+
raise LisaException(
2836+
"failed to load storage account cache for "
2837+
f"subscription '{subscription_id}' "
2838+
f"after 3 attempts: {e}"
2839+
) from e
27142840

27152841
# Search in cached list
27162842
for sc in _storage_account_cache[subscription_id]:

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ azure = [
4949
"azure-mgmt-msi ~= 7.0.0",
5050
"azure-mgmt-network ~= 27.0.0",
5151
"azure-mgmt-privatedns ~= 1.0.0",
52+
"azure-mgmt-resourcegraph ~= 8.0.0",
5253
"azure-mgmt-resource ~= 21.0.0",
5354
"azure-mgmt-serialconsole ~= 1.0.0",
5455
"azure-mgmt-storage ~= 21.2.1",

0 commit comments

Comments
 (0)