From 31aae6369369cda359476b1e3328e8f8fb7e13cc Mon Sep 17 00:00:00 2001 From: Xu Tian Date: Mon, 23 Sep 2019 20:40:38 +0800 Subject: [PATCH] V2.3 Signed-off-by: Xu Tian --- virttest/virt_storage/__init__.py | 0 virttest/virt_storage/exception.py | 36 +++ virttest/virt_storage/storage_admin.py | 211 ++++++++++++++ virttest/virt_storage/storage_pool.py | 304 ++++++++++++++++++++ virttest/virt_storage/storage_volume.py | 49 ++++ virttest/virt_storage/unit_test.py | 74 +++++ virttest/virt_storage/utils/__init__.py | 97 +++++++ virttest/virt_storage/utils/fscli.py | 84 ++++++ virttest/virt_storage/utils/glustercli.py | 90 ++++++ virttest/virt_storage/utils/iscsicli.py | 146 ++++++++++ virttest/virt_storage/utils/nfscli.py | 64 +++++ virttest/virt_storage/utils/rbdcli.py | 80 ++++++ virttest/virt_storage/utils/state.py | 25 ++ virttest/virt_storage/utils/utils_volume.py | 30 ++ virttest/virt_storage/virt_device.py | 44 +++ virttest/virt_storage/virt_encryption.py | 81 ++++++ virttest/virt_storage/virt_permission.py | 15 + virttest/virt_storage/virt_source.py | 53 ++++ virttest/virt_storage/virt_target.py | 20 ++ 19 files changed, 1503 insertions(+) create mode 100644 virttest/virt_storage/__init__.py create mode 100644 virttest/virt_storage/exception.py create mode 100644 virttest/virt_storage/storage_admin.py create mode 100644 virttest/virt_storage/storage_pool.py create mode 100644 virttest/virt_storage/storage_volume.py create mode 100644 virttest/virt_storage/unit_test.py create mode 100644 virttest/virt_storage/utils/__init__.py create mode 100644 virttest/virt_storage/utils/fscli.py create mode 100644 virttest/virt_storage/utils/glustercli.py create mode 100644 virttest/virt_storage/utils/iscsicli.py create mode 100644 virttest/virt_storage/utils/nfscli.py create mode 100644 virttest/virt_storage/utils/rbdcli.py create mode 100644 virttest/virt_storage/utils/state.py create mode 100644 virttest/virt_storage/utils/utils_volume.py create mode 100644 virttest/virt_storage/virt_device.py create mode 100644 virttest/virt_storage/virt_encryption.py create mode 100644 virttest/virt_storage/virt_permission.py create mode 100644 virttest/virt_storage/virt_source.py create mode 100644 virttest/virt_storage/virt_target.py diff --git a/virttest/virt_storage/__init__.py b/virttest/virt_storage/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/virttest/virt_storage/exception.py b/virttest/virt_storage/exception.py new file mode 100644 index 00000000000..587320f50f6 --- /dev/null +++ b/virttest/virt_storage/exception.py @@ -0,0 +1,36 @@ +class UnsupportedStoragePoolException(Exception): + + def __init__(self, sp_manager, sp_type): + self.sp_manager = sp_manager + self.sp_type = sp_type + self.message = "Unsupported StoragePool type '%s', supported type are: %s" % ( + self.sp_type, sp_manager.supported_storage_backend.keys()) + + def __str__(self): + return "UnsupportedStoragePoolException:%s" % self.message + + +class UnsupportedVolumeFormatException(Exception): + """""" + + def __init__(self, sp_manager, fmt): + self.sp_manager = sp_manager + self.fmt = fmt + self.message = "Unsupported volume format '%s', supported format are: %s" % ( + self.fmt, sp_manager.SUPPORTED_VOLUME_FORMAT.keys()) + + def __str__(self): + return "UnsupportedVolumeFormatException:%s" % self.message + + +class UnsupportedVolumeProtocolException(Exception): + """""" + + def __init__(self, sp_manager, protocol): + self.sp_manager = sp_manager + self.protocol = protocol + self.message = "Unsupported volume protocol '%s', supported protocol are: %s" % ( + self.protocol, sp_manager.SUPPORTED_VOLUME_PROTOCOL.keys()) + + def __str__(self): + return "UnsupportedVolumeProtocolException:%s" % self.message diff --git a/virttest/virt_storage/storage_admin.py b/virttest/virt_storage/storage_admin.py new file mode 100644 index 00000000000..0b234b8df03 --- /dev/null +++ b/virttest/virt_storage/storage_admin.py @@ -0,0 +1,211 @@ +import logging + +from virttest.virt_storage import exception +from virttest.virt_storage import storage_pool +from virttest.virt_storage import storage_volume +from virttest.virt_storage.utils import state + + +class StoragePoolAdmin(object): + supported_storage_backend = { + "directory": storage_pool.VirtDirectoryPool, + "nfs": storage_pool.NfsPool, + "gluster": storage_pool.GlusterPool, + "iscsi-direct": storage_pool.IscsiDriectPool, + "rbd": storage_pool.RbdPool + } + + pools = {} + + @classmethod + def _find_storage_driver(cls, backend_type): + try: + return cls.supported_storage_backend[backend_type] + except KeyError: + raise exception.UnsupportedStoragePoolException(cls, backend_type) + + @classmethod + def pool_define_by_params(cls, name, params): + """ + Define logical storage pool object by test params, + initial status of the pool is dead. + + :param params: pool params object + :param name: storage pool name + + :return StoragePool object + """ + driver = cls._find_storage_driver(params["storage_type"]) + pool = driver.pool_define_by_params(name, params) + state.register_pool_state_machine(pool) + pool.name = name + cls.pools[name] = pool + return pool + + @classmethod + def pools_define_by_params(cls, params): + pools = list() + for name in params.objects("storage_pools"): + _params = params.object_params(name) + pool = cls.pool_define_by_params(name, _params) + pools.append(pool) + return pools + + @classmethod + def list_volumes(cls): + """List all volumes in host""" + volumes = list() + for pool in cls.pools.values(): + volumes += pool.volumes.values() + return volumes + + @classmethod + def list_pools(cls): + return cls.pools.values() + + @classmethod + def find_pool_by_name(cls, name, driver=None): + try: + pool = cls.pools[name] + if not driver: + return pool + if driver and pool.TYPE == driver: + return pool + except KeyError: + logging.warn("no storage pool with matching name '%s'" % name) + return None + + @classmethod + def find_pool_by_uuid(cls, uuid): + try: + return filter(lambda x: x.uuid == uuid, cls.list_pools())[0] + except IndexError: + logging.warn("no storage pool with matching uuid '%s'" % uuid) + return None + + @staticmethod + def find_pool_by_volume(volume): + return volume.pool + + @classmethod + def find_pool_by_path(cls, path): + try: + return filter(lambda x: x.target.path == path, cls.list_pools())[0] + except IndexError: + logging.warn("no storage pool with matching path '%s'" % path) + return None + + @classmethod + def _get_next_pool(cls): + return next([_ for _ in cls.list_pools() if not _.isDead()]) + + @classmethod + def undefine_pool(cls, pool): + """Remove a pool if the pool is not in running state""" + if not pool.isRunning(): + try: + del cls.pools[pool.name] + except KeyError: + logging.warn("Pool '%s' not found" % pool.name) + else: + logging.warn("Pool '%s' is running, skip it" % pool.name) + + @staticmethod + def start_pool(pool): + return pool.start_pool() + + @staticmethod + def stop_pool(pool): + return pool.stop_pool() + + @staticmethod + def destroy_pool(pool): + return pool.destroy_pool() + + @classmethod + def undefine_all_pools(cls): + """Undefine pool, remove pool object from pools""" + return list(map(cls.undefine_pool, cls.list_pools())) + + @staticmethod + def refresh_pool(pool): + return pool.refresh() + + @staticmethod + def acquire_pool(pool): + """ Create storage pool""" + pool.create_pool() + logging.info("build pool '%s', it's %s " % (pool.name, pool.state)) + return pool + + @staticmethod + def acqurie_volume(volume): + """Acqurie a volume""" + return volume.pool.acqurie_volume(volume) + + @staticmethod + def release_volume(volume): + del volume.pool.volumes[str(volume.uuid)] + + @staticmethod + def find_sources(pool, params): + return pool.find_sources(params) + + @classmethod + def volumes_define_by_params(cls, params): + volumes = list() + for name in params.objects("images"): + vol_params = params.object_params(name) + volume = cls.volume_define_by_params(name, vol_params) + volumes.append(volume) + + for volume in volumes: + vol_params = params.object_params(volume.name) + backing_store_name = vol_params.get("backing") + if not backing_store_name: + continue + backing_store = filter(lambda x: x.name == name, volumes)[0] + volume.backing_store = backing_store + return volumes + + @classmethod + def volume_define_by_params(cls, name, params): + pool_name = params.get("storage_pool") + pool_params = params.object_params(pool_name) + storage_type = pool_params.get("storage_type") + pool = cls.find_pool_by_name(pool_name, storage_type) + if not pool: + pool = cls.pool_define_by_params(pool_name, pool_params) + volume = storage_volume.StorageVolume(pool) + volume.name = name + volume.format = params.get("image_format") + return volume + + @classmethod + def acquire_volume(cls, volume): + def get_volume_chain(top): + chain = [top] + backing_store = top.backing_store + while backing_store: + chain.insert(0, backing_store) + backing_store = backing_store.backing_store + return chain + + assert volume, "volume should not None" + for vol in get_volume_chain(volume): + pool = cls.find_pool_by_volume(vol) + key = str(vol.uuid) + _vol = pool.get_volume_by_uuid(key) + if _vol != vol: + pool.acquire_volume(vol) + + @classmethod + def find_volume_by_name(cls, name): + volumes = cls.list_volumes() + for volume in volumes: + if volume.name == name: + return volume + return None + + +sp_admin = StoragePoolAdmin() diff --git a/virttest/virt_storage/storage_pool.py b/virttest/virt_storage/storage_pool.py new file mode 100644 index 00000000000..1bf939afcf4 --- /dev/null +++ b/virttest/virt_storage/storage_pool.py @@ -0,0 +1,304 @@ +import copy +import uuid + +from virttest.virt_storage import storage_volume +from virttest.virt_storage import utils +from virttest.virt_storage import virt_source +from virttest.virt_storage import virt_target +from virttest.virt_storage.utils import utils_volume + + +class VirtStoragePool(object): + TYPE = "none" + + def __init__(self, name, params): + self.name = name + self.uuid = None + # keep acquired volumes + self.volumes = {} + self.sources = [] + self._capacity = 0 + self._available = 0 + self._allocation = 0 + self.target = virt_target.VirtStoragePoolTarget() + self._params = params + utils.make_pool_dir(self) + + @property + def capacity(self): + self._capacity = self.conn.capacity + return self._capacity + + @property + def allocation(self): + if not self._allocation: + self._allocation = self.conn.allocation + return self._allocation + + @allocation.setter + def allocation(self, size): + if not self._allocation: + self._allocation = self.conn.allocation + self._allocation += int(size) + + @property + def available(self): + self._available = self.conn.available + return self._available + + @property + def conn(self): + return utils.get_pool_connection(self) + + def sources_define_by_params(self, params=None): + sources = list() + sources_params = params or self._params + for item in sources_params.objects("sources"): + _params = sources_params.object_params(item) + source = virt_source.VirtStoragePoolSource.source_define_by_params( + _params) + source.name = item + sources.append(source) + return sources + + @classmethod + def pool_define_by_params(cls, name, params): + instance = utils.get_instance_by_params(cls, params, cls(name, params)) + instance.uuid = uuid.uuid1() + return instance + + def start(self): + raise NotImplementedError + + def stop(self): + raise NotImplementedError + + def destroy(self): + raise NotImplementedError + + def find_sources(self): + raise NotImplementedError + + def find_volume_by_name(self, name): + for volume in self.volumes.values(): + if volume.name == name: + return volume + return None + + def find_volume_by_path(self, path): + for volume in self.volumes.values(): + if volume.key == path: + return volume + return None + + def list_free_volumes(self): + """List free volume""" + + def _get_volume_by_path(path): + volume = storage_volume.StorageVolume(self) + volume.pool = self + volume.key = self.conn.get_path(path) + volume.capacity = self.conn.get_size(path) + return volume + + volumes = [_get_volume_by_path(p) for p in self.find_sources()] + return filter(lambda x: not (self.find_volume_by_path(x.key)), volumes) + + def info(self): + out = {"type": self.TYPE} + _info = copy.deepcopy(vars(self)) + for key, val in _info.items(): + if callable(val): + continue + if key.startswith("_"): + key = key.lstrip("_") + if hasattr(self, key): + out[key] = getattr(self, key) + continue + if isinstance(val, list): + val = list(map(str, val)) + out[key] = val + continue + elif isinstance(val, dict): + for k, v in val.items(): + val[k] = str(v) + out[key] = val + continue + else: + out[key] = str(val) + continue + return out + + def create_volume(self, volume): + raise NotImplementedError + + def acquire_volume(self, volume): + assert self.state == "running", "Please start pool ('%s') before acquire a volume" % self.name + volume = self.create_volume(volume) + self.allocation += volume.capacity + key = str(volume.uuid) + self.volumes[key] = volume + return volume + + def get_volumes(self): + return self.volumes.values() + + def get_volume_by_uuid(self, _uuid): + for volume in self.get_volumes(): + if str(volume.uuid) == _uuid: + return volume + return None + + def __str__(self): + return "%s:%s" % (self.__class__.__name__, self.name) + + +class VirtDirectoryPool(VirtStoragePool): + TYPE = "directory" + + def find_sources(self): + return self.conn.list_files() + + def start(self): + return self.conn.create() + + def create_volume(self, volume): + if not volume.key: + name = str(volume.uuid) + volume.key = self.conn.get_path_by_name(name) + utils_volume.create_volume(volume) + return volume + + def stop(self): + pass + + def destroy(self): + self.conn.remove() + + +class NfsPool(VirtStoragePool): + TYPE = "nfs" + + @classmethod + def pool_define_by_params(cls, name, params): + """Get nfs pool object and set special options""" + instance = super(NfsPool, cls).pool_define_by_params(name, params) + instance = utils.set_special_opts_by_params( + instance, params, "mount_opts") + return instance + + def find_sources(self): + return self.conn.list_files() + + def start(self): + return self.conn.mount() + + def stop(self): + return self.conn.umount() + + def create_volume(self, volume): + if not volume.key: + name = str(volume.uuid) + volume.key = self.conn.get_path_by_name(name) + utils_volume.create_volume(volume) + return volume + + def destroy(self): + self.conn.remove() + + +class GlusterPool(VirtStoragePool): + TYPE = "gluster" + + def find_sources(self): + top_dir = self.sources[0].dir_path + return self.conn.list_files(top_dir) + + def start(self): + self.conn.mount() + + def stop(self): + self.conn.umount() + + def create_volume(self, volume): + if not volume.key: + name = str(volume.uuid) + volume.key = self.conn.get_path_by_name(name) + utils_volume.create_volume(volume) + return volume + + def destroy(self): + self.conn.remove() + + +class IscsiDriectPool(VirtStoragePool): + TYPE = "iscsi-direct" + + def find_sources(self): + return self.conn.list_luns() + + def start(self): + self.conn.login() + + def stop(self): + self.conn.logout() + + def _get_nearest_volume(self, vol): + """ Get a lun value of size nearest to give volume""" + + def sort_func(v): + return v.capacity - vol.capacity + + volumes = list(self.list_free_volumes()) + volumes.sort(key=sort_func) + for volume in volumes: + if volume.capacity >= vol.capacity: + return volume + return None + + def create_volume(self, vol): + volume = self._get_nearest_volume(vol) + if volume is None: + raise ValueError("No suitable lun found") + vol.pool = self + vol.key = volume.key + vol.allocation = volume.allocation + vol.capacity = volume.capacity + return vol + + def destroy(self): + pass + + +class RbdPool(VirtStoragePool): + TYPE = 'rbd' + + @classmethod + def pool_define_by_params(cls, name, params): + """Get RBD pool object and set special options""" + instance = super(RbdPool, cls).pool_define_by_params(name, params) + instance = utils.set_special_opts_by_params( + instance, params, "config_opts") + return instance + + def find_sources(self): + return self.conn.list_objects() + + def start(self): + return self.conn.connect() + + def stop(self): + return self.conn.shutdown() + + def create(self, volume): + if not volume.key: + name = str(volume.uuid) + volume.key = self.conn.get_path_by_name(name) + utils_volume.create_volume(volume) + return volume + + def destroy(self): + pass + + def create_volume(self, volume): + pass diff --git a/virttest/virt_storage/storage_volume.py b/virttest/virt_storage/storage_volume.py new file mode 100644 index 00000000000..cbe43171de0 --- /dev/null +++ b/virttest/virt_storage/storage_volume.py @@ -0,0 +1,49 @@ +import copy +import uuid + + +class StorageVolume(object): + + def __init__(self, pool, name=None, _format="raw"): + self.name = name + self.uuid = uuid.uuid1() + self.pool = pool + self.key = None + self.format = _format + self.allocation = 0 + self.capacity = 0 + self._backing_store = None + + def info(self): + output = dict() + _info = copy.deepcopy(vars(self)) + for key, val in _info.items(): + if callable(val): + continue + output[key] = str(val) + return output + + def __str__(self): + return "%s: %s, %s" % (self.__class__.__name__, + self.name, str(self.uuid)) + + def __eq__(self, vol): + return self.uuid == vol.uuid or self.key == vol.key + + def generate_qemu_img_options(self): + options = " -f %s" % self.format + if self.format == "qcow2" and self.backing_store: + assert self.backing_store.key, "key of backing file is None: %s" % self.backing_store.name + options += " -b %s" % self.backing_store.key + return options + + @property + def backing_store(self): + return self._backing_store + + @backing_store.setter + def backing_store(self, backing): + if self.format == "qcow2": + self._backing_store = backing + else: + self._backing_store = None diff --git a/virttest/virt_storage/unit_test.py b/virttest/virt_storage/unit_test.py new file mode 100644 index 00000000000..b4046b82c5c --- /dev/null +++ b/virttest/virt_storage/unit_test.py @@ -0,0 +1,74 @@ +import pprint + +from virttest import utils_params +from virttest.virt_storage.storage_admin import sp_admin + +if __name__ == "__main__": + params = utils_params.Params() + params["storage_pools"] = "sp1 sp2 sp3" + params["storage_type_sp1"] = "directory" + params["path_sp1"] = "/tmp/avocado" + + params["storage_type_sp2"] = "iscsi-direct" + params["sources_sp2"] = "s2" + params["initiator_s2"] = "iqn.2018-01.redhat.tianxu" + params["hosts_s2"] = "h1" + params["hostname_h1"] = '10.66.10.26' + params["port_h1"] = '3260' + params["devices_s2"] = "s2d1" + params["path_s2d1"] = "iqn.2019-09.com.example:t1" + params["auth_s2"] = "au2" + params["username_au2"] = "admin" + params["password_au2"] = "password" + params["type_au2"] = "chap" + params["secret_au2"] = "sc2" + params["usage_sc2"] = "us2" + params["name_us2"] = "libvirt" + + params["storage_type_sp3"] = "nfs" + params["target_path_sp3"] = "/tmp/nfs" + params["sources_sp3"] = "s3" + params["dir_path_s3"] = "/nfs" + params["hosts_s3"] = "h3" + params["hostname_h3"] = "127.0.0.1" + + params["images"] = "img1 img2 img3 img4 img5 img6" + + params["storage_pool_img1"] = "sp1" + params["image_format_img1"] = "qcow2" + + params["storage_pool_img2"] = "sp2" + params["image_format_img2"] = "luks" + + params["storage_pool_img3"] = "sp3" + params["image_format_img3"] = "raw" + + params["storage_pool_img4"] = "sp1" + params["image_format_img4"] = "qcow2" + + params["storage_pool_img5"] = "sp1" + params["image_format_img5"] = "qcow2" + + params["storage_pool_img6"] = "sp1" + params["image_format_img6"] = "raw" + + params["image_filename_img4"] = "/tmp/b.qcow2" + params["image_filename_img5"] = "/tmp/c.qcow2" + params["image_filename_img6"] = "/tmp/d.qcow2" + params["nfs_image_name_img3"] = "img3_nfs" + + params["backing_img1"] = "img4" + params["backing_img4"] = "img5" + + pools = sp_admin.pools_define_by_params(params) + map(sp_admin.start_pool, pools) + for pool in sp_admin.list_pools(): + info = pool.info() + pprint.pprint(info) + volumes = sp_admin.volumes_define_by_params(params) + map(sp_admin.acquire_volume, volumes) + for volume in sp_admin.list_volumes(): + info = volume.info() + pprint.pprint(info) + map(sp_admin.stop_pool, pools) + map(sp_admin.destroy_pool, pools) diff --git a/virttest/virt_storage/utils/__init__.py b/virttest/virt_storage/utils/__init__.py new file mode 100644 index 00000000000..e50997fd9bc --- /dev/null +++ b/virttest/virt_storage/utils/__init__.py @@ -0,0 +1,97 @@ +import copy +import logging +import os +import re + +from virttest import data_dir +from . import fscli +from . import glustercli +from . import iscsicli +from . import nfscli +from . import rbdcli + + +def get_instance_by_params(cls, params, instance=None): + if instance is None: + instance = cls() + if not hasattr(instance, "_params"): + setattr(instance, "_params", params) + _info = copy.deepcopy(vars(instance)) + for key, val in _info.items(): + if key.startswith("_"): + continue + if val is not None: + if isinstance(val, list): + func_name = "%s_define_by_params" % key + try: + func = getattr(instance, func_name) + val = func(params) + except AttributeError: + logging.warn( + "'%s' not define in '%s'" % + (func_name, type(instance))) + continue + else: + if not hasattr(val, 'TYPE'): + continue + func_name = "%s_define_by_params" % val.TYPE + try: + func = getattr(val, func_name) + val = func(params) + except AttributeError: + logging.warn( + "'%s' not define in '%s'" % + (func_name, type(val))) + continue + else: + val = params.get(key) + setattr(instance, key, val) + + return instance + + +def set_special_opts_by_params(instance, params, key): + """ set special options for instance """ + options = params.get(key, "").split(",") + pattern = re.compile(r"(\w+)\s*=(\w+)\s*") + for option in options: + match = pattern.search(option) + if match: + key, val = match.groups() + setattr(instance, key, val) + return instance + + +def get_module_by_driver(driver): + if "iscsi" in driver: + return iscsicli + elif driver == "gluster": + return glustercli + elif driver == "nfs": + return nfscli + elif driver == "rbd": + return rbdcli + elif driver == "directory": + return fscli + raise ValueError("unsupported driver %s" % driver) + + +def get_pool_connection(pool): + driver = get_module_by_driver(pool.TYPE) + func_name = "get_pool_connection" + func = getattr(driver, func_name) + return func(pool) + + +def create_volume(volume): + driver = get_module_by_driver(volume.pool.TYPE) + func_name = "create_volume" + func = getattr(driver, func_name) + return func(volume) + + +def make_pool_dir(pool): + root_dir = data_dir.get_data_dir() + base_dir = os.path.join(root_dir, "pools") + pool_dir = os.path.join(base_dir, pool.TYPE) + os.makedirs(os.path.join(pool_dir, pool.name)) diff --git a/virttest/virt_storage/utils/fscli.py b/virttest/virt_storage/utils/fscli.py new file mode 100644 index 00000000000..92c5df3d95d --- /dev/null +++ b/virttest/virt_storage/utils/fscli.py @@ -0,0 +1,84 @@ +import logging +import os +import shutil + +from avocado.utils import process + + +def get_pool_connection(pool): + return FsCli(pool.target.path) + + +class FsCli(object): + + def __init__(self, dir_path): + self.dir_path = dir_path + self._is_exists = None + + def list_files(self, _root=None): + """List all files in top directory""" + + def _list_files(_dir): + for root, dirs, files in os.walk(_dir): + for f in files: + yield os.path.join(root, f) + for d in dirs: + _d = os.path.join(root, d) + _list_files(_d) + + root_dir = _root or self.dir_path + return _list_files(root_dir) + + @staticmethod + def get_size(f): + """Get file size""" + return os.path.getsize(f) + + @staticmethod + def get_path(f): + """Get file realpath""" + return os.path.realpath(f) + + def get_url(self, f): + """Get url schema path""" + return "file://%s" % f + + @property + def is_exists(self): + if self._is_exists is None: + self._is_exists = os.path.isdir(self.dir_path) + return self._is_exists + + def create(self): + if not self.is_exists: + os.makedirs(self.dir_path) + else: + logging.warn("Dir '%s' is exists!" % self.dir_path) + self._is_exists = True + + def remove(self): + if self.is_exists: + # os.removedirs(self.dir_path) + shutil.rmtree(self.dir_path) + self._is_exists = False + + @property + def capacity(self): + cmd = "df -k --output=size %s |tail -n1" % self.dir_path + output = process.system_output(cmd, shell=True) + return int(output) + + @property + def available(self): + cmd = "df -k --output=avail %s |tail -n1" % self.dir_path + output = process.system_output(cmd, shell=True) + return int(output) + + @property + def allocation(self): + cmd = "df -k --output=used %s |tail -n1" % self.dir_path + output = process.system_output(cmd, shell=True) + return int(output) + + def get_path_by_name(self, name): + return os.path.join(self.dir_path, name) diff --git a/virttest/virt_storage/utils/glustercli.py b/virttest/virt_storage/utils/glustercli.py new file mode 100644 index 00000000000..e0afab250fe --- /dev/null +++ b/virttest/virt_storage/utils/glustercli.py @@ -0,0 +1,90 @@ +import os + +from gluster import gfapi + + +def getPoolConnection(pool): + source = pool.sources[0] + root_dir = source.dir_path + hosts = map(lambda x: x.hostname, source.hosts) + port = source.hosts[0].port + volume = source.name + return GlusterCli(hosts, volume, port, root_dir) + + +class GlusterCli(object): + + def __init__(self, hosts, volume_name, port, root_dir=None): + self.root_dir = root_dir + self.volume = gfapi.Volume(hosts, volume_name, port) + self.hosts = hosts + self._is_mounted = None + self.volume_name = volume_name + + def list_files(self, root_dir=None): + + def _list_files(_dir): + for root, dirs, files in self.volume.walk(_dir): + for f in files: + yield os.path.join(root, f) + for d in dirs: + _list_files(os.path.join(root, d)) + + self.mount() + root_dir = root_dir or self.root_dir + if not self.volume.isdir(root_dir): + self.volume.makedirs(root_dir) + return _list_files(root_dir) + + def get_path(self, path): + return "gluster://%s/%s" % (self.hosts[0], path) + + def get_path_by_name(self, name): + return "gluster://%s/%s/%s/%s" % ( + self.hosts[0], self.volume_name, self.root_dir, name) + + def get_size(self, path): + return self.volume.getsize(path) / 1024 + + def refresh(self): + if self.volume.mounted: + self.volume.umount() + self.volume.mount() + self.volume.umount() + + def mount(self): + if not self.is_mounted: + self.volume.mount() + self._is_mounted = True + + def umount(self): + if self.is_mounted: + self.volume.umount() + self._is_mounted = False + + @property + def is_mounted(self): + if self._is_mounted is None: + self._is_mounted = self.volume.mounted + return self._is_mounted + + @property + def capacity(self): + # FixMe: + # how to get capacity of gluster volume? + # I assume we have 10G gluster now. + return 10485760 + + @property + def available(self): + return self.capacity - self.available + + @property + def allocation(self): + size = 0 + for f in self.list_files("/"): + size += self.get_size(f) + return size + + def remove(self): + self.volume.rmtree(self.root_dir) diff --git a/virttest/virt_storage/utils/iscsicli.py b/virttest/virt_storage/utils/iscsicli.py new file mode 100644 index 00000000000..2b117eaf1ad --- /dev/null +++ b/virttest/virt_storage/utils/iscsicli.py @@ -0,0 +1,146 @@ +import re + +from avocado.utils import genio +from avocado.utils import process +from avocado.utils import service + + +def get_pool_connection(pool): + source = pool.sources[0] + host = source.hosts[0].hostname + port = source.hosts[0].port or 3260 + target = source.devices[0].path + initiator = source.initiator + auth = source.auth + return IscsiCli(host, port, target, initiator, auth) + + +class IscsiCli(object): + dev_root = '/dev/disk/by-path/' + initiator_file = "/etc/iscsi/initiatorname.iscsi" + + def __init__(self, host, port=3260, + target=None, initiator=None, auth=None): + self.portal = "%s:%s" % (host, port) + self.target = target + self.initiator = initiator + self.auth = auth + self._is_logged_in = False + if self.auth: + if self.auth.type == "chap": + cmd = ("iscsiadm -m node --targetname %s -p %s -o update -n discovery.sendtargets.auth.authmethod" + " -v %s" % (self.target, self.portal, "CHAP")) + process.system(cmd, shell=True, verbose=True) + if self.auth.username: + cmd = "iscsiadm -m node --targetname %s -p %s -o update -n node.session.auth.username -v %s" % ( + self.target, self.portal, self.auth.username) + process.system(cmd, shell=True, verbose=True) + if self.auth.password: + cmd = "iscsiadm -m node --targetname %s -p %s -o update -n node.session.auth.password -v %s" % ( + self.target, self.portal, self.auth.password) + process.system(cmd, shell=True, verbose=True) + if self.initiator: + context = "InitiatorName=%s" % self.initiator + genio.write_one_line(self.initiator_file, context) + service.SpecificServiceManager("iscsi").restart() + + def discovery_all_targets(self): + targets = list() + cmd = "iscsiadm -m discovery --type sendtargets -p %s" % self.portal + for line in process.system_output( + cmd, shell=True, verbose=True).splitlines(): + target = line.split()[1] + targets.append(target) + return targets + + def login(self): + if not self.is_logged: + targets = self.discovery_all_targets() + if self.target is None and targets: + self.target = targets[0] + assert self.target in self.discovery_all_targets( + ), "No target '%s' not discovey" % self.target + cmd = "iscsiadm -m node --targetname %s -p %s -l" % ( + self.target, self.portal) + process.system(cmd, shell=True, verbose=True) + self._is_logged_in = True + + def list_disks(self): + self.login() + cmd = "ls %s" % self.dev_root + dev_regex = r"ip-%s-iscsi-%s-lun-\d+" % (self.portal, self.target) + output = process.system_output(cmd, shell=True, verbose=True) + dev_pattern = re.compile(dev_regex, re.M | re.I) + return map(lambda x: "%s/%s" % + (self.dev_root, x), dev_pattern.findall(output)) + + def list_luns(self): + self.login() + cmd = "iscsiadm -m session -P 3" + output = process.system_output(cmd, shell=True, verbose=True) + lun_pattern = re.compile( + r"scsi[\w\s]+Channel[\w\s]+Id[\w\s]+Lun:\s+(\d+)", + re.M | re.I) + return lun_pattern.findall(output) + + def logout(self): + if self.is_logged: + cmd = "iscsiadm -m node --targetname %s -p %s -u" % ( + self.target, self.portal) + process.system(cmd, shell=True, verbose=True) + self._is_logged_in = False + + def get_host_path(self, lun): + return "%s/ip-%s-iscsi-%s-lun-%s" % (self.dev_root, + self.portal, self.target, lun) + + def get_size(self, lun): + path = self.get_host_path(lun) + cmd = "blockdev --getsize64 %s" % path + out = process.system_output(cmd, shell=True, verbose=True) + return int(out) / 1024 + + def get_path(self, lun=0): + secret = "" + if self.auth: + username = self.auth.username + if username: + secret += username + password = self.auth.password + if password: + secret += ":%s" % password + if secret: + secret += "@" + return "iscsi://%s%s:%s/%s" % (secret, + self.portal, + self.target, + lun) + + @property + def is_logged(self): + if self._is_logged_in is None: + cmd = "iscsiadm -m session " + output = process.system_output( + cmd, shell=True, ignore_status=True, verbose=True) + for line in output.splitlines(): + if self.portal in line and self.target in line: + self._is_logged_in = True + break + else: + self._is_logged_in = False + return self._is_logged_in + + @property + def capacity(self): + size = 0 + for lun in self.list_luns(): + size += self.get_size(lun) + return size + + @property + def allocation(self): + return 0 + + @property + def available(self): + return self.capacity - self.allocation diff --git a/virttest/virt_storage/utils/nfscli.py b/virttest/virt_storage/utils/nfscli.py new file mode 100644 index 00000000000..026187fe7c5 --- /dev/null +++ b/virttest/virt_storage/utils/nfscli.py @@ -0,0 +1,64 @@ +import os +import tempfile + +from avocado.utils import process + +import fscli + + +def get_pool_connection(pool): + source = pool.sources[0] + host = source.hosts[0].hostname + dir_path = source.dir_path + target = pool.target.path + return NfsCli(host, dir_path, target) + + +class NfsCli(fscli.FsCli): + + def __init__(self, host, dir_path, target=None): + self.host = host + self.export_dir = dir_path + self._target = target or tempfile.mkdtemp() + self._src = "%s:%s" % (host, dir_path) + self._is_mounted = None + self._is_exists = None + super(NfsCli, self).__init__(self._target) + + @property + def is_mounted(self): + return self._is_mounted + + def umount(self): + if self.is_mounted: + cmd = "umount -f %s" % self._target + process.system(cmd, shell=True, ignore_status=False) + os.removedirs(self._target) + self._is_mounted = False + + def mount(self): + print("============ %s" % self.is_exists) + assert self.is_exists, "'%s' not export in host '%s'" % ( + self.export_dir, self.host) + if not os.path.isdir(self._target): + os.makedirs(self._target) + cmd = "mount -t nfs %s %s" % (self._src, self._target) + process.system(cmd, shell=True, ignore_status=False) + self._is_mounted = True + + def list_files(self, top=None): + if not self.is_mounted: + self.mount() + return super(NfsCli, self).list_files(top) + + def get_url(self, f): + return f.replace(self._target, "nfs://%s" % self._src) + + @property + def is_exists(self): + if self._is_exists is None: + cmd = "showmount --no-headers -e %s |grep %s" % ( + self.host, self.export_dir) + ret = not process.system(cmd, shell=True, ignore_status=True) + self._is_exists = ret + return self._is_exists diff --git a/virttest/virt_storage/utils/rbdcli.py b/virttest/virt_storage/utils/rbdcli.py new file mode 100644 index 00000000000..2e096888029 --- /dev/null +++ b/virttest/virt_storage/utils/rbdcli.py @@ -0,0 +1,80 @@ +import rados + + +def get_pool_connection(pool): + source = pool.sources[0] + conf = {"keyring": source.keyring} + conffile = source.conf_file + pool_name = source.name + return RbdCli(conffile, conf, pool_name) + + +class RbdCli(object): + def __init__(self, conffile, conf, pool): + self.cluster = rados.Rados(conffile=conffile, conf=conf) + self._pool = pool + self._is_connect = False + + def list_objects(self, pool=None): + out = list() + self.connect() + _pool = pool or self._pool + if self.cluster.pool_exists(_pool): + ioctx = self.cluster.open_ioctx(_pool) + for obj in ioctx.list_objects(): + out.append(obj) + return out + + def list_all_objects(self): + objs = list() + for pool in self.list_pools(): + objs += self.list_objects(pool) + return objs + + def list_pools(self): + return self.cluster.list_pools() + + def get_path(self, o): + return "rbd:%s/%s" % (self._pool, o) + + def get_url(self, o): + return self.get_path(o) + + @staticmethod + def get_size(o): + return o.stat()["num_bytes"] + + def connect(self): + if not self.is_connected: + self.cluster.connect() + self._is_connect = True + + def shutdown(self): + if self.is_connected: + self.cluster.shutdown() + self._is_connect = False + + @property + def is_connected(self): + return self._is_connect + + @property + def capacity(self): + self.connect() + stats = self.cluster.get_cluster_stats() + return stats['kb'] * 1024 + + @property + def available(self): + self.connect() + stats = self.cluster.get_cluster_stats() + return stats['kb_avail'] * 1024 + + @property + def allocation(self): + self.connect() + stats = self.cluster.get_cluster_stats() + return stats['kb_used'] * 1024 + + def get_path_by_name(self, name): + pass diff --git a/virttest/virt_storage/utils/state.py b/virttest/virt_storage/utils/state.py new file mode 100644 index 00000000000..b7c3989fae7 --- /dev/null +++ b/virttest/virt_storage/utils/state.py @@ -0,0 +1,25 @@ +from transitions import Machine + + +def register_pool_state_machine(instance): + states = ['dead', 'ready', 'running'] + transitions = [ + {'trigger': 'start_pool', + 'source': ['dead', 'ready'], + 'dest': 'running', + 'after': 'start'}, + {'trigger': 'stop_pool', + 'source': 'running', + 'dest': 'ready', + 'after': 'stop'}, + {'trigger': 'destroy_pool', + 'source': ['stop', 'ready'], + 'dest': 'dead', + 'after': 'destroy'} + ] + machine = Machine( + model=instance, + states=states, + transitions=transitions, + initial="dead") + return machine diff --git a/virttest/virt_storage/utils/utils_volume.py b/virttest/virt_storage/utils/utils_volume.py new file mode 100644 index 00000000000..df6767160e7 --- /dev/null +++ b/virttest/virt_storage/utils/utils_volume.py @@ -0,0 +1,30 @@ +import json + +from avocado.utils import genio +from avocado.utils import process + + +def get_volume_info(url): + cmd = "qemu-img info {url} --output=json".format(url=url) + out = process.system_output(cmd, shell=True, ignore_status=False) + return json.loads(out) + + +def get_volume_capacity(url): + info = get_volume_info(url) + return info.get("virtual-size"), info.get("actual-size") + + +def wipe_volume(volume): + cmd = "qemu-img create %s %s" % (volume.url, volume.size) + return process.system(cmd, shell=True, ignore_status=False) + + +def create_volume(volume): + options = volume.generate_qemu_img_options() + cmd = "qemu-img create %s %s %sK" % (options, volume.key, volume.capacity) + process.system(cmd, shell=True, ignore_status=False) + + +def write_volume_file(volume): + return genio.write_one_line(volume.entry_point, volume.url) diff --git a/virttest/virt_storage/virt_device.py b/virttest/virt_storage/virt_device.py new file mode 100644 index 00000000000..a76c58c044b --- /dev/null +++ b/virttest/virt_storage/virt_device.py @@ -0,0 +1,44 @@ +from virttest.virt_storage import utils + + +class VirtStorageAdapter(object): + TYPE = "adapter" + + def __init__(self): + self.name = None + self.type = None + self.wwnn = None + self.wwpn = None + self.parent = None + self.parent_wwn = None + self.parent_wwpn = None + self.parent_fabric_wwn = None + self.managed = None + self.parent_addr = None + + @classmethod + def adapter_define_by_params(cls, params): + return utils.get_instance_by_params(cls, params) + + +class VirtStorageDevice(object): + TYPE = "device" + + def __init__(self, path=None): + self.path = path + + @classmethod + def device_define_by_params(cls, params): + return utils.get_instance_by_params(cls, params) + + +class VirtStorageHost(object): + TYPE = "host" + + def __init__(self, hostname=None, port=None): + self.hostname = hostname + self.port = port + + @classmethod + def host_define_by_params(cls, params): + return utils.get_instance_by_params(cls, params) diff --git a/virttest/virt_storage/virt_encryption.py b/virttest/virt_storage/virt_encryption.py new file mode 100644 index 00000000000..463045afd75 --- /dev/null +++ b/virttest/virt_storage/virt_encryption.py @@ -0,0 +1,81 @@ +from virttest.virt_storage import utils + + +class VirtCipher(object): + TYPE = "cipher" + + def __init__(self): + self.name = None + self.size = None + self.mode = None + self.hash = None + + @classmethod + def cipher_define_by_params(cls, params): + return utils.get_instance_by_params(cls, params) + + +class VirtIvgen(object): + TYPE = "ivgen" + + def __init__(self): + self.name = None + self.hash = None + + @classmethod + def ivgen_define_by_params(cls, params): + return utils.get_instance_by_params(cls, params) + + +class VirtSecretUsage(object): + TYPE = "usage" + + def __init__(self, _type=None, name=None, volume=None): + self.type = _type + self.name = name + self.volume = volume + + @classmethod + def usage_define_by_params(cls, params): + return utils.get_instance_by_params(cls, params) + + +class VirtSecret(object): + TYPE = "secret" + + def __init__(self, uuid=None, usage=None): + self.uuid = uuid + self.usage = usage or VirtSecretUsage() + + @classmethod + def secret_define_by_params(cls, params): + return utils.get_instance_by_params(cls, params) + + +class VirtEncryption(object): + TYPE = "encryption" + + def __init__(self): + self.format = None + self.secret = VirtSecret() + self.cipher = None + self.ivgen = None + + @classmethod + def encryption_define_by_params(cls, params): + return utils.get_instance_by_params(cls, params) + + +class VirtStorageAuth(object): + TYPE = "auth" + mandatorys = ["username"] + + def __init__(self, _type=None, username=None, password=None, secret=None): + self.type = _type + self.username = username + self.password = password + self.secret = secret or VirtSecret() + + @classmethod + def auth_define_by_params(cls, params): + return utils.get_instance_by_params(cls, params) diff --git a/virttest/virt_storage/virt_permission.py b/virttest/virt_storage/virt_permission.py new file mode 100644 index 00000000000..904a4c0ab79 --- /dev/null +++ b/virttest/virt_storage/virt_permission.py @@ -0,0 +1,15 @@ +from virttest.virt_storage import utils + + +class VirtStoragePerm(object): + TYPE = "permission" + + def __init__(self, mode=None, uid=None, gid=None, label=None): + self.mode = mode + self.uid = uid + self.gid = gid + self.label = label + + @classmethod + def permission_define_by_params(cls, params): + return utils.get_instance_by_params(cls, params) diff --git a/virttest/virt_storage/virt_source.py b/virttest/virt_storage/virt_source.py new file mode 100644 index 00000000000..3ce506aa4cd --- /dev/null +++ b/virttest/virt_storage/virt_source.py @@ -0,0 +1,53 @@ +from virttest.virt_storage import utils +from virttest.virt_storage import virt_device +from virttest.virt_storage import virt_encryption + + +class VirtStoragePoolSource(object): + TYPE = "source" + + def __init__(self, name=None): + self.name = name + self.hosts = [] + self.devices = [] + self.dir_path = None + self.vendor = None + self.product = None + self.protocol = None + self.initiator = None + self.format_type = None + self.auth = virt_encryption.VirtStorageAuth() + self.keyring = None + self.conf_file = None + self.adapter = virt_device.VirtStorageAdapter() + self._params = None + + def hosts_define_by_params(self, params=None): + if params is None: + params = self._params + hosts = list() + for item in params.objects("hosts"): + _params = params.object_params(item) + host = virt_device.VirtStorageHost.host_define_by_params(_params) + hosts.append(host) + self.hosts = hosts + return hosts + + def devices_define_by_params(self, params=None): + devices = list() + if not params: + params = self._params + for item in params.objects("devices"): + _params = params.object_params(item) + device = virt_device.VirtStorageDevice.device_define_by_params( + _params) + devices.append(device) + self.devices = devices + return devices + + @classmethod + def source_define_by_params(cls, params): + return utils.get_instance_by_params(cls, params) + + def __str__(self): + return "%s: %s" % (self.__class__.__name__, self.name) diff --git a/virttest/virt_storage/virt_target.py b/virttest/virt_storage/virt_target.py new file mode 100644 index 00000000000..74dc57f4ecd --- /dev/null +++ b/virttest/virt_storage/virt_target.py @@ -0,0 +1,20 @@ +from virttest.virt_storage import utils +from virttest.virt_storage import virt_encryption +from virttest.virt_storage import virt_permission + + +class VirtStoragePoolTarget(object): + TYPE = "target" + + def __init__(self): + self.path = None + self.format = None + self.perm = virt_permission.VirtStoragePerm() + self.encryption = virt_encryption.VirtEncryption() + + @classmethod + def target_define_by_params(cls, params): + return utils.get_instance_by_params(cls, params) + + def __str__(self): + return "%s: %s" % (self.__class__.__name__, self.path)