tests: Embed test framework within OCL repository
Signed-off-by: Robert Baldyga <robert.baldyga@intel.com>
This commit is contained in:
117
test/functional/test-framework/storage_devices/device.py
Normal file
117
test/functional/test-framework/storage_devices/device.py
Normal file
@@ -0,0 +1,117 @@
|
||||
#
|
||||
# Copyright(c) 2019-2022 Intel Corporation
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
#
|
||||
import posixpath
|
||||
|
||||
from core.test_run import TestRun
|
||||
from test_tools import disk_utils, fs_utils
|
||||
from test_tools.disk_utils import get_device_filesystem_type, get_sysfs_path
|
||||
from test_utils.io_stats import IoStats
|
||||
from test_utils.size import Size, Unit
|
||||
|
||||
|
||||
class Device:
|
||||
def __init__(self, path):
|
||||
disk_utils.validate_dev_path(path)
|
||||
self.path = path
|
||||
self.size = Size(disk_utils.get_size(self.get_device_id()), Unit.Byte)
|
||||
self.filesystem = get_device_filesystem_type(self.get_device_id())
|
||||
self.mount_point = None
|
||||
|
||||
def create_filesystem(self, fs_type: disk_utils.Filesystem, force=True, blocksize=None):
|
||||
disk_utils.create_filesystem(self, fs_type, force, blocksize)
|
||||
self.filesystem = fs_type
|
||||
|
||||
def wipe_filesystem(self, force=True):
|
||||
disk_utils.wipe_filesystem(self, force)
|
||||
self.filesystem = None
|
||||
|
||||
def is_mounted(self):
|
||||
output = TestRun.executor.run(f"findmnt {self.path}")
|
||||
if output.exit_code != 0:
|
||||
return False
|
||||
else:
|
||||
mount_point_line = output.stdout.split('\n')[1]
|
||||
device_path = fs_utils.readlink(self.path)
|
||||
self.mount_point = mount_point_line[0:mount_point_line.find(device_path)].strip()
|
||||
return True
|
||||
|
||||
def mount(self, mount_point, options: [str] = None):
|
||||
if not self.is_mounted():
|
||||
if disk_utils.mount(self, mount_point, options):
|
||||
self.mount_point = mount_point
|
||||
else:
|
||||
raise Exception(f"Device is already mounted! Actual mount point: {self.mount_point}")
|
||||
|
||||
def unmount(self):
|
||||
if not self.is_mounted():
|
||||
TestRun.LOGGER.info("Device is not mounted.")
|
||||
elif disk_utils.unmount(self):
|
||||
self.mount_point = None
|
||||
|
||||
def get_device_link(self, directory: str):
|
||||
items = self.get_all_device_links(directory)
|
||||
return next(i for i in items if i.full_path.startswith(directory))
|
||||
|
||||
def get_device_id(self):
|
||||
return fs_utils.readlink(self.path).split('/')[-1]
|
||||
|
||||
def get_all_device_links(self, directory: str):
|
||||
from test_tools import fs_utils
|
||||
output = fs_utils.ls(f"$(find -L {directory} -samefile {self.path})")
|
||||
return fs_utils.parse_ls_output(output, self.path)
|
||||
|
||||
def get_io_stats(self):
|
||||
return IoStats.get_io_stats(self.get_device_id())
|
||||
|
||||
def get_sysfs_property(self, property_name):
|
||||
path = posixpath.join(disk_utils.get_sysfs_path(self.get_device_id()),
|
||||
"queue", property_name)
|
||||
return TestRun.executor.run_expect_success(f"cat {path}").stdout
|
||||
|
||||
def set_sysfs_property(self, property_name, value):
|
||||
TestRun.LOGGER.info(
|
||||
f"Setting {property_name} for device {self.get_device_id()} to {value}.")
|
||||
path = posixpath.join(disk_utils.get_sysfs_path(self.get_device_id()), "queue",
|
||||
property_name)
|
||||
fs_utils.write_file(path, str(value))
|
||||
|
||||
def set_max_io_size(self, new_max_io_size: Size):
|
||||
self.set_sysfs_property("max_sectors_kb",
|
||||
int(new_max_io_size.get_value(Unit.KibiByte)))
|
||||
|
||||
def get_max_io_size(self):
|
||||
return Size(int(self.get_sysfs_property("max_sectors_kb")), Unit.KibiByte)
|
||||
|
||||
def get_max_hw_io_size(self):
|
||||
return Size(int(self.get_sysfs_property("max_hw_sectors_kb")), Unit.KibiByte)
|
||||
|
||||
def get_discard_granularity(self):
|
||||
return self.get_sysfs_property("discard_granularity")
|
||||
|
||||
def get_discard_max_bytes(self):
|
||||
return self.get_sysfs_property("discard_max_bytes")
|
||||
|
||||
def get_discard_zeroes_data(self):
|
||||
return self.get_sysfs_property("discard_zeroes_data")
|
||||
|
||||
def get_numa_node(self):
|
||||
return int(TestRun.executor.run_expect_success(
|
||||
f"cat {get_sysfs_path(self.get_device_id())}/device/numa_node").stdout)
|
||||
|
||||
def __str__(self):
|
||||
return (
|
||||
f'system path: {self.path}, short link: /dev/{self.get_device_id()},'
|
||||
f' filesystem: {self.filesystem}, mount point: {self.mount_point}, size: {self.size}'
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return str(self)
|
||||
|
||||
@staticmethod
|
||||
def get_scsi_debug_devices():
|
||||
scsi_debug_devices = TestRun.executor.run_expect_success(
|
||||
"lsscsi --scsi_id | grep scsi_debug").stdout
|
||||
return [Device(f'/dev/disk/by-id/scsi-{device.split()[-1]}')
|
||||
for device in scsi_debug_devices.splitlines()]
|
237
test/functional/test-framework/storage_devices/disk.py
Normal file
237
test/functional/test-framework/storage_devices/disk.py
Normal file
@@ -0,0 +1,237 @@
|
||||
#
|
||||
# Copyright(c) 2019-2022 Intel Corporation
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
#
|
||||
import itertools
|
||||
import json
|
||||
import re
|
||||
from datetime import timedelta
|
||||
from enum import IntEnum
|
||||
|
||||
from core.test_run import TestRun
|
||||
from storage_devices.device import Device
|
||||
from test_tools import disk_utils, fs_utils, nvme_cli
|
||||
from test_utils import disk_finder
|
||||
from test_utils.os_utils import wait
|
||||
from test_utils.size import Unit
|
||||
from test_tools.disk_utils import get_pci_address
|
||||
|
||||
|
||||
class DiskType(IntEnum):
|
||||
hdd = 0
|
||||
hdd4k = 1
|
||||
sata = 2
|
||||
nand = 3
|
||||
optane = 4
|
||||
|
||||
|
||||
class DiskTypeSetBase:
|
||||
def resolved(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def types(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def json(self):
|
||||
return json.dumps({
|
||||
"type": "set",
|
||||
"values": [t.name for t in self.types()]
|
||||
})
|
||||
|
||||
def __lt__(self, other):
|
||||
return min(self.types()) < min(other.types())
|
||||
|
||||
def __le__(self, other):
|
||||
return min(self.types()) <= min(other.types())
|
||||
|
||||
def __eq__(self, other):
|
||||
return min(self.types()) == min(other.types())
|
||||
|
||||
def __ne__(self, other):
|
||||
return min(self.types()) != min(other.types())
|
||||
|
||||
def __gt__(self, other):
|
||||
return min(self.types()) > min(other.types())
|
||||
|
||||
def __ge__(self, other):
|
||||
return min(self.types()) >= min(other.types())
|
||||
|
||||
|
||||
class DiskTypeSet(DiskTypeSetBase):
|
||||
def __init__(self, *args):
|
||||
self.__types = set(*args)
|
||||
|
||||
def resolved(self):
|
||||
return True
|
||||
|
||||
def types(self):
|
||||
return self.__types
|
||||
|
||||
|
||||
class DiskTypeLowerThan(DiskTypeSetBase):
|
||||
def __init__(self, disk_name):
|
||||
self.__disk_name = disk_name
|
||||
|
||||
def resolved(self):
|
||||
return self.__disk_name in TestRun.disks
|
||||
|
||||
def types(self):
|
||||
if not self.resolved():
|
||||
raise LookupError("Disk type not resolved!")
|
||||
disk_type = TestRun.disks[self.__disk_name].disk_type
|
||||
return set(filter(lambda d: d < disk_type, [*DiskType]))
|
||||
|
||||
def json(self):
|
||||
return json.dumps({
|
||||
"type": "operator",
|
||||
"name": "lt",
|
||||
"args": [self.__disk_name]
|
||||
})
|
||||
|
||||
|
||||
class Disk(Device):
|
||||
def __init__(
|
||||
self,
|
||||
path,
|
||||
disk_type: DiskType,
|
||||
serial_number,
|
||||
block_size,
|
||||
):
|
||||
Device.__init__(self, path)
|
||||
self.serial_number = serial_number
|
||||
self.block_size = Unit(block_size)
|
||||
self.disk_type = disk_type
|
||||
self.partitions = []
|
||||
|
||||
def create_partitions(
|
||||
self,
|
||||
sizes: [],
|
||||
partition_table_type=disk_utils.PartitionTable.gpt):
|
||||
disk_utils.create_partitions(self, sizes, partition_table_type)
|
||||
|
||||
def remove_partition(self, part):
|
||||
part_number = int(part.path.split("part")[1])
|
||||
disk_utils.remove_parition(self, part_number)
|
||||
self.partitions.remove(part)
|
||||
|
||||
def umount_all_partitions(self):
|
||||
TestRun.LOGGER.info(
|
||||
f"Umounting all partitions from: {self.path}")
|
||||
cmd = f'umount -l {fs_utils.readlink(self.path)}*?'
|
||||
TestRun.executor.run(cmd)
|
||||
|
||||
def remove_partitions(self):
|
||||
for part in self.partitions:
|
||||
if part.is_mounted():
|
||||
part.unmount()
|
||||
if disk_utils.remove_partitions(self):
|
||||
self.partitions.clear()
|
||||
|
||||
def is_detected(self):
|
||||
if self.serial_number:
|
||||
serial_numbers = disk_finder.get_all_serial_numbers()
|
||||
return self.serial_number in serial_numbers
|
||||
elif self.path:
|
||||
output = fs_utils.ls_item(f"{self.path}")
|
||||
return fs_utils.parse_ls_output(output)[0] is not None
|
||||
raise Exception("Couldn't check if device is detected by the system")
|
||||
|
||||
def wait_for_plug_status(self, should_be_visible):
|
||||
if not wait(lambda: should_be_visible == self.is_detected(),
|
||||
timedelta(minutes=1),
|
||||
timedelta(seconds=1)):
|
||||
raise Exception(f"Timeout occurred while trying to "
|
||||
f"{'plug' if should_be_visible else 'unplug'} disk.")
|
||||
|
||||
def plug(self):
|
||||
if self.is_detected():
|
||||
return
|
||||
TestRun.executor.run_expect_success(self.plug_command)
|
||||
self.wait_for_plug_status(True)
|
||||
|
||||
def unplug(self):
|
||||
if not self.is_detected():
|
||||
return
|
||||
TestRun.executor.run_expect_success(self.unplug_command)
|
||||
self.wait_for_plug_status(False)
|
||||
|
||||
@staticmethod
|
||||
def plug_all_disks():
|
||||
TestRun.executor.run_expect_success(NvmeDisk.plug_all_command)
|
||||
TestRun.executor.run_expect_success(SataDisk.plug_all_command)
|
||||
|
||||
def __str__(self):
|
||||
disk_str = f'system path: {self.path}, type: {self.disk_type.name}, ' \
|
||||
f'serial: {self.serial_number}, size: {self.size}, ' \
|
||||
f'block size: {self.block_size}, partitions:\n'
|
||||
for part in self.partitions:
|
||||
disk_str += f'\t{part}'
|
||||
return disk_str
|
||||
|
||||
@staticmethod
|
||||
def create_disk(path,
|
||||
disk_type: DiskType,
|
||||
serial_number,
|
||||
block_size):
|
||||
if disk_type is DiskType.nand or disk_type is DiskType.optane:
|
||||
return NvmeDisk(path, disk_type, serial_number, block_size)
|
||||
else:
|
||||
return SataDisk(path, disk_type, serial_number, block_size)
|
||||
|
||||
|
||||
class NvmeDisk(Disk):
|
||||
plug_all_command = "echo 1 > /sys/bus/pci/rescan"
|
||||
|
||||
def __init__(self, path, disk_type, serial_number, block_size):
|
||||
Disk.__init__(self, path, disk_type, serial_number, block_size)
|
||||
self.plug_command = NvmeDisk.plug_all_command
|
||||
self.unplug_command = f"echo 1 > /sys/block/{self.get_device_id()}/device/remove || " \
|
||||
f"echo 1 > /sys/block/{self.get_device_id()}/device/device/remove"
|
||||
self.pci_address = get_pci_address(self.get_device_id())
|
||||
|
||||
def __str__(self):
|
||||
disk_str = super().__str__()
|
||||
disk_str = f"pci address: {self.pci_address}, " + disk_str
|
||||
return disk_str
|
||||
|
||||
def format_disk(self, metadata_size=None, block_size=None,
|
||||
force=True, format_params=None, reset=True):
|
||||
nvme_cli.format_disk(self, metadata_size, block_size, force, format_params, reset)
|
||||
|
||||
def get_lba_formats(self):
|
||||
return nvme_cli.get_lba_formats(self)
|
||||
|
||||
def get_lba_format_in_use(self):
|
||||
return nvme_cli.get_lba_format_in_use(self)
|
||||
|
||||
|
||||
class SataDisk(Disk):
|
||||
plug_all_command = "for i in $(find -H /sys/devices/ -path '*/scsi_host/*/scan' -type f); " \
|
||||
"do echo '- - -' > $i; done;"
|
||||
|
||||
def __init__(self, path, disk_type, serial_number, block_size):
|
||||
Disk.__init__(self, path, disk_type, serial_number, block_size)
|
||||
self.plug_command = SataDisk.plug_all_command
|
||||
self.unplug_command = \
|
||||
f"echo 1 > {self.get_sysfs_properties(self.get_device_id()).full_path}/device/delete"
|
||||
|
||||
def get_sysfs_properties(self, device_id):
|
||||
ls_command = f"$(find -H /sys/devices/ -name {device_id} -type d)"
|
||||
output = fs_utils.ls_item(f"{ls_command}")
|
||||
sysfs_addr = fs_utils.parse_ls_output(output)[0]
|
||||
if not sysfs_addr:
|
||||
raise Exception(f"Failed to find sysfs address: ls -l {ls_command}")
|
||||
dirs = sysfs_addr.full_path.split('/')
|
||||
scsi_address = dirs[-3]
|
||||
matches = re.search(
|
||||
r"^(?P<controller>\d+)[-:](?P<port>\d+)[-:](?P<target>\d+)[-:](?P<lun>\d+)$",
|
||||
scsi_address)
|
||||
controller_id = matches["controller"]
|
||||
port_id = matches["port"]
|
||||
target_id = matches["target"]
|
||||
lun = matches["lun"]
|
||||
|
||||
host_path = "/".join(itertools.takewhile(lambda x: not x.startswith("host"), dirs))
|
||||
self.plug_command = f"echo '{port_id} {target_id} {lun}' > " \
|
||||
f"{host_path}/host{controller_id}/scsi_host/host{controller_id}/scan"
|
||||
return sysfs_addr
|
66
test/functional/test-framework/storage_devices/drbd.py
Normal file
66
test/functional/test-framework/storage_devices/drbd.py
Normal file
@@ -0,0 +1,66 @@
|
||||
#
|
||||
# Copyright(c) 2022 Intel Corporation
|
||||
# SPDX-License-Identifier: BSD-3-Clause-Clear
|
||||
#
|
||||
|
||||
import os
|
||||
import posixpath
|
||||
|
||||
from core.test_run import TestRun
|
||||
from storage_devices.device import Device
|
||||
from test_tools.drbdadm import Drbdadm
|
||||
from test_utils.filesystem.symlink import Symlink
|
||||
from test_utils.output import CmdException
|
||||
|
||||
|
||||
class Drbd(Device):
|
||||
def __init__(self, config):
|
||||
if Drbdadm.dump_config(config.name).exit_code != 0:
|
||||
raise ValueError(f"Resource {config.name} not found")
|
||||
self.config = config
|
||||
|
||||
def create_metadata(self, force):
|
||||
return Drbdadm.create_metadata(self.config.name, force)
|
||||
|
||||
def up(self):
|
||||
output = Drbdadm.up(self.config.name)
|
||||
if output.exit_code != 0:
|
||||
raise CmdException(f"Failed to create {self.config.name} drbd instance")
|
||||
|
||||
self.path = posixpath.join("/dev/disk/by-id/", posixpath.basename(self.config.device))
|
||||
self.symlink = Symlink.get_symlink(self.path, self.config.device, True)
|
||||
self.device = Device(self.path)
|
||||
|
||||
return self.device
|
||||
|
||||
def wait_for_sync(self):
|
||||
return Drbdadm.wait_for_sync(self.config.name)
|
||||
|
||||
def is_in_sync(self):
|
||||
return Drbdadm.in_sync(self.config.name)
|
||||
|
||||
def get_status(self):
|
||||
return Drbdadm.get_status(self.config.name)
|
||||
|
||||
def set_primary(self, force=False):
|
||||
return Drbdadm.set_node_primary(self.config.name, force)
|
||||
|
||||
def down(self):
|
||||
output = Drbdadm.down(self.config.name)
|
||||
if output.exit_code != 0:
|
||||
raise CmdException(f"Failed to stop {self.config.name} drbd instance")
|
||||
|
||||
self.device = None
|
||||
self.symlink.remove(True, True)
|
||||
|
||||
@staticmethod
|
||||
def down_all():
|
||||
try:
|
||||
Drbdadm.down_all()
|
||||
except CmdException as e:
|
||||
if "no resources defined" not in str(e):
|
||||
raise e
|
||||
|
||||
@staticmethod
|
||||
def is_installed():
|
||||
return TestRun.executor.run("which drbdadm && modinfo drbd").exit_code == 0
|
531
test/functional/test-framework/storage_devices/lvm.py
Normal file
531
test/functional/test-framework/storage_devices/lvm.py
Normal file
@@ -0,0 +1,531 @@
|
||||
#
|
||||
# Copyright(c) 2022 Intel Corporation
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
#
|
||||
import threading
|
||||
|
||||
from typing import Union
|
||||
|
||||
from api.cas.core import Core
|
||||
from core.test_run import TestRun
|
||||
from storage_devices.device import Device
|
||||
from storage_devices.disk import Disk, NvmeDisk
|
||||
from storage_devices.partition import Partition
|
||||
from test_tools.fs_utils import readlink
|
||||
from test_utils.disk_finder import resolve_to_by_id_link
|
||||
from test_utils.filesystem.symlink import Symlink
|
||||
from test_utils.size import Size
|
||||
|
||||
lvm_config_path = "/etc/lvm/lvm.conf"
|
||||
filter_prototype_regex = r"^\sfilter\s=\s\["
|
||||
types_prototype_regex = r"^\stypes\s=\s\["
|
||||
global_filter_prototype_regex = r"^\sglobal_filter\s=\s\["
|
||||
tab = "\\\\t"
|
||||
|
||||
|
||||
class LvmConfiguration:
|
||||
def __init__(
|
||||
self,
|
||||
lvm_filters: [] = None,
|
||||
pv_num: int = None,
|
||||
vg_num: int = None,
|
||||
lv_num: int = None,
|
||||
cache_num: int = None,
|
||||
cas_dev_num: int = None
|
||||
):
|
||||
self.lvm_filters = lvm_filters
|
||||
self.pv_num = pv_num
|
||||
self.vg_num = vg_num
|
||||
self.lv_num = lv_num
|
||||
self.cache_num = cache_num
|
||||
self.cas_dev_num = cas_dev_num
|
||||
|
||||
@staticmethod
|
||||
def __read_definition_from_lvm_config(
|
||||
prototype_regex: str
|
||||
):
|
||||
cmd = f"grep '{prototype_regex}' {lvm_config_path}"
|
||||
output = TestRun.executor.run(cmd).stdout
|
||||
|
||||
return output
|
||||
|
||||
@classmethod
|
||||
def __add_block_dev_to_lvm_config(
|
||||
cls,
|
||||
block_device_type: str,
|
||||
number_of_partitions: int = 16
|
||||
):
|
||||
types_definition = cls.read_types_definition_from_lvm_config()
|
||||
|
||||
if types_definition:
|
||||
if block_device_type in types_definition:
|
||||
TestRun.LOGGER.info(f"Device type '{block_device_type}' already present in config")
|
||||
return
|
||||
|
||||
TestRun.LOGGER.info(f"Add block device type to existing list")
|
||||
new_type_prefix = f"types = [\"{block_device_type}\", {number_of_partitions}, "
|
||||
|
||||
config_update_cmd = f"sed -i 's/{types_prototype_regex}/\t{new_type_prefix}/g'" \
|
||||
f" {lvm_config_path}"
|
||||
else:
|
||||
TestRun.LOGGER.info(f"Create new types variable")
|
||||
new_types = f"types = [\"{block_device_type}\", {number_of_partitions}]"
|
||||
characteristic_line = f"# Configuration option devices\\/sysfs_scan."
|
||||
config_update_cmd = f"sed -i /'{characteristic_line}'/i\\ '{tab}{new_types}' " \
|
||||
f"{lvm_config_path}"
|
||||
|
||||
TestRun.LOGGER.info(f"Adding {block_device_type} ({number_of_partitions} partitions) "
|
||||
f"to supported types in {lvm_config_path}")
|
||||
TestRun.executor.run(config_update_cmd)
|
||||
|
||||
@classmethod
|
||||
def __add_filter_to_lvm_config(
|
||||
cls,
|
||||
filter: str
|
||||
):
|
||||
if filter is None:
|
||||
TestRun.LOGGER.error(f"Lvm filter for lvm config not provided.")
|
||||
|
||||
filters_definition = cls.read_filter_definition_from_lvm_config()
|
||||
|
||||
if filters_definition:
|
||||
if filter in filters_definition:
|
||||
TestRun.LOGGER.info(f"Filter definition '{filter}' already present in config")
|
||||
return
|
||||
|
||||
new_filter_formatted = filter.replace("/", "\\/")
|
||||
new_filter_prefix = f"filter = [ \"{new_filter_formatted}\", "
|
||||
|
||||
TestRun.LOGGER.info(f"Adding filter to existing list")
|
||||
config_update_cmd = f"sed -i 's/{filter_prototype_regex}/\t{new_filter_prefix}/g'" \
|
||||
f" {lvm_config_path}"
|
||||
else:
|
||||
TestRun.LOGGER.info(f"Create new filter variable")
|
||||
new_filter = f"filter = [\"{filter}\"]"
|
||||
characteristic_line = f"# Configuration option devices\\/global_filter."
|
||||
config_update_cmd = f"sed -i /'{characteristic_line}'/i\\ '{tab}{new_filter}' " \
|
||||
f"{lvm_config_path}"
|
||||
|
||||
TestRun.LOGGER.info(f"Adding filter '{filter}' to {lvm_config_path}")
|
||||
TestRun.executor.run(config_update_cmd)
|
||||
|
||||
@classmethod
|
||||
def read_types_definition_from_lvm_config(cls):
|
||||
return cls.__read_definition_from_lvm_config(types_prototype_regex)
|
||||
|
||||
@classmethod
|
||||
def read_filter_definition_from_lvm_config(cls):
|
||||
return cls.__read_definition_from_lvm_config(filter_prototype_regex)
|
||||
|
||||
@classmethod
|
||||
def read_global_filter_definition_from_lvm_config(cls):
|
||||
return cls.__read_definition_from_lvm_config(global_filter_prototype_regex)
|
||||
|
||||
@classmethod
|
||||
def add_block_devices_to_lvm_config(
|
||||
cls,
|
||||
device_type: str
|
||||
):
|
||||
if device_type is None:
|
||||
TestRun.LOGGER.error(f"No device provided.")
|
||||
|
||||
cls.__add_block_dev_to_lvm_config(device_type)
|
||||
|
||||
@classmethod
|
||||
def add_filters_to_lvm_config(
|
||||
cls,
|
||||
filters: []
|
||||
):
|
||||
if filters is None:
|
||||
TestRun.LOGGER.error(f"Lvm filters for lvm config not provided.")
|
||||
|
||||
for f in filters:
|
||||
cls.__add_filter_to_lvm_config(f)
|
||||
|
||||
@classmethod
|
||||
def configure_dev_types_in_config(
|
||||
cls,
|
||||
devices: ([Device], Device)
|
||||
):
|
||||
if isinstance(devices, list):
|
||||
devs = []
|
||||
for device in devices:
|
||||
dev = device.parent_device if isinstance(device, Partition) else device
|
||||
devs.append(dev)
|
||||
|
||||
if any(isinstance(dev, Core) for dev in devs):
|
||||
cls.add_block_devices_to_lvm_config("cas")
|
||||
if any(isinstance(dev, NvmeDisk) for dev in devs):
|
||||
cls.add_block_devices_to_lvm_config("nvme")
|
||||
else:
|
||||
dev = devices.parent_device if isinstance(devices, Partition) else devices
|
||||
if isinstance(dev, Core):
|
||||
cls.add_block_devices_to_lvm_config("cas")
|
||||
if isinstance(dev, NvmeDisk):
|
||||
cls.add_block_devices_to_lvm_config("nvme")
|
||||
|
||||
@classmethod
|
||||
def configure_filters(
|
||||
cls,
|
||||
lvm_filters: [],
|
||||
devices: ([Device], Device)
|
||||
):
|
||||
if lvm_filters:
|
||||
TestRun.LOGGER.info(f"Preparing configuration for LVMs - filters.")
|
||||
LvmConfiguration.add_filters_to_lvm_config(lvm_filters)
|
||||
|
||||
cls.configure_dev_types_in_config(devices)
|
||||
|
||||
@staticmethod
|
||||
def remove_global_filter_from_config():
|
||||
cmd = f"sed -i '/{global_filter_prototype_regex}/d' {lvm_config_path}"
|
||||
TestRun.executor.run(cmd)
|
||||
|
||||
@staticmethod
|
||||
def remove_filters_from_config():
|
||||
cmd = f"sed -i '/{filter_prototype_regex}/d' {lvm_config_path}"
|
||||
TestRun.executor.run(cmd)
|
||||
|
||||
|
||||
class VolumeGroup:
|
||||
__unique_vg_id = 0
|
||||
__lock = threading.Lock()
|
||||
|
||||
def __init__(self, name: str = None):
|
||||
self.name = name
|
||||
|
||||
def __eq__(self, other):
|
||||
try:
|
||||
return self.name == other.name
|
||||
except AttributeError:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def __get_vg_name(cls, prefix: str = "vg"):
|
||||
with cls.__lock:
|
||||
cls.__unique_vg_id += 1
|
||||
return f"{prefix}{cls.__unique_vg_id}"
|
||||
|
||||
@staticmethod
|
||||
def get_all_volume_groups():
|
||||
output_lines = TestRun.executor.run(f"pvscan").stdout.splitlines()
|
||||
|
||||
volume_groups = {}
|
||||
for line in output_lines:
|
||||
if "PV" not in line:
|
||||
continue
|
||||
|
||||
line_elements = line.split()
|
||||
pv = line_elements[line_elements.index("PV") + 1]
|
||||
vg = ""
|
||||
if "VG" in line:
|
||||
vg = line_elements[line_elements.index("VG") + 1]
|
||||
|
||||
if vg not in volume_groups:
|
||||
volume_groups[vg] = []
|
||||
volume_groups[vg].append(pv)
|
||||
|
||||
return volume_groups
|
||||
|
||||
@staticmethod
|
||||
def create_vg(vg_name: str, device_paths: str):
|
||||
if not vg_name:
|
||||
raise ValueError("Name needed for VG creation.")
|
||||
if not device_paths:
|
||||
raise ValueError("Device paths needed for VG creation.")
|
||||
|
||||
cmd = f"vgcreate --yes {vg_name} {device_paths} "
|
||||
TestRun.executor.run_expect_success(cmd)
|
||||
|
||||
@classmethod
|
||||
def is_vg_already_present(cls, dev_number: int, device_paths: str = None):
|
||||
if not device_paths:
|
||||
TestRun.LOGGER.exception("No devices provided.")
|
||||
|
||||
volume_groups = cls.get_all_volume_groups()
|
||||
|
||||
for vg in volume_groups:
|
||||
for pv in volume_groups[vg]:
|
||||
if len(volume_groups[vg]) == dev_number and pv in device_paths:
|
||||
return cls(vg)
|
||||
|
||||
for vg in volume_groups:
|
||||
for pv in volume_groups[vg]:
|
||||
if pv in device_paths:
|
||||
TestRun.LOGGER.warning(f"Some devices are used in other LVM volume group")
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def create(cls, device_paths: str = None):
|
||||
vg_name = cls.__get_vg_name()
|
||||
|
||||
VolumeGroup.create_vg(vg_name, device_paths)
|
||||
|
||||
volume_groups = VolumeGroup.get_all_volume_groups()
|
||||
|
||||
if vg_name in volume_groups:
|
||||
return cls(vg_name)
|
||||
else:
|
||||
TestRun.LOGGER.error("Had not found newly created VG.")
|
||||
|
||||
@staticmethod
|
||||
def remove(vg_name: str):
|
||||
if not vg_name:
|
||||
raise ValueError("Name needed for VG remove operation.")
|
||||
|
||||
cmd = f"vgremove {vg_name}"
|
||||
return TestRun.executor.run(cmd)
|
||||
|
||||
@staticmethod
|
||||
def get_logical_volumes_path(vg_name: str):
|
||||
cmd = f"lvdisplay | grep /dev/{vg_name}/ | awk '{{print $3}}'"
|
||||
paths = TestRun.executor.run(cmd).stdout.splitlines()
|
||||
|
||||
return paths
|
||||
|
||||
|
||||
class Lvm(Disk):
|
||||
__unique_lv_id = 0
|
||||
__lock = threading.Lock()
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
path_dm: str, # device mapper path
|
||||
volume_group: VolumeGroup,
|
||||
volume_name: str = None
|
||||
):
|
||||
Device.__init__(self, resolve_to_by_id_link(path_dm))
|
||||
self.device_name = path_dm.split('/')[-1]
|
||||
self.volume_group = volume_group
|
||||
self.volume_name = volume_name
|
||||
|
||||
def __eq__(self, other):
|
||||
try:
|
||||
return self.device_name == other.device_name and \
|
||||
self.volume_group == other.volume_group and \
|
||||
self.volume_name == other.volume_name
|
||||
except AttributeError:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def __get_unique_lv_name(cls, prefix: str = "lv"):
|
||||
with cls.__lock:
|
||||
cls.__unique_lv_id += 1
|
||||
return f"{prefix}{cls.__unique_lv_id}"
|
||||
|
||||
@classmethod
|
||||
def __create(
|
||||
cls,
|
||||
name: str,
|
||||
volume_size_cmd: str,
|
||||
volume_group: VolumeGroup
|
||||
):
|
||||
TestRun.LOGGER.info(f"Creating LV '{name}'.")
|
||||
cmd = f"lvcreate {volume_size_cmd} --name {name} {volume_group.name} --yes"
|
||||
TestRun.executor.run_expect_success(cmd)
|
||||
|
||||
volumes = cls.discover_logical_volumes()
|
||||
for volume in volumes:
|
||||
if name == volume.volume_name:
|
||||
return volume
|
||||
|
||||
@classmethod
|
||||
def configure_global_filter(
|
||||
cls,
|
||||
dev_first: Device,
|
||||
lv_amount: int,
|
||||
pv_devs: ([Device], Device)
|
||||
):
|
||||
device_first = dev_first.parent_device if isinstance(dev_first, Partition) else dev_first
|
||||
if lv_amount > 1 and isinstance(device_first, Core):
|
||||
|
||||
global_filter_def = LvmConfiguration.read_global_filter_definition_from_lvm_config()
|
||||
if not isinstance(pv_devs, list):
|
||||
pv_devs = [pv_devs]
|
||||
|
||||
if global_filter_def:
|
||||
TestRun.LOGGER.info(f"Configure 'global filter' variable")
|
||||
links = []
|
||||
for pv_dev in pv_devs:
|
||||
link = pv_dev.get_device_link("/dev/disk/by-id")
|
||||
links.append(str(link))
|
||||
|
||||
for link in links:
|
||||
if link in global_filter_def:
|
||||
TestRun.LOGGER.info(f"Global filter definition already contains '{link}'")
|
||||
continue
|
||||
|
||||
new_link_formatted = link.replace("/", "\\/")
|
||||
new_global_filter_prefix = f"global_filter = [ \"r|{new_link_formatted}|\", "
|
||||
|
||||
TestRun.LOGGER.info(f"Adding global filter '{link}' to existing list")
|
||||
config_update_cmd = f"sed -i 's/{global_filter_prototype_regex}/\t" \
|
||||
f"{new_global_filter_prefix}/g' {lvm_config_path}"
|
||||
TestRun.executor.run(config_update_cmd)
|
||||
else:
|
||||
for pv_dev in pv_devs:
|
||||
link = pv_dev.get_device_link("/dev/disk/by-id")
|
||||
global_filter = f"\"r|{link}|\""
|
||||
global_filter += ", "
|
||||
global_filter = global_filter[:-2]
|
||||
|
||||
TestRun.LOGGER.info(f"Create new 'global filter' variable")
|
||||
|
||||
new_global = f"global_filter = [{global_filter}]"
|
||||
characteristic_line = f"# Configuration option devices\\/types."
|
||||
config_update_cmd = f"sed -i /'{characteristic_line}'/i\\ " \
|
||||
f"'{tab}{new_global}' {lvm_config_path}"
|
||||
|
||||
TestRun.LOGGER.info(f"Adding global filter '{global_filter}' to {lvm_config_path}")
|
||||
TestRun.executor.run(config_update_cmd)
|
||||
|
||||
TestRun.LOGGER.info(f"Remove 'filter' in order to 'global_filter' to be used")
|
||||
if LvmConfiguration.read_filter_definition_from_lvm_config():
|
||||
LvmConfiguration.remove_filters_from_config()
|
||||
|
||||
@classmethod
|
||||
def create_specific_lvm_configuration(
|
||||
cls,
|
||||
devices: ([Device], Device),
|
||||
lvm_configuration: LvmConfiguration,
|
||||
lvm_as_core: bool = False
|
||||
):
|
||||
pv_per_vg = int(lvm_configuration.pv_num / lvm_configuration.vg_num)
|
||||
lv_per_vg = int(lvm_configuration.lv_num / lvm_configuration.vg_num)
|
||||
lv_size_percentage = int(100 / lv_per_vg)
|
||||
|
||||
LvmConfiguration.configure_filters(lvm_configuration.lvm_filters, devices)
|
||||
|
||||
logical_volumes = []
|
||||
|
||||
for vg_iter in range(lvm_configuration.vg_num):
|
||||
if isinstance(devices, list):
|
||||
pv_devs = []
|
||||
start_range = vg_iter * pv_per_vg
|
||||
end_range = start_range + pv_per_vg
|
||||
for i in range(start_range, end_range):
|
||||
pv_devs.append(devices[i])
|
||||
device_first = devices[0]
|
||||
else:
|
||||
pv_devs = devices
|
||||
device_first = devices
|
||||
|
||||
for j in range(lv_per_vg):
|
||||
lv = cls.create(lv_size_percentage, pv_devs)
|
||||
logical_volumes.append(lv)
|
||||
|
||||
if lvm_as_core:
|
||||
cls.configure_global_filter(device_first, lv_per_vg, pv_devs)
|
||||
|
||||
return logical_volumes
|
||||
|
||||
@classmethod
|
||||
def create(
|
||||
cls,
|
||||
volume_size_or_percent: Union[Size, int],
|
||||
devices: ([Device], Device),
|
||||
name: str = None
|
||||
):
|
||||
if isinstance(volume_size_or_percent, Size):
|
||||
size_cmd = f"--size {volume_size_or_percent.get_value()}B"
|
||||
elif isinstance(volume_size_or_percent, int):
|
||||
size_cmd = f"--extents {volume_size_or_percent}%VG"
|
||||
else:
|
||||
TestRun.LOGGER.error(f"Incorrect type of the first argument (volume_size_or_percent).")
|
||||
|
||||
if not name:
|
||||
name = cls.__get_unique_lv_name()
|
||||
|
||||
devices_paths = cls.get_devices_path(devices)
|
||||
dev_number = len(devices) if isinstance(devices, list) else 1
|
||||
|
||||
vg = VolumeGroup.is_vg_already_present(dev_number, devices_paths)
|
||||
|
||||
if not vg:
|
||||
vg = VolumeGroup.create(devices_paths)
|
||||
|
||||
return cls.__create(name, size_cmd, vg)
|
||||
|
||||
@staticmethod
|
||||
def get_devices_path(devices: ([Device], Device)):
|
||||
if isinstance(devices, list):
|
||||
return " ".join([Symlink(dev.path).get_target() for dev in devices])
|
||||
else:
|
||||
return Symlink(devices.path).get_target()
|
||||
|
||||
@classmethod
|
||||
def discover_logical_volumes(cls):
|
||||
vol_groups = VolumeGroup.get_all_volume_groups()
|
||||
volumes = []
|
||||
for vg in vol_groups:
|
||||
lv_discovered = VolumeGroup.get_logical_volumes_path(vg)
|
||||
if lv_discovered:
|
||||
for lv_path in lv_discovered:
|
||||
cls.make_sure_lv_is_active(lv_path)
|
||||
lv_name = lv_path.split('/')[-1]
|
||||
volumes.append(
|
||||
cls(
|
||||
readlink(lv_path),
|
||||
VolumeGroup(vg),
|
||||
lv_name
|
||||
)
|
||||
)
|
||||
else:
|
||||
TestRun.LOGGER.info(f"No LVMs present in the system.")
|
||||
|
||||
return volumes
|
||||
|
||||
@classmethod
|
||||
def discover(cls):
|
||||
TestRun.LOGGER.info("Discover LVMs in system...")
|
||||
return cls.discover_logical_volumes()
|
||||
|
||||
@staticmethod
|
||||
def remove(lv_name: str, vg_name: str):
|
||||
if not lv_name:
|
||||
raise ValueError("LV name needed for LV remove operation.")
|
||||
if not vg_name:
|
||||
raise ValueError("VG name needed for LV remove operation.")
|
||||
|
||||
cmd = f"lvremove -f {vg_name}/{lv_name}"
|
||||
return TestRun.executor.run(cmd)
|
||||
|
||||
@staticmethod
|
||||
def remove_pv(pv_name: str):
|
||||
if not pv_name:
|
||||
raise ValueError("Name needed for PV remove operation.")
|
||||
|
||||
cmd = f"pvremove {pv_name}"
|
||||
return TestRun.executor.run(cmd)
|
||||
|
||||
@classmethod
|
||||
def remove_all(cls):
|
||||
cmd = f"lvdisplay | grep 'LV Path' | awk '{{print $3}}'"
|
||||
lvm_paths = TestRun.executor.run(cmd).stdout.splitlines()
|
||||
for lvm_path in lvm_paths:
|
||||
lv_name = lvm_path.split('/')[-1]
|
||||
vg_name = lvm_path.split('/')[-2]
|
||||
cls.remove(lv_name, vg_name)
|
||||
|
||||
cmd = f"vgdisplay | grep 'VG Name' | awk '{{print $3}}'"
|
||||
vg_names = TestRun.executor.run(cmd).stdout.splitlines()
|
||||
for vg_name in vg_names:
|
||||
TestRun.executor.run(f"vgchange -an {vg_name}")
|
||||
VolumeGroup.remove(vg_name)
|
||||
|
||||
cmd = f"pvdisplay | grep 'PV Name' | awk '{{print $3}}'"
|
||||
pv_names = TestRun.executor.run(cmd).stdout.splitlines()
|
||||
for pv_name in pv_names:
|
||||
cls.remove_pv(pv_name)
|
||||
|
||||
TestRun.LOGGER.info(f"Successfully removed all LVMs.")
|
||||
|
||||
@staticmethod
|
||||
def make_sure_lv_is_active(lv_path: str):
|
||||
cmd = f"lvscan"
|
||||
output_lines = TestRun.executor.run_expect_success(cmd).stdout.splitlines()
|
||||
|
||||
for line in output_lines:
|
||||
if "inactive " in line and lv_path in line:
|
||||
cmd = f"lvchange -ay {lv_path}"
|
||||
TestRun.executor.run_expect_success(cmd)
|
22
test/functional/test-framework/storage_devices/partition.py
Normal file
22
test/functional/test-framework/storage_devices/partition.py
Normal file
@@ -0,0 +1,22 @@
|
||||
#
|
||||
# Copyright(c) 2019-2021 Intel Corporation
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
#
|
||||
|
||||
from storage_devices.device import Device
|
||||
from test_tools import disk_utils
|
||||
from test_utils.size import Size
|
||||
|
||||
|
||||
class Partition(Device):
|
||||
def __init__(self, parent_dev, type, number, begin: Size, end: Size):
|
||||
Device.__init__(self, disk_utils.get_partition_path(parent_dev.path, number))
|
||||
self.number = number
|
||||
self.parent_device = parent_dev
|
||||
self.type = type
|
||||
self.begin = begin
|
||||
self.end = end
|
||||
|
||||
def __str__(self):
|
||||
return f"\tsystem path: {self.path}, size: {self.size}, type: {self.type}, " \
|
||||
f"parent device: {self.parent_device.path}\n"
|
182
test/functional/test-framework/storage_devices/raid.py
Normal file
182
test/functional/test-framework/storage_devices/raid.py
Normal file
@@ -0,0 +1,182 @@
|
||||
#
|
||||
# Copyright(c) 2020-2021 Intel Corporation
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
#
|
||||
import threading
|
||||
from enum import IntEnum, Enum
|
||||
|
||||
from core.test_run import TestRun
|
||||
from storage_devices.device import Device
|
||||
from storage_devices.disk import Disk
|
||||
from test_tools.fs_utils import readlink
|
||||
from test_tools.mdadm import Mdadm
|
||||
from test_utils.disk_finder import resolve_to_by_id_link
|
||||
from test_utils.size import Size, Unit
|
||||
|
||||
|
||||
def get_devices_paths_string(devices: [Device]):
|
||||
return " ".join([d.path for d in devices])
|
||||
|
||||
|
||||
class Level(IntEnum):
|
||||
Raid0 = 0
|
||||
Raid1 = 1
|
||||
Raid4 = 4
|
||||
Raid5 = 5
|
||||
Raid6 = 6
|
||||
Raid10 = 10
|
||||
|
||||
|
||||
class StripSize(IntEnum):
|
||||
Strip4K = 4
|
||||
Strip8K = 8
|
||||
Strip16K = 16
|
||||
Strip32K = 32
|
||||
Strip64K = 64
|
||||
Strip128K = 128
|
||||
Strip256K = 256
|
||||
Strip1M = 1024
|
||||
|
||||
|
||||
class MetadataVariant(Enum):
|
||||
Legacy = "legacy"
|
||||
Imsm = "imsm"
|
||||
|
||||
|
||||
class RaidConfiguration:
|
||||
def __init__(
|
||||
self,
|
||||
level: Level = None,
|
||||
metadata: MetadataVariant = MetadataVariant.Imsm,
|
||||
number_of_devices: int = 0,
|
||||
size: Size = None,
|
||||
strip_size: StripSize = None,
|
||||
name: str = None,
|
||||
):
|
||||
self.level = level
|
||||
self.metadata = metadata
|
||||
self.number_of_devices = number_of_devices
|
||||
self.size = size
|
||||
self.strip_size = strip_size
|
||||
self.name = name
|
||||
|
||||
|
||||
class Raid(Disk):
|
||||
__unique_id = 0
|
||||
__lock = threading.Lock()
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
path: str,
|
||||
level: Level,
|
||||
uuid: str,
|
||||
container_uuid: str = None,
|
||||
container_path: str = None,
|
||||
metadata: MetadataVariant = MetadataVariant.Imsm,
|
||||
array_devices: [Device] = [],
|
||||
volume_devices: [Device] = [],
|
||||
):
|
||||
Device.__init__(self, resolve_to_by_id_link(path.replace("/dev/", "")))
|
||||
self.device_name = path.split('/')[-1]
|
||||
self.level = level
|
||||
self.uuid = uuid
|
||||
self.container_uuid = container_uuid
|
||||
self.container_path = container_path
|
||||
self.metadata = metadata
|
||||
self.array_devices = array_devices if array_devices else volume_devices.copy()
|
||||
self.volume_devices = volume_devices
|
||||
self.partitions = []
|
||||
self.__block_size = None
|
||||
|
||||
def __eq__(self, other):
|
||||
try:
|
||||
return self.uuid == other.uuid
|
||||
except AttributeError:
|
||||
return False
|
||||
|
||||
@property
|
||||
def block_size(self):
|
||||
if not self.__block_size:
|
||||
self.__block_size = Unit(int(self.get_sysfs_property("logical_block_size")))
|
||||
return self.__block_size
|
||||
|
||||
def stop(self):
|
||||
Mdadm.stop(self.path)
|
||||
if self.container_path:
|
||||
Mdadm.stop(self.container_path)
|
||||
|
||||
@classmethod
|
||||
def discover(cls):
|
||||
TestRun.LOGGER.info("Discover RAIDs in system...")
|
||||
raids = []
|
||||
for raid in Mdadm.examine_result():
|
||||
raids.append(
|
||||
cls(
|
||||
raid["path"],
|
||||
Level[raid["level"]],
|
||||
raid["uuid"],
|
||||
raid["container"]["uuid"] if "container" in raid else None,
|
||||
raid["container"]["path"] if "container" in raid else None,
|
||||
MetadataVariant(raid["metadata"]),
|
||||
[Device(d) for d in raid["array_devices"]],
|
||||
[Device(d) for d in raid["devices"]]
|
||||
)
|
||||
)
|
||||
|
||||
return raids
|
||||
|
||||
@classmethod
|
||||
def create(
|
||||
cls,
|
||||
raid_configuration: RaidConfiguration,
|
||||
devices: [Device]
|
||||
):
|
||||
import copy
|
||||
raid_conf = copy.deepcopy(raid_configuration)
|
||||
|
||||
if not raid_conf.number_of_devices:
|
||||
raid_conf.number_of_devices = len(devices)
|
||||
elif len(devices) < raid_conf.number_of_devices:
|
||||
raise ValueError("RAID configuration requires at least "
|
||||
f"{raid_conf.number_of_devices} devices")
|
||||
|
||||
md_dir_path = "/dev/md/"
|
||||
array_devices = devices
|
||||
volume_devices = devices[:raid_conf.number_of_devices]
|
||||
|
||||
if raid_conf.metadata != MetadataVariant.Legacy:
|
||||
container_conf = RaidConfiguration(
|
||||
name=cls.__get_unique_name(raid_conf.metadata.value),
|
||||
metadata=raid_conf.metadata,
|
||||
number_of_devices=len(array_devices)
|
||||
)
|
||||
Mdadm.create(container_conf, get_devices_paths_string(array_devices))
|
||||
|
||||
if not raid_conf.name:
|
||||
raid_conf.name = cls.__get_unique_name()
|
||||
|
||||
Mdadm.create(raid_conf, get_devices_paths_string(volume_devices))
|
||||
|
||||
raid_link = md_dir_path + raid_conf.name
|
||||
raid = [r for r in Mdadm.examine_result() if readlink(r["path"]) == readlink(raid_link)][0]
|
||||
|
||||
return cls(
|
||||
raid["path"],
|
||||
raid_conf.level,
|
||||
raid["uuid"],
|
||||
raid["container"]["uuid"] if "container" in raid else None,
|
||||
raid["container"]["path"] if "container" in raid else None,
|
||||
raid_conf.metadata,
|
||||
array_devices,
|
||||
volume_devices
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def remove_all():
|
||||
Mdadm.stop()
|
||||
|
||||
@classmethod
|
||||
def __get_unique_name(cls, prefix: str = "Raid"):
|
||||
with cls.__lock:
|
||||
cls.__unique_id += 1
|
||||
return f"{prefix}{cls.__unique_id}"
|
80
test/functional/test-framework/storage_devices/ramdisk.py
Normal file
80
test/functional/test-framework/storage_devices/ramdisk.py
Normal file
@@ -0,0 +1,80 @@
|
||||
#
|
||||
# Copyright(c) 2021 Intel Corporation
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
#
|
||||
|
||||
import posixpath
|
||||
|
||||
from core.test_run import TestRun
|
||||
from storage_devices.device import Device
|
||||
from test_tools import disk_utils
|
||||
from test_tools.fs_utils import ls, parse_ls_output
|
||||
from test_utils.filesystem.symlink import Symlink
|
||||
from test_utils.os_utils import reload_kernel_module, unload_kernel_module, is_kernel_module_loaded
|
||||
from test_utils.size import Size, Unit
|
||||
|
||||
|
||||
class RamDisk(Device):
|
||||
_module = "brd"
|
||||
|
||||
@classmethod
|
||||
def create(cls, disk_size: Size, disk_count: int = 1):
|
||||
if disk_count < 1:
|
||||
raise ValueError("Wrong number of RAM disks requested")
|
||||
|
||||
TestRun.LOGGER.info("Configure RAM disks...")
|
||||
params = {
|
||||
"rd_size": int(disk_size.get_value(Unit.KiB)),
|
||||
"rd_nr": disk_count
|
||||
}
|
||||
reload_kernel_module(cls._module, params)
|
||||
|
||||
if not cls._is_configured(disk_size, disk_count):
|
||||
raise EnvironmentError(f"Wrong RAM disk configuration after loading '{cls._module}' "
|
||||
"module")
|
||||
|
||||
return cls.list()
|
||||
|
||||
@classmethod
|
||||
def remove_all(cls):
|
||||
if not is_kernel_module_loaded(cls._module):
|
||||
return
|
||||
|
||||
for ram_disk in cls._list_devices():
|
||||
TestRun.executor.run(f"umount {ram_disk.full_path}")
|
||||
link_path = posixpath.join("/dev/disk/by-id", ram_disk.name)
|
||||
try:
|
||||
link = Symlink.get_symlink(link_path=link_path, target=ram_disk.full_path)
|
||||
link.remove(force=True)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
TestRun.LOGGER.info("Removing RAM disks...")
|
||||
unload_kernel_module(cls._module)
|
||||
|
||||
@classmethod
|
||||
def list(cls):
|
||||
ram_disks = []
|
||||
for ram_disk in cls._list_devices():
|
||||
link_path = posixpath.join("/dev/disk/by-id", ram_disk.name)
|
||||
link = Symlink.get_symlink(
|
||||
link_path=link_path, target=ram_disk.full_path, create=True
|
||||
)
|
||||
ram_disks.append(cls(link.full_path))
|
||||
|
||||
return ram_disks
|
||||
|
||||
@classmethod
|
||||
def _is_configured(cls, disk_size: Size, disk_count: int):
|
||||
ram_disks = cls._list_devices()
|
||||
return (
|
||||
len(ram_disks) >= disk_count
|
||||
and Size(disk_utils.get_size(ram_disks[0].name), Unit.Byte).align_down(Unit.MiB.value)
|
||||
== disk_size.align_down(Unit.MiB.value)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _list_devices():
|
||||
ls_ram_disks = ls("/dev/ram*")
|
||||
if "No such file or directory" in ls_ram_disks:
|
||||
return []
|
||||
return parse_ls_output(ls_ram_disks)
|
Reference in New Issue
Block a user