From 67fc094ba113d5158bcbebf931703669fe440716 Mon Sep 17 00:00:00 2001 From: "Shekhar Sorot ( MSFT )" Date: Thu, 25 Dec 2025 20:45:56 +0530 Subject: [PATCH 1/3] add support for PV2 file shares --- .../testsuites/xfstests/xfstesting.py | 50 ++++++++++++++++--- lisa/sut_orchestrator/azure/common.py | 32 ++++++++++-- lisa/sut_orchestrator/azure/features.py | 4 ++ pyproject.toml | 2 +- 4 files changed, 74 insertions(+), 14 deletions(-) diff --git a/lisa/microsoft/testsuites/xfstests/xfstesting.py b/lisa/microsoft/testsuites/xfstests/xfstesting.py index 4165f82f6d..922eb28c79 100644 --- a/lisa/microsoft/testsuites/xfstests/xfstesting.py +++ b/lisa/microsoft/testsuites/xfstests/xfstesting.py @@ -2,7 +2,7 @@ # Licensed under the MIT license. import string from pathlib import Path -from typing import Any, Dict, Union, cast +from typing import Any, Dict, Optional, Union, cast from microsoft.testsuites.xfstests.xfstests import Xfstests @@ -112,9 +112,12 @@ def _prepare_data_disk( node.execute(f"mkdir {mount_point}", sudo=True) -# Updates as of march 2025. -# Default premium SKU will be used for file share creation. -# This will ensure SMB multi channel is enabled by default +# Updates as of December 2025. +# Default to Provisioned v2 (PV2) billing model for file share creation. +# PV2 allows independent provisioning of storage, IOPS, and throughput. +# PV2 supports smaller minimum quota (32 GiB vs 100 GiB for PV1). +# PV1 fallback is available via use_pv1_model=True for regions without PV2. +# SMB multi channel is enabled by default with premium SKUs. def _deploy_azure_file_share( node: Node, environment: Environment, @@ -122,16 +125,43 @@ def _deploy_azure_file_share( azure_file_share: Union[AzureFileShare, Nfs], allow_shared_key_access: bool = True, enable_private_endpoint: bool = True, - storage_account_sku: str = "Premium_LRS", + storage_account_sku: str = "PremiumV2_LRS", storage_account_kind: str = "FileStorage", - file_share_quota_in_gb: int = 100, + file_share_quota_in_gb: int = 32, + provisioned_iops: Optional[int] = None, + provisioned_bandwidth_mibps: Optional[int] = None, + use_pv1_model: bool = False, ) -> Dict[str, str]: """ - About: This method will provision azure file shares on a new // existing - storage account. + About: This method will provision azure file shares on a new or existing + storage account using the Provisioned v2 (PV2) billing model by default. + + PV2 Billing Model (default): + - SKU: PremiumV2_LRS (SSD) or StandardV2_LRS (HDD) + - Minimum quota: 32 GiB + - Independent IOPS/throughput provisioning + - More cost-effective for testing workloads + + PV1 Billing Model (legacy, use_pv1_model=True): + - SKU: Premium_LRS (SSD) + - Minimum quota: 100 GiB + - IOPS/throughput computed from storage size + - Use for regions where PV2 is not available + + Args: + provisioned_iops: Optional IOPS for PV2 (None = use Azure defaults) + provisioned_bandwidth_mibps: Optional throughput for PV2 (None = defaults) + use_pv1_model: Set True to use legacy PV1 billing model + Returns: Dict[str, str] - A dictionary containing the file share names and their respective URLs. """ + # Handle PV1 fallback - override SKU and quota for compatibility + if use_pv1_model: + storage_account_sku = "Premium_LRS" + file_share_quota_in_gb = max(file_share_quota_in_gb, 100) # PV1 minimum + provisioned_iops = None # PV1 doesn't support explicit IOPS + provisioned_bandwidth_mibps = None # PV1 doesn't support explicit throughput if isinstance(azure_file_share, AzureFileShare): fs_url_dict: Dict[str, str] = azure_file_share.create_file_share( file_share_names=list(names.values()), @@ -141,6 +171,8 @@ def _deploy_azure_file_share( allow_shared_key_access=allow_shared_key_access, enable_private_endpoint=enable_private_endpoint, quota_in_gb=file_share_quota_in_gb, + provisioned_iops=provisioned_iops, + provisioned_bandwidth_mibps=provisioned_bandwidth_mibps, ) test_folders_share_dict: Dict[str, str] = {} for key, value in names.items(): @@ -615,6 +647,8 @@ def verify_azure_file_share( _scratch_folder: scratch_name, }, azure_file_share=azure_file_share, + provisioned_bandwidth_mibps=550, + provisioned_iops=15550, ) # Get credential file path from the feature (uses storage account name) mount_opts = ( diff --git a/lisa/sut_orchestrator/azure/common.py b/lisa/sut_orchestrator/azure/common.py index 017750a9d8..5a3e5d3e13 100644 --- a/lisa/sut_orchestrator/azure/common.py +++ b/lisa/sut_orchestrator/azure/common.py @@ -2247,9 +2247,22 @@ def get_or_create_file_share( log: Logger, protocols: str = "SMB", quota_in_gb: int = 100, + provisioned_iops: Optional[int] = None, + provisioned_bandwidth_mibps: Optional[int] = None, ) -> str: """ Create an Azure Storage file share if it does not exist. + + For Provisioned v2 (PV2) billing model, you can optionally specify: + - provisioned_iops: The provisioned IOPS for the share (PV2 only) + - provisioned_bandwidth_mibps: The provisioned throughput in MiB/s (PV2 only) + + If these are not specified, Azure will use default recommendations based + on the provisioned storage size. + + PV2 requires storage account SKU: PremiumV2_LRS, PremiumV2_ZRS, + StandardV2_LRS, StandardV2_ZRS, StandardV2_GRS, or StandardV2_GZRS. + PV2 minimum quota is 32 GiB (vs 100 GiB for PV1). """ share_service_client = get_share_service_client( credential, @@ -2261,11 +2274,20 @@ def get_or_create_file_share( all_shares = list(share_service_client.list_shares()) if file_share_name not in (x.name for x in all_shares): log.debug(f"creating file share {file_share_name} with protocols {protocols}") - share_service_client.create_share( - file_share_name, - protocols=protocols, - quota=quota_in_gb, - ) + # Build kwargs for create_share - only include PV2 params if specified + create_kwargs: Dict[str, Any] = { + "protocols": protocols, + "quota": quota_in_gb, + } + # PV2-specific parameters (requires API version 2025-01-05+) + # These are only valid for PV2 storage accounts (PremiumV2_*, StandardV2_*) + if provisioned_iops is not None: + create_kwargs["provisioned_iops"] = provisioned_iops + log.debug(f" provisioned_iops: {provisioned_iops}") + if provisioned_bandwidth_mibps is not None: + create_kwargs["provisioned_bandwidth_mibps"] = provisioned_bandwidth_mibps + log.debug(f" provisioned_bandwidth_mibps: {provisioned_bandwidth_mibps}") + share_service_client.create_share(file_share_name, **create_kwargs) return str("//" + share_service_client.primary_hostname + "/" + file_share_name) diff --git a/lisa/sut_orchestrator/azure/features.py b/lisa/sut_orchestrator/azure/features.py index 9caa4bf6a7..74b008c890 100644 --- a/lisa/sut_orchestrator/azure/features.py +++ b/lisa/sut_orchestrator/azure/features.py @@ -3812,6 +3812,8 @@ def create_file_share( enable_https_traffic_only: bool = True, enable_private_endpoint: bool = False, quota_in_gb: int = 100, + provisioned_iops: Optional[int] = None, + provisioned_bandwidth_mibps: Optional[int] = None, ) -> Dict[str, str]: platform: AzurePlatform = self._platform # type: ignore information = environment.get_information() @@ -3850,6 +3852,8 @@ def create_file_share( resource_group_name=resource_group_name, log=self._log, quota_in_gb=quota_in_gb, + provisioned_iops=provisioned_iops, + provisioned_bandwidth_mibps=provisioned_bandwidth_mibps, ) # Create file private endpoint, always after all shares have been created # There is a known issue in API preventing access to data plane diff --git a/pyproject.toml b/pyproject.toml index 82c7666890..02795f621d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,7 +51,7 @@ azure = [ "azure-mgmt-serialconsole ~= 1.0.0", "azure-mgmt-storage ~= 21.2.1", "azure-storage-blob ~= 12.23.0", - "azure-storage-file-share ~= 12.18.0", + "azure-storage-file-share ~= 12.20.0", "azure-keyvault-secrets ~= 4.7.0", "azure-keyvault-certificates ~= 4.7.0", "msrestazure ~= 0.6.4", From 16ab10f366bee726e7131628a0721aa6c1772d64 Mon Sep 17 00:00:00 2001 From: "Shekhar Sorot ( MSFT )" Date: Mon, 29 Dec 2025 17:21:29 +0530 Subject: [PATCH 2/3] Adjust provisioned bandwidth and IOPS values --- lisa/microsoft/testsuites/xfstests/xfstesting.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lisa/microsoft/testsuites/xfstests/xfstesting.py b/lisa/microsoft/testsuites/xfstests/xfstesting.py index 922eb28c79..c17ec3b5b5 100644 --- a/lisa/microsoft/testsuites/xfstests/xfstesting.py +++ b/lisa/microsoft/testsuites/xfstests/xfstesting.py @@ -647,8 +647,8 @@ def verify_azure_file_share( _scratch_folder: scratch_name, }, azure_file_share=azure_file_share, - provisioned_bandwidth_mibps=550, - provisioned_iops=15550, + provisioned_bandwidth_mibps=110, + provisioned_iops=3110, ) # Get credential file path from the feature (uses storage account name) mount_opts = ( From c835006ff640c6f5e33affeb16337ef17357e1ad Mon Sep 17 00:00:00 2001 From: "Shekhar Sorot ( MSFT )" Date: Thu, 1 Jan 2026 20:00:59 +0530 Subject: [PATCH 3/3] Clean up before_case method in xfstesting.py Removed commented-out code and global variable declarations in before_case method. --- .../testsuites/xfstests/xfstesting.py | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/lisa/microsoft/testsuites/xfstests/xfstesting.py b/lisa/microsoft/testsuites/xfstests/xfstesting.py index c17ec3b5b5..ea94bbd2b1 100644 --- a/lisa/microsoft/testsuites/xfstests/xfstesting.py +++ b/lisa/microsoft/testsuites/xfstests/xfstesting.py @@ -232,30 +232,10 @@ class Xfstesting(TestSuite): + " generic/680" ) - # def before_case(self, log: Logger, **kwargs: Any) -> None: - # node = kwargs["node"] - # if isinstance(node.os, Oracle) and (node.os.information.version <= "9.0.0"): - # self.excluded_tests = self.excluded_tests + " btrfs/299" - def before_case(self, log: Logger, **kwargs: Any) -> None: - global _default_smb_mount, _default_smb_excluded_tests - global _default_smb_testcases, _scratch_folder, _test_folder - node = kwargs["node"] if isinstance(node.os, Oracle) and (node.os.information.version <= "9.0.0"): self.excluded_tests = self.excluded_tests + " btrfs/299" - # check for user provided SMB mount options, excluded and included test cases - # for azure file share - variables: Dict[str, Any] = kwargs["variables"] - # check for overrides. pass variables with property case_visible: True - # in runbook - _default_smb_mount = variables.get("smb_mount_opts", _default_smb_mount) - _default_smb_excluded_tests = variables.get( - "smb_excluded_tests", _default_smb_excluded_tests - ) - _default_smb_testcases = variables.get("smb_testcases", _default_smb_testcases) - _scratch_folder = variables.get("scratch_folder", _scratch_folder) - _test_folder = variables.get("test_folder", _test_folder) @TestCaseMetadata( description="""