tests: Embed test framework within OCL repository

Signed-off-by: Robert Baldyga <robert.baldyga@intel.com>
This commit is contained in:
Robert Baldyga
2022-12-23 12:50:17 +01:00
parent bc0c8c1bf5
commit 849f59855c
91 changed files with 9930 additions and 2 deletions

View 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()]

View 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

View 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

View 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)

View 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"

View 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}"

View 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)