From mboxrd@z Thu Jan 1 00:00:00 1970 From: ckulkarnilinux@gmail.com (Chaitanya Kulkarni) Date: Tue, 24 Oct 2017 18:30:33 -0700 Subject: [PATCH V2 16/46] nvmftests-nvmf: add support for host subsystem In-Reply-To: <1508895063-6280-1-git-send-email-ckulkarnilinux@gmail.com> References: <1508895063-6280-1-git-send-email-ckulkarnilinux@gmail.com> Message-ID: <1508895063-6280-17-git-send-email-ckulkarnilinux@gmail.com> From: Chaitanya Kulkarni This adds a new class to handle the host subsystem operations. Signed-off-by: Chaitanya Kulkarni --- .../nvmftests/nvmf/host/host_subsystem.py | 527 +++++++++++++++++++++ 1 file changed, 527 insertions(+) create mode 100644 tools/testing/selftests/nvmftests/nvmf/host/host_subsystem.py diff --git a/tools/testing/selftests/nvmftests/nvmf/host/host_subsystem.py b/tools/testing/selftests/nvmftests/nvmf/host/host_subsystem.py new file mode 100644 index 0000000..e32d952 --- /dev/null +++ b/tools/testing/selftests/nvmftests/nvmf/host/host_subsystem.py @@ -0,0 +1,527 @@ +# Copyright (c) 2016-2017 Western Digital Corporation or its affiliates. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# Author: Chaitanya Kulkarni +# +""" Represents NVMe Over Fabric Host Controller. +""" + +import re +import os +import stat +import time +import random +import string +import subprocess +from natsort import natsorted + +from utils.const import Const +from utils.shell import Cmd +from utils.log import Log +from nvmf.host.host_ns import NVMFHostNamespace + + +class NVMFHostController(object): + """ + Represents a host controller. + + - Attributes : + - nqn : ctrl nqn. + - ctrl_dev : controller device. + - ctrl_dict : controller attributes. + - ns_list : list of namespaces. + - ns_dev_list : namespace device list. + - transport : transport type. + """ + def __init__(self, nqn, transport): + self.nqn = nqn + self.ctrl_dev = None + self.ctrl_dict = {} + self.ns_list = [] + self.ns_dev_list = [] + self.transport = transport + self.ns_list_index = 0 + self.logger = Log.get_logger(__name__, 'host_subsystem') + + def __iter__(self): + self.ns_list_index = 0 + return self + + def __next__(self): + index = self.ns_list_index + self.ns_list_index += 1 + if (len(self.ns_list) > index): + return self.ns_list[index] + raise StopIteration + + def next(self): + """ Iterator next function """ + return self.__next__() + + def run_io_all_ns(self, iocfg): + """ Start IOs on all the namespaces of this controller parallelly. + - Args : + - iocfg : io configuration. + - Returns : + - True on success, False on failure. + """ + ret = True + for ns in iter(self): + try: + if ns.start_io(iocfg) is False: + self.logger.error("start IO " + ns.ns_dev + ".") + ret = False + break + self.logger.info("start IO " + ns.ns_dev + " SUCCESS.") + except StopIteration: + break + + return ret + + def wait_io_all_ns(self): + """ Wait until workqueue is empty. + - Args : + - None. + - Returns : + - True if namespace wait IO is successful, False otherwise. + """ + for ns in iter(self): + try: + ret = ns.wait_io() + if ret is False: + return False + except StopIteration: + break + return True + + def run_io_seq(self, iocfg): + """ Exercise IOs on each namespace. + - Args : + - iocfg : io configuration. + - Returns : + - True on success, False on failure. + """ + ret = True + for ns in iter(self): + try: + if ns.start_io(iocfg) is False: + self.logger.error("start IO " + ns.ns_dev + ".") + ret = False + break + ret = ns.wait_io() + except StopIteration: + break + return ret + + def run_mkfs_seq(self, fs_type): + """ Run mkfs. + - Args : + - fs_type : file system type. + - Returns : + - True on success, False on failure. + """ + ret = True + for ns in iter(self): + try: + if ns.mkfs(fs_type) is False: + ret = False + break + except StopIteration: + break + return ret + + def run_fs_ios(self, iocfg): + """ Run IOs on mounted file system. + - Args : + - iocfg : io configuration. + - Returns : + - True on success, False on failure. + """ + ret = True + for ns in iter(self): + try: + if ns.run_fs_ios(iocfg) is False: + ret = False + break + except StopIteration: + break + return ret + + def run_io_random(self, iocfg): + """ Select the namespce randomely and wait for the IOs completion, + repeat this for all the namespaces. + - Args : + - iocfg : io configuration. + - Returns : + - True on success, False on failure. + """ + num_list = range(0, len(self.ns_list)) + + for i in range(0, len(self.ns_list)): + random.shuffle(num_list) + ns_id = num_list.pop() + + host_ns = self.ns_list[ns_id] + if host_ns.start_io(iocfg) is False: + return False + host_ns.wait_io() + + return True + + def __ctrl_set_attr__(self, attr): + """ Set host controller attribute. + - Args : + - attr : sysfs attribute to set. + - Returns : + - True on success, False on failure. + """ + sysfs_path = "/sys/class/nvme-fabrics/ctl/" + cmd = "echo 1 >" + sysfs_path + self.ctrl_dev.split('/')[-1] \ + + "/" + attr + return Cmd.exec_cmd(cmd) + + def ctrl_rescan(self): + """ Issue controller rescan. + - Args : + - None. + - Returns : + - True on success, False on failure. + """ + return self.__ctrl_set_attr__("rescan_controller") + + def ctrl_reset(self): + """ Issue controller reset. + - Args : + - None. + - Returns : + - True on success, False on failure. + """ + return self.__ctrl_set_attr__("reset_controller") + + def run_smart_log(self, nsid="0xFFFFFFFF"): + """ Wrapper for nvme smart-log command. + - Args : + - nsid : namespace id for smart log, defaults to ctrl. + - Returns: + - True on success, False on failure. + """ + smart_log_cmd = "nvme smart-log " + self.ctrl_dev + " -n " + str(nsid) + self.logger.info(smart_log_cmd) + proc = subprocess.Popen(smart_log_cmd, + shell=True, + stdout=subprocess.PIPE) + err = proc.wait() + if err != 0: + self.logger.error("nvme smart log failed.") + return False + + for line in proc.stdout: + if "data_units_read" in line: + temp_str = line.split(":")[Const.SMART_LOG_VALUE].strip() + data_units_read = string.replace(temp_str, ",", "") + if "data_units_written" in line: + temp_str = line.split(":")[Const.SMART_LOG_VALUE].strip() + data_units_written = string.replace(temp_str, ",", "") + if "host_read_commands" in line: + temp_str = line.split(":")[Const.SMART_LOG_VALUE].strip() + host_read_commands = string.replace(temp_str, ",", "") + if "host_write_commands" in line: + temp_str = line.split(":")[Const.SMART_LOG_VALUE].strip() + host_write_commands = string.replace(temp_str, ",", "") + + self.logger.info("data_units_read " + data_units_read) + self.logger.info("data_units_written " + data_units_written) + self.logger.info("host_read_commands " + host_read_commands) + self.logger.info("host_write_commands " + host_write_commands) + return True + + def smart_log(self): + """ Execute smart-log command. + - Args : + - None. + - Returns : + - True on success, False on failure. + """ + # Run smart log on controller + self.run_smart_log() + i = 1 + for ns in iter(self): + try: + if self.run_smart_log(i) is False: + return False + i += 1 + except StopIteration: + break + return True + + def validate_sysfs_ns(self): + """ Validate sysfs entries for the host controller and namespace(s). + - Args : + - None. + - Returns : + - True on success, False on failure. + """ + ctrl_bdev = self.ctrl_dev.split("/")[Const.CTRL_BLK_FILE_NAME] + # Validate ctrl in the sysfs + cmd = "basename $(dirname $(grep -ls " + self.nqn + \ + " /sys/class/nvme-fabrics/ctl/*/subsysnqn))" + proc = subprocess.Popen(cmd, + shell=True, + stdout=subprocess.PIPE) + for line in proc.stdout: + line = line.strip('\n') + # compare nvmeN in /dev/nvmeN in sysfs + if line != ctrl_bdev: + self.logger.error("host ctrl " + self.ctrl_dev + + " not present.") + return False + dir_list = os.listdir("/sys/class/nvme-fabrics/ctl/" + ctrl_bdev + "/") + + pat = re.compile("^" + ctrl_bdev + "+n[0-9]+$") + for line in dir_list: + line = line.strip('\n') + if pat.match(line): + if "/dev/" + line not in self.ns_dev_list: + self.logger.error("ns " + line + " not found in sysfs.") + return False + + self.logger.info("sysfs entries for ctrl and ns created successfully.") + return True + + def build_ns_list(self): + """ Generate next available controller and namespace id on the fly. + Build the ns list for this controller. + - Args : + - None. + - Returns : + - ctrl and ns list on success, None on failure. + """ + ctrl = Const.XXX + ns_list = [] + try: + dev_list = os.listdir("/dev/") + except Exception, err: + self.logger.error(str(err) + ".") + return None, None + dev_list = natsorted(dev_list, key=lambda y: y.lower()) + # we assume that atleast one namespace is created on target + # find ctrl + pat = re.compile("^nvme[0-9]+$") + for line in dev_list: + line = line.strip('\n') + if pat.match(line): + ctrl = line + + if ctrl == Const.XXX: + self.logger.error("controller '/dev/nvme*' not found.") + return None, None + # allow namespaces to appear in the /dev/ + time.sleep(2) + # find namespace(s) associated with ctrl + try: + dir_list = os.listdir("/dev/") + except Exception, err: + self.logger.error(str(err)) + return None, None + pat = re.compile("^" + ctrl + "+n[0-9]+$") + for line in dir_list: + line = line.strip('\n') + if pat.match(line): + self.logger.info("Generated namespace name /dev/" + line + ".") + ns_list.append("/dev/" + line) + + if len(ns_list) == 0: + self.logger.error("host ns not found for ctrl " + ctrl + ".") + return None, None + + ctrl = "/dev/" + ctrl + return ctrl, ns_list + + def init_ns(self): + """ Initialize and build namespace list and validate sysfs entries. + - Args : + - None. + - Returns : + - True on success, False on failure. + """ + self.logger.info("Expecting following namespaces " + + str(self.ns_dev_list) + ".") + for ns_dev in self.ns_dev_list: + if not stat.S_ISBLK(os.stat(ns_dev).st_mode): + self.logger.error("expected block dev " + ns_dev + ".") + return False + + self.logger.info("Found NS " + ns_dev + ".") + host_ns = NVMFHostNamespace(ns_dev) + host_ns.init() + self.ns_list.append(host_ns) + # allow sysfs entries to populate + time.sleep(1) + if self.validate_sysfs_ns() is False: + self.logger.error("unable to verify sysfs entries.") + return False + self.logger.info("Host sysfs entries are validated.") + return True + + def validate_fabric_ctrl(self): + """ Validate this is a fabric controller. + - Args : + - None. + - Returns : + - True on success, False on failure. + """ + + if not stat.S_ISCHR(os.stat(self.ctrl_dev).st_mode): + self.logger.error("failed to find char device for host ctrl.") + + cmd = "find /sys/devices -name " + self.ctrl_dev.split("/")[-2] \ + + " | grep nvme-fabric" + self.logger.info(cmd) + return Cmd.exec_cmd(cmd) + + def init_ctrl(self): + """ Initialize controller and build controller attributes. + - Args : + - None. + - Returns : + - True on success, False on failure. + """ + # initialize nqn and transport + cmd = "echo \"transport=" + self.transport + ",nqn=" + \ + self.nqn + "\" > /dev/nvme-fabrics" + self.logger.info("Host Connect command : " + cmd) + if Cmd.exec_cmd(cmd) is False: + self.logger.info("ERROR : host connect command failed") + return False + self.ctrl_dev, self.ns_dev_list = self.build_ns_list() + + if self.validate_fabric_ctrl() is False or self.id_ctrl() is False: + return False + + return self.init_ns() + + def id_ctrl(self): + """ Wrapper for executing id-ctrl command. + - Args : + - None. + - Returns : + - True on success, False on failure. + """ + id_ctrl_cmd = "nvme id-ctrl " + self.ctrl_dev + proc = subprocess.Popen(id_ctrl_cmd, + shell=True, + stdout=subprocess.PIPE) + err = proc.wait() + if err != 0: + self.logger.error("nvme id-ctrl failed.") + return False + + for line in proc.stdout: + if line.startswith('subnqn') or \ + line.startswith('NVME Identify Controller'): + continue + if line.startswith('ps ') or line.strip().startswith('rwt'): + continue + key, val = line.split(':') + self.ctrl_dict[key.strip()] = val.strip() + + self.logger.info("ID controller :- ") + self.logger.info(self.ctrl_dict) + return True + + def id_ns(self): + """ Wrapper for executing id-ns command on all namespaces of this + controller. + - Args : + - None. + - Returns : + - True on success, False on failure. + """ + for host_ns in iter(self): + if host_ns.id_ns() is False: + return False + return True + + def ns_descs(self): + """ Wrapper for executing ns_descs command on all namespaces of this + controller. + - Args : + - None. + - Returns : + - True on success, False on failure. + """ + for host_ns in iter(self): + if host_ns.ns_descs() is False: + return False + return True + + def get_ns_id(self): + """ Wrapper for executing get-ns-id command on all namespaces of this + controller. + - Args : + - None. + - Returns : + - True on success, False on failure. + """ + for host_ns in iter(self): + if host_ns.get_ns_id() is False: + return False + return True + + def generate_next_ns_id(self): + """ Return next namespace id. + - Args : + - None. + - Returns : + - next namespace id. + """ + return len(self.ns_list) + 1 + + def delete(self): + """ Delete subsystem and associated namespace(s). + - Args : + - None. + - Returns : + - True on success, False on failure. + """ + self.logger.info("Deleting subsystem " + self.nqn + ".") + for host_ns in self.ns_list: + host_ns.delete() + cmd = "dirname $(grep -ls " + self.nqn + \ + " /sys/class/nvme-fabrics/ctl/*/subsysnqn)" + try: + proc = subprocess.Popen(cmd, + shell=True, + stdout=subprocess.PIPE) + for line in proc.stdout: + line = line.strip('\n') + if not os.path.isdir(line): + self.logger.error("host ctrl dir " + self.nqn + + " not present.") + return False + cmd = "nvme disconnect -n " + self.nqn + self.logger.info("disconnecting : " + cmd) + ret = Cmd.exec_cmd(cmd) + if ret is False: + self.logger.error("failed to delete ctrl " + + self.nqn + ".") + return False + except Exception, err: + self.logger.error(str(err) + ".") + return False + + return True -- 1.8.3.1