summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorLee Duncan <lduncan@suse.com>2020-02-19 11:14:35 -0800
committerLee Duncan <lduncan@suse.com>2020-02-19 11:14:35 -0800
commitf66c504430a6c568bf90a5cdf2a64eb995b557de (patch)
tree8a844ce6e7bd6e9f08194df9567cf6dcf9383c52 /test
parent44d7058555cd8498d1086db0464b169f4a988282 (diff)
downloadopen-iscsi-f66c504430a6c568bf90a5cdf2a64eb995b557de.tar.gz
Beginning to get python tests set up.
Created a framework. Replacing disktest with fio, sfdisk with parted, and moving to a PyUnit test framework.
Diffstat (limited to 'test')
-rw-r--r--test/.setup3
-rw-r--r--test/README78
-rw-r--r--test/TODO19
-rw-r--r--test/harness/__init__.py5
-rw-r--r--test/harness/iscsi.py63
-rw-r--r--test/harness/util.py257
-rwxr-xr-xtest/regression.sh23
-rwxr-xr-xtest/test-open-iscsi.py74
8 files changed, 512 insertions, 10 deletions
diff --git a/test/.setup b/test/.setup
new file mode 100644
index 0000000..cfa31b4
--- /dev/null
+++ b/test/.setup
@@ -0,0 +1,3 @@
+target="iqn.2003-01.org.linux-iscsi.linux-dell.x8664:sn.ae8d5828b4b4"
+ipnr="192.168.20.3:3260"
+dev="/dev/sdb"
diff --git a/test/README b/test/README
index 295b96f..af54c3c 100644
--- a/test/README
+++ b/test/README
@@ -1,3 +1,5 @@
+From the original test/README:
+---------------------------------------------------------------------------
This directory contains regression suite.
I would appreciate if developer will run it at least once after
@@ -13,3 +15,79 @@ in current directory:
Thanks!
Dmitry
+---------------------------------------------------------------------------
+Call:
+
+> # ./regression.sh -f/--format <device>
+
+to run "mkfs" on the device and exit, or
+
+> # ./regression.sh <targetname> <ipnumber#> <device> [test#[:#]] [bsize]
+
+Where:
+ <targetname> ?
+ <ipnumber#> ?
+ <device> the device on which to test (e.g. /dev/sd?)
+ test#[:#] test(s) to run, i.e. single or range (default: all)
+ bsize disktest block size (default: a range of 10 sizes)
+ special value "bonnie" => skip disktest
+
+And env var "SKIP_WARNING" skips a big warning about writing on the device?
+
+---------------------------------------------------------------------------
+
+The problem is that these tests have not been run for a while, and,
+additionally, disktest seems to be extinct and unfindable.
+
+I plan to get these tests working, and consider these the steps I plan
+to take:
+
+* understand the current tests
+ - particularly, what disktest and bonnie++ are doing
+
+* try to replace disktest and/or bonnie++, if needed
+ - there are a lot of good disk test packages these days
+
+* replace the shell code with PyTest code, to make it
+ easier to use
+
+Lee Duncan -- 2/13/2020
+
+Analysis of disktest usage:
+
+ options used: fio option for this?
+ ======================================= ==================================
+ --name=test (or whatever)
+ -T2 -- run 2 seconds --runtime=2
+ -K8 -- run with 8 threads --numjobs=8
+ -B<bs> -- set block xfer size --blocksize=<bs> (default 4k?)
+ -ID -- use direct IO --direct=1
+ <dev> -- use device --filename=<dev>
+
+ in read mode:
+ -r -- read mode --readwrite=randread
+
+ in write mode:
+ -w -- write mode --readwrite=randwrite
+ -E 16 -- compare 16 bytes --verify=md5? (lots of options)
+
+It looks like the "fio" program may address these needs?
+
+e.g. running
+
+with file "test.fio":
+> [test]
+> rw=randread
+> bs=8k
+> filename=/dev/sdb
+> direct=1
+> numjobs=4
+> runtime=60s
+
+run:
+
+> # fio test.fio
+
+The output is interactive? But results are ridiculously verbose,
+but include a nice summary line or two that could be used as a
+go/no-go?
diff --git a/test/TODO b/test/TODO
new file mode 100644
index 0000000..ce98f4e
--- /dev/null
+++ b/test/TODO
@@ -0,0 +1,19 @@
+* get current tests running
+ * this will mean replacing disktest with something
+ (like fio?)
+
+* convert to PyTest insteal of shell
+
+* have tests create their own target using targetcli and
+ a file?
+
+* have tests do discovery themselves, instead of requiring
+ that to be done already
+
+* Augment tests
+ * framework for adding new tests and test types,e.g.:
+ - multipathing
+ - using interface files or not
+ - discovery, with and without authentication
+ - session creation, w/ & w/o auth
+ - etc
diff --git a/test/harness/__init__.py b/test/harness/__init__.py
new file mode 100644
index 0000000..44fd09e
--- /dev/null
+++ b/test/harness/__init__.py
@@ -0,0 +1,5 @@
+"""
+Harness functions for the open-iscsi test suite.
+"""
+
+__version__ = "1.0"
diff --git a/test/harness/iscsi.py b/test/harness/iscsi.py
new file mode 100644
index 0000000..569a66e
--- /dev/null
+++ b/test/harness/iscsi.py
@@ -0,0 +1,63 @@
+"""
+ISCSI classes and utilities
+"""
+
+from .util import *
+
+class IscsiData:
+ """
+ Gather all the iscsi data in one place
+ """
+ imm_data_en = 'Yes'
+ initial_r2t_en = 'No'
+ hdrdgst_en = 'None,CRC32C'
+ datdgst_en = 'None,CRC32C'
+ first_burst = 256 * 1024
+ max_burst = 16 * 1024 * 1024 - 1024
+ max_recv_dlength = 128 * 1024
+ max_r2t = 1
+ # the target-name and IP:Port
+ target = None
+ ipnr = None
+
+ def __init__(self,
+ imm_data_en=imm_data_en,
+ initial_r2t_en=initial_r2t_en,
+ hdrdgst_en=hdrdgst_en,
+ datdgst_en=datdgst_en,
+ first_burst=first_burst,
+ max_burst=max_burst,
+ max_recv_dlength=max_recv_dlength,
+ max_r2t=max_r2t):
+ self.imm_data_en = imm_data_en
+ self.initial_r2t_en = initial_r2t_en
+ self.hdrdgst_en = hdrdgst_en
+ self.datdgst_en = datdgst_en
+ self.first_burst = first_burst
+ self.max_burst = max_burst
+ self.max_recv_dlength = max_recv_dlength
+ self.max_r2t = max_r2t
+
+ def update_cfg(self, target, ipnr):
+ """
+ Update the configuration -- we could do this by hacking on the
+ appropriate DB file, but this is safer (and slower) by far
+ """
+ if Global.verbosity > 1:
+ print('* ImmediateData = %s' % self.imm_data_en)
+ print('* InitialR2T = %s' % self.initial_r2t_en)
+ print('* HeaderDigest = %s' % self.hdrdgst_en)
+ print('* DataDigest = %s' % self.datdgst_en)
+ print('* FirstBurstLength = %d' % self.first_burst)
+ print('* MaxBurstLength = %d' % self.max_burst)
+ print('* MaxRecvDataSegmentLength = %d' % self.max_recv_dlength)
+ print('* MaxOutstandingR2T = %d' % self.max_r2t)
+ c = ['iscsiadm', '-m', 'node', '-T', target, '-p', ipnr, '-o', 'update']
+ run_cmd(c + ['-n', 'node.session.iscsi.ImmediateData', '-v', self.imm_data_en], quiet_mode=True)
+ run_cmd(c + ['-n', 'node.session.iscsi.InitialR2T', '-v', self.initial_r2t_en], quiet_mode=True)
+ run_cmd(c + ['-n', 'node.conn[0].iscsi.HeaderDigest', '-v', self.hdrdgst_en], quiet_mode=True)
+ run_cmd(c + ['-n', 'node.conn[0].iscsi.DataDigest', '-v', self.datdgst_en], quiet_mode=True)
+ run_cmd(c + ['-n', 'node.session.iscsi.FirstBurstLength', '-v', str(self.first_burst)], quiet_mode=True)
+ run_cmd(c + ['-n', 'node.session.iscsi.MaxBurstLength', '-v', str(self.max_burst)], quiet_mode=True)
+ run_cmd(c + ['-n', 'node.conn[0].iscsi.MaxRecvDataSegmentLength', '-v', str(self.max_recv_dlength)], quiet_mode=True)
+ run_cmd(c + ['-n', 'node.session.iscsi.MaxOutstandingR2T', '-v', str(self.max_r2t)], quiet_mode=True)
diff --git a/test/harness/util.py b/test/harness/util.py
new file mode 100644
index 0000000..3d21480
--- /dev/null
+++ b/test/harness/util.py
@@ -0,0 +1,257 @@
+"""
+harness stuff (support) -- utility routines
+"""
+
+import os
+import shutil
+import sys
+import unittest
+
+#
+# globals
+#
+class Global:
+ _FSTYPE = os.getenv('FSTYPE', 'ext3')
+ _MOUNTOPTIONS = os.getenv('MOUNTOPTIONS', '').split(' ') + [' -t ', _FSTYPE]
+ _MKFSCMD = [os.getenv('MKFSCMD', 'mkfs.' + _FSTYPE)]
+ if os.getenv('MKFSOPTS'):
+ _MKFSCMD += os.getenv('MKFSOPTS').split(' ')
+ _PARTITIONSUFFIX = '1'
+ _BONNIEPARAMS = os.getenv('BONNIEPARAMS', '--r0 -n10:0:0 -s16 -uroot -f -q').split(' ')
+ verbosity = 1
+ debug = False
+ # the target (e.g. "iqn.*")
+ target = None
+ # the IP and optional port (e.g. "linux-system", "192.168.10.1:3260")
+ ipnr = None
+ # the device that will be created when our target is connected
+ device = None
+ # the first and only partition on said device
+ partition = None
+ # optional override for fio disk testing block size(s)
+ blocksize = None
+
+
+def dprint(*args):
+ """
+ Print a debug message if in debug mode
+ """
+ if Global.debug:
+ print('DEBUG: ', file=sys.stderr, end='')
+ for arg in args:
+ print(arg, file=sys.stderr, end='')
+ print('', file=sys.stderr)
+
+def vprint(*args):
+ """
+ Print a verbose message
+ """
+ if Global.verbosity > 1 and args:
+ for arg in args:
+ print(arg, end='')
+ print('')
+
+def notice(*args):
+ """
+ Print if not in quiet mode
+ """
+ if Global.verbosity > 0:
+ for arg in args:
+ print(arg, end='')
+ print('')
+
+def run_cmd(cmd, output_save_file=None, quiet_mode=False):
+ """
+ run specified command, waiting for and returning result
+ """
+ if (Global.verbosity > 1 and not quiet_mode) or Global.debug:
+ cmd_str = ' '.join(cmd)
+ if output_save_file:
+ cmd_str += ' >& %s' % output_save_file
+ vprint(cmd_str)
+ pid = os.fork()
+ if pid < 0:
+ print("Error: cannot fork!", flie=sys.stderr)
+ sys.exit(1)
+ if pid == 0:
+ # the child
+ if output_save_file or quiet_mode:
+ stdout_fileno = sys.stdout.fileno()
+ stderr_fileno = sys.stderr.fileno()
+ if output_save_file:
+ new_stdout = os.open(output_save_file, os.O_WRONLY|os.O_CREAT|os.O_TRUNC,
+ mode=0o664)
+ else:
+ new_stdout = os.open('/dev/null', os.O_WRONLY)
+ os.dup2(new_stdout, stdout_fileno)
+ os.dup2(new_stdout, stderr_fileno)
+ os.execvp(cmd[0], cmd)
+ # not reached
+
+ # the parent
+ wpid, wstat = os.waitpid(pid, 0)
+ if wstat != 0:
+ dprint("exit status: (%d) %d" % (wstat, os.WEXITSTATUS(wstat)))
+ return os.WEXITSTATUS(wstat)
+
+def new_initArgParsers(self):
+ """
+ Add some options to the normal unittest main options
+ """
+ global old_initArgParsers
+
+ old_initArgParsers(self)
+ self._main_parser.add_argument('-d', '--debug', dest='debug',
+ action='store_true',
+ help='Enable developer debugging')
+ self._main_parser.add_argument('-t', '--target', dest='target',
+ action='store',
+ help='Required: target name')
+ self._main_parser.add_argument('-i', '--ipnr', dest='ipnr',
+ action='store',
+ help='Required: name-or-ip[:port]')
+ self._main_parser.add_argument('-D', '--device', dest='device',
+ action='store',
+ help='Required: device')
+ self._main_parser.add_argument('-B', '--blocksize', dest='blocksize',
+ action='store',
+ help='block size (defaults to an assortment of sizes)')
+
+def new_parseArgs(self, argv):
+ """
+ Gather globals from unittest main for local consumption
+ """
+ global old_parseArgs
+
+ old_parseArgs(self, argv)
+ Global.verbosity = self.verbosity
+ Global.debug = self.debug
+ for v in ['target', 'ipnr', 'device']:
+ if getattr(self, v) is None:
+ print('Error: "%s" required' % v.upper())
+ sys.exit(1)
+ setattr(Global, v, getattr(self, v))
+ Global.blocksize = self.blocksize
+ dprint("found: verbosity=%d, target=%s, ipnr=%s, device=%s, bs=%s" % \
+ (Global.verbosity, Global.target, Global.ipnr, Global.device, Global.blocksize))
+ # get partition from path
+ device_dir = os.path.dirname(Global.device)
+ if device_dir == '/dev':
+ Global.partition = '%s1' % Global.device
+ elif device_dir in ['/dev/disk/by-id', '/dev/disk/by-path']:
+ Global.partition = '%s-part1' % Global.device
+ else:
+ print('Error: must start with "/dev" or "/dev/disk/by-{id,path}": %s' % \
+ Global.device, file=sys.sttderr)
+ sys.exit(1)
+
+def setup_testProgram_overrides():
+ """
+ Add in special handling for a couple of the methods in TestProgram (main)
+ so that we can add parameters and detect some globals we care about
+ """
+ global old_parseArgs, old_initArgParsers
+
+ old_initArgParsers = unittest.TestProgram._initArgParsers
+ unittest.TestProgram._initArgParsers = new_initArgParsers
+ old_parseArgs = unittest.TestProgram.parseArgs
+ unittest.TestProgram.parseArgs = new_parseArgs
+
+
+def verify_needed_commands_exist(cmd_list):
+ """
+ Verify that the commands in the supplied list are in our path
+ """
+ path_list = os.getenv('PATH').split(':')
+ for cmd in cmd_list:
+ found = False
+ for a_path in path_list:
+ if os.path.exists('%s/%s' % (a_path, cmd)):
+ found = True
+ break
+ if not found:
+ print('Error: %s must be in your PATH' % cmd)
+ sys.exit(1)
+
+
+def run_fio():
+ """
+ Run the fio benchmark for various block sizes.
+
+ Return zero for success.
+ Return non-zero for failure and a failure reason.
+
+ Uses Globals: device, blocksize
+ """
+ if Global.blocksize is not None:
+ dprint('Found a block size passed in: %s' % Global.blocksize)
+ blocksizes = Global.blocksize.split(' ')
+ else:
+ dprint('NO Global block size pass in?')
+ blocksizes = ['512', '1k', '2k', '4k', '8k',
+ '16k', '32k', '75536', '128k', '1000000']
+ # for each block size, do a read test, then a write test
+ for bs in blocksizes:
+ vprint('Running "fio" read test: 2 seconds, 8 threads, bs=%s' % bs)
+ res = run_cmd(['fio', '--name=read-test', '--readwrite=randread',
+ '--runtime=2s', '--numjobs=8', '--blocksize=%s' % bs,
+ '--direct=1', '--filename=%s' % Global.device], quiet_mode=True)
+ if res != 0:
+ return (res, 'fio failed')
+ vprint('Running "fio" write test: 2 seconds, 8 threads, bs=%s' % bs)
+ res = run_cmd(['fio', '--name=write-test', '--readwrite=randwrite',
+ '--runtime=2s', '--numjobs=8', '--blocksize=%s' % bs,
+ '--direct=1', '--filename=%s' % Global.device], quiet_mode=True)
+ if res != 0:
+ return (res, 'fio failed')
+ vprint('Running "fio" verify test: 2 seconds, 8 threads, bs=%s' % bs)
+ res = run_cmd(['fio', '--name=verify-test', '--readwrite=randwrite',
+ '--runtime=2s', '--numjobs=1', '--blocksize=%s' % bs,
+ '--direct=1', '--filename=%s' % Global.device,
+ '--verify=md5', '--verify_state_save=0'], quiet_mode=True)
+ if res != 0:
+ return (res, 'fio failed')
+ return (0, None)
+
+def run_parted():
+ """
+ Run the parted program to ensure there is one partition,
+ and that it covers the whole disk
+
+ Return zero for success and the device pathname.
+ Return non-zero for failure and a failure reason.
+
+ Uses Globals: device, partition
+ """
+ # zero out the label and parition table
+ res = run_cmd(['dd', 'if=/dev/zero', 'of=%s' % Global.device, 'bs=4k', 'count=100'],
+ quiet_mode=True)
+ if res != 0:
+ return (res, '%s: could not zero out label' % Global.device)
+ # ensure our partition file is not there, to be safe
+ if os.path.exists(Global.partition):
+ return (1, '%s: Partition already exists?' % Global.partition)
+ # make a label, then a partition table with one partition
+ res = run_cmd(['parted', Global.device, 'mklabel', 'gpt'], quiet_mode=True)
+ if res != 0:
+ return (res, '%s: Could not create a GPT label' % Global.device)
+ res = run_cmd(['parted', '-a', 'none', Global.device, 'mkpart', 'primary', '0', '100%'],
+ quiet_mode=True)
+ if res != 0:
+ return (res, '%s: Could not create a primary partition' % Global.device)
+ # wait for the partition to show up
+ for i in range(10):
+ if os.path.exists(Global.partition):
+ break
+ os.sleep(1)
+ if not os.path.exists(Global.partition):
+ return (1, '%s: Partition never showed up?' % Global.partition)
+ return (0, None)
+
+def run_mkfs():
+ #return (1, "Not Yet Implemented: mkfs")
+ return (0, None)
+
+def run_bonnie():
+ #return (1, "Not Yet Implemented: bonnie")
+ return (0, None)
diff --git a/test/regression.sh b/test/regression.sh
index 25d4a28..aeef74d 100755
--- a/test/regression.sh
+++ b/test/regression.sh
@@ -64,13 +64,16 @@ function disktest_run() {
test "x$bsize" = xbonnie && return 0;
for bs in $bsizes; do
echo -n "disktest -T2 -K8 -B$bs -r -ID $device: "
- if ! ${disktest} -T2 -K8 -B$bs -r -ID $device >/dev/null; then
+ #if ! ${disktest} -T2 -K8 -B$bs -r -ID $device >/dev/null; then
+ if ! ${disktest} -T2 -K8 -B$bs -r -ID $device; then
echo "FAILED"
return 1;
fi
echo "PASSED"
- echo -n "disktest -T2 -K8 -B$bs -E16 -w -ID $device: "
- if ! ${disktest} -T2 -K8 -B$bs -E16 -w -ID $device >/dev/null;then
+ #echo -n "disktest -T2 -K8 -B$bs -E16 -w -ID $device: "
+ #if ! ${disktest} -T2 -K8 -B$bs -E16 -w -ID $device >/dev/null;then
+ echo -n "disktest -T2 -K8 -B$bs -E16 -ID $device: "
+ if ! ${disktest} -T2 -K8 -B$bs -E16 -ID $device; then
echo "FAILED"
return 1;
fi
@@ -80,12 +83,11 @@ function disktest_run() {
}
function fdisk_run() {
- echo -n "sfdisk -Lqf $device: "
- sfdisk -Lqf $device >/dev/null 2>/dev/null <<-EOF
- 0,
- ;
- ;
- ;
+ echo -n "sfdisk -qf $device: "
+ #sfdisk -Lqf $device >/dev/null 2>/dev/null <<-EOF
+ sfdisk -Lqf $device <<-EOF
+ ,
+ quit
EOF
rc=$?
if [ $rc -ne 0 ]; then
@@ -98,7 +100,8 @@ function fdisk_run() {
function mkfs_run() {
echo -n "${MKFSCMD} $device_partition: "
- if ! ${MKFSCMD} $device_partition 2>/dev/null >/dev/null; then
+ #if ! ${MKFSCMD} $device_partition 2>/dev/null >/dev/null; then
+ if ! ${MKFSCMD} $device_partition ; then
echo "FAILED"
return 1;
fi
diff --git a/test/test-open-iscsi.py b/test/test-open-iscsi.py
new file mode 100755
index 0000000..8d5bdeb
--- /dev/null
+++ b/test/test-open-iscsi.py
@@ -0,0 +1,74 @@
+#!/usr/bin/env python3
+"""
+Unit tests for open-iscsi, using the unittest built-in package
+"""
+
+import sys
+import unittest
+import os
+from harness import util
+from harness.util import Global
+from harness.iscsi import IscsiData
+
+
+class TestRegression(unittest.TestCase):
+ """
+ Regression testing
+ """
+
+ @classmethod
+ def setUpClass(cls):
+ util.vprint('*** Starting %s' % cls.__name__)
+ # XXX validate that target exists?
+ cls.first_burst_values = [4096, 8192, 16384, 32768, 65536, 131972]
+ cls.max_burst_values = [4096, 8192, 16384, 32768, 65536, 131072]
+ cls.max_recv_values = [4096, 8192, 16384, 32768, 65536, 131072]
+
+ def setUp(self):
+ if Global.debug or Global.verbosity > 1:
+ # this makes debug printing a little more clean
+ print('', file=sys.stderr)
+ res = util.run_cmd(['iscsiadm', '-m', 'node', '-T', Global.target, '-p', Global.ipnr, '--logout'], quiet_mode=True)
+ if res not in [0, 21]:
+ self.fail('logout failed')
+ self.assertFalse(os.path.exists(Global.device), '%s: exists after logout!' % Global.device)
+
+ def test_immediate_data(self):
+ """
+ Test No Immediate Data but Initial Request to Transmit
+ """
+ iscsi_data = IscsiData('No', 'Yes', 'None', 'None', 4096, 4096, 4096)
+ iscsi_data.update_cfg(Global.target, Global.ipnr)
+ res = util.run_cmd(['iscsiadm', '-m', 'node', '-T', Global.target, '-p', Global.ipnr, '--login'], quiet_mode=True)
+ self.assertEqual(res, 0, 'cannot login to device')
+ # wait a few seconds for the device to show up
+ for i in range(10):
+ if os.path.exists(Global.device):
+ break
+ os.sleep(1)
+ self.assertTrue(os.path.exists(Global.device), '%s: does not exist after login' % Global.device)
+ (res, reason) = util.run_fio()
+ self.assertEqual(res, 0, reason)
+ (res, reason) = util.run_parted()
+ self.assertEqual(res, 0, reason)
+ (res, reason) = util.run_mkfs()
+ self.assertEqual(res, 0, reason)
+ (res, reason) = util.run_bonnie()
+ self.assertEqual(res, 0, reason)
+
+ @classmethod
+ def tearDownClass(cls):
+ # restore iscsi config
+ iscsi_data = IscsiData()
+ iscsi_data.update_cfg(Global.target, Global.ipnr)
+ # log out of iscsi connection
+ util.run_cmd(['iscsiadm', '-m', 'node', '-T', Global.target, '-p', Global.ipnr, '--logout'], quiet_mode=True)
+
+
+if __name__ == '__main__':
+ util.verify_needed_commands_exist(['parted', 'fio', 'mkfs', 'bonnie++', 'dd', 'iscsiadm'])
+ # do our own hackery first, to get access to verbosity, debug, etc,
+ # as well as add our own command-line options
+ util.setup_testProgram_overrides()
+ # now run the tests
+ unittest.main()