Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NVMe and resource disc testcase fixes #3432

Merged
merged 1 commit into from
Nov 25, 2024
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
69 changes: 43 additions & 26 deletions lisa/features/disks.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@

import re
from functools import partial
from typing import Any, Dict, List, Optional, Type

from assertpy.assertpy import assert_that
from typing import Any, Dict, List, Optional, Type, Union

from lisa import schema
from lisa.feature import Feature
Expand All @@ -27,22 +25,25 @@ def can_disable(cls) -> bool:
def enabled(self) -> bool:
return True

def get_partition_with_mount_point(self, mount_point: str) -> PartitionInfo:
def get_partition_with_mount_point(
self, mount_point: str
) -> Union[PartitionInfo, None]:
partition_info = self._node.tools[Mount].get_partition_info()
matched_partitions = [
partition
for partition in partition_info
if partition.mount_point == mount_point
]
assert_that(
matched_partitions,
f"Exactly one partition with mount point {mount_point} should be present",
).is_length(1)

partition = matched_partitions[0]
self._log.debug(f"disk: {partition}, mount_point: {mount_point}")
if matched_partitions:
partition = matched_partitions[0]
self._log.debug(f"disk: {partition}, mount_point: {mount_point}")
return partition
else:
return None

return partition
def check_resource_disk_mounted(self) -> bool:
return False

def get_raw_data_disks(self) -> List[str]:
raise NotImplementedError
Expand Down Expand Up @@ -71,6 +72,12 @@ def _initialize(self, *args: Any, **kwargs: Any) -> None:
def get_resource_disk_mount_point(self) -> str:
raise NotImplementedError

def get_resource_disks(self) -> List[str]:
squirrelsc marked this conversation as resolved.
Show resolved Hide resolved
return []

def get_resource_disk_type(self) -> schema.ResourceDiskType:
return schema.ResourceDiskType.SCSI

def get_luns(self) -> Dict[str, int]:
raise NotImplementedError

Expand Down Expand Up @@ -98,25 +105,35 @@ def get_os_boot_partition(self) -> Optional[PartitionInfo]:
break
return boot_partition

# Get disk controller type from the VM by checking the boot partition
def get_os_disk_controller_type(self) -> schema.DiskControllerType:
boot_partition = self.get_os_boot_partition()
assert boot_partition, "'boot_partition' must not be 'None'"
def get_disk_type(self, disk: str) -> schema.StorageInterfaceType:
if isinstance(self._node.os, BSD):
if boot_partition.disk.startswith("da"):
os_disk_controller_type = schema.DiskControllerType.SCSI
elif boot_partition.disk.startswith("nvd"):
os_disk_controller_type = schema.DiskControllerType.NVME
# Sample disk names in FreeBSD:
# /dev/da1p1 -> SCSI
# /dev/nvd1p1 -> NVME
if "da" in disk:
disk_type = schema.StorageInterfaceType.SCSI
elif ("nvd" in disk) or ("nvme" in disk):
disk_type = schema.StorageInterfaceType.NVME
else:
raise LisaException(f"Unknown OS boot disk type {boot_partition.disk}")
raise LisaException(f"Unknown disk type {disk}")
else:
if boot_partition.disk.startswith("nvme"):
os_disk_controller_type = schema.DiskControllerType.NVME
elif boot_partition.disk.startswith("sd"):
os_disk_controller_type = schema.DiskControllerType.SCSI
# Sample disk names in Linux:
# /dev/sda1 -> SCSI
# /dev/nvme0n1p1 -> NVME
if "nvme" in disk:
disk_type = schema.StorageInterfaceType.NVME
elif "sd" in disk:
disk_type = schema.StorageInterfaceType.SCSI
else:
raise LisaException(f"Unknown OS boot disk type {boot_partition.disk}")
return os_disk_controller_type
raise LisaException(f"Unknown disk type {disk}")
return disk_type

# Get disk controller type from the VM by checking the boot partition
def get_os_disk_controller_type(self) -> schema.DiskControllerType:
boot_partition = self.get_os_boot_partition()
assert boot_partition, "'boot_partition' must not be 'None'"
os_disk_controller_type = self.get_disk_type(boot_partition.disk)
return schema.DiskControllerType(os_disk_controller_type)


DiskEphemeral = partial(
Expand Down
53 changes: 39 additions & 14 deletions lisa/features/nvme.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from lisa.tools import Ls, Lspci, Nvmecli
from lisa.tools.lspci import PciDevice
from lisa.util import field_metadata, get_matched_str
from lisa.util.constants import DEVICE_TYPE_NVME


class Nvme(Feature):
Expand All @@ -42,6 +43,9 @@ class Nvme(Feature):
# /dev/nvme0n1p15 -> /dev/nvme0n1
NVME_NAMESPACE_PATTERN = re.compile(r"/dev/nvme[0-9]+n[0-9]+", re.M)

# /dev/nvme0n1p15 -> /dev/nvme0n1
NVME_DEVICE_PATTERN = re.compile(r"/dev/nvme[0-9]+", re.M)

_pci_device_name = "Non-Volatile memory controller"
_ls_devices: str = ""

Expand All @@ -63,7 +67,7 @@ def get_devices(self) -> List[str]:
matched_result = self._device_pattern.match(row)
if matched_result:
devices_list.append(matched_result.group("device_name"))
return devices_list
return self._remove_nvme_os_disk(devices_list)

def get_namespaces(self) -> List[str]:
namespaces = []
Expand All @@ -75,10 +79,28 @@ def get_namespaces(self) -> List[str]:
matched_result = self._namespace_pattern.match(row)
if matched_result:
namespaces.append(matched_result.group("namespace"))
return namespaces
return self._remove_nvme_os_disk(namespaces)

# With disk controller type NVMe, OS disk along with all remote iSCSI devices
# appears as NVMe.
# Removing OS disk from the list of NVMe devices will remove all the
# remote non-NVME disks.
def _remove_nvme_os_disk(self, disk_list: List[str]) -> List[str]:
if (
self._node.features[Disk].get_os_disk_controller_type()
== schema.DiskControllerType.NVME
):
os_disk_nvme_device = self._get_os_disk_nvme_device()
# Removing OS disk/device from the list.
for disk in disk_list:
if os_disk_nvme_device in disk:
disk_list.remove(disk)
break
return disk_list

def get_namespaces_from_cli(self) -> List[str]:
return self._node.tools[Nvmecli].get_namespaces()
namespaces_list = self._node.tools[Nvmecli].get_namespaces()
return self._remove_nvme_os_disk(namespaces_list)

def get_os_disk_nvme_namespace(self) -> str:
node_disk = self._node.features[Disk]
Expand All @@ -93,10 +115,23 @@ def get_os_disk_nvme_namespace(self) -> str:
)
return os_partition_namespace

# This method returns NVMe device name of the OS disk.
def _get_os_disk_nvme_device(self) -> str:
os_disk_nvme_device = ""
os_disk_nvme_namespace = self.get_os_disk_nvme_namespace()
# Sample os_boot_partition when disc controller type is NVMe:
# name: /dev/nvme0n1p15, disk: nvme, mount_point: /boot/efi, type: vfat
if os_disk_nvme_namespace:
os_disk_nvme_device = get_matched_str(
os_disk_nvme_namespace,
self.NVME_DEVICE_PATTERN,
)
return os_disk_nvme_device

def get_devices_from_lspci(self) -> List[PciDevice]:
devices_from_lspci = []
lspci_tool = self._node.tools[Lspci]
device_list = lspci_tool.get_devices()
device_list = lspci_tool.get_devices_by_type(DEVICE_TYPE_NVME)
devices_from_lspci = [
x for x in device_list if self._pci_device_name == x.device_class
]
Expand All @@ -108,16 +143,6 @@ def get_raw_data_disks(self) -> List[str]:
def get_raw_nvme_disks(self) -> List[str]:
# This routine returns Local NVMe devices as a list.
nvme_namespaces = self.get_namespaces()

# With disk controller type NVMe, OS disk appears as NVMe.
# It should be removed from the list of disks for NVMe tests as it is
# not an actual NVMe device.
# disk_controller_type == NVME
node_disk = self._node.features[Disk]
if node_disk.get_os_disk_controller_type() == schema.DiskControllerType.NVME:
os_disk_nvme_namespace = self.get_os_disk_nvme_namespace()
# Removing OS disk from the list.
nvme_namespaces.remove(os_disk_nvme_namespace)
return nvme_namespaces

def _get_device_from_ls(self, force_run: bool = False) -> None:
Expand Down
14 changes: 12 additions & 2 deletions lisa/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -431,9 +431,19 @@ class DiskType(str, Enum):
]


class StorageInterfaceType(str, Enum):
SCSI = constants.STORAGE_INTERFACE_TYPE_SCSI
NVME = constants.STORAGE_INTERFACE_TYPE_NVME


class DiskControllerType(str, Enum):
SCSI = "SCSI"
NVME = "NVMe"
SCSI = constants.STORAGE_INTERFACE_TYPE_SCSI
NVME = constants.STORAGE_INTERFACE_TYPE_NVME


class ResourceDiskType(str, Enum):
SCSI = constants.STORAGE_INTERFACE_TYPE_SCSI
NVME = constants.STORAGE_INTERFACE_TYPE_NVME


disk_controller_type_priority: List[DiskControllerType] = [
Expand Down
36 changes: 36 additions & 0 deletions lisa/sut_orchestrator/azure/features.py
Original file line number Diff line number Diff line change
Expand Up @@ -1925,6 +1925,42 @@ def remove_data_disk(self, names: Optional[List[str]] = None) -> None:
self._node.capability.disk.data_disk_count -= len(names)
self._node.close()

# verify that resource disk is mounted
# function returns successfully if disk matching mount point is present.
# raises exception if the resource disk is not mounted
# in Azure only SCSI disks are mounted but not NVMe disks
def check_resource_disk_mounted(self) -> bool:
resource_disk_mount_point = self.get_resource_disk_mount_point()
resourcedisk = self.get_partition_with_mount_point(resource_disk_mount_point)
if not resourcedisk:
raise LisaException(
f"Resource disk is not mounted at {resource_disk_mount_point}"
)
return True

# get resource disk type
# function returns the type of resource disk/disks available on the VM
# raises exception if no resource disk is available
def get_resource_disk_type(self) -> schema.ResourceDiskType:
resource_disks = self.get_resource_disks()
if not resource_disks:
raise LisaException("No Resource disks are available on VM")
return schema.ResourceDiskType(
self._node.features[Disk].get_disk_type(disk=resource_disks[0])
)

def get_resource_disks(self) -> List[str]:
resource_disk_list = []
resource_disk_mount_point = self.get_resource_disk_mount_point()
resourcedisk = self._node.features[Disk].get_partition_with_mount_point(
resource_disk_mount_point
)
if resourcedisk:
resource_disk_list = [resourcedisk.name]
else:
resource_disk_list = self._node.features[Nvme].get_raw_nvme_disks()
return resource_disk_list

def get_resource_disk_mount_point(self) -> str:
# get customize mount point from cloud-init configuration file from /etc/cloud/
# if not found, use default mount point /mnt for cloud-init
Expand Down
4 changes: 4 additions & 0 deletions lisa/util/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,7 @@
SIGINT = 2
SIGTERM = 15
SIGKILL = 9

# StorageInterfaceTypes
STORAGE_INTERFACE_TYPE_NVME = "NVMe"
STORAGE_INTERFACE_TYPE_SCSI = "SCSI"
28 changes: 20 additions & 8 deletions microsoft/testsuites/core/azure_image_standard.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
TestCaseMetadata,
TestSuite,
TestSuiteMetadata,
schema,
simple_requirement,
)
from lisa.features import Disk
Expand Down Expand Up @@ -1115,11 +1116,16 @@ def verify_no_pre_exist_users(self, node: Node) -> None:
),
)
def verify_resource_disk_readme_file(self, node: RemoteNode) -> None:
resource_disk_mount_point = node.features[Disk].get_resource_disk_mount_point()
if schema.ResourceDiskType.NVME == node.features[Disk].get_resource_disk_type():
raise SkippedException(
"Resource disk type is NVMe. NVMe disks are not formatted or mounted by"
" default and readme file wont be available"
)

# verify that resource disk is mounted. raise exception if not
node.features[Disk].check_resource_disk_mounted()

# verify that resource disk is mounted
# function returns successfully if disk matching mount point is present
node.features[Disk].get_partition_with_mount_point(resource_disk_mount_point)
resource_disk_mount_point = node.features[Disk].get_resource_disk_mount_point()

# Verify lost+found folder exists
# Skip this step for BSD as it does not have lost+found folder
Expand Down Expand Up @@ -1159,13 +1165,19 @@ def verify_resource_disk_readme_file(self, node: RemoteNode) -> None:
),
)
def verify_resource_disk_file_system(self, node: RemoteNode) -> None:
resource_disk_mount_point = node.features[Disk].get_resource_disk_mount_point()
node.features[Disk].get_partition_with_mount_point(resource_disk_mount_point)
node_disc = node.features[Disk]
if schema.ResourceDiskType.NVME == node.features[Disk].get_resource_disk_type():
raise SkippedException(
"Resource disk type is NVMe. NVMe disks are not formatted or mounted by default" # noqa: E501
)
# verify that resource disk is mounted. raise exception if not
node_disc.check_resource_disk_mounted()
resource_disk_mount_point = node_disc.get_resource_disk_mount_point()
disk_info = node.tools[Lsblk].find_disk_by_mountpoint(resource_disk_mount_point)
for partition in disk_info.partitions:
# by default, resource disk comes with ntfs type
# waagent or cloud-init will format it unless there are some commands hung
# or interrupt
# waagent or cloud-init will format it unless there are some commands
# hung or interrupt
assert_that(
partition.fstype,
"Resource disk file system type should not equal to ntfs",
Expand Down
Loading
Loading