Skip to content
Open
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
20 changes: 11 additions & 9 deletions lisa/sut_orchestrator/azure/arm_template.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -473,16 +473,18 @@ resource nodes_vms 'Microsoft.Compute/virtualMachines@2024-03-01' = [for i in ra
imageReference: getImageReference(nodes[i])
osDisk: getVMOsDisk(nodes[i])
diskControllerType: (nodes[i].disk_controller_type == 'SCSI') ? null : nodes[i].disk_controller_type
dataDisks: concat(
map(
filter(range(0, length(data_disks)), j => !shouldAttachDataDisk(data_disks[j])),
j => getCreateDisk(data_disks[j], '${nodes[i].name}-data-disk-${j}', j)
),
map(
filter(range(0, length(data_disks)), j => shouldAttachDataDisk(data_disks[j])),
j => getAttachDisk(data_disks[j], '${nodes[i].name}-data-disk-${j}', j)
dataDisks: empty(data_disks)
? null
: concat(
map(
filter(range(0, length(data_disks)), j => !shouldAttachDataDisk(data_disks[j])),
j => getCreateDisk(data_disks[j], '${nodes[i].name}-data-disk-${j}', j)
),
map(
filter(range(0, length(data_disks)), j => shouldAttachDataDisk(data_disks[j])),
j => getAttachDisk(data_disks[j], '${nodes[i].name}-data-disk-${j}', j)
)
)
)
}
networkProfile: {
networkInterfaces: [for j in range(0, nodes[i].nic_count): {
Expand Down
2 changes: 1 addition & 1 deletion lisa/sut_orchestrator/azure/autogen_arm_template.json
Original file line number Diff line number Diff line change
Expand Up @@ -816,7 +816,7 @@
"imageReference": "[__bicep.getImageReference(parameters('nodes')[range(0, variables('node_count'))[copyIndex()]])]",
"osDisk": "[__bicep.getVMOsDisk(parameters('nodes')[range(0, variables('node_count'))[copyIndex()]])]",
"diskControllerType": "[if(equals(parameters('nodes')[range(0, variables('node_count'))[copyIndex()]].disk_controller_type, 'SCSI'), null(), parameters('nodes')[range(0, variables('node_count'))[copyIndex()]].disk_controller_type)]",
"dataDisks": "[concat(map(filter(range(0, length(parameters('data_disks'))), lambda('j', not(__bicep.shouldAttachDataDisk(parameters('data_disks')[lambdaVariables('j')])))), lambda('j', __bicep.getCreateDisk(parameters('data_disks')[lambdaVariables('j')], format('{0}-data-disk-{1}', parameters('nodes')[range(0, variables('node_count'))[copyIndex()]].name, lambdaVariables('j')), lambdaVariables('j')))), map(filter(range(0, length(parameters('data_disks'))), lambda('j', __bicep.shouldAttachDataDisk(parameters('data_disks')[lambdaVariables('j')]))), lambda('j', __bicep.getAttachDisk(parameters('data_disks')[lambdaVariables('j')], format('{0}-data-disk-{1}', parameters('nodes')[range(0, variables('node_count'))[copyIndex()]].name, lambdaVariables('j')), lambdaVariables('j')))))]"
"dataDisks": "[if(empty(parameters('data_disks')), null(), concat(map(filter(range(0, length(parameters('data_disks'))), lambda('j', not(__bicep.shouldAttachDataDisk(parameters('data_disks')[lambdaVariables('j')])))), lambda('j', __bicep.getCreateDisk(parameters('data_disks')[lambdaVariables('j')], format('{0}-data-disk-{1}', parameters('nodes')[range(0, variables('node_count'))[copyIndex()]].name, lambdaVariables('j')), lambdaVariables('j')))), map(filter(range(0, length(parameters('data_disks'))), lambda('j', __bicep.shouldAttachDataDisk(parameters('data_disks')[lambdaVariables('j')]))), lambda('j', __bicep.getAttachDisk(parameters('data_disks')[lambdaVariables('j')], format('{0}-data-disk-{1}', parameters('nodes')[range(0, variables('node_count'))[copyIndex()]].name, lambdaVariables('j')), lambdaVariables('j'))))))]"
},
"networkProfile": {
"copy": [
Expand Down
47 changes: 46 additions & 1 deletion lisa/sut_orchestrator/azure/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -2971,6 +2971,7 @@ def check_or_create_gallery_image_version(
vhd_resource_group_name: str,
vhd_storage_account_name: str,
gallery_image_target_regions: List[str],
data_vhd_paths: Optional[List[Dict[str, Any]]] = None,
) -> None:
try:
compute_client = get_compute_client(platform)
Expand All @@ -2992,7 +2993,7 @@ def check_or_create_gallery_image_version(
"storage_account_type": storage_account_type,
}
)
image_version_post_body = {
image_version_post_body: Dict[str, Any] = {
"location": gallery_image_location,
"publishing_profile": {"target_regions": target_regions},
"storageProfile": {
Expand All @@ -3010,6 +3011,50 @@ def check_or_create_gallery_image_version(
},
},
}

if data_vhd_paths:
data_disk_images: List[Dict[str, Any]] = []
assigned_luns: set[int] = set()
for index, data_vhd_path_item in enumerate(data_vhd_paths):
lun = index
raw_path = data_vhd_path_item.get("url")
if not isinstance(raw_path, str) or not raw_path:
continue
data_vhd_path = raw_path

raw_lun = data_vhd_path_item.get("lun")
if isinstance(raw_lun, int) and raw_lun >= 0:
lun = raw_lun
elif isinstance(raw_lun, str) and raw_lun.strip().isdigit():
lun = int(raw_lun.strip())

if lun in assigned_luns:
raise LisaException(
f"duplicated data disk lun '{lun}' in data_vhd_paths"
)
assigned_luns.add(lun)

data_vhd_details = get_vhd_details(platform, data_vhd_path)
data_disk_images.append(
{
"lun": lun,
"hostCaching": host_caching_type,
"source": {
"uri": data_vhd_path,
"storageAccountId": (
f"/subscriptions/{platform.subscription_id}/"
f"resourceGroups/"
f"{data_vhd_details['resource_group_name']}"
"/providers/Microsoft.Storage/storageAccounts/"
f"{data_vhd_details['account_name']}"
),
},
}
)
image_version_post_body["storageProfile"][
"dataDiskImages"
] = data_disk_images

operation = compute_client.gallery_image_versions.begin_create_or_update(
gallery_resource_group_name,
gallery_name,
Expand Down
115 changes: 102 additions & 13 deletions lisa/sut_orchestrator/azure/transformers.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
get_date_str,
get_datetime_path,
)
from lisa.secret import PATTERN_URL, add_secret

from .common import (
AZURE_SHARED_RG_NAME,
Expand Down Expand Up @@ -481,9 +482,21 @@ class SigTransformerSchema(schema.Transformer):
azure_sig_url: shared_gallery
"""

# raw vhd URL, it can be the blob under the same subscription of SIG
# or SASURL
vhd: str = field(default="", metadata=field_metadata(required=True))
# Raw VHD URL or VHD schema object. String form keeps backward compatibility:
# vhd: "https://.../os.vhd"
# Object form supports data disk VHDs:
# vhd:
# vhd_path: "https://.../os.vhd"
# data_vhd_paths:
# - data_vhd:
# lun: 0
# url: "https://.../data0.vhd"
# - data_vhd:
# lun: 1
# url: "https://.../data1.vhd"
vhd: Union[str, Dict[Any, Any]] = field(
default="", metadata=field_metadata(required=True)
)
# if not specify gallery_resource_group_name, use shared resource group name
gallery_resource_group_name: str = field(default=AZURE_SHARED_RG_NAME)
# if not specified, will use the first location of gallery image
Expand Down Expand Up @@ -610,18 +623,24 @@ def _internal_run(self) -> Dict[str, Any]:
runbook.gallery_resource_group_location = image_location
if not runbook.gallery_location:
runbook.gallery_location = image_location

source_vhd_path, source_data_vhd_paths = self._resolve_vhd_sources(runbook)
vhd_path = get_deployable_storage_path(
platform, runbook.vhd, image_location, self._log
)
vhd_details = get_vhd_details(platform, vhd_path)
check_blob_exist(
platform=platform,
account_name=vhd_details["account_name"],
container_name=vhd_details["container_name"],
resource_group_name=vhd_details["resource_group_name"],
blob_name=vhd_details["blob_name"],
raise_error=True,
platform, source_vhd_path, image_location, self._log
)
vhd_details = self._check_blob_exists(platform, vhd_path)

data_vhd_paths: List[Dict[str, Any]] = []
for source_data_vhd in source_data_vhd_paths:
source_data_vhd_path = source_data_vhd["url"]
data_vhd_path = get_deployable_storage_path(
platform, source_data_vhd_path, image_location, self._log
)
self._check_blob_exists(platform, data_vhd_path)
data_vhd: Dict[str, Any] = {"url": data_vhd_path}
if "lun" in source_data_vhd:
data_vhd["lun"] = source_data_vhd["lun"]
data_vhd_paths.append(data_vhd)

# Get features from marketplace image if specified
features = self._get_image_features(platform, runbook.marketplace_source)
Expand Down Expand Up @@ -696,6 +715,7 @@ def _internal_run(self) -> Dict[str, Any]:
vhd_details["resource_group_name"],
vhd_details["account_name"],
runbook.gallery_image_location,
data_vhd_paths=data_vhd_paths,
)

sig_url = (
Expand All @@ -707,6 +727,75 @@ def _internal_run(self) -> Dict[str, Any]:
self._log.info(f"SIG Url: {sig_url}")
return {self.__sig_name: sig_url}

def _check_blob_exists(
self, platform: AzurePlatform, vhd_path: str
) -> Dict[str, str]:
vhd_details = cast(Dict[str, str], get_vhd_details(platform, vhd_path))
check_blob_exist(
platform=platform,
account_name=vhd_details["account_name"],
container_name=vhd_details["container_name"],
resource_group_name=vhd_details["resource_group_name"],
blob_name=vhd_details["blob_name"],
raise_error=True,
)
return vhd_details

def _resolve_vhd_sources(
self, runbook: SigTransformerSchema
) -> tuple[str, List[Dict[str, Any]]]:
data_vhd_paths: List[Dict[str, Any]] = []

def _add_data_vhd(url: str, lun: Optional[int] = None) -> None:
normalized_url = url.strip()
if not normalized_url:
return
add_secret(normalized_url, PATTERN_URL)
item: Dict[str, Any] = {"url": normalized_url}
if lun is not None:
item["lun"] = lun
data_vhd_paths.append(item)

def _parse_lun(raw_lun: Any) -> Optional[int]:
if isinstance(raw_lun, int) and raw_lun >= 0:
return raw_lun
if isinstance(raw_lun, str):
raw_lun = raw_lun.strip()
if raw_lun.isdigit():
return int(raw_lun)
return None

if isinstance(runbook.vhd, str):
vhd_path = runbook.vhd
elif isinstance(runbook.vhd, dict):
raw_vhd_path = runbook.vhd.get("vhd_path", "")
vhd_path = raw_vhd_path.strip() if isinstance(raw_vhd_path, str) else ""

raw_data_vhd_paths = runbook.vhd.get("data_vhd_paths")
if isinstance(raw_data_vhd_paths, list):
for item in raw_data_vhd_paths:
if not isinstance(item, dict):
continue

# Supported format:
# - data_vhd:
# lun: 0
# url: "https://.../data0.vhd"
raw_data_vhd = item.get("data_vhd")
if isinstance(raw_data_vhd, dict):
url = raw_data_vhd.get("url")
if isinstance(url, str) and url.strip():
_add_data_vhd(url, _parse_lun(raw_data_vhd.get("lun")))
else:
raise LisaException(
f"unsupported type for transformer vhd: {type(runbook.vhd)}"
)

if not vhd_path:
raise LisaException("vhd or vhd.vhd_path must not be empty.")

return vhd_path, data_vhd_paths

def _get_image_features(
self, platform: AzurePlatform, marketplace: str
) -> Dict[str, Any]:
Expand Down
Loading