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
198 changes: 196 additions & 2 deletions lisa/microsoft/testsuites/core/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,26 @@
SecurityProfileType,
)
from lisa.node import Node
from lisa.operating_system import BSD, Posix, Windows
from lisa.operating_system import BSD, AlmaLinux, CBLMariner, Posix, Redhat, Windows
from lisa.schema import DiskControllerType, DiskOptionSettings, DiskType
from lisa.sut_orchestrator import AZURE, HYPERV
from lisa.sut_orchestrator.azure.features import AzureDiskOptionSettings, AzureFileShare
from lisa.sut_orchestrator.azure.tools import Waagent
from lisa.tools import Blkid, Cat, Dmesg, Echo, Lsblk, Mount, NFSClient, Swap, Sysctl
from lisa.tools import (
Blkid,
Cat,
Dmesg,
Echo,
Ls,
Lsblk,
Mount,
NFSClient,
Rm,
SmbClient,
SmbServer,
Swap,
Sysctl,
)
from lisa.tools.blkid import PartitionInfo
from lisa.tools.journalctl import Journalctl
from lisa.tools.kernel_config import KernelConfig
Expand Down Expand Up @@ -590,6 +604,186 @@ def after_case(self, log: Logger, **kwargs: Any) -> None:
except Exception:
raise BadEnvironmentStateException

@TestCaseMetadata(
description="""
A comprehensive test to verify CIFS module and SMB share functionality between
two Linux VMs.
This test case will
1. Create 2 VMs in Azure
2. Check if CONFIG_CIFS is enabled in KCONFIG
3. Configure one VM as SMB server and create a share
4. Mount the other VM to the SMB share
5. Verify mount is successful
6. Write a test file to the SMB share and read it back to verify IO
7. Clean up the SMB share and unmount
8. repeat steps 4-7 for SMB versions ["2.0", "2.1", "3.0", "3.1.1"]
""",
timeout=TIME_OUT,
requirement=simple_requirement(
min_count=2,
unsupported_os=[Redhat, CBLMariner, AlmaLinux, BSD, Windows],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is Redhat, CBLMariner and Alma unsupported?
In the SMBTool, the below line exists

if isinstance(self.node.os, (CBLMariner, Redhat, Fedora, Oracle, Suse)):

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SMB "server" packages are not officially supported by Mariner. Redhat family have issues SELinux etc.
Only Ubuntu, Debian and SUSE are supported for this test case now.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only Ubuntu, Debian and SUSE are supported for this test case now.

Do you want to rather use supported_os requirement in that case?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redhat has issues because of SELinux, we can't make it work in test code?
Do we have a link for SMB "server" packages are not officially supported by Mariner

),
Comment on lines +624 to +625
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test excludes Redhat, CBLMariner, and AlmaLinux from the supported_os list, but there's no clear documentation explaining why these distributions are unsupported. Consider adding a comment explaining the technical reason (e.g., package availability issues, known bugs, or version compatibility problems) to help future maintainers understand this decision.

Copilot uses AI. Check for mistakes.
priority=1,
)
def verify_smb_linux(
self, log: Logger, node: Node, environment: Environment
) -> None:
# Assign server and client roles to the 2 VMs
server_node = cast(RemoteNode, environment.nodes[0])
client_node = cast(RemoteNode, environment.nodes[1])

# Check if CONFIG_CIFS is enabled in KCONFIG on both nodes
for role_name, role_node in (("server", server_node), ("client", client_node)):
if not role_node.tools[KernelConfig].is_enabled("CONFIG_CIFS"):
raise LisaException(
f"CIFS module must be present for SMB testing on {role_name} node"
Copy link
Collaborator

@adityagesh adityagesh Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we are not printing the node being assigned as server and client, printing the role_name does not add value in the logging.
Do you rather want to print the node name? or I think it's fine to just have it like "CIFS module must be present on the node", as both nodes are identical

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since both the nodes are identical its okay this way.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that case "{role_name}" does not add any value in the logging statement, you can replace it with "CIFS module must be present" ?

)
# Install and setup SMB tools on both nodes
smb_server = server_node.tools[SmbServer]
smb_client = client_node.tools[SmbClient]

# SMB versions to test
smb_versions = ["3.0", "3.1.1", "2.1", "2.0"]

# Test configuration
share_name = "testshare"
share_path = f"/tmp/{share_name}"
mount_point = f"/mnt/{share_name}"

try:
# Step 3: Configure SMB server and create a share
smb_server.create_share(share_name, share_path)

# Step 8: Repeat for different SMB versions
for smb_version in smb_versions:
Copy link
Collaborator

@adityagesh adityagesh Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the current logic, if one version fails, the case fails and exits.
Is that the expected? or do you want to verify each version independently?

log.info(f"Testing SMB version {smb_version}")

# Step 4: Mount the SMB share on client
smb_client.mount_share(
server_node.internal_address, share_name, mount_point, smb_version
)

# Step 5 & 6: Verify mount is successful
self._verify_smb_mount(
client_node,
mount_point,
server_node,
share_path,
log,
)

# Step 7: Cleanup between version tests
smb_client.unmount_share(mount_point)
finally:
# Cleanup
self._cleanup_smb_test(
server_node, client_node, share_path, mount_point, log
)

def _verify_smb_mount(
self,
client_node: RemoteNode,
mount_point: str,
server_node: RemoteNode,
share_path: str,
log: Logger,
) -> None:
"""
Verify SMB mount is working by creating and reading a file from
both client and server.
"""
test_file = "smb_test.txt"
test_content = "SMB test content"
mount = client_node.tools[Mount]

# Verify mount point exists and is mounted
mount_point_exists = mount.check_mount_point_exist(mount_point)
if not mount_point_exists:
raise LisaException(
f"Mount point {mount_point} does not exist or is not mounted"
)

# Create test file on mounted share from client
test_file_path = f"{mount_point}/{test_file}"
echo = client_node.tools[Echo]
echo.write_to_file(
test_content,
client_node.get_pure_path(test_file_path),
sudo=True,
ignore_error=False,
)

# Read and verify file content from client side
file_content_client = client_node.tools[Cat].read(
test_file_path, sudo=True, force_run=True
)

assert_that(file_content_client).described_as(
"SMB file content should match written content on client"
).is_equal_to(test_content)
log.info(f"Successfully verified file content on client: '{test_content}'")

# Read and verify file content from server side
server_file_path = f"{share_path}/{test_file}"

# Check if file exists on server
if not server_node.tools[Ls].path_exists(server_file_path, sudo=True):
raise LisaException(f"Test file {server_file_path} not found on server VM")

# Read file content directly from server VM
file_content_server = server_node.tools[Cat].read(
server_file_path, sudo=True, force_run=True
)

assert_that(file_content_server).described_as(
"SMB file content should match on server VM"
).is_equal_to(test_content)

log.info(
f"Successfully verified file content on both client and server: "
f"'{test_content}'"
)

# Clean up test file from client (will also remove from server via SMB)
client_node.tools[Rm].remove_file(test_file_path, sudo=True)

def _cleanup_smb_test(
self,
server_node: RemoteNode,
client_node: RemoteNode,
share_path: str,
mount_point: str,
log: Logger,
) -> None:
"""Clean up SMB test resources."""
bad_cleanup = False
# Cleanup on client
try:
smb_client = client_node.tools[SmbClient]
if smb_client.is_mounted(mount_point):
smb_client.unmount_share(mount_point)
smb_client.cleanup_mount_point(mount_point)
except Exception as e:
log.error(
f"Failed to cleanup SMB client mount point {mount_point}: "
f"{e}. Continuing cleanup..."
)
bad_cleanup = True

# Cleanup on server
try:
smb_server = server_node.tools[SmbServer]
smb_server.stop()
smb_server.remove_share(share_path)
except Exception as e:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If cleanup fails, mark node as dirty

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed as suggested.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added comment below

log.error(
f"Failed to remove share {share_path} from SMB server: "
f"{e}. Finishing cleanup..."
)
bad_cleanup = True
if bad_cleanup:
raise BadEnvironmentStateException("SMB test cleanup encountered errors.")
Copy link
Collaborator

@adityagesh adityagesh Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

raising BadEnvironmentStateException in the case will cause test case failure.

You are running the cleanup in the case itself, hence even if your test logic succeeds, but the cleanup fails, the case will fail.

You can move the cleanup to after case, or ensure that cleanup does not raise an Exception within the case.
If you are moving the cleanup to after case, use self.node.mark_dirty() because BadEnvironmentStateException does not mark the env as dirty in after case


@TestCaseMetadata(
description="""
This test case will
Expand Down
3 changes: 3 additions & 0 deletions lisa/tools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@
from .resize_partition import ResizePartition
from .rm import Rm
from .sar import Sar
from .smb import SmbClient, SmbServer
from .sockperf import Sockperf
from .ssh import Ssh
from .sshpass import Sshpass
Expand Down Expand Up @@ -281,6 +282,8 @@
"Sed",
"Service",
"ServiceInternal",
"SmbClient",
"SmbServer",
"Sockperf",
"Ssh",
"Sshpass",
Expand Down
1 change: 1 addition & 0 deletions lisa/tools/mkfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"mkfs",
[
"xfs",
"cifs",
"ext2",
"ext3",
"ext4",
Expand Down
Loading
Loading