From f66c504430a6c568bf90a5cdf2a64eb995b557de Mon Sep 17 00:00:00 2001 From: Lee Duncan Date: Wed, 19 Feb 2020 11:14:35 -0800 Subject: Beginning to get python tests set up. Created a framework. Replacing disktest with fio, sfdisk with parted, and moving to a PyUnit test framework. --- test/.setup | 3 + test/README | 78 ++++++++++++++ test/TODO | 19 ++++ test/harness/__init__.py | 5 + test/harness/iscsi.py | 63 ++++++++++++ test/harness/util.py | 257 +++++++++++++++++++++++++++++++++++++++++++++++ test/regression.sh | 23 +++-- test/test-open-iscsi.py | 74 ++++++++++++++ 8 files changed, 512 insertions(+), 10 deletions(-) create mode 100644 test/.setup create mode 100644 test/TODO create mode 100644 test/harness/__init__.py create mode 100644 test/harness/iscsi.py create mode 100644 test/harness/util.py create mode 100755 test/test-open-iscsi.py 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 + +to run "mkfs" on the device and exit, or + +> # ./regression.sh [test#[:#]] [bsize] + +Where: + ? + ? + 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 -- set block xfer size --blocksize= (default 4k?) + -ID -- use direct IO --direct=1 + -- use device --filename= + + 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() -- cgit v1.2.1 From 8293415f4b0e32348d4ba183b5877d304efc0632 Mon Sep 17 00:00:00 2001 From: Lee Duncan Date: Wed, 19 Feb 2020 15:39:13 -0800 Subject: First 32 tests working? At least they are coded. --- test/TODO | 5 ++ test/harness/iscsi.py | 16 +++---- test/harness/util.py | 121 ++++++++++++++++++++++++++++++------------------ test/test-open-iscsi.py | 76 +++++++++++++++++++++++------- 4 files changed, 148 insertions(+), 70 deletions(-) diff --git a/test/TODO b/test/TODO index ce98f4e..830e7f2 100644 --- a/test/TODO +++ b/test/TODO @@ -17,3 +17,8 @@ - discovery, with and without authentication - session creation, w/ & w/o auth - etc + +* Gather actual regression data! + - Since we are testing all of these combinations, why not + keep historical data to see if there are any negative + trends (i.e. regressions)? diff --git a/test/harness/iscsi.py b/test/harness/iscsi.py index 569a66e..a252d7a 100644 --- a/test/harness/iscsi.py +++ b/test/harness/iscsi.py @@ -53,11 +53,11 @@ class IscsiData: 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) + run_cmd(c + ['-n', 'node.session.iscsi.ImmediateData', '-v', self.imm_data_en]) + run_cmd(c + ['-n', 'node.session.iscsi.InitialR2T', '-v', self.initial_r2t_en]) + run_cmd(c + ['-n', 'node.conn[0].iscsi.HeaderDigest', '-v', self.hdrdgst_en]) + run_cmd(c + ['-n', 'node.conn[0].iscsi.DataDigest', '-v', self.datdgst_en]) + run_cmd(c + ['-n', 'node.session.iscsi.FirstBurstLength', '-v', str(self.first_burst)]) + run_cmd(c + ['-n', 'node.session.iscsi.MaxBurstLength', '-v', str(self.max_burst)]) + run_cmd(c + ['-n', 'node.conn[0].iscsi.MaxRecvDataSegmentLength', '-v', str(self.max_recv_dlength)]) + run_cmd(c + ['-n', 'node.session.iscsi.MaxOutstandingR2T', '-v', str(self.max_r2t)]) diff --git a/test/harness/util.py b/test/harness/util.py index 3d21480..8c310ed 100644 --- a/test/harness/util.py +++ b/test/harness/util.py @@ -6,18 +6,23 @@ import os import shutil import sys import unittest +import time +import tempfile # # globals # class Global: - _FSTYPE = os.getenv('FSTYPE', 'ext3') - _MOUNTOPTIONS = os.getenv('MOUNTOPTIONS', '').split(' ') + [' -t ', _FSTYPE] - _MKFSCMD = [os.getenv('MKFSCMD', 'mkfs.' + _FSTYPE)] + FSTYPE = os.getenv('FSTYPE', 'ext3') + if os.getenv('MOUNTOPTIONS'): + MOUNTOPTIONS = os.getenv('MOUNTOPTIONS').split(' ') + else: + MOUNTOPTIONS = [] + MOUNTOPTIONS += ['-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(' ') + MKFSCMD += os.getenv('MKFSOPTS').split(' ') + BONNIEPARAMS = os.getenv('BONNIEPARAMS', '-r0 -n10:0:0 -s16 -uroot -f -q').split(' ') verbosity = 1 debug = False # the target (e.g. "iqn.*") @@ -51,31 +56,22 @@ def vprint(*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): +def run_cmd(cmd, output_save_file=None): """ run specified command, waiting for and returning result """ - if (Global.verbosity > 1 and not quiet_mode) or Global.debug: + if Global.debug: cmd_str = ' '.join(cmd) if output_save_file: cmd_str += ' >& %s' % output_save_file - vprint(cmd_str) + dprint(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: + if output_save_file or not Global.debug: stdout_fileno = sys.stdout.fileno() stderr_fileno = sys.stderr.fileno() if output_save_file: @@ -87,6 +83,7 @@ def run_cmd(cmd, output_save_file=None, quiet_mode=False): os.dup2(new_stdout, stderr_fileno) os.execvp(cmd[0], cmd) # not reached + sys.exit(1) # the parent wpid, wstat = os.waitpid(pid, 0) @@ -163,6 +160,7 @@ 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(':') + any_cmd_not_found = False for cmd in cmd_list: found = False for a_path in path_list: @@ -171,7 +169,9 @@ def verify_needed_commands_exist(cmd_list): break if not found: print('Error: %s must be in your PATH' % cmd) - sys.exit(1) + any_cmd_not_found = True + if any_cmd_not_found: + sys.exit(1) def run_fio(): @@ -192,26 +192,46 @@ def run_fio(): '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) + vprint('Running "fio" read test: 8 threads, bs=%s' % bs) + # only support direct IO with aligned reads + if bs.endswith('k'): + direct=1 + else: + direct=0 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) + '--direct=%d' % direct, '--filename=%s' % Global.device]) if res != 0: return (res, 'fio failed') - vprint('Running "fio" write test: 2 seconds, 8 threads, bs=%s' % bs) + vprint('Running "fio" write test: 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) + '--direct=%d' % direct, '--filename=%s' % Global.device]) if res != 0: return (res, 'fio failed') - vprint('Running "fio" verify test: 2 seconds, 8 threads, bs=%s' % bs) + vprint('Running "fio" verify test: 1 thread, 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) + '--direct=%d' % direct, '--filename=%s' % Global.device, + '--verify=md5', '--verify_state_save=0']) if res != 0: return (res, 'fio failed') - return (0, None) + return (0, 'Success') + +def wait_for_path(path, present=True): + """Wait until a path exists or is gone""" + dprint("Looking for path=%s, present=%s" % (path, present)) + for i in range(10): + if os.path.exists(path) == present: + dprint("Found path!") + break + time.sleep(1) + if os.path.exists(path) != present: + dprint("Did NOT find path!") + return False + dprint("%s: present=%d after %d second(s)" % \ + (path, present, i)) + return True def run_parted(): """ @@ -224,34 +244,47 @@ def run_parted(): 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) + res = run_cmd(['dd', 'if=/dev/zero', 'of=%s' % Global.device, 'bs=4k', 'count=100']) 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): + if not wait_for_path(Global.partition, present=False): 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) + vprint('Running "parted" to create a label and partition table') + res = run_cmd(['parted', Global.device, 'mklabel', 'gpt']) 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) + res = run_cmd(['parted', '-a', 'none', Global.device, 'mkpart', 'primary', '0', '100%']) 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): + if not wait_for_path(Global.partition): return (1, '%s: Partition never showed up?' % Global.partition) - return (0, None) + # success + return (0, 'Success') def run_mkfs(): - #return (1, "Not Yet Implemented: mkfs") - return (0, None) + vprint('Running "mkfs" to to create filesystem') + res = run_cmd(Global.MKFSCMD + [ Global.partition ] ) + if res != 0: + return (res, '%s: mkfs failed (%d)' % (Global.partition, res)) + return (0, 'Success') def run_bonnie(): - #return (1, "Not Yet Implemented: bonnie") - return (0, None) + # make a temp dir and mount the device there + with tempfile.TemporaryDirectory() as tmp_dir: + vprint('Mounting the filesystem') + res = run_cmd(['mount'] + Global.MOUNTOPTIONS + [Global.partition, tmp_dir]) + if res != 0: + return (res, '%s: mount failed (%d)' % (Global.partition, res)) + # run bonnie++ on the new directory + vprint('Running "bonnie++" on the filesystem') + res = run_cmd(['bonnie++'] + Global.BONNIEPARAMS + ['-d', tmp_dir]) + if res != 0: + return (res, '%s: umount failed (%d)' % (tmp_dir, res)) + # unmount the device and remove the temp dir + res = run_cmd(['umount', tmp_dir]) + if res != 0: + return (res, '%s: umount failed (%d)' % (tmp_dir, res)) + return (0, 'Success') diff --git a/test/test-open-iscsi.py b/test/test-open-iscsi.py index 8d5bdeb..7c06c59 100755 --- a/test/test-open-iscsi.py +++ b/test/test-open-iscsi.py @@ -6,6 +6,7 @@ Unit tests for open-iscsi, using the unittest built-in package import sys import unittest import os +import time from harness import util from harness.util import Global from harness.iscsi import IscsiData @@ -18,35 +19,72 @@ class TestRegression(unittest.TestCase): @classmethod def setUpClass(cls): + util.verify_needed_commands_exist(['parted', 'fio', 'mkfs', 'bonnie++', 'dd', 'iscsiadm']) 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] + # an array of first burts, max burst, and max recv values, for testing + cls.param_values = [[4096, 4096, 4096], + [8192, 4096, 4096], + [16384, 4096, 4096], + [32768, 4096, 4096], + [65536, 4096, 4096], + [131972, 4096, 4096], + [4096, 8192, 4096], + [4096, 16384, 4096], + [4096, 32768, 4096], + [4096, 65536, 4096], + [4096, 131072, 4096], + [4096, 4096, 8192], + [4096, 4096, 16384], + [4096, 4096, 32768], + [4096, 4096, 65536], + [4096, 4096, 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) + + def iscsi_logout(self): + res = util.run_cmd(['iscsiadm', '-m', 'node', + '-T', Global.target, + '-p', Global.ipnr, + '--logout']) 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) + def test_InitialR2T(self): + """Test Initial Request to Transmit set, but no Immediate Data""" + i = 1 + for v in self.param_values: + with self.subTest('Testing FirstBurst={} MaxBurts={} MaxRecv={}'.format(*v), i=i): + self.iscsi_logout() + iscsi_data = IscsiData('No', 'Yes', 'None', 'None', v[0], v[1], v[2]) + iscsi_data.update_cfg(Global.target, Global.ipnr) + self.run_the_rest() + i += 1 + + def test_ImmediateData(self): + """Test Initial Request to Transmit set, but no Immediate Data""" + i = 1 + for v in self.param_values: + with self.subTest('Testing FirstBurst={} MaxBurts={} MaxRecv={}'.format(*v), i=i): + self.iscsi_logout() + iscsi_data = IscsiData('Yes', 'No', 'None', 'None', v[0], v[1], v[2]) + iscsi_data.update_cfg(Global.target, Global.ipnr) + self.run_the_rest() + i += 1 + + def run_the_rest(self): + res = util.run_cmd(['iscsiadm', '-m', 'node', + '-T', Global.target, + '-p', Global.ipnr, + '--login']) 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) + if not util.wait_for_path(Global.device): + self.fail('%s: does not exist after login' % Global.device) (res, reason) = util.run_fio() self.assertEqual(res, 0, reason) (res, reason) = util.run_parted() @@ -62,11 +100,13 @@ class TestRegression(unittest.TestCase): 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) + util.run_cmd(['iscsiadm', '-m', 'node', + '-T', Global.target, + '-p', Global.ipnr, + '--logout']) 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() -- cgit v1.2.1 From dcf514967a8b530c71f2b5531936b9953df25960 Mon Sep 17 00:00:00 2001 From: Lee Duncan Date: Thu, 20 Feb 2020 10:17:37 -0800 Subject: 64 tests now working and passing Tested on Tumbleweed, and all tests pass so far. Run time is around 3 hours so far, for the whole suite. --- test/TODO | 14 +++++++++++--- test/harness/util.py | 44 +++++++++++++++++++++++++++----------------- test/test-open-iscsi.py | 35 ++++++++++++++++++++++++++++++----- 3 files changed, 68 insertions(+), 25 deletions(-) diff --git a/test/TODO b/test/TODO index 830e7f2..1ab7048 100644 --- a/test/TODO +++ b/test/TODO @@ -2,13 +2,14 @@ * this will mean replacing disktest with something (like fio?) -* convert to PyTest insteal of shell +* ensure user is root? * have tests create their own target using targetcli and a file? * have tests do discovery themselves, instead of requiring - that to be done already + that to be done already. Either way, we may still need + to know the IQN of our target and the host where it lives. * Augment tests * framework for adding new tests and test types,e.g.: @@ -21,4 +22,11 @@ * Gather actual regression data! - Since we are testing all of these combinations, why not keep historical data to see if there are any negative - trends (i.e. regressions)? + trends (i.e. regressions)? Need to understand fio and bonnie++ + output better to find a way to gather one or two datapoints + (max) per test, out of all the info dumped by these + programs. + +* Add in test cases for Discovery and/or Connection validation, + which would require either a separate target set up for that, + or control of our own target diff --git a/test/harness/util.py b/test/harness/util.py index 8c310ed..35f56cb 100644 --- a/test/harness/util.py +++ b/test/harness/util.py @@ -116,7 +116,9 @@ def new_initArgParsers(self): def new_parseArgs(self, argv): """ - Gather globals from unittest main for local consumption + Gather globals from unittest main for local consumption -- this + called to parse then validate the arguments, inside each TestCase + instance. """ global old_parseArgs @@ -218,21 +220,32 @@ def run_fio(): return (res, 'fio failed') return (0, 'Success') -def wait_for_path(path, present=True): +def wait_for_path(path, present=True, amt=10): """Wait until a path exists or is gone""" dprint("Looking for path=%s, present=%s" % (path, present)) - for i in range(10): - if os.path.exists(path) == present: - dprint("Found path!") - break + for i in range(amt): time.sleep(1) - if os.path.exists(path) != present: - dprint("Did NOT find path!") - return False - dprint("%s: present=%d after %d second(s)" % \ - (path, present, i)) - return True + if os.path.exists(path) == present: + dprint("We are Happy :) present=%s, cnt=%d" % (present, i)) + return True + dprint("We are not happy :( present=%s actual=%s after %d seconds" % \ + (present, os.path.exists(path), amt)) + return False +def wipe_disc(): + """ + Wipe the label and partition table from the disc drive -- the sleep-s + are needed to give the async OS and udev a chance to notice the partition + table has been erased + """ + # zero out the label and parition table + vprint('Running "sgdisk" to wipe disc label and partitions') + time.sleep(1) + res = run_cmd(['sgdisk', '-Z', Global.device]) + if res != 0: + return (res, '%s: could not zero out label: %d' % (Global.device, res)) + return (0, 'Success') + def run_parted(): """ Run the parted program to ensure there is one partition, @@ -243,12 +256,9 @@ def run_parted(): 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']) - if res != 0: - return (res, '%s: could not zero out label' % Global.device) + wipe_disc() # ensure our partition file is not there, to be safe - if not wait_for_path(Global.partition, present=False): + if not wait_for_path(Global.partition, present=False, amt=30): return (1, '%s: Partition already exists?' % Global.partition) # make a label, then a partition table with one partition vprint('Running "parted" to create a label and partition table') diff --git a/test/test-open-iscsi.py b/test/test-open-iscsi.py index 7c06c59..9967e21 100755 --- a/test/test-open-iscsi.py +++ b/test/test-open-iscsi.py @@ -19,7 +19,7 @@ class TestRegression(unittest.TestCase): @classmethod def setUpClass(cls): - util.verify_needed_commands_exist(['parted', 'fio', 'mkfs', 'bonnie++', 'dd', 'iscsiadm']) + util.verify_needed_commands_exist(['parted', 'fio', 'mkfs', 'bonnie++', 'sgdisk', 'iscsiadm']) util.vprint('*** Starting %s' % cls.__name__) # XXX validate that target exists? # an array of first burts, max burst, and max recv values, for testing @@ -54,19 +54,20 @@ class TestRegression(unittest.TestCase): self.fail('logout failed') self.assertFalse(os.path.exists(Global.device), '%s: exists after logout!' % Global.device) - def test_InitialR2T(self): - """Test Initial Request to Transmit set, but no Immediate Data""" + def test_InitialR2T_on(self): + """Test Initial Request to Transmit on, but Immediate Data off""" i = 1 for v in self.param_values: with self.subTest('Testing FirstBurst={} MaxBurts={} MaxRecv={}'.format(*v), i=i): + util.vprint('Running subtest %d: FirstBurst={} MaxBurts={} MaxRecv={}'.format(*v) % i) self.iscsi_logout() iscsi_data = IscsiData('No', 'Yes', 'None', 'None', v[0], v[1], v[2]) iscsi_data.update_cfg(Global.target, Global.ipnr) self.run_the_rest() i += 1 - def test_ImmediateData(self): - """Test Initial Request to Transmit set, but no Immediate Data""" + def test_ImmediateData_on(self): + """Test Initial Request to Transmit off, Immediate Data on""" i = 1 for v in self.param_values: with self.subTest('Testing FirstBurst={} MaxBurts={} MaxRecv={}'.format(*v), i=i): @@ -76,6 +77,30 @@ class TestRegression(unittest.TestCase): self.run_the_rest() i += 1 + def test_InitialR2T_and_ImmediateData_on(self): + """Test Initial Request to Transmit and Immediate Data on""" + i = 1 + for v in self.param_values: + with self.subTest('Testing FirstBurst={} MaxBurts={} MaxRecv={}'.format(*v), i=i): + util.vprint('Running subtest %d: FirstBurst={} MaxBurts={} MaxRecv={}'.format(*v) % i) + self.iscsi_logout() + iscsi_data = IscsiData('Yes', 'Yes', 'None', 'None', v[0], v[1], v[2]) + iscsi_data.update_cfg(Global.target, Global.ipnr) + self.run_the_rest() + i += 1 + + def test_InitialR2T_and_ImmediateData_off(self): + """Test Initial Request to Transmit and Immediate Data off""" + i = 1 + for v in self.param_values: + with self.subTest('Testing FirstBurst={} MaxBurts={} MaxRecv={}'.format(*v), i=i): + util.vprint('Running subtest %d: FirstBurst={} MaxBurts={} MaxRecv={}'.format(*v) % i) + self.iscsi_logout() + iscsi_data = IscsiData('No', 'No', 'None', 'None', v[0], v[1], v[2]) + iscsi_data.update_cfg(Global.target, Global.ipnr) + self.run_the_rest() + i += 1 + def run_the_rest(self): res = util.run_cmd(['iscsiadm', '-m', 'node', '-T', Global.target, -- cgit v1.2.1 From 6f4ac8f03fcae294479e5d11410744c7712c913e Mon Sep 17 00:00:00 2001 From: Lee Duncan Date: Thu, 20 Feb 2020 10:32:05 -0800 Subject: Update TODO list --- test/TODO | 10 ++++++++++ test/harness/.gitignore | 1 + 2 files changed, 11 insertions(+) create mode 100644 test/harness/.gitignore diff --git a/test/TODO b/test/TODO index 1ab7048..06509c5 100644 --- a/test/TODO +++ b/test/TODO @@ -30,3 +30,13 @@ * Add in test cases for Discovery and/or Connection validation, which would require either a separate target set up for that, or control of our own target + +* Only allow /dev/disk/by-* paths, as /dev/sd? paths are + inherently problematic, since they can change names. + +* Add back in the "big warning" from regression.sh? + +* Add info to the README about how to run the python tests + +* Leave the regression test around, for now? It doesn't run, + so maybe it should just be removed? diff --git a/test/harness/.gitignore b/test/harness/.gitignore new file mode 100644 index 0000000..bee8a64 --- /dev/null +++ b/test/harness/.gitignore @@ -0,0 +1 @@ +__pycache__ -- cgit v1.2.1 From 1720aeaeeb1c42cc8ca54c0e8c1fe3a775d2667a Mon Sep 17 00:00:00 2001 From: Lee Duncan Date: Thu, 20 Feb 2020 10:32:15 -0800 Subject: Add in "-V" for version info --- test/harness/util.py | 18 ++++++++++++++---- test/test-open-iscsi.py | 3 ++- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/test/harness/util.py b/test/harness/util.py index 35f56cb..a2b0173 100644 --- a/test/harness/util.py +++ b/test/harness/util.py @@ -9,6 +9,8 @@ import unittest import time import tempfile +from . import __version__ as lib_version + # # globals # @@ -113,6 +115,9 @@ def new_initArgParsers(self): self._main_parser.add_argument('-B', '--blocksize', dest='blocksize', action='store', help='block size (defaults to an assortment of sizes)') + self._main_parser.add_argument('-V', '--version', dest='version_request', + action='store_true', + help='Display Version info and exit') def new_parseArgs(self, argv): """ @@ -120,9 +125,13 @@ def new_parseArgs(self, argv): called to parse then validate the arguments, inside each TestCase instance. """ - global old_parseArgs + global old_parseArgs, prog_name, parent_version, lib_version old_parseArgs(self, argv) + if self.version_request: + print('%s Version %s, harnes version %s' % \ + (prog_name, parent_version, lib_version)) + sys.exit(0) Global.verbosity = self.verbosity Global.debug = self.debug for v in ['target', 'ipnr', 'device']: @@ -144,18 +153,19 @@ def new_parseArgs(self, argv): Global.device, file=sys.sttderr) sys.exit(1) -def setup_testProgram_overrides(): +def setup_testProgram_overrides(version_str, name): """ 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 + global old_parseArgs, old_initArgParsers, parent_version, prog_name old_initArgParsers = unittest.TestProgram._initArgParsers unittest.TestProgram._initArgParsers = new_initArgParsers old_parseArgs = unittest.TestProgram.parseArgs unittest.TestProgram.parseArgs = new_parseArgs - + parent_version = version_str + prog_name = name def verify_needed_commands_exist(cmd_list): """ diff --git a/test/test-open-iscsi.py b/test/test-open-iscsi.py index 9967e21..2c06372 100755 --- a/test/test-open-iscsi.py +++ b/test/test-open-iscsi.py @@ -11,6 +11,7 @@ from harness import util from harness.util import Global from harness.iscsi import IscsiData +__version__ = '1.0' class TestRegression(unittest.TestCase): """ @@ -134,6 +135,6 @@ class TestRegression(unittest.TestCase): if __name__ == '__main__': # 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() + util.setup_testProgram_overrides(__version__, 'test-open-iscsi.py') # now run the tests unittest.main() -- cgit v1.2.1 From 4560ee8aaabc862e9dd4eef48b38702fea4d2297 Mon Sep 17 00:00:00 2001 From: Lee Duncan Date: Thu, 20 Feb 2020 11:56:33 -0800 Subject: Test code rearranged to make discovery work. Now you can run: > python3 -m unittest discover to run all the tests, using discovery, or > ./test-open-iscsi -l to list the tests. --- test/.gitignore | 3 ++ test/harness/.gitignore | 1 + test/harness/__init__.py | 4 +- test/harness/tests.py | 131 +++++++++++++++++++++++++++++++++++++++++++++++ test/harness/util.py | 15 ++++++ test/test-open-iscsi.py | 124 ++------------------------------------------ 6 files changed, 156 insertions(+), 122 deletions(-) create mode 100644 test/.gitignore create mode 100644 test/harness/tests.py diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 0000000..8ac4f0c --- /dev/null +++ b/test/.gitignore @@ -0,0 +1,3 @@ +__pycache__ +\#*# +.#* diff --git a/test/harness/.gitignore b/test/harness/.gitignore index bee8a64..8025bc0 100644 --- a/test/harness/.gitignore +++ b/test/harness/.gitignore @@ -1 +1,2 @@ __pycache__ +*~ diff --git a/test/harness/__init__.py b/test/harness/__init__.py index 44fd09e..b93fb7e 100644 --- a/test/harness/__init__.py +++ b/test/harness/__init__.py @@ -2,4 +2,6 @@ Harness functions for the open-iscsi test suite. """ -__version__ = "1.0" +__version__ = '1.0' + +__all__ = ['util', 'iscsi', 'tests'] diff --git a/test/harness/tests.py b/test/harness/tests.py new file mode 100644 index 0000000..7cc960b --- /dev/null +++ b/test/harness/tests.py @@ -0,0 +1,131 @@ +""" +tests -- the actual TestCase (just one) +""" + +import sys +import os +import unittest + +from . import util +from .util import Global +from .iscsi import IscsiData + + +class TestRegression(unittest.TestCase): + """ + Regression testing + """ + + @classmethod + def setUpClass(cls): + util.verify_needed_commands_exist(['parted', 'fio', 'mkfs', 'bonnie++', 'sgdisk', 'iscsiadm']) + util.vprint('*** Starting %s' % cls.__name__) + # XXX validate that target exists? + # an array of first burts, max burst, and max recv values, for testing + cls.param_values = [[4096, 4096, 4096], + [8192, 4096, 4096], + [16384, 4096, 4096], + [32768, 4096, 4096], + [65536, 4096, 4096], + [131972, 4096, 4096], + [4096, 8192, 4096], + [4096, 16384, 4096], + [4096, 32768, 4096], + [4096, 65536, 4096], + [4096, 131072, 4096], + [4096, 4096, 8192], + [4096, 4096, 16384], + [4096, 4096, 32768], + [4096, 4096, 65536], + [4096, 4096, 131072]] + + def setUp(self): + if Global.debug or Global.verbosity > 1: + # this makes debug printing a little more clean + print('', file=sys.stderr) + + def iscsi_logout(self): + res = util.run_cmd(['iscsiadm', '-m', 'node', + '-T', Global.target, + '-p', Global.ipnr, + '--logout']) + 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_InitialR2T_on_ImmediateData_off(self): + """Test Initial Request to Transmit on, but Immediate Data off""" + i = 1 + for v in self.param_values: + with self.subTest('Testing FirstBurst={} MaxBurts={} MaxRecv={}'.format(*v), i=i): + util.vprint('Running subtest %d: FirstBurst={} MaxBurts={} MaxRecv={}'.format(*v) % i) + self.iscsi_logout() + iscsi_data = IscsiData('No', 'Yes', 'None', 'None', v[0], v[1], v[2]) + iscsi_data.update_cfg(Global.target, Global.ipnr) + self.run_the_rest() + i += 1 + + def test_InitialR2T_off_ImmediateData_on(self): + """Test Initial Request to Transmit off, Immediate Data on""" + i = 1 + for v in self.param_values: + with self.subTest('Testing FirstBurst={} MaxBurts={} MaxRecv={}'.format(*v), i=i): + self.iscsi_logout() + iscsi_data = IscsiData('Yes', 'No', 'None', 'None', v[0], v[1], v[2]) + iscsi_data.update_cfg(Global.target, Global.ipnr) + self.run_the_rest() + i += 1 + + def test_InitialR2T_on_ImmediateData_on(self): + """Test Initial Request to Transmit and Immediate Data on""" + i = 1 + for v in self.param_values: + with self.subTest('Testing FirstBurst={} MaxBurts={} MaxRecv={}'.format(*v), i=i): + util.vprint('Running subtest %d: FirstBurst={} MaxBurts={} MaxRecv={}'.format(*v) % i) + self.iscsi_logout() + iscsi_data = IscsiData('Yes', 'Yes', 'None', 'None', v[0], v[1], v[2]) + iscsi_data.update_cfg(Global.target, Global.ipnr) + self.run_the_rest() + i += 1 + + def test_InitialR2T_off_ImmediateData_off(self): + """Test Initial Request to Transmit and Immediate Data off""" + i = 1 + for v in self.param_values: + with self.subTest('Testing FirstBurst={} MaxBurts={} MaxRecv={}'.format(*v), i=i): + util.vprint('Running subtest %d: FirstBurst={} MaxBurts={} MaxRecv={}'.format(*v) % i) + self.iscsi_logout() + iscsi_data = IscsiData('No', 'No', 'None', 'None', v[0], v[1], v[2]) + iscsi_data.update_cfg(Global.target, Global.ipnr) + self.run_the_rest() + i += 1 + + def run_the_rest(self): + res = util.run_cmd(['iscsiadm', '-m', 'node', + '-T', Global.target, + '-p', Global.ipnr, + '--login']) + self.assertEqual(res, 0, 'cannot login to device') + # wait a few seconds for the device to show up + if not util.wait_for_path(Global.device): + self.fail('%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']) + diff --git a/test/harness/util.py b/test/harness/util.py index a2b0173..ff0fc13 100644 --- a/test/harness/util.py +++ b/test/harness/util.py @@ -118,6 +118,18 @@ def new_initArgParsers(self): self._main_parser.add_argument('-V', '--version', dest='version_request', action='store_true', help='Display Version info and exit') + self._main_parser.add_argument('-l', '--list', dest='list_tests', + action='store_true', + help='List test cases and exit') + +def print_suite(suite): + """Print a list of tests from a test suite""" + dprint("print_suite: entering for", suite) + if hasattr(suite, '__iter__'): + for x in suite: + print_suite(x) + else: + print(suite) def new_parseArgs(self, argv): """ @@ -134,6 +146,9 @@ def new_parseArgs(self, argv): sys.exit(0) Global.verbosity = self.verbosity Global.debug = self.debug + if self.list_tests: + print_suite(unittest.defaultTestLoader.discover('.')) + sys.exit(0) for v in ['target', 'ipnr', 'device']: if getattr(self, v) is None: print('Error: "%s" required' % v.upper()) diff --git a/test/test-open-iscsi.py b/test/test-open-iscsi.py index 2c06372..2b8f2b9 100755 --- a/test/test-open-iscsi.py +++ b/test/test-open-iscsi.py @@ -8,133 +8,15 @@ import unittest import os import time from harness import util -from harness.util import Global -from harness.iscsi import IscsiData +#from harness import tests +#from tests import TestRegression __version__ = '1.0' -class TestRegression(unittest.TestCase): - """ - Regression testing - """ - - @classmethod - def setUpClass(cls): - util.verify_needed_commands_exist(['parted', 'fio', 'mkfs', 'bonnie++', 'sgdisk', 'iscsiadm']) - util.vprint('*** Starting %s' % cls.__name__) - # XXX validate that target exists? - # an array of first burts, max burst, and max recv values, for testing - cls.param_values = [[4096, 4096, 4096], - [8192, 4096, 4096], - [16384, 4096, 4096], - [32768, 4096, 4096], - [65536, 4096, 4096], - [131972, 4096, 4096], - [4096, 8192, 4096], - [4096, 16384, 4096], - [4096, 32768, 4096], - [4096, 65536, 4096], - [4096, 131072, 4096], - [4096, 4096, 8192], - [4096, 4096, 16384], - [4096, 4096, 32768], - [4096, 4096, 65536], - [4096, 4096, 131072]] - - def setUp(self): - if Global.debug or Global.verbosity > 1: - # this makes debug printing a little more clean - print('', file=sys.stderr) - - def iscsi_logout(self): - res = util.run_cmd(['iscsiadm', '-m', 'node', - '-T', Global.target, - '-p', Global.ipnr, - '--logout']) - 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_InitialR2T_on(self): - """Test Initial Request to Transmit on, but Immediate Data off""" - i = 1 - for v in self.param_values: - with self.subTest('Testing FirstBurst={} MaxBurts={} MaxRecv={}'.format(*v), i=i): - util.vprint('Running subtest %d: FirstBurst={} MaxBurts={} MaxRecv={}'.format(*v) % i) - self.iscsi_logout() - iscsi_data = IscsiData('No', 'Yes', 'None', 'None', v[0], v[1], v[2]) - iscsi_data.update_cfg(Global.target, Global.ipnr) - self.run_the_rest() - i += 1 - - def test_ImmediateData_on(self): - """Test Initial Request to Transmit off, Immediate Data on""" - i = 1 - for v in self.param_values: - with self.subTest('Testing FirstBurst={} MaxBurts={} MaxRecv={}'.format(*v), i=i): - self.iscsi_logout() - iscsi_data = IscsiData('Yes', 'No', 'None', 'None', v[0], v[1], v[2]) - iscsi_data.update_cfg(Global.target, Global.ipnr) - self.run_the_rest() - i += 1 - - def test_InitialR2T_and_ImmediateData_on(self): - """Test Initial Request to Transmit and Immediate Data on""" - i = 1 - for v in self.param_values: - with self.subTest('Testing FirstBurst={} MaxBurts={} MaxRecv={}'.format(*v), i=i): - util.vprint('Running subtest %d: FirstBurst={} MaxBurts={} MaxRecv={}'.format(*v) % i) - self.iscsi_logout() - iscsi_data = IscsiData('Yes', 'Yes', 'None', 'None', v[0], v[1], v[2]) - iscsi_data.update_cfg(Global.target, Global.ipnr) - self.run_the_rest() - i += 1 - - def test_InitialR2T_and_ImmediateData_off(self): - """Test Initial Request to Transmit and Immediate Data off""" - i = 1 - for v in self.param_values: - with self.subTest('Testing FirstBurst={} MaxBurts={} MaxRecv={}'.format(*v), i=i): - util.vprint('Running subtest %d: FirstBurst={} MaxBurts={} MaxRecv={}'.format(*v) % i) - self.iscsi_logout() - iscsi_data = IscsiData('No', 'No', 'None', 'None', v[0], v[1], v[2]) - iscsi_data.update_cfg(Global.target, Global.ipnr) - self.run_the_rest() - i += 1 - - def run_the_rest(self): - res = util.run_cmd(['iscsiadm', '-m', 'node', - '-T', Global.target, - '-p', Global.ipnr, - '--login']) - self.assertEqual(res, 0, 'cannot login to device') - # wait a few seconds for the device to show up - if not util.wait_for_path(Global.device): - self.fail('%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']) - if __name__ == '__main__': # 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(__version__, 'test-open-iscsi.py') # now run the tests - unittest.main() + unittest.main(module = 'harness.tests') -- cgit v1.2.1 From 8248e122bd184f23860bc2575bd650e6b8d0a468 Mon Sep 17 00:00:00 2001 From: Lee Duncan Date: Thu, 20 Feb 2020 13:56:47 -0800 Subject: more things to do --- test/TODO | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/test/TODO b/test/TODO index 06509c5..096f250 100644 --- a/test/TODO +++ b/test/TODO @@ -1,23 +1,45 @@ +# +# Things to do for testing +# + * get current tests running * this will mean replacing disktest with something (like fio?) -* ensure user is root? + PASS -- no need to spend time getting disktest working + when (1) it's hard to find, and (2) it's going to be + replaced anyway. + +* ensure user is root? -- easy to do * have tests create their own target using targetcli and - a file? + a file? -- this would be much better, but would pull + in a requirement for targetcli-fb and friends, plus we + would still need a place for a decent-sized (1G?) file. * have tests do discovery themselves, instead of requiring that to be done already. Either way, we may still need to know the IQN of our target and the host where it lives. + PASS -- we still would have to know two things (IQN and + IP:Port). See next item. + +* Have tests figure out the device path, so it doesn't have + to be passed in. Passing it in requires the called to + login to the remote iscsi target and look at the path + in /dev/disk/by-id (for example). If we created the + disk, we might have a better chance of guessing its name? + * Augment tests - * framework for adding new tests and test types,e.g.: + Right now, the test is a long-ass regression test. Very + repetitive and time-consuming. But we also have need of + regular unit tests, e.g. for functionality, where a new + test could be added each time we find a bug? New tests + could include things like: - multipathing - using interface files or not - discovery, with and without authentication - session creation, w/ & w/o auth - - etc * Gather actual regression data! - Since we are testing all of these combinations, why not @@ -40,3 +62,7 @@ * Leave the regression test around, for now? It doesn't run, so maybe it should just be removed? + +* Add in option to specify which subtests (of 16) are run + for each test case. Would make it much faster for testing + and go/no-go testing? -- cgit v1.2.1 From 874767df96db070d59c4151ab5a44bd9a23d75b0 Mon Sep 17 00:00:00 2001 From: Lee Duncan Date: Thu, 20 Feb 2020 13:56:57 -0800 Subject: Allow sub-tests to be specified. Using the new "-s" option, one can specify a single subtest or a range of subtests. Together with the blocksize arguments and being able to specify the actual test case if you wish, you can now run just a single test. --- test/harness/tests.py | 80 +++++++++++++++++++++++++++++++++++++++------------ test/harness/util.py | 62 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 121 insertions(+), 21 deletions(-) diff --git a/test/harness/tests.py b/test/harness/tests.py index 7cc960b..7833aac 100644 --- a/test/harness/tests.py +++ b/test/harness/tests.py @@ -58,11 +58,14 @@ class TestRegression(unittest.TestCase): i = 1 for v in self.param_values: with self.subTest('Testing FirstBurst={} MaxBurts={} MaxRecv={}'.format(*v), i=i): - util.vprint('Running subtest %d: FirstBurst={} MaxBurts={} MaxRecv={}'.format(*v) % i) - self.iscsi_logout() - iscsi_data = IscsiData('No', 'Yes', 'None', 'None', v[0], v[1], v[2]) - iscsi_data.update_cfg(Global.target, Global.ipnr) - self.run_the_rest() + if i not in Global.subtest_list: + util.vprint('Skipping subtest %d: FirstBurst={} MaxBurts={} MaxRecv={}'.format(*v) % i) + else: + util.vprint('Running subtest %d: FirstBurst={} MaxBurts={} MaxRecv={}'.format(*v) % i) + self.iscsi_logout() + iscsi_data = IscsiData('No', 'Yes', 'None', 'None', v[0], v[1], v[2]) + iscsi_data.update_cfg(Global.target, Global.ipnr) + self.run_the_rest() i += 1 def test_InitialR2T_off_ImmediateData_on(self): @@ -70,10 +73,13 @@ class TestRegression(unittest.TestCase): i = 1 for v in self.param_values: with self.subTest('Testing FirstBurst={} MaxBurts={} MaxRecv={}'.format(*v), i=i): - self.iscsi_logout() - iscsi_data = IscsiData('Yes', 'No', 'None', 'None', v[0], v[1], v[2]) - iscsi_data.update_cfg(Global.target, Global.ipnr) - self.run_the_rest() + if i not in Global.subtest_list: + util.vprint('Skipping subtest %d: FirstBurst={} MaxBurts={} MaxRecv={}'.format(*v) % i) + else: + self.iscsi_logout() + iscsi_data = IscsiData('Yes', 'No', 'None', 'None', v[0], v[1], v[2]) + iscsi_data.update_cfg(Global.target, Global.ipnr) + self.run_the_rest() i += 1 def test_InitialR2T_on_ImmediateData_on(self): @@ -81,11 +87,14 @@ class TestRegression(unittest.TestCase): i = 1 for v in self.param_values: with self.subTest('Testing FirstBurst={} MaxBurts={} MaxRecv={}'.format(*v), i=i): - util.vprint('Running subtest %d: FirstBurst={} MaxBurts={} MaxRecv={}'.format(*v) % i) - self.iscsi_logout() - iscsi_data = IscsiData('Yes', 'Yes', 'None', 'None', v[0], v[1], v[2]) - iscsi_data.update_cfg(Global.target, Global.ipnr) - self.run_the_rest() + if i not in Global.subtest_list: + util.vprint('Skipping subtest %d: FirstBurst={} MaxBurts={} MaxRecv={}'.format(*v) % i) + else: + util.vprint('Running subtest %d: FirstBurst={} MaxBurts={} MaxRecv={}'.format(*v) % i) + self.iscsi_logout() + iscsi_data = IscsiData('Yes', 'Yes', 'None', 'None', v[0], v[1], v[2]) + iscsi_data.update_cfg(Global.target, Global.ipnr) + self.run_the_rest() i += 1 def test_InitialR2T_off_ImmediateData_off(self): @@ -93,11 +102,44 @@ class TestRegression(unittest.TestCase): i = 1 for v in self.param_values: with self.subTest('Testing FirstBurst={} MaxBurts={} MaxRecv={}'.format(*v), i=i): - util.vprint('Running subtest %d: FirstBurst={} MaxBurts={} MaxRecv={}'.format(*v) % i) - self.iscsi_logout() - iscsi_data = IscsiData('No', 'No', 'None', 'None', v[0], v[1], v[2]) - iscsi_data.update_cfg(Global.target, Global.ipnr) - self.run_the_rest() + if i not in Global.subtest_list: + util.vprint('Skipping subtest %d: FirstBurst={} MaxBurts={} MaxRecv={}'.format(*v) % i) + else: + util.vprint('Running subtest %d: FirstBurst={} MaxBurts={} MaxRecv={}'.format(*v) % i) + self.iscsi_logout() + iscsi_data = IscsiData('No', 'No', 'None', 'None', v[0], v[1], v[2]) + iscsi_data.update_cfg(Global.target, Global.ipnr) + self.run_the_rest() + i += 1 + + def test_HdrDigest_on_DataDigest_off(self): + """Test With Header Digest""" + i = 1 + for v in self.param_values: + with self.subTest('Testing FirstBurst={} MaxBurts={} MaxRecv={}'.format(*v), i=i): + if i not in Global.subtest_list: + util.vprint('Skipping subtest %d: FirstBurst={} MaxBurts={} MaxRecv={}'.format(*v) % i) + else: + util.vprint('Running subtest %d: FirstBurst={} MaxBurts={} MaxRecv={}'.format(*v) % i) + self.iscsi_logout() + iscsi_data = IscsiData('No', 'Yes', 'CRC32C', 'None', v[0], v[1], v[2]) + iscsi_data.update_cfg(Global.target, Global.ipnr) + self.run_the_rest() + i += 1 + + def test_HdrDigest_on_DataDigest_on(self): + """Test With Header Digest""" + i = 1 + for v in self.param_values: + with self.subTest('Testing FirstBurst={} MaxBurts={} MaxRecv={}'.format(*v), i=i): + if i not in Global.subtest_list: + util.vprint('Skipping subtest %d: FirstBurst={} MaxBurts={} MaxRecv={}'.format(*v) % i) + else: + util.vprint('Running subtest %d: FirstBurst={} MaxBurts={} MaxRecv={}'.format(*v) % i) + self.iscsi_logout() + iscsi_data = IscsiData('No', 'Yes', 'CRC32C', 'CRC32C', v[0], v[1], v[2]) + iscsi_data.update_cfg(Global.target, Global.ipnr) + self.run_the_rest() i += 1 def run_the_rest(self): diff --git a/test/harness/util.py b/test/harness/util.py index ff0fc13..f6c2a32 100644 --- a/test/harness/util.py +++ b/test/harness/util.py @@ -8,6 +8,7 @@ import sys import unittest import time import tempfile +import re from . import __version__ as lib_version @@ -37,6 +38,10 @@ class Global: partition = None # optional override for fio disk testing block size(s) blocksize = None + # subtests to run -- by default, all of them + # XXX we should really look how many subtests there are, but there's + # no good way to detect that. + subtest_list = [i+1 for i in range(16)] def dprint(*args): @@ -121,6 +126,9 @@ def new_initArgParsers(self): self._main_parser.add_argument('-l', '--list', dest='list_tests', action='store_true', help='List test cases and exit') + self._main_parser.add_argument('-s', '--subtests', dest='subtest_list', + action='store', + help='Subtests to execute [default all, i.e. "1-16"]') def print_suite(suite): """Print a list of tests from a test suite""" @@ -139,7 +147,10 @@ def new_parseArgs(self, argv): """ global old_parseArgs, prog_name, parent_version, lib_version + # actually parse the arguments old_parseArgs(self, argv) + + # now validate stuff if self.version_request: print('%s Version %s, harnes version %s' % \ (prog_name, parent_version, lib_version)) @@ -167,7 +178,49 @@ def new_parseArgs(self, argv): print('Error: must start with "/dev" or "/dev/disk/by-{id,path}": %s' % \ Global.device, file=sys.sttderr) sys.exit(1) + if self.subtest_list: + if not user_spec_to_list(self.subtest_list): + self._print_help() + sys.exit(1) + +def user_spec_to_list(user_spec): + """ + We have 16 subtests. By default, we run them all, but if + the user has specified a subset, like 'N' or 'N-M', then + a list of the indicies they requested. + + XXX: expand to handle groups, e.g. 1,3-4,12 ??? + XXX: should we validate that the range will work, or just + let an exception happen in that case? + """ + pat_single = re.compile(r'(\d+)$') + pat_range = re.compile(r'(\d+)-(\d+)$') + found = False + start_idx = None + end_idx = None + res = pat_range.match(user_spec) + if res: + # user wants just one subtest + start_idx = int(res.group(1)) - 1 + end_idx = int(res.group(2)) + dprint("Found request for range: %d-%d" % (start_idx, end_idx)) + found = True + else: + res = pat_single.match(user_spec) + if res: + start_idx = int(res.group(1)) - 1 + end_idx = start_idx + 1 + dprint("Found request for single: %d-%d" % (start_idx, end_idx)) + found = True + if not found: + print('Error: subtest spec does not match N or N-M: %s' % user_spec) + else: + dprint("subtest_list before:", Global.subtest_list) + Global.subtest_list = Global.subtest_list[start_idx:end_idx] + dprint("subtest_list after:", Global.subtest_list) + return found + def setup_testProgram_overrides(version_str, name): """ Add in special handling for a couple of the methods in TestProgram (main) @@ -264,11 +317,14 @@ def wipe_disc(): table has been erased """ # zero out the label and parition table - vprint('Running "sgdisk" to wipe disc label and partitions') + vprint('Running "sgdisk" and "dd" to wipe disc label, partitions, and filesystem') time.sleep(1) res = run_cmd(['sgdisk', '-Z', Global.device]) if res != 0: return (res, '%s: could not zero out label: %d' % (Global.device, res)) + res = run_cmd(['dd', 'if=/dev/zero', 'of=%s' % Global.device, 'bs=256k', 'count=20', 'oflag=direct']) + if res != 0: + return (res, '%s: could not zero out filesystem: %d' % (Global.device, res)) return (0, 'Success') def run_parted(): @@ -281,7 +337,9 @@ def run_parted(): Uses Globals: device, partition """ - wipe_disc() + (res, reason) = wipe_disc() + if res != 0: + return (res, resason) # ensure our partition file is not there, to be safe if not wait_for_path(Global.partition, present=False, amt=30): return (1, '%s: Partition already exists?' % Global.partition) -- cgit v1.2.1