[PATCH] selftests: nvmftests: unittest framework for NVMe Over Fabrics.
Chaitanya Kulkarni
chaitanya.kulkarni at hgst.com
Sun Jun 11 20:26:10 PDT 2017
This adds NVMe Over Fabrics unittests framework.
---
tools/testing/selftests/nvmftests/Makefile | 45 +++
tools/testing/selftests/nvmftests/README.md | 140 +++++++
tools/testing/selftests/nvmftests/host.py | 254 +++++++++++++
tools/testing/selftests/nvmftests/host_ns.py | 285 ++++++++++++++
.../testing/selftests/nvmftests/host_subsystem.py | 420 +++++++++++++++++++++
tools/testing/selftests/nvmftests/loop.json | 248 ++++++++++++
tools/testing/selftests/nvmftests/loopback.py | 116 ++++++
tools/testing/selftests/nvmftests/nvmf_test.py | 85 +++++
.../selftests/nvmftests/nvmf_test_logger.py | 50 +++
tools/testing/selftests/nvmftests/port.py | 139 +++++++
tools/testing/selftests/nvmftests/target.py | 209 ++++++++++
tools/testing/selftests/nvmftests/target_ns.py | 132 +++++++
.../selftests/nvmftests/target_subsystem.py | 148 ++++++++
.../nvmftests/test_nvmf_create_delete_fabric.py | 72 ++++
.../selftests/nvmftests/test_nvmf_create_host.py | 67 ++++
.../selftests/nvmftests/test_nvmf_create_target.py | 59 +++
.../selftests/nvmftests/test_nvmf_host_template.py | 69 ++++
.../selftests/nvmftests/test_nvmf_id_ctrl.py | 69 ++++
.../testing/selftests/nvmftests/test_nvmf_id_ns.py | 69 ++++
.../testing/selftests/nvmftests/test_nvmf_mkfs.py | 70 ++++
.../selftests/nvmftests/test_nvmf_parallel_io.py | 71 ++++
.../selftests/nvmftests/test_nvmf_random_io.py | 71 ++++
.../selftests/nvmftests/test_nvmf_simple_io.py | 71 ++++
.../selftests/nvmftests/test_nvmf_smart_log.py | 69 ++++
.../nvmftests/test_nvmf_target_template.py | 64 ++++
25 files changed, 3092 insertions(+)
create mode 100644 tools/testing/selftests/nvmftests/Makefile
create mode 100644 tools/testing/selftests/nvmftests/README.md
create mode 100644 tools/testing/selftests/nvmftests/host.py
create mode 100644 tools/testing/selftests/nvmftests/host_ns.py
create mode 100644 tools/testing/selftests/nvmftests/host_subsystem.py
create mode 100644 tools/testing/selftests/nvmftests/loop.json
create mode 100644 tools/testing/selftests/nvmftests/loopback.py
create mode 100644 tools/testing/selftests/nvmftests/nvmf_test.py
create mode 100644 tools/testing/selftests/nvmftests/nvmf_test_logger.py
create mode 100644 tools/testing/selftests/nvmftests/port.py
create mode 100644 tools/testing/selftests/nvmftests/target.py
create mode 100644 tools/testing/selftests/nvmftests/target_ns.py
create mode 100644 tools/testing/selftests/nvmftests/target_subsystem.py
create mode 100644 tools/testing/selftests/nvmftests/test_nvmf_create_delete_fabric.py
create mode 100644 tools/testing/selftests/nvmftests/test_nvmf_create_host.py
create mode 100644 tools/testing/selftests/nvmftests/test_nvmf_create_target.py
create mode 100644 tools/testing/selftests/nvmftests/test_nvmf_host_template.py
create mode 100644 tools/testing/selftests/nvmftests/test_nvmf_id_ctrl.py
create mode 100644 tools/testing/selftests/nvmftests/test_nvmf_id_ns.py
create mode 100644 tools/testing/selftests/nvmftests/test_nvmf_mkfs.py
create mode 100644 tools/testing/selftests/nvmftests/test_nvmf_parallel_io.py
create mode 100644 tools/testing/selftests/nvmftests/test_nvmf_random_io.py
create mode 100644 tools/testing/selftests/nvmftests/test_nvmf_simple_io.py
create mode 100644 tools/testing/selftests/nvmftests/test_nvmf_smart_log.py
create mode 100644 tools/testing/selftests/nvmftests/test_nvmf_target_template.py
diff --git a/tools/testing/selftests/nvmftests/Makefile b/tools/testing/selftests/nvmftests/Makefile
new file mode 100644
index 0000000..c4a929d1
--- /dev/null
+++ b/tools/testing/selftests/nvmftests/Makefile
@@ -0,0 +1,45 @@
+###############################################################################
+#
+# Makefile : Allows user to run testcases, generate documentation, and
+# perform static code analysis.
+#
+###############################################################################
+
+NOSE2_OPTIONS="--verbose"
+
+help: all
+
+all:
+ @echo "Usage:"
+ @echo
+ @echo " make run - Run all testcases."
+ @echo " make doc - Generate Documentation."
+ @echo " make cleanall - removes *pyc, documentation."
+ @echo " make static_check- runs pep8, flake8, pylint on code."
+
+doc:
+ @epydoc -v --output=Documentation *.py
+
+run:
+ nose2 ${NOSE2_OPTIONS}
+
+static_check:
+ @for i in `ls *.py`; \
+ do \
+ echo "Pylint :- " ; \
+ printf "%10s " $${i}; \
+ pylint $${i} 2>&1 | grep "^Your code" | awk '{print $$7}';\
+ echo "--------------------------------------------";\
+ pep8 $${i}; \
+ echo "pep8 :- "; \
+ echo "flake8 :- "; \
+ flake8 $${i}; \
+ done
+
+cleanall: clean
+ @rm -fr Documentation logs/*
+
+clean_logs:
+ @rm -fr logs/*
+clean:
+ @rm -fr *.pyc
diff --git a/tools/testing/selftests/nvmftests/README.md b/tools/testing/selftests/nvmftests/README.md
new file mode 100644
index 0000000..f5e5409
--- /dev/null
+++ b/tools/testing/selftests/nvmftests/README.md
@@ -0,0 +1,140 @@
+nvmftests
+=========
+
+
+1. Introduction
+---------------
+
+ This contains NVMe Over Fabrics unit test framework. The purpose of this
+ framework is to provide a platform to create different scenarios and test
+ specific functionality for NVMe Over Fabrics Subsystem.
+
+2. Overview
+-----------
+
+ The main objective of this framework is to have a platform in place which
+ can provide :-
+ 1. Classes and methods for each component in the NVMeOF subsystem.
+ 2. Ability to build NVMeOF subsystem based on the configuration file.
+ 3. Ability to issue sequential and parallel commands to the different
+ namespaces and controllers from the host side.
+
+ All the testcases are written in python nose2 format. This framework
+ follows a simple class hierarchy. Each test is a direct subclass or indirect
+ subclass of NVMeOFTest. To write a new testcase one can copy an existing
+ template test_nvmf_target_template.py or test_nvmf_host_template.py and
+ start adding new testcase specific functionality.
+
+3. Class hierarchy and Design Considerations
+--------------------------------------------
+
+ 3.1. Core Classes :-
+ Target Classes :-
+ NVMeOFTarget :- Represents Target.
+ NVMeOFTargetSubsystem :- Represents a Target Subsystem.
+ NVMeOFTargetNamespace :- Represents a Target Namespace.
+ NVMeOFTargetPort :- Represents a Target Port.
+ Host Classes
+ NVMeOFHost :- Represents Host.
+ NVMeOFHostController :- Represents a Host Controller.
+ NVMeOFHostNamespace :- Represents a Host Namespace.
+
+ We divide host and target side components into two different class
+ hierarchies. On the host side, we have a controller represented as a
+ character device and each namespace as a block device. In order to
+ add any new functionality to the framework please modify core classes
+ for each component and propagate new interfaces to the top
+ level (in host.py/target.py). On the target side, we have subsystem(s),
+ namespace(s), and port(s) which is mainly configured using configfs.
+ For detailed class hierarchy please look into Documentation/index.html.
+ 3.2. Testcase class:-
+ NVMeOFTest :- Base class for each testcase, contains common functions.
+
+4. Adding new testcases
+-----------------------
+
+ 4.1. Please refer host or target template testcase.
+ 4.2. Copy the template file with your testcase name.
+ 4.3. Update the class name with testcase name.
+ 4.4. Update the test case function name.
+ 4.5. If necessary update the core files and add new functionality.
+ 4.6. Add testcase main function to determine success or failure.
+ 4.7. Update setUp() and tearDown() to add pre and post functionality.
+ 4.8. Once testcase is ready make sure :-
+ 4.8.1. Run pep8, flake8, pylint and fix errors/warnings.
+ -Example "$ make static_check" will run pep8, flake8 and pylint
+ on all the python files in current directory.
+ 4.8.2. Execute make doc to generate the documentation.
+ -Example "$ make doc" will create and update existing
+ documentation.
+
+5. Running testcases with framework
+-----------------------------------
+
+ Here are some examples of running testcases with nose2 :-
+ 5.1. Running single testcase with nose2 :-
+ # nose2 --verbose test_nvmf_create_target
+ # nose2 --verbose test_nvmf_create_host
+
+ 5.2. Running all the testcases :-
+ # nose2 --verbose
+
+ Some notes on execution:-
+ In the current implementation, it uses file backed loop device on the
+ target side. For each testcase execution, new file is created and linked
+ with loop device. It expects that "/mnt/" has enough space available to
+ store backend files which are used for target namespaces. Please edit
+ the target subsystems and namespace configuration in loop.json and
+ size of the loop device on the target side in the nvmf_test.py
+ according to your need.
+
+ For host and target setup you may have to configure timeout (sleep())
+ values in the code to make sure previous steps are completed
+ successfully and resources are online before executing next the steps.
+
+6. Logging
+----------
+
+ For each testcase, it will create a separate log directory with the test
+ name under logs/. This directory will be used for temporary files and
+ storing execution logs of each testcase. Current implementation stores
+ stdout and stderr for each testcase under log directory, e.g.:-
+ logs/
+ |-- TestNVMeOFParallelFabric
+ | |-- stderr.log
+ | |-- stdout.log
+ |-- TestNVMeOFRandomFabric
+ | |-- stderr.log
+ | |-- stdout.log
+ |--- TestNVMeOFSeqFabric
+ |-- stderr.log
+ |-- stdout.log
+ .
+ .
+ .
+
+7. Dependencies
+---------------
+
+ 6.1. Python(>= 2.7.5 or >= 3.3)
+ 6.2. nose(http://nose.readthedocs.io/en/latest/)
+ 6.3. nose2(Installation guide http://nose2.readthedocs.io/)
+ 6.4. pep8(https://pypi.python.org/pypi/setuptools-pep8)
+ 6.5. flake8(https://pypi.python.org/pypi/flake8)
+ 6.6. pylint(https://www.pylint.org/)
+ 6.7. Epydoc(http://epydoc.sourceforge.net/)
+ 6.8. nvme-cli(https://github.com/linux-nvme/nvme-cli.git)
+
+ Python package management system pip can be used to install most of the
+ listed packages(https://pip.pypa.io/en/stable/installing/) :-
+
+ $ pip install nose nose2 natsort pep8 flake8 pylint epydoc
+
+8. Future Work
+--------------
+
+ 7.1. Add support for thread safe logging.
+ 7.2. Improve error handling for host namespaces.
+ 7.3. Improve support for parallel command submission and return code.
+ 7.4. Report generation mechanism.
+ 7.5 Add support for better testcase configuration.
diff --git a/tools/testing/selftests/nvmftests/host.py b/tools/testing/selftests/nvmftests/host.py
new file mode 100644
index 0000000..c0866c6
--- /dev/null
+++ b/tools/testing/selftests/nvmftests/host.py
@@ -0,0 +1,254 @@
+# 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 <chaitanya.kulkarni at hgst.com>
+#
+""" Represents NVMe Over Fabric Host Subsystem.
+"""
+
+import json
+import random
+import subprocess
+
+from host_subsystem import NVMeOFHostController
+
+
+class NVMeOFHost(object):
+
+ """
+ Represents a host.
+ - Attributes :
+ - target_type : rdma/loop/fc. (only loop supported now)
+ - ctrl_list : list of the host controllers.
+ """
+ def __init__(self, target_type):
+ """ Constructor for NVMeOFHost.
+ - Args :
+ - target_type : represents target transport type.
+ - ctrl_list : list of host controllers.
+ - Returns :
+ - None.
+ """
+ self.target_type = target_type
+ self.ctrl_list = []
+ self.err_str = "ERROR : " + self.__class__.__name__ + " : "
+ self.load_modules()
+
+ def load_modules(self):
+ """ Wrapper for Loading NVMeOF Host modules.
+ - Args :
+ - None.
+ - Returns :
+ - True on success, False on failure.
+ """
+ ret = self.exec_cmd("modprobe nvme-fabrics")
+ if ret is False:
+ print self.err_str + "unable to load nvme-fabrics."
+ return False
+ return True
+
+ def exec_cmd(self, cmd):
+ """ Wrapper for executing a shell command.
+ - Args :
+ - cmd : command to execute.
+ - Returns :
+ - True if cmd returns 0, False otherwise.
+ """
+ proc = None
+ try:
+ proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
+ except Exception, err:
+ print self.err_str + str(err)
+ return False
+
+ return True if proc.wait() == 0 else False
+
+ def config_loop_host(self, config_file):
+ """ Configure host for loop target :-
+ 1. Load Loop module(s).
+ 2. Load config from json file.
+ 3. Create Controller list.
+ - Args :
+ - None
+ -Returns :
+ - True on success, False on failure.
+ """
+ ret = self.exec_cmd("modprobe nvme-loop")
+ if ret is False:
+ print self.err_str + "failed to load nvme-loop."
+ return False
+ try:
+ config_file_handle = open(config_file, "r")
+ config = json.loads(config_file_handle.read())
+ config_file_handle.close()
+ except Exception, err:
+ print self.err_str + str(err)
+ return False
+
+ for sscfg in config['subsystems']:
+ ctrl = NVMeOFHostController(sscfg['nqn'], "loop")
+ ret = ctrl.init_ctrl()
+ if ret is False:
+ print self.err_str + "failed init_ctrl() " + \
+ str(ctrl.ctrl_dev) + "."
+ return False
+ self.ctrl_list.append(ctrl)
+ return True
+
+ def run_ios_parallel(self, iocfg):
+ """ Run parallel IOs on all host controller(s) and
+ wait for completion.
+ - Args :
+ - iocfg : io configuration.
+ - Returns :
+ - None.
+ """
+ print "Starting IOs parallelly on all controllers ..."
+ for ctrl in self.ctrl_list:
+ if ctrl.run_io_all_ns(iocfg) is False:
+ return False
+
+ print "Waiting for all threads to finish the IOs..."
+ for ctrl in self.ctrl_list:
+ ctrl.wait_io_all_ns()
+
+ return True
+
+ def run_ios_seq(self, iocfg):
+ """ Run IOs on all host controllers one by one.
+ - Args :
+ - None.
+ - Returns :
+ - None.
+ """
+ print "Starting IOs seq ..."
+ ret = None
+ for ctrl in self.ctrl_list:
+ ret = ctrl.run_io_seq(iocfg)
+ if ret is False:
+ break
+
+ return ret
+
+ def run_ios_random(self, iocfg):
+ """ Select a controller from the list of controllers
+ randomly and run IOs. Exhaust entire list.
+ - Args :
+ - None.
+ - Returns :
+ - None.
+ """
+ ctrl_list = range(0, len(self.ctrl_list))
+
+ ret = True
+ for i in range(0, len(self.ctrl_list)):
+ random.shuffle(ctrl_list)
+ ctrl_id = ctrl_list.pop()
+ ctrl = self.ctrl_list[ctrl_id]
+ if ctrl.run_io_random(iocfg) is False:
+ ret = False
+ break
+
+ return ret
+
+ def smart_log(self):
+ """ Execute smart log.
+ - Args :
+ - None.
+ - Returns :
+ - True on success, False on failure.
+ """
+ ret = True
+ for ctrl in self.ctrl_list:
+ if ctrl.smart_log() is False:
+ ret = False
+ break
+
+ return ret
+
+ def id_ctrl(self):
+ """ Execute id-ctrl on all the controllers(s).
+ - Args :
+ - None.
+ - Returns :
+ - True on success, False on failure.
+ """
+ ret = True
+ for ctrl in self.ctrl_list:
+ if ctrl.id_ctrl() is False:
+ ret = False
+ break
+
+ return ret
+
+ def id_ns(self):
+ """ Execute id-ns on controllers(s) and all its namespace(s).
+ - Args :
+ - None.
+ - Returns :
+ - True on success, False on failure.
+ """
+ ret = True
+ for ctrl in self.ctrl_list:
+ if ctrl.id_ns() is False:
+ ret = False
+ break
+
+ return ret
+
+ def mkfs_seq(self):
+ """ Run mkfs, mount fs, run IOs.
+ - Args :
+ - None.
+ - Returns :
+ - None.
+ """
+ ret = True
+ for ctrl in self.ctrl_list:
+ if ctrl.run_mkfs_seq() is False:
+ ret = False
+ break
+
+ return ret
+
+ def config_host(self, config_file="loop.json"):
+ """ Configure Host based on the target transport.
+ - Args :
+ - None.
+ - Returns :
+ - True on success, False on failure.
+ """
+ ret = False
+ if self.target_type == "loop":
+ print "Configuring loop host"
+ ret = self.config_loop_host(config_file)
+ else:
+ print self.err_str + "only loop target type is supported."
+ return ret
+
+ def del_host(self):
+ """ Delete all the Host Controllers.
+ - Args :
+ - None.
+ - Returns :
+ - True on success, False on failure.
+ """
+ ret = True
+ for subsys in self.ctrl_list:
+ if subsys.del_ctrl() is False:
+ ret = False
+ return ret
diff --git a/tools/testing/selftests/nvmftests/host_ns.py b/tools/testing/selftests/nvmftests/host_ns.py
new file mode 100644
index 0000000..ef475e7
--- /dev/null
+++ b/tools/testing/selftests/nvmftests/host_ns.py
@@ -0,0 +1,285 @@
+
+# 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 <chaitanya.kulkarni at hgst.com>
+#
+""" Represents NVMe Over Fabric Host Namespace.
+"""
+
+import os
+import Queue
+import threading
+import subprocess
+
+
+class NVMeOFNSThread(threading.Thread):
+ """
+ Represents a worker thread.
+
+ - Attributes :
+ - target : thread Target.
+ - workq : workqueue shared between producer and worker thread.
+ """
+ def __init__(self, group=None, target=None, name=None,
+ args=[None], kwargs=None, verbose=None):
+ """Default Thread Constructor."""
+ super(NVMeOFNSThread, self).__init__()
+ self.target = target
+ self.name = name
+ self.workq = args[0]
+
+ def run(self):
+ """ Default Thread Function """
+ while True:
+ if not self.workq.empty():
+ item = self.workq.get()
+ if item is None:
+ break
+ ret = item['THREAD'](item)
+ self.workq.task_done()
+ # On Error just shutdown the worker thread.
+ # Need to implement qid based work queue implementation.
+ if ret is False:
+ self.workq.put(None)
+
+
+class NVMeOFHostNamespace(object):
+ """
+ Represents a host namespace.
+
+ - Attributes :
+ - ns_dev : block device associated with this namespace.
+ - lbaf_cnt : logical block format count.
+ - ns_dict : namespace attributes.
+ - lbaf : dictionary for logical block format.
+ - ms : dictionary to store medata size information.
+ - lbads : dictionary to store LBA Data Size.
+ - rp : dictionary to store relative performance.
+ - mount_path : mounted directory.
+ - worker_thread : handle for io worker thread.
+ - workq : workqueue shared between producer and worker thread.
+ """
+ def __init__(self, ns_dev):
+ self.ns_dev = ns_dev
+ self.lbaf_cnt = 0
+ self.ns_dict = {}
+ self.lbaf = {}
+ self.ms = {}
+ self.lbads = {}
+ self.rp = {}
+ self.mount_path = None
+ self.worker_thread = None
+ self.workq = Queue.Queue()
+
+ self.err_str = "ERROR : " + self.__class__.__name__ + " : "
+
+ def exec_cmd(self, cmd):
+ """ Wrapper for executing a shell command.
+ - Args :
+ - cmd : command to execute.
+ - Returns :
+ - True if cmd returns 0, False otherwise.
+ """
+ proc = None
+ try:
+ proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
+ except Exception, err:
+ print self.err_str + str(err)
+ return False
+
+ return True if proc.wait() == 0 else False
+
+ def init_ns(self):
+ """ Initialize nameapce, create worker thread and
+ build controller attributes.
+ - Args :
+ - None.
+ - Returns :
+ - True on success, False on failure.
+ """
+ if self.id_ns() is False:
+ return False
+
+ # Create IO worker thread for this ns
+ self.worker_thread = NVMeOFNSThread(args=[self.workq])
+ self.worker_thread.setDaemon(True)
+ self.worker_thread.start()
+ return True
+
+ def id_ns(self):
+ """ Wrapper for id-ns command.
+ - Args :
+ - None.
+ - Returns :
+ - True on success, False on failure.
+ """
+ id_ctrl_cmd = "nvme id-ns " + self.ns_dev
+ proc = subprocess.Popen(id_ctrl_cmd,
+ shell=True,
+ stdout=subprocess.PIPE)
+ ret = proc.wait()
+ if ret != 0:
+ print self.err_str + "nvme id-ctrl failed"
+ return False
+
+ i = 0
+ for line in proc.stdout:
+ if line.startswith('subnqn') or \
+ line.startswith('NVME Identify Nameapce'):
+ continue
+ if line.startswith('lbaf'):
+ self.lbaf[i] = line.split(':')[0].split(' ')[1]
+ self.ms[i] = line.split(':')[2].split(' ')[0]
+ self.lbads[i] = line.split(':')[3].split(' ')[0]
+ self.rp[i] = line.split(':')[4].split(' ')[0]
+ i += 1
+ continue
+
+ key, value = line.split(':')
+ self.ns_dict[key.strip()] = value.strip()
+ return True
+
+ def get_value(self, k):
+ """ Access nvme namespace attribute's value based on the key.
+ - Args :
+ - k : represents namespace's attribute.
+ - Returns :
+ - None.
+ """
+ return self.ns_dict[k]
+
+ def mkfs_seq(self):
+ """ Format namespace with file system and mount on the unique
+ namespace directory.
+ - Args :
+ - None.
+ - Returns :
+ - True on success, False on failure.
+ """
+ cmd = "mkfs.ext4 " + self.ns_dev
+ print "Running " + cmd + "."
+ ret = self.exec_cmd(cmd)
+ if ret is False:
+ print self.err_str + "mkfs.ext4 failed " + self.ns_dev + "."
+ return False
+
+ self.mount_path = "/mnt/" + self.ns_dev.split("/")[2]
+ if os.path.exists(self.mount_path) is True:
+ print self.err_str + "path " + self.mount_path + " exists."
+ return False
+
+ try:
+ os.makedirs(self.mount_path)
+ except Exception, err:
+ print self.err_str + str(err)
+ return False
+
+ ret = self.exec_cmd("mount " + self.ns_dev + " " + self.mount_path)
+ if ret is False:
+ print self.err_str + "mount failed " + self.ns_dev + "."
+ return False
+
+ print "mount " + self.ns_dev + " " + self.mount_path + "successful."
+ return True
+
+ def is_mounted(self):
+ """ Check if namespace is mounted.
+ - Args :
+ - None.
+ - Returns :
+ - True on success, False on failure.
+ """
+ ret = False
+ if self.mount_path is not None:
+ ret = self.exec_cmd("mountpoint -q " + self.mount_path)
+
+ return ret
+
+ def unmount_cleanup(self):
+ """ Unmount the namespace and cleanup the mount path.
+ - Args :
+ - None.
+ - Returns :
+ - True on success, False on failure.
+ """
+ if self.is_mounted() is False:
+ print self.err_str + self.ns_dev + " is not mounted."
+ return False
+
+ cmd = "umount " + self.mount_path
+ ret = self.exec_cmd(cmd)
+ if ret is False:
+ print self.err_str + "umount failed " + self.ns_dev + "."
+ return False
+
+ print "##### UNMOUNT SUCCESS " + cmd + "."
+ try:
+ os.rmdir(self.mount_path)
+ except Exception, err:
+ print self.err_str + str(err)
+ ret = False
+
+ return ret
+
+ def start_io(self, iocfg):
+ """ Add new work item to workqueue. Triggers wake up in worker thread.
+ - Args :
+ - IO Configuration passed to worker thread.
+ - Returns :
+ - True on success, False on failure.
+ """
+ if iocfg['IODIR'] == "read":
+ iocfg['IF'] = self.ns_dev
+ elif iocfg['IODIR'] == "write":
+ iocfg['OF'] = self.ns_dev
+ else:
+ print self.err_str + "io config " + iocfg + " not supported."
+ return False
+
+ if self.worker_thread.is_alive():
+ self.workq.put(iocfg)
+ else:
+ print self.err_str + "worker thread is not running."
+ return False
+
+ return True
+
+ def wait_io(self):
+ """ Wait until all the items are completed from workqueue.
+ - Args :
+ - None.
+ - Returns :
+ - None.
+ """
+ print "Checking for worker thread " + self.ns_dev + "."
+ if self.worker_thread.is_alive():
+ print "Waiting for thread completion " + self.ns_dev + "."
+ self.workq.join()
+ print "# WAIT COMPLETE " + self.ns_dev + "."
+
+ def del_ns(self):
+ """ Namespace clanup.
+ - Args :
+ - None.
+ - Returns :
+ - None.
+ """
+ print "##### Deleting Namespace "
+ self.workq.put(None)
+ if self.is_mounted() is True:
+ self.unmount_cleanup()
diff --git a/tools/testing/selftests/nvmftests/host_subsystem.py b/tools/testing/selftests/nvmftests/host_subsystem.py
new file mode 100644
index 0000000..4c0d3e9
--- /dev/null
+++ b/tools/testing/selftests/nvmftests/host_subsystem.py
@@ -0,0 +1,420 @@
+# 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 <chaitanya.kulkarni at hgst.com>
+#
+""" 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 host_ns import NVMeOFHostNamespace
+
+
+class NVMeOFHostController(object):
+ """
+ Represents a host controller.
+
+ - Attributes :
+ - nqn : ctrltem 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.err_str = "ERROR : " + self.__class__.__name__ + " : "
+
+ def exec_cmd(self, cmd):
+ """ Wrapper for executing a shell command.
+ - Args :
+ - cmd : command to execute.
+ - Returns :
+ - True if cmd returns 0, False otherwise.
+ """
+ proc = None
+ try:
+ proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
+ except Exception, err:
+ print self.err_str + str(err)
+ return False
+
+ return True if proc.wait() == 0 else False
+
+ 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 host_ns in self.ns_list:
+ if host_ns.start_io(iocfg) is False:
+ print "start IO " + host_ns.ns_dev + " failed."
+ ret = False
+ break
+ print "start IO " + host_ns.ns_dev + " SUCCESS."
+
+ return ret
+
+ def wait_io_all_ns(self):
+ """ Wait until workqueue is empty.
+ - Args :
+ - None.
+ - Returns :
+ - None.
+ """
+ for host_ns in self.ns_list:
+ host_ns.wait_io()
+
+ def run_io_seq(self, iocfg):
+ """ Issue IOs to each namespace and wait, repeat for all the
+ namespaces of this controller.
+ - Args :
+ - iocfg : io configuration.
+ - Returns :
+ - True on success, False on failure.
+ """
+ ret = True
+ for host_ns in self.ns_list:
+ if host_ns.start_io(iocfg) is False:
+ print "start IO " + host_ns.ns_dev + " failed."
+ ret = False
+ host_ns.wait_io()
+ return ret
+
+ def run_mkfs_seq(self):
+ """ Run mkfs, mount fs.
+ - Args :
+ - None.
+ - Returns :
+ - True on success, False on failure.
+ """
+ ret = True
+ for host_ns in self.ns_list:
+ if host_ns.mkfs_seq() is False:
+ ret = False
+ 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 exec_smart_log(self, nsid="0xFFFFFFFF"):
+ """ Wrapper for nvme smart-log command.
+ - Args :
+ - None.
+ - Returns:
+ - True on success, False on failure.
+ """
+ smart_log_cmd = "nvme smart-log " + self.ctrl_dev + " -n " + str(nsid)
+ print smart_log_cmd
+ proc = subprocess.Popen(smart_log_cmd,
+ shell=True,
+ stdout=subprocess.PIPE)
+ err = proc.wait()
+ if err != 0:
+ print self.err_str + "nvme smart log failed"
+ return False
+
+ for line in proc.stdout:
+ if "data_units_read" in line:
+ data_units_read = \
+ string.replace(line.split(":")[1].strip(), ",", "")
+ if "data_units_written" in line:
+ data_units_written = \
+ string.replace(line.split(":")[1].strip(), ",", "")
+ if "host_read_commands" in line:
+ host_read_commands = \
+ string.replace(line.split(":")[1].strip(), ",", "")
+ if "host_write_commands" in line:
+ host_write_commands = \
+ string.replace(line.split(":")[1].strip(), ",", "")
+
+ print "data_units_read " + data_units_read
+ print "data_units_written " + data_units_written
+ print "host_read_commands " + host_read_commands
+ print "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.
+ """
+ self.exec_smart_log()
+ i = 1
+ for namespace in self.ns_list:
+ if self.exec_smart_log(i) is False:
+ return False
+ i += 1
+ return True
+
+ def validate_sysfs_host_ctrl_ns(self, ctrl):
+ """ Validate sysfs entries for the host controller and namespace(s)
+ - Args :
+ - ctrl : controller id.
+ - Returns :
+ - True on success, False on failure.
+ """
+ # 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')
+ if line != ctrl.split("/")[2]:
+ print self.err_str + "host ctrl " + ctrl + " not present."
+ return False
+ dir_list = os.listdir("/sys/class/nvme-fabrics/ctl/" +
+ ctrl.split("/")[2] + "/")
+
+ pat = re.compile("^" + ctrl.split("/")[2] + "+n[0-9]+$")
+ for line in dir_list:
+ line = line.strip('\n')
+ if pat.match(line):
+ if not "/dev/" + line in self.ns_dev_list:
+ print self.err_str + "ns " + line + " not found in sysfs."
+ return False
+
+ print "sysfs entries for ctrl and ns created successfully."
+ return True
+
+ def build_ctrl_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 = "XXX"
+ ns_list = []
+ try:
+ dev_list = os.listdir("/dev/")
+ except Exception, err:
+ print self.err_str + 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 == "XXX":
+ print self.err_str + "controller '/dev/nvme*' not found."
+ return None, None
+
+ # find namespace(s) associated with ctrl
+ try:
+ dir_list = os.listdir("/dev/")
+ except Exception, err:
+ print self.err_str + 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):
+ ns_list.append("/dev/" + line)
+
+ if len(ns_list) == 0:
+ print self.err_str + "host ns not found for ctrl " + ctrl + "."
+ return None, None
+
+ ctrl = "/dev/" + ctrl
+ return ctrl, ns_list
+
+ def get_value(self, k):
+ """ Access nvme controller attribute's value based on the kay.
+ - Args :
+ - k : represents controller's attribute.
+ - Returns :
+ - value of controller's attribute.
+ """
+ return self.ctrl_dict[k]
+
+ def init_ctrl_ns(self):
+ """ Initialize and build namespace list and validate sysfs entries.
+ - Args :
+ - None.
+ - Returns :
+ - True on success, False on failure.
+ """
+ for ns_dev in self.ns_dev_list:
+ if not stat.S_ISBLK(os.stat(ns_dev).st_mode):
+ print self.err_str + "expected block dev " + ns_dev + "."
+ return False
+
+ print "Found NS " + ns_dev
+ host_ns = NVMeOFHostNamespace(ns_dev)
+ host_ns.init_ns()
+ self.ns_list.append(host_ns)
+ time.sleep(1)
+ ret = self.validate_sysfs_host_ctrl_ns(self.ctrl_dev)
+ print "Host sysfs entries are validated " + str(ret)
+ return ret
+
+ 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"
+ print "CMD :- " + cmd
+ if self.exec_cmd(cmd) is False:
+ return False
+ time.sleep(1)
+ self.ctrl_dev, self.ns_dev_list = self.build_ctrl_ns_list()
+
+ if not stat.S_ISCHR(os.stat(self.ctrl_dev).st_mode):
+ print self.err_str + "failed to find char device for host ctrl."
+ return False
+
+ ret = self.id_ctrl()
+ if ret is False:
+ return False
+
+ return self.init_ctrl_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:
+ print self.err_str + "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.startswith(" rwt"):
+ continue
+ key, val = line.split(':')
+ self.ctrl_dict[key.strip()] = val.strip()
+
+ print self.ctrl_dict
+ print "--------------------------------------------------"
+ 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 self.ns_list:
+ host_ns.id_ns()
+ print "--------------------------------------------------"
+ 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 del_ctrl(self):
+ """ Delete subsystem and associated namespace(s).
+ - Args :
+ - None.
+ - Returns :
+ - True on success, False on failure.
+ """
+ print "Deleting subsystem " + self.nqn
+ for host_ns in self.ns_list:
+ host_ns.del_ns()
+ 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):
+ print self.err_str + "host ctrl dir " + self.nqn + \
+ " not present."
+ return False
+ ret = self.exec_cmd("echo > " + line + "/delete_controller")
+ if ret is False:
+ print self.err_str + "failed to delete ctrl " + \
+ self.nqn + "."
+ return False
+ except Exception, err:
+ print self.err_str + str(err)
+ return False
+
+ return True
diff --git a/tools/testing/selftests/nvmftests/loop.json b/tools/testing/selftests/nvmftests/loop.json
new file mode 100644
index 0000000..4aecfc3
--- /dev/null
+++ b/tools/testing/selftests/nvmftests/loop.json
@@ -0,0 +1,248 @@
+{
+ "hosts": [
+ {
+ "nqn": "hostnqn"
+ }
+ ],
+ "ports": [
+ {
+ "addr": {
+ "adrfam": "",
+ "traddr": "",
+ "treq": "not specified",
+ "trsvcid": "",
+ "trtype": "loop"
+ },
+ "portid": 1,
+ "referrals": [],
+ "subsystems": [
+ "testnqn1",
+ "testnqn2",
+ "testnqn3",
+ "testnqn4",
+ "testnqn5",
+ "testnqn6",
+ "testnqn7",
+ "testnqn8"
+ ]
+ }
+ ],
+ "subsystems": [
+ {
+ "allowed_hosts": [
+ "hostnqn"
+ ],
+ "attr": {
+ "allow_any_host": "1"
+ },
+ "namespaces": [
+ {
+ "device": {
+ "nguid": "ef90689c-6c46-d44c-89c1-4067801309a8",
+ "path": "/dev/loop0"
+ },
+ "enable": 1,
+ "nsid": 1
+ },
+ {
+ "device": {
+ "nguid": "6c46-d44c-89c1-4067801309a8",
+ "path": "/dev/loop1"
+ },
+ "enable": 1,
+ "nsid": 2
+ }
+ ],
+ "nqn": "testnqn1"
+ },
+ {
+ "allowed_hosts": [
+ "hostnqn"
+ ],
+ "attr": {
+ "allow_any_host": "1"
+ },
+ "namespaces": [
+ {
+ "device": {
+ "nguid": "d44c-89c1-4067801309a8",
+ "path": "/dev/loop2"
+ },
+ "enable": 1,
+ "nsid": 1
+ },
+ {
+ "device": {
+ "nguid": "89c1-4067801309a8",
+ "path": "/dev/loop3"
+ },
+ "enable": 1,
+ "nsid": 2
+ }
+ ],
+ "nqn": "testnqn2"
+ },
+ {
+ "allowed_hosts": [
+ "hostnqn"
+ ],
+ "attr": {
+ "allow_any_host": "1"
+ },
+ "namespaces": [
+ {
+ "device": {
+ "nguid": "89c1-4067801309a8",
+ "path": "/dev/loop4"
+ },
+ "enable": 1,
+ "nsid": 1
+ },
+ {
+ "device": {
+ "nguid": "067801309a8",
+ "path": "/dev/loop5"
+ },
+ "enable": 1,
+ "nsid": 2
+ }
+ ],
+ "nqn": "testnqn3"
+ },
+ {
+ "allowed_hosts": [
+ "hostnqn"
+ ],
+ "attr": {
+ "allow_any_host": "1"
+ },
+ "namespaces": [
+ {
+ "device": {
+ "nguid": "89c1-4067801309a8",
+ "path": "/dev/loop6"
+ },
+ "enable": 1,
+ "nsid": 1
+ },
+ {
+ "device": {
+ "nguid": "067801309a8",
+ "path": "/dev/loop7"
+ },
+ "enable": 1,
+ "nsid": 2
+ }
+ ],
+ "nqn": "testnqn4"
+ },
+ {
+ "allowed_hosts": [
+ "hostnqn"
+ ],
+ "attr": {
+ "allow_any_host": "1"
+ },
+ "namespaces": [
+ {
+ "device": {
+ "nguid": "89c1-4067801309a8",
+ "path": "/dev/loop8"
+ },
+ "enable": 1,
+ "nsid": 1
+ },
+ {
+ "device": {
+ "nguid": "067801309a8",
+ "path": "/dev/loop9"
+ },
+ "enable": 1,
+ "nsid": 2
+ }
+ ],
+ "nqn": "testnqn5"
+ },
+ {
+ "allowed_hosts": [
+ "hostnqn"
+ ],
+ "attr": {
+ "allow_any_host": "1"
+ },
+ "namespaces": [
+ {
+ "device": {
+ "nguid": "89c1-4067801309a8",
+ "path": "/dev/loop0"
+ },
+ "enable": 1,
+ "nsid": 1
+ },
+ {
+ "device": {
+ "nguid": "067801309a8",
+ "path": "/dev/loop1"
+ },
+ "enable": 1,
+ "nsid": 2
+ }
+ ],
+ "nqn": "testnqn6"
+ },
+ {
+ "allowed_hosts": [
+ "hostnqn"
+ ],
+ "attr": {
+ "allow_any_host": "1"
+ },
+ "namespaces": [
+ {
+ "device": {
+ "nguid": "89c1-4067801309a8",
+ "path": "/dev/loop2"
+ },
+ "enable": 1,
+ "nsid": 1
+ },
+ {
+ "device": {
+ "nguid": "067801309a8",
+ "path": "/dev/loop3"
+ },
+ "enable": 1,
+ "nsid": 2
+ }
+ ],
+ "nqn": "testnqn7"
+ },
+ {
+ "allowed_hosts": [
+ "hostnqn"
+ ],
+ "attr": {
+ "allow_any_host": "1"
+ },
+ "namespaces": [
+ {
+ "device": {
+ "nguid": "89c1-4067801309a8",
+ "path": "/dev/loop4"
+ },
+ "enable": 1,
+ "nsid": 1
+ },
+ {
+ "device": {
+ "nguid": "067801309a8",
+ "path": "/dev/loop5"
+ },
+ "enable": 1,
+ "nsid": 2
+ }
+ ],
+ "nqn": "testnqn8"
+ }
+ ]
+}
diff --git a/tools/testing/selftests/nvmftests/loopback.py b/tools/testing/selftests/nvmftests/loopback.py
new file mode 100644
index 0000000..5716974
--- /dev/null
+++ b/tools/testing/selftests/nvmftests/loopback.py
@@ -0,0 +1,116 @@
+# 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 <chaitanya.kulkarni at hgst.com>
+#
+""" Represents Loopback driver block devices.
+"""
+
+import os
+import time
+import subprocess
+
+
+class Loopback:
+ """
+ Represents Loopback driver block devices.
+
+ - Attributes :
+ - path : path to create backend files.
+ - dev_size : device file size.
+ - block size : block size to create file.
+ - max_loop : max loop devices.
+ - dev_list : list of loop back files.
+ """
+ def __init__(self, path, dev_size, block_size, max_loop):
+ """ Constructor for Loopback.
+ - Args :
+ path : directory path where backend file is stired.
+ dev_size : device size.
+ block_size : block size to write backend file.
+ max_loop : number of loop back devices.
+ """
+ self.path = path
+ self.dev_size = dev_size
+ self.block_size = block_size
+ self.max_loop = max_loop
+ self.dev_list = []
+
+ self.exec_cmd("losetup -D")
+ self.exec_cmd("modprobe -r loop")
+ self.exec_cmd("modprobe loop max_loop=" + str(max_loop))
+
+ def exec_cmd(self, cmd):
+ """ Wrapper for executing a shell command.
+ - Args :
+ - cmd : command to execute.
+ - Returns :
+ - Value of the command.
+ """
+ proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
+ return proc.wait()
+
+ def init_loopback(self):
+ """ Create and initialize Loopback.
+ - Args :
+ - None.
+ - Returns :
+ - True on success, False on failure.
+ """
+ count = self.dev_size / self.block_size
+
+ for i in range(0, self.max_loop):
+ dev = self.path + "/test" + str(i)
+ cmd = "dd if=/dev/zero of=" + dev + \
+ " count=" + str(count) + " bs=" + str(self.block_size)
+ print cmd
+ ret = self.exec_cmd(cmd)
+ if ret != 0:
+ print "ERROR : file creation " + self.dev_list[i]
+ self.del_loopback()
+ return False
+ cmd = "losetup /dev/loop" + str(i) + " " + dev
+ print cmd
+ if self.exec_cmd(cmd) != 0:
+ print "ERROR : " + cmd + " failed."
+ return False
+
+ self.dev_list.append(dev)
+ return True
+
+ def del_loopback(self):
+ """ Delete this Loopback.
+ - Args :
+ - None.
+ -Returns :
+ - True on success, False on failure.
+ """
+ ret = True
+ loop_cnt = 0
+ for i in self.dev_list:
+ cmd = "losetup -d /dev/loop" + str(loop_cnt)
+ print cmd
+ self.exec_cmd(cmd)
+ os.remove(i)
+ loop_cnt += 1
+
+ time.sleep(1)
+ if self.exec_cmd("modprobe -r loop") != 0:
+ print "ERROR : failed to remove loop module."
+ ret = False
+
+ return ret
diff --git a/tools/testing/selftests/nvmftests/nvmf_test.py b/tools/testing/selftests/nvmftests/nvmf_test.py
new file mode 100644
index 0000000..bcafcb1
--- /dev/null
+++ b/tools/testing/selftests/nvmftests/nvmf_test.py
@@ -0,0 +1,85 @@
+# 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 <chaitanya.kulkarni at hgst.com>
+#
+""" Base Class for all NVMeOF testcases.
+"""
+
+import os
+import sys
+import subprocess
+from nvmf_test_logger import NVMeOFLogger
+
+
+def __dd_worker__(iocfg):
+ """ dd worker thread function.
+ - Args :
+ - iocfg : io configuration.
+ - Returns :
+ - None.
+ """
+ cmd = "dd if=" + iocfg['IF'] + " of=" + iocfg['OF'] + \
+ " bs=" + iocfg['BS'] + " count=" + iocfg['COUNT']
+ print " Running IOs now CMD :------- " + cmd
+ proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
+ ret = True
+ if proc.wait() != iocfg['RC']:
+ print "ERROR : Failed to execute " + cmd + "."
+ ret = False
+
+ return ret
+
+
+class NVMeOFTest(object):
+
+ def __init__(self):
+ self.data_size = 128 * 1024 * 1024
+ self.block_size = 4096
+ self.nr_devices = 10
+ self.mount_path = "/mnt/nvme0n1/"
+ self.test_log_dir = "XXX"
+ self.log_dir = "./logs/" + self.__class__.__name__ + "/"
+
+ self.dd_read = {"IODIR": "read",
+ "THREAD": __dd_worker__,
+ "IF": None,
+ "OF": "/dev/null",
+ "BS": "4K",
+ "COUNT": str(self.data_size / self.block_size),
+ "RC": 0}
+
+ self.dd_write = {"IODIR": "write",
+ "THREAD": __dd_worker__,
+ "IF": "/dev/zero",
+ "OF": None,
+ "BS": "4K",
+ "COUNT": str(self.data_size / self.block_size),
+ "RC": 0}
+
+ def setup_log_dir(self, test_name):
+ """ Set up the log directory for a testcase
+ Args:
+ - test_name : name of the testcase.
+ Returns:
+ - None
+ """
+ self.test_log_dir = self.log_dir + "/" + test_name
+ if not os.path.exists(self.test_log_dir):
+ os.makedirs(self.test_log_dir)
+ sys.stdout = NVMeOFLogger(self.test_log_dir + "/" + "stdout.log")
+ sys.stderr = NVMeOFLogger(self.test_log_dir + "/" + "stderr.log")
diff --git a/tools/testing/selftests/nvmftests/nvmf_test_logger.py b/tools/testing/selftests/nvmftests/nvmf_test_logger.py
new file mode 100644
index 0000000..9ca7fc6
--- /dev/null
+++ b/tools/testing/selftests/nvmftests/nvmf_test_logger.py
@@ -0,0 +1,50 @@
+# Copyright (c) 2015-2016 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 <chaitanya.kulkarni at hgst.com>
+#
+"""Logger for NVMe Test Framwwork.
+"""
+import sys
+
+
+class NVMeOFLogger(object):
+ """ Represents Logger for NVMe Testframework. """
+ def __init__(self, log_file_path):
+ """ Logger setup
+ - Args:
+ log_file_path : path to store the log.
+ """
+ self.terminal = sys.stdout
+ self.log = open(log_file_path, "w")
+
+ def write(self, log_message):
+ """ Logger setup
+ - Args:
+ log_message: string to write in the log file.
+ - Returns:
+ None
+ """
+ self.terminal.write(log_message)
+ self.log.write(log_message)
+
+ def flush(self):
+ """ This flush method is needed for python 3 compatibility.
+ this handles the flush command by doing nothing.
+ you might want to specify some extra behavior here.
+ """
+ pass
diff --git a/tools/testing/selftests/nvmftests/port.py b/tools/testing/selftests/nvmftests/port.py
new file mode 100644
index 0000000..67ef7fc
--- /dev/null
+++ b/tools/testing/selftests/nvmftests/port.py
@@ -0,0 +1,139 @@
+# 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 <chaitanya.kulkarni at hgst.com>
+#
+""" Represents NVMe Over Fabric Target Port.
+"""
+
+import os
+import shutil
+import subprocess
+
+
+class NVMeOFTargetPort(object):
+ """
+ Represents a target port.
+
+ - Attributes :
+ - cfgfs : configfs mountpoint.
+ - port_id : port identification number.
+ - port_conf : dictionary to hold the port attributes.
+ """
+ def __init__(self, cfgfs, port_id, **port_conf):
+ """ Constructor for NVMeOFTargetPort.
+ - Args :
+ - cfgfs : configfs path.
+ - port_id : port identifier.
+ - port_conf : port configuration list.
+ """
+ self.cfgfs = cfgfs
+ self.port_id = port_id
+ self.port_conf = {}
+ self.port_path = self.cfgfs + "/nvmet/ports/" + port_id + "/"
+ self.port_conf['addr_treq'] = port_conf['addr_treq']
+ self.port_conf['addr_traddr'] = port_conf['addr_traddr']
+ self.port_conf['addr_trtype'] = port_conf['addr_trtype']
+ self.port_conf['addr_adrfam'] = port_conf['addr_adrfam']
+ self.port_conf['addr_trsvcid'] = port_conf['addr_trsvcid']
+ self.port_conf['referrals'] = "XXX"
+ self.port_conf['subsystems'] = port_conf['subsystems']
+ self.err_str = "ERROR : " + self.__class__.__name__ + " : "
+
+ def init_port(self):
+ """ Create and initialize port.
+ - Args :
+ - None.
+ - Returns :
+ - True on success, False on failure.
+ """
+ if self.port_conf['addr_trtype'] != "loop":
+ print self.err_str + "only loop transport type is supported."
+ return False
+
+ ret = self.exec_cmd("mkdir -p " + self.port_path)
+ if ret is False:
+ print self.err_str + "failed to create " + self.port_path + "."
+ return False
+
+ # initialize transport type
+ print "Port " + self.port_path + " created successfully."
+
+ ret = self.exec_cmd("echo -n \"" + self.port_conf['addr_trtype'] +
+ "\" > " + self.port_path + "/addr_trtype")
+ status = "Port " + self.port_path + " initialized successfully."
+ if ret is False:
+ status = self.err_str + "trtype " + self.port_path + " failed."
+
+ print status
+ return ret
+
+ def exec_cmd(self, cmd):
+ """ Wrapper for executing a shell command.
+ - Args :
+ - cmd : command to execute.
+ - Returns :
+ - Value of the command.
+ """
+ proc = None
+ try:
+ proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
+ except Exception, err:
+ print self.err_str + str(err) + "."
+ return False
+
+ return True if proc.wait() == 0 else False
+
+ def add_subsys(self, subsys_name):
+ """ Link Subsystem to this port.
+ - Args :
+ - subsys_name : subsstem nqn to be linked
+ - Returns :
+ - True on success, False on failure.
+ """
+ src = self.cfgfs + "/nvmet/subsystems/" + subsys_name
+ if not os.path.exists(src):
+ print self.err_str + "subsystem '" + src + "' not present."
+ return False
+ dest = self.port_path + "/subsystems/"
+ ret = self.exec_cmd("ln -s " + src + " " + dest)
+ return ret
+
+ def del_port(self):
+ """ Delete this port.
+ - Args :
+ - None
+ -Returns :
+ - True on success, False on failure.
+ """
+ print "Deleeting port " + self.port_id
+ subsys_symlink = self.port_path + "/subsystem/"
+ try:
+
+ if os.path.isdir(subsys_symlink):
+ shutil.rmtree(subsys_symlink, ignore_errors=True)
+ print "Unlink subsystem fromn port successfully."
+
+ if os.path.isdir(self.port_path):
+ shutil.rmtree(self.port_path, ignore_errors=True)
+
+ except Exception, err:
+ print self.err_str + str(err) + "."
+ return False
+
+ print "Removed port " + self.port_path + " successfully."
+ return True
diff --git a/tools/testing/selftests/nvmftests/target.py b/tools/testing/selftests/nvmftests/target.py
new file mode 100644
index 0000000..717764e
--- /dev/null
+++ b/tools/testing/selftests/nvmftests/target.py
@@ -0,0 +1,209 @@
+# 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 <chaitanya.kulkarni at hgst.com>
+#
+""" Represents NVMe Over Fabric Target.
+"""
+
+import sys
+import json
+import time
+import subprocess
+from nose.tools import assert_equal
+from port import NVMeOFTargetPort
+from target_subsystem import NVMeOFTargetSubsystem
+
+
+class NVMeOFTarget(object):
+
+ """
+ Represents a target subsystem.
+
+ - Attributes:
+ - subsys_list : list of the subsystem.
+ - port_list : list of the ports.
+ - target_type : target type for ports.
+ - cfgfs : configfs mount point.
+ """
+ def __init__(self, target_type):
+ self.subsys_list = []
+ self.port_list = []
+ self.target_type = target_type
+ self.cfgfs = "/sys/kernel/config/"
+ self.err_str = "ERROR : " + self.__class__.__name__ + " : "
+
+ assert_equal(self.load_configfs(), True)
+
+ def exec_cmd(self, cmd):
+ """ Wrapper for executing a shell command.
+ - Args :
+ - cmd : command to execute.
+ - Returns :
+ - True if cmd returns 0, False otherwise.
+ """
+ proc = None
+ try:
+ proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
+ except Exception, err:
+ print self.err_str + str(err) + "."
+ return False
+
+ return True if proc.wait() == 0 else False
+
+ def load_configfs(self):
+ """ Load configfs.
+ - Args :
+ - None
+ -Returns :
+ - True on success, False on failure.
+ """
+ ret = self.exec_cmd("mountpoint -q " + self.cfgfs)
+ if ret is False:
+ ret = self.exec_cmd("mount -t configfs none " + self.cfgfs)
+ if ret is False:
+ print self.err_str + "failed to mount configfs."
+ sys.exit(ret)
+ ret = self.exec_cmd("mountpoint -q " + self.cfgfs)
+ if ret is True:
+ print "Configfs mounted at " + self.cfgfs + "."
+ ret = True
+ else:
+ print self.err_str + "unable to mount configfs at " + \
+ self.cfgfs + "."
+ ret = False
+ return ret
+
+ def config_loop_target(self, config_file):
+ """ Configure loop target :-
+ 1. Create subsystem(s) and respective namespace(s).
+ 2. Create port(s) and linked them to respective subsystem(s).
+ 3. Create in memory configuration from JSON config file.
+ - Args :
+ - None
+ -Returns :
+ - True on success, False on failure.
+ """
+ ret = self.exec_cmd("modprobe nvme-loop")
+ if ret is False:
+ print self.err_str + "failed to load nvme-loop."
+ return False
+
+ try:
+ config_file_handle = open(config_file, "r")
+ config = json.loads(config_file_handle.read())
+ config_file_handle.close()
+ except Exception, err:
+ print self.err_str + str(err)
+ return False
+
+ # Subsystem
+ for sscfg in config['subsystems']:
+ # Create Subsystem
+ subsys = NVMeOFTargetSubsystem(self.cfgfs,
+ sscfg['nqn'],
+ sscfg['allowed_hosts'][0],
+ sscfg['attr']['allow_any_host'])
+ ret = subsys.init_subsys()
+ if ret is False:
+ # call unwind code here.
+ return False
+
+ self.subsys_list.append(subsys)
+
+ for nscfg in sscfg['namespaces']:
+ ns_attr = {}
+ ns_attr['device_nguid'] = str(nscfg['device']['nguid'])
+ ns_attr['device_path'] = str(nscfg['device']['path'])
+ ns_attr['enable'] = str(nscfg['enable'])
+ ns_attr['nsid'] = str(nscfg['nsid'])
+ ret = subsys.create_ns(**ns_attr)
+ if ret is False:
+ # call unwind code here.
+ return False
+
+ # Port
+ for pcfg in config['ports']:
+ port_cfg = {}
+ port_cfg['addr_treq'] = pcfg['addr']['treq']
+ port_cfg['addr_traddr'] = pcfg['addr']['traddr']
+ port_cfg['addr_trtype'] = pcfg['addr']['trtype']
+ port_cfg['addr_adrfam'] = pcfg['addr']['adrfam']
+ port_cfg['addr_trsvcid'] = pcfg['addr']['trsvcid']
+ port_cfg['portid'] = str(pcfg['portid'])
+ port_cfg['subsystems'] = pcfg['subsystems']
+
+ port = NVMeOFTargetPort(self.cfgfs, port_cfg['portid'], **port_cfg)
+ ret = port.init_port()
+ if ret is False:
+ # call unwind code here.
+ return False
+
+ self.port_list.append(port)
+
+ for subsys in port_cfg['subsystems']:
+ ret = port.add_subsys(subsys)
+ if ret is False:
+ # call unwind code here.
+ return False
+
+ return True
+
+ def config_target(self, config_file="loop.json"):
+ """ Wrapper for creating target configuration.
+ - Args :
+ - None
+ -Returns :
+ - None
+ """
+ ret = self.exec_cmd("modprobe nvmet")
+ if ret is False:
+ print self.err_str + "unable to load nvmet module."
+ return False
+
+ ret = False
+ if self.target_type == "loop":
+ print "Configuring loop target"
+ ret = self.config_loop_target(config_file)
+ else:
+ print self.err_str + "only loop target type is supported."
+
+ return ret
+
+ def del_target(self):
+ """ Target Cleanup.
+ - Args :
+ - None
+ -Returns :
+ - None
+ """
+ print "Cleanup is in progress..."
+
+ for port in self.port_list:
+ port.del_port()
+
+ for subsys in self.subsys_list:
+ subsys.del_subsys()
+
+ time.sleep(1)
+ print "Removing Modules :- "
+ self.exec_cmd("modprobe -r nvme_loop")
+ time.sleep(1)
+ self.exec_cmd("modprobe -r nvmet")
+ time.sleep(1)
+ self.exec_cmd("modprobe -r nvme_fabrics")
+ print "DONE."
diff --git a/tools/testing/selftests/nvmftests/target_ns.py b/tools/testing/selftests/nvmftests/target_ns.py
new file mode 100644
index 0000000..f8c0935
--- /dev/null
+++ b/tools/testing/selftests/nvmftests/target_ns.py
@@ -0,0 +1,132 @@
+# 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 <chaitanya.kulkarni at hgst.com>
+#
+""" Represents NVMe Over Fabric Target Namespace.
+"""
+
+import os
+import shutil
+import subprocess
+
+
+class NVMeOFTargetNamespace(object):
+ """
+ Represents a target namespace.
+
+ - Attributes :
+ - ns_attr : namespace attributes.
+ - cfgfs : configfs path
+ - nqn : subsystem nqn
+ - ns_id : namespace identifier.
+ - ns_path : namespace path in configfs path.
+ - ns_attr : namespace attributes.
+ """
+ def __init__(self, cfgfs, nqn, ns_id, **ns_attr):
+ self.ns_attr = {}
+ self.cfgfs = cfgfs
+ self.nqn = nqn
+ self.ns_id = ns_id
+ self.ns_path = (self.cfgfs + "/nvmet/subsystems/" +
+ nqn + "/namespaces/" + str(ns_id) + "/")
+ self.ns_attr['device_nguid'] = ns_attr['device_nguid']
+ self.ns_attr['device_path'] = ns_attr['device_path']
+ self.ns_attr['enable'] = ns_attr['enable']
+ self.err_str = "ERROR : " + self.__class__.__name__ + " : "
+
+ def init_ns(self):
+ """ Create and initialize namespace.
+ - Args :
+ - None.
+ - Returns :
+ - True on success, False on failure.
+ """
+ print "####Creating ns " + self.ns_path
+ try:
+ os.makedirs(self.ns_path)
+ except Exception, err:
+ print self.err_str + str(err) + "."
+ return False
+
+ cmd = "echo -n " + self.ns_attr['device_path'] + " > " + \
+ self.ns_path + "/device_path"
+ ret = self.exec_cmd(cmd)
+ if ret is False:
+ print self.err_str + "failed to configure device path."
+ return False
+
+ if self.ns_attr['enable'] == '1':
+ ret = self.ns_enable()
+ if ret is False:
+ print self.err_str + "enable ns " + self.ns_path + " failed."
+ return False
+
+ print "NS " + self.ns_path + " enabled."
+ return True
+
+ def exec_cmd(self, cmd):
+ """ Wrapper for executing a shell command.
+ - Args :
+ - cmd : command to execute.
+ - Returns :
+ - True if cmd returns 0, False otherwise.
+ """
+ proc = None
+ try:
+ proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
+ except Exception, err:
+ print self.err_str + str(err) + "."
+ return False
+
+ return True if proc.wait() == 0 else False
+
+ def ns_disable(self):
+ """ Disable Namespace.
+ - Args :
+ - None.
+ - Returns :
+ - True on success, False on failure.
+ """
+ self.ns_attr['enable'] = "0"
+ return self.exec_cmd("echo 0 > " + self.ns_path + "/enable")
+
+ def ns_enable(self):
+ """ Enable Namespace.
+ - Args :
+ - None.
+ - Returns :
+ - True on success, False on failure.
+ """
+ self.ns_attr['enable'] = "1"
+ return self.exec_cmd("echo 1 > " + self.ns_path + "/enable")
+
+ def del_ns(self):
+ """ Delete This Namespace.
+ - Args :
+ - None.
+ - Returns :
+ - True on success, False on failure.
+ """
+ print "Removing NS " + self.ns_path
+ ret = os.path.exists(self.ns_path)
+ if ret is True:
+ # TODO : improve cleanup funcitonality.
+ shutil.rmtree(self.ns_path, ignore_errors=True)
+ else:
+ print self.err_str + "path " + self.ns_path + " doesn't exists."
+ return ret
diff --git a/tools/testing/selftests/nvmftests/target_subsystem.py b/tools/testing/selftests/nvmftests/target_subsystem.py
new file mode 100644
index 0000000..0d5d6d5
--- /dev/null
+++ b/tools/testing/selftests/nvmftests/target_subsystem.py
@@ -0,0 +1,148 @@
+# 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 <chaitanya.kulkarni at hgst.com>
+#
+""" Represents NVMe Over Fabric Target Subsystem.
+"""
+
+import os
+import shutil
+import subprocess
+
+from target_ns import NVMeOFTargetNamespace
+
+
+class NVMeOFTargetSubsystem(object):
+ """
+ Represents a target controller.
+
+ - Attributes :
+ - ns_list : list of namespaces.
+ - cfgfs : configfs path.
+ - nqn : subsystem nqn.
+ - subsys_path : subsystem path in configfs.
+ - allowed_hosts : configfs allowed host attribute.
+ - attr_allow_any_host : configfs allow any host attribute.
+ """
+ def __init__(self, cfgfs, nqn, allowed_hosts, attr_allow_any_host):
+ self.ns_list = []
+ self.cfgfs = cfgfs
+ self.nqn = nqn
+ self.subsys_path = self.cfgfs + "/nvmet/subsystems/" + nqn + "/"
+ self.allowed_hosts = allowed_hosts
+ self.attr_allow_any_host = attr_allow_any_host
+ self.err_str = "ERROR : " + self.__class__.__name__ + " : "
+
+ def init_subsys(self):
+ """ create and initialize subsystem.
+ - Args :
+ - None.
+ - Returns :
+ - True on success, False on failure.
+ """
+ print "Loading nvme-loop module ..."
+ ret = self.exec_cmd("modprobe nvme-loop")
+ if ret is False:
+ print self.err_str + "unable to load nvme-loop module."
+ return False
+ # create subsystem dir
+ print "Creating subsys path " + self.subsys_path + "."
+ try:
+ os.makedirs(self.subsys_path)
+ except Exception, err:
+ print self.err_str + str(err)
+ return False
+ # allow any host
+ print "Configuring allowed hosts ..."
+ ret = self.exec_cmd("echo " + self.attr_allow_any_host + " >" +
+ self.subsys_path + "/attr_allow_any_host")
+ status = "Target Subsys " + self.subsys_path + " created successfully."
+ if ret is False:
+ status = self.err_str + "create " + self.subsys_path + " failed."
+ print status
+
+ return ret
+
+ def exec_cmd(self, cmd):
+ """ Wrapper for executing a shell command.
+ - Args :
+ - cmd : command to execute.
+ - Returns :
+ - True if cmd returns 0, False otherwise.
+ """
+ proc = None
+ try:
+ proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
+ except Exception, err:
+ print self.err_str + str(err) + "."
+ return False
+
+ return True if proc.wait() == 0 else False
+
+ def create_ns(self, **ns_attr):
+ """ Create, initialize and store namespace in subsystem's list.
+ - Args :
+ - ns_attr : namespace attributes.
+ - Returns :
+ - namespace handle on success, None on error.
+ """
+ ns_id = self.generate_next_ns_id()
+
+ ns = NVMeOFTargetNamespace(self.cfgfs, self.nqn, ns_id, **ns_attr)
+ if ns.init_ns() is False:
+ return None
+ self.ns_list.append(ns)
+ return ns
+
+ def del_ns(self, ns):
+ """ Delete single namespace.
+ - Args :
+ - None.
+ - Returns :
+ - None.
+ """
+ print "Deleting namespace " + self.nqn + " : " + ns.ns_path
+
+ ret = ns.del_ns()
+ if ret is False:
+ print "ERROR : delete ns failed for " + ns.ns_path + "."
+
+ return ret
+
+ def del_subsys(self):
+ """ Delete subsystem and associated namespace(s).
+ - Args :
+ - None.
+ - Returns :
+ - None.
+ """
+ print "Deleting subsystem " + self.nqn
+ for ns in self.ns_list:
+ self.del_ns(ns)
+
+ if os.path.exists(self.subsys_path):
+ shutil.rmtree(self.subsys_path, ignore_errors=True)
+
+ def generate_next_ns_id(self):
+ """ Return next namespace id.
+ - Args :
+ - None.
+ - Returns :
+ - next namespace id.
+ """
+ return len(self.ns_list) + 1
diff --git a/tools/testing/selftests/nvmftests/test_nvmf_create_delete_fabric.py b/tools/testing/selftests/nvmftests/test_nvmf_create_delete_fabric.py
new file mode 100644
index 0000000..4c1df6c
--- /dev/null
+++ b/tools/testing/selftests/nvmftests/test_nvmf_create_delete_fabric.py
@@ -0,0 +1,72 @@
+# Copyright (c) 2015-2016 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 <chaitanya.kulkarni at hgst.com>
+#
+"""
+NVMeOF Create/Delete Host, Target :-
+
+ 1. From the config file create Target.
+ 2. From the config file create host and connect to target.
+ 3. Delete Host.
+ 4. Delete Target.
+"""
+
+import time
+from loopback import Loopback
+from nvmf_test import NVMeOFTest
+from target import NVMeOFTarget
+from host import NVMeOFHost
+from nose.tools import assert_equal
+
+
+class TestNVMFCreateDeleteFabric(NVMeOFTest):
+
+ """ Represents Create Delete Fabric testcase """
+
+ def __init__(self):
+ NVMeOFTest.__init__(self)
+ self.loopdev = None
+ self.host_subsys = None
+ self.target_subsys = None
+
+ self.setup_log_dir(self.__class__.__name__)
+ self.loopdev = Loopback(self.mount_path, self.data_size,
+ self.block_size, self.nr_devices)
+ time.sleep(1)
+
+ def setUp(self):
+ print "configuering loopback"
+ self.loopdev.init_loopback()
+ time.sleep(1)
+ target_type = "loop"
+ self.target_subsys = NVMeOFTarget(target_type)
+ self.target_subsys.config_target()
+ self.host_subsys = NVMeOFHost(target_type)
+
+ def tearDown(self):
+ time.sleep(1)
+ self.host_subsys.del_host()
+ time.sleep(1)
+ self.target_subsys.del_target()
+ print "deleting loopback"
+ self.loopdev.del_loopback()
+
+ def test_create_delete_fabric(self):
+ """ Testcase main """
+ ret = self.host_subsys.config_host()
+ assert_equal(ret, True, "ERROR : config host failed")
diff --git a/tools/testing/selftests/nvmftests/test_nvmf_create_host.py b/tools/testing/selftests/nvmftests/test_nvmf_create_host.py
new file mode 100644
index 0000000..c918932
--- /dev/null
+++ b/tools/testing/selftests/nvmftests/test_nvmf_create_host.py
@@ -0,0 +1,67 @@
+# Copyright (c) 2015-2016 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 <chaitanya.kulkarni at hgst.com>
+#
+"""
+NVMeOF Create/Delete Host, Target :-
+
+ 1. From the config file create Target.
+ 2. From the config file create host and connect to target.
+ 3. Delete Host.
+ 4. Delete Target.
+"""
+
+from loopback import Loopback
+from nvmf_test import NVMeOFTest
+from target import NVMeOFTarget
+from host import NVMeOFHost
+from nose.tools import assert_equal
+
+
+class TestNVMFCreateHost(NVMeOFTest):
+
+ """ Represents Create Delete Fabric testcase """
+
+ def __init__(self):
+ NVMeOFTest.__init__(self)
+ self.loopdev = None
+ self.host_subsys = None
+ self.target_subsys = None
+
+ self.setup_log_dir(self.__class__.__name__)
+ self.loopdev = Loopback(self.mount_path, self.data_size,
+ self.block_size, self.nr_devices)
+
+ def setUp(self):
+ """ Pre section of testcase """
+ self.loopdev.init_loopback()
+ target_type = "loop"
+ self.target_subsys = NVMeOFTarget(target_type)
+ self.target_subsys.config_target()
+ self.host_subsys = NVMeOFHost(target_type)
+
+ def tearDown(self):
+ """ Post section of testcase """
+ self.host_subsys.del_host()
+ self.target_subsys.del_target()
+ self.loopdev.del_loopback()
+
+ def test_create_host(self):
+ """ Testcase main """
+ ret = self.host_subsys.config_host()
+ assert_equal(ret, True, "ERROR : config host failed")
diff --git a/tools/testing/selftests/nvmftests/test_nvmf_create_target.py b/tools/testing/selftests/nvmftests/test_nvmf_create_target.py
new file mode 100644
index 0000000..82ebf83
--- /dev/null
+++ b/tools/testing/selftests/nvmftests/test_nvmf_create_target.py
@@ -0,0 +1,59 @@
+# Copyright (c) 2015-2016 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 <chaitanya.kulkarni at hgst.com>
+#
+"""
+NVMeOF Create/Delete Target :-
+
+ 1. From the config file create Target.
+ 2. Delete Target.
+"""
+
+from loopback import Loopback
+from nvmf_test import NVMeOFTest
+from target import NVMeOFTarget
+from nose.tools import assert_equal
+
+
+class TestNVMFCreateTarget(NVMeOFTest):
+
+ """ Represents Create Delete Target testcase """
+
+ def __init__(self):
+ NVMeOFTest.__init__(self)
+ self.target_subsys = None
+ self.loopdev = None
+ self.setup_log_dir(self.__class__.__name__)
+ self.loopdev = Loopback(self.mount_path, self.data_size,
+ self.block_size, self.nr_devices)
+
+ def setUp(self):
+ """ Pre section of testcase """
+ self.loopdev.init_loopback()
+ target_type = "loop"
+ self.target_subsys = NVMeOFTarget(target_type)
+
+ def tearDown(self):
+ """ Post section of testcase """
+ self.target_subsys.del_target()
+ self.loopdev.del_loopback()
+
+ def test_create_target(self):
+ """ Testcase main """
+ ret = self.target_subsys.config_target()
+ assert_equal(ret, True, "ERROR : config target failed.")
diff --git a/tools/testing/selftests/nvmftests/test_nvmf_host_template.py b/tools/testing/selftests/nvmftests/test_nvmf_host_template.py
new file mode 100644
index 0000000..c1cb054
--- /dev/null
+++ b/tools/testing/selftests/nvmftests/test_nvmf_host_template.py
@@ -0,0 +1,69 @@
+
+# Copyright (c) 2015-2016 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 <chaitanya.kulkarni at hgst.com>
+#
+"""
+NVMeOF host template :-
+
+ 1. From the config file create Target.
+ 2. From the config file create host and connect to target.
+ 3. Write testcase code here.
+ 4. Delete Host.
+ 5. Delete Target.
+"""
+
+from loopback import Loopback
+from nvmf_test import NVMeOFTest
+from target import NVMeOFTarget
+from host import NVMeOFHost
+from nose.tools import assert_equal
+
+
+class TestNVMFHostTemplate(NVMeOFTest):
+
+ """ Represents host template testcase """
+
+ def __init__(self):
+ NVMeOFTest.__init__(self)
+ self.loopdev = None
+ self.host_subsys = None
+ self.target_subsys = None
+
+ self.setup_log_dir(self.__class__.__name__)
+ self.loopdev = Loopback(self.mount_path, self.data_size,
+ self.block_size, self.nr_devices)
+
+ def setUp(self):
+ """ Pre section of testcase """
+ self.loopdev.init_loopback()
+ target_type = "loop"
+ self.target_subsys = NVMeOFTarget(target_type)
+ self.target_subsys.config_target()
+ self.host_subsys = NVMeOFHost(target_type)
+ self.host_subsys.config_host()
+
+ def tearDown(self):
+ """ Post section of testcase """
+ self.host_subsys.del_host()
+ self.target_subsys.del_target()
+ self.loopdev.del_loopback()
+
+ def test_mkfs(self):
+ """ Testcase main """
+ assert_equal(0, 0, "")
diff --git a/tools/testing/selftests/nvmftests/test_nvmf_id_ctrl.py b/tools/testing/selftests/nvmftests/test_nvmf_id_ctrl.py
new file mode 100644
index 0000000..76849b3
--- /dev/null
+++ b/tools/testing/selftests/nvmftests/test_nvmf_id_ctrl.py
@@ -0,0 +1,69 @@
+# Copyright (c) 2015-2016 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 <chaitanya.kulkarni at hgst.com>
+#
+"""
+NVMeOF test id-ctrl on each controller :-
+
+ 1. From the config file create Target.
+ 2. From the config file create host and connect to target.
+ 3. Execute id-ctrl on all controllers.
+ 4. Delete Host.
+ 5. Delete Target.
+"""
+
+from loopback import Loopback
+from nvmf_test import NVMeOFTest
+from target import NVMeOFTarget
+from host import NVMeOFHost
+from nose.tools import assert_equal
+
+
+class TestNVMFIdentifyController(NVMeOFTest):
+
+ """ Represents Identify Controller Test testcase """
+
+ def __init__(self):
+ NVMeOFTest.__init__(self)
+ self.loopdev = None
+ self.host_subsys = None
+ self.target_subsys = None
+
+ self.setup_log_dir(self.__class__.__name__)
+ self.loopdev = Loopback(self.mount_path, self.data_size,
+ self.block_size, self.nr_devices)
+
+ def setUp(self):
+ """ Pre section of testcase """
+ self.loopdev.init_loopback()
+ target_type = "loop"
+ self.target_subsys = NVMeOFTarget(target_type)
+ self.target_subsys.config_target()
+ self.host_subsys = NVMeOFHost(target_type)
+ self.host_subsys.config_host()
+
+ def tearDown(self):
+ """ Post section of testcase """
+ self.host_subsys.del_host()
+ self.target_subsys.del_target()
+ self.loopdev.del_loopback()
+
+ def test_identify_controller(self):
+ """ Testcase main """
+ ret = self.host_subsys.id_ctrl()
+ assert_equal(ret, True, "ERROR : id controller failed.")
diff --git a/tools/testing/selftests/nvmftests/test_nvmf_id_ns.py b/tools/testing/selftests/nvmftests/test_nvmf_id_ns.py
new file mode 100644
index 0000000..900f3c9
--- /dev/null
+++ b/tools/testing/selftests/nvmftests/test_nvmf_id_ns.py
@@ -0,0 +1,69 @@
+# Copyright (c) 2015-2016 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 <chaitanya.kulkarni at hgst.com>
+#
+"""
+NVMeOF test id-ns on each namespace :-
+
+ 1. From the config file create Target.
+ 2. From the config file create host and connect to target.
+ 3. Execute id-ns namespaces.
+ 4. Delete Host.
+ 5. Delete Target.
+"""
+
+from loopback import Loopback
+from nvmf_test import NVMeOFTest
+from target import NVMeOFTarget
+from host import NVMeOFHost
+from nose.tools import assert_equal
+
+
+class TestNVMFIdentifyNamespace(NVMeOFTest):
+
+ """ Represents Identify Namespace Test testcase """
+
+ def __init__(self):
+ NVMeOFTest.__init__(self)
+ self.loopdev = None
+ self.host_subsys = None
+ self.target_subsys = None
+
+ self.setup_log_dir(self.__class__.__name__)
+ self.loopdev = Loopback(self.mount_path, self.data_size,
+ self.block_size, self.nr_devices)
+
+ def setUp(self):
+ """ Pre section of testcase """
+ self.loopdev.init_loopback()
+ target_type = "loop"
+ self.target_subsys = NVMeOFTarget(target_type)
+ self.target_subsys.config_target()
+ self.host_subsys = NVMeOFHost(target_type)
+ self.host_subsys.config_host()
+
+ def tearDown(self):
+ """ Post section of testcase """
+ self.host_subsys.del_host()
+ self.target_subsys.del_target()
+ self.loopdev.del_loopback()
+
+ def test_identify_namespace(self):
+ """ Testcase main """
+ ret = self.host_subsys.id_ns()
+ assert_equal(ret, True, "ERROR : id ns failed.")
diff --git a/tools/testing/selftests/nvmftests/test_nvmf_mkfs.py b/tools/testing/selftests/nvmftests/test_nvmf_mkfs.py
new file mode 100644
index 0000000..cfc0d60
--- /dev/null
+++ b/tools/testing/selftests/nvmftests/test_nvmf_mkfs.py
@@ -0,0 +1,70 @@
+
+# Copyright (c) 2015-2016 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 <chaitanya.kulkarni at hgst.com>
+#
+"""
+NVMeOF test mkfs on each subsystem :-
+
+ 1. From the config file create Target.
+ 2. From the config file create host and connect to target.
+ 3. Execute mkfs on all controllers and its namespaces.
+ 4. Delete Host.
+ 5. Delete Target.
+"""
+
+from loopback import Loopback
+from nvmf_test import NVMeOFTest
+from target import NVMeOFTarget
+from host import NVMeOFHost
+from nose.tools import assert_equal
+
+
+class TestNVMFMKFS(NVMeOFTest):
+
+ """ Represents mkfs testcase """
+
+ def __init__(self):
+ NVMeOFTest.__init__(self)
+ self.loopdev = None
+ self.host_subsys = None
+ self.target_subsys = None
+
+ self.setup_log_dir(self.__class__.__name__)
+ self.loopdev = Loopback(self.mount_path, self.data_size,
+ self.block_size, self.nr_devices)
+
+ def setUp(self):
+ """ Pre section of testcase """
+ self.loopdev.init_loopback()
+ target_type = "loop"
+ self.target_subsys = NVMeOFTarget(target_type)
+ self.target_subsys.config_target()
+ self.host_subsys = NVMeOFHost(target_type)
+ self.host_subsys.config_host()
+
+ def tearDown(self):
+ """ Post section of testcase """
+ self.host_subsys.del_host()
+ self.target_subsys.del_target()
+ self.loopdev.del_loopback()
+
+ def test_mkfs(self):
+ """ Testcase main """
+ ret = self.host_subsys.mkfs_seq()
+ assert_equal(ret, True, "ERROR : mkfs failed.")
diff --git a/tools/testing/selftests/nvmftests/test_nvmf_parallel_io.py b/tools/testing/selftests/nvmftests/test_nvmf_parallel_io.py
new file mode 100644
index 0000000..ba70f03
--- /dev/null
+++ b/tools/testing/selftests/nvmftests/test_nvmf_parallel_io.py
@@ -0,0 +1,71 @@
+# Copyright (c) 2015-2016 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 <chaitanya.kulkarni at hgst.com>
+#
+"""
+NVMeOF test parallel IOs on all controllers :-
+
+ 1. From the config file create Target.
+ 2. From the config file create host and connect to target.
+ 3. Run parallel IOs on all available controller(s) and its nvmespace(s).
+ 4. Delete Host.
+ 5. Delete Target.
+"""
+
+from loopback import Loopback
+from nvmf_test import NVMeOFTest
+from target import NVMeOFTarget
+from host import NVMeOFHost
+from nose.tools import assert_equal
+
+
+class TestNVMeOFParallelFabric(NVMeOFTest):
+
+ """ Represents Parallel Subsystem IO testcase """
+
+ def __init__(self):
+ NVMeOFTest.__init__(self)
+ self.loopdev = None
+ self.host_subsys = None
+ self.target_subsys = None
+
+ self.setup_log_dir(self.__class__.__name__)
+ self.loopdev = Loopback(self.mount_path, self.data_size,
+ self.block_size, self.nr_devices)
+
+ def setUp(self):
+ """ Pre section of testcase """
+ self.loopdev.init_loopback()
+ target_type = "loop"
+ self.target_subsys = NVMeOFTarget(target_type)
+ self.target_subsys.config_target()
+ self.host_subsys = NVMeOFHost(target_type)
+ self.host_subsys.config_host()
+
+ def tearDown(self):
+ """ Post section of testcase """
+ self.host_subsys.del_host()
+ self.target_subsys.del_target()
+ self.loopdev.del_loopback()
+
+ def test_parallel_io(self):
+ """ Testcase main """
+ ret = self.host_subsys.run_ios_parallel(self.dd_read)
+ assert_equal(ret, True, "ERROR : running IOs failed.")
+ ret = self.host_subsys.run_ios_parallel(self.dd_write)
+ assert_equal(ret, True, "ERROR : running IOs failed.")
diff --git a/tools/testing/selftests/nvmftests/test_nvmf_random_io.py b/tools/testing/selftests/nvmftests/test_nvmf_random_io.py
new file mode 100644
index 0000000..d3d125b
--- /dev/null
+++ b/tools/testing/selftests/nvmftests/test_nvmf_random_io.py
@@ -0,0 +1,71 @@
+# Copyright (c) 2015-2016 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 <chaitanya.kulkarni at hgst.com>
+#
+"""
+NVMeOF randomly select a controller and run IOs :-
+
+ 1. From the config file create Target.
+ 2. From the config file create host and connect to target.
+ 3. Randomly select the controller and its namespaces(s) for IOs.
+ 4. Delete Host.
+ 5. Delete Target.
+"""
+
+from loopback import Loopback
+from nvmf_test import NVMeOFTest
+from target import NVMeOFTarget
+from host import NVMeOFHost
+from nose.tools import assert_equal
+
+
+class TestNVMeOFRandomFabric(NVMeOFTest):
+
+ """ Represents Random Susbsytem IO testcase """
+
+ def __init__(self):
+ NVMeOFTest.__init__(self)
+ self.loopdev = None
+ self.host_subsys = None
+ self.target_subsys = None
+
+ self.setup_log_dir(self.__class__.__name__)
+ self.loopdev = Loopback(self.mount_path, self.data_size,
+ self.block_size, self.nr_devices)
+
+ def setUp(self):
+ """ Pre section of testcase """
+ self.loopdev.init_loopback()
+ target_type = "loop"
+ self.target_subsys = NVMeOFTarget(target_type)
+ self.target_subsys.config_target()
+ self.host_subsys = NVMeOFHost(target_type)
+ self.host_subsys.config_host()
+
+ def tearDown(self):
+ """ Post section of testcase """
+ self.host_subsys.del_host()
+ self.target_subsys.del_target()
+ self.loopdev.del_loopback()
+
+ def test_random_io(self):
+ """ Testcase main """
+ ret = self.host_subsys.run_ios_random(self.dd_read)
+ assert_equal(ret, True, "ERROR : running IOs failed.")
+ ret = self.host_subsys.run_ios_random(self.dd_write)
+ assert_equal(ret, True, "ERROR : running IOs failed.")
diff --git a/tools/testing/selftests/nvmftests/test_nvmf_simple_io.py b/tools/testing/selftests/nvmftests/test_nvmf_simple_io.py
new file mode 100644
index 0000000..cc0428e
--- /dev/null
+++ b/tools/testing/selftests/nvmftests/test_nvmf_simple_io.py
@@ -0,0 +1,71 @@
+# Copyright (c) 2015-2016 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 <chaitanya.kulkarni at hgst.com>
+#
+"""
+NVMeOF sequntially select a controller and run IOs:-
+
+ 1. From the config file create Target.
+ 2. From the config file create host and connect to target.
+ 3. Run IOs sequentially iterating over controller(s) ans its namespaces(s).
+ 3. Delete Host.
+ 4. Delete Target.
+"""
+
+from loopback import Loopback
+from nvmf_test import NVMeOFTest
+from target import NVMeOFTarget
+from host import NVMeOFHost
+from nose.tools import assert_equal
+
+
+class TestNVMeOFSeqFabric(NVMeOFTest):
+
+ """ Represents Sequential Subsystem IO testcase """
+
+ def __init__(self):
+ NVMeOFTest.__init__(self)
+ self.loopdev = None
+ self.host_subsys = None
+ self.target_subsys = None
+
+ self.setup_log_dir(self.__class__.__name__)
+ self.loopdev = Loopback(self.mount_path, self.data_size,
+ self.block_size, self.nr_devices)
+
+ def setUp(self):
+ """ Pre section of testcase """
+ self.loopdev.init_loopback()
+ target_type = "loop"
+ self.target_subsys = NVMeOFTarget(target_type)
+ self.target_subsys.config_target()
+ self.host_subsys = NVMeOFHost(target_type)
+ self.host_subsys.config_host()
+
+ def tearDown(self):
+ """ Post section of testcase """
+ self.host_subsys.del_host()
+ self.target_subsys.del_target()
+ self.loopdev.del_loopback()
+
+ def test_seq_io(self):
+ """ Testcase main """
+ ret = self.host_subsys.run_ios_seq(self.dd_read)
+ assert_equal(ret, True, "ERROR : running IOs failed.")
+ ret = self.host_subsys.run_ios_seq(self.dd_write)
+ assert_equal(ret, True, "ERROR : running IOs failed.")
diff --git a/tools/testing/selftests/nvmftests/test_nvmf_smart_log.py b/tools/testing/selftests/nvmftests/test_nvmf_smart_log.py
new file mode 100644
index 0000000..172e0a8
--- /dev/null
+++ b/tools/testing/selftests/nvmftests/test_nvmf_smart_log.py
@@ -0,0 +1,69 @@
+# Copyright (c) 2015-2016 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 <chaitanya.kulkarni at hgst.com>
+#
+"""
+NVMeOF test smart log on all controllers :-
+
+ 1. From the config file create Target.
+ 2. From the config file create host and connect to target.
+ 3. Execute smart-log on all controllers and its namespaces.
+ 4. Delete Host.
+ 5. Delete Target.
+"""
+
+from loopback import Loopback
+from nvmf_test import NVMeOFTest
+from target import NVMeOFTarget
+from host import NVMeOFHost
+from nose.tools import assert_equal
+
+
+class TestNVMFSmartLog(NVMeOFTest):
+
+ """ Represents Smart Log testcase """
+
+ def __init__(self):
+ NVMeOFTest.__init__(self)
+ self.loopdev = None
+ self.host_subsys = None
+ self.target_subsys = None
+
+ self.setup_log_dir(self.__class__.__name__)
+ self.loopdev = Loopback(self.mount_path, self.data_size,
+ self.block_size, self.nr_devices)
+
+ def setUp(self):
+ """ Pre section of testcase """
+ self.loopdev.init_loopback()
+ target_type = "loop"
+ self.target_subsys = NVMeOFTarget(target_type)
+ self.target_subsys.config_target()
+ self.host_subsys = NVMeOFHost(target_type)
+ self.host_subsys.config_host()
+
+ def tearDown(self):
+ """ Post section of testcase """
+ self.host_subsys.del_host()
+ self.target_subsys.del_target()
+ self.loopdev.del_loopback()
+
+ def test_smart_log(self):
+ """ Testcase main """
+ ret = self.host_subsys.smart_log()
+ assert_equal(ret, True, "ERROR : running smart log failed.")
diff --git a/tools/testing/selftests/nvmftests/test_nvmf_target_template.py b/tools/testing/selftests/nvmftests/test_nvmf_target_template.py
new file mode 100644
index 0000000..2358a8d
--- /dev/null
+++ b/tools/testing/selftests/nvmftests/test_nvmf_target_template.py
@@ -0,0 +1,64 @@
+
+# Copyright (c) 2015-2016 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 <chaitanya.kulkarni at hgst.com>
+#
+"""
+NVMeOF host template :-
+
+ 1. From the config file create Target.
+ 2. From the config file create host and connect to target.
+ 3. Write testcase code here.
+ 4. Delete Host.
+ 5. Delete Target.
+"""
+
+from loopback import Loopback
+from nvmf_test import NVMeOFTest
+from target import NVMeOFTarget
+from nose.tools import assert_equal
+
+
+class TestNVMFTargetTemplate(NVMeOFTest):
+
+ """ Represents target template testcase """
+
+ def __init__(self):
+ NVMeOFTest.__init__(self)
+ self.loopdev = None
+ self.target_subsys = None
+
+ self.setup_log_dir(self.__class__.__name__)
+ self.loopdev = Loopback(self.mount_path, self.data_size,
+ self.block_size, self.nr_devices)
+
+ def setUp(self):
+ """ Pre section of testcase """
+ self.loopdev.init_loopback()
+ target_type = "loop"
+ self.target_subsys = NVMeOFTarget(target_type)
+ self.target_subsys.config_target()
+
+ def tearDown(self):
+ """ Post section of testcase """
+ self.target_subsys.del_target()
+ self.loopdev.del_loopback()
+
+ def test_mkfs(self):
+ """ Testcase main """
+ assert_equal(0, 0, "")
--
2.7.4
More information about the Linux-nvme
mailing list