Blame test/harness/util.py

Packit Service 646995
"""
Packit Service 646995
harness stuff (support) -- utility routines
Packit Service 646995
"""
Packit Service 646995
Packit Service 646995
import os
Packit Service 646995
import shutil
Packit Service 646995
import sys
Packit Service 646995
import unittest
Packit Service 646995
import time
Packit Service 646995
import tempfile
Packit Service 646995
import re
Packit Service 646995
Packit Service 646995
from . import __version__ as lib_version
Packit Service 646995
Packit Service 646995
#
Packit Service 646995
# globals
Packit Service 646995
#
Packit Service 646995
class Global:
Packit Service 646995
    FSTYPE = os.getenv('FSTYPE', 'ext3')
Packit Service 646995
    if os.getenv('MOUNTOPTIONS'):
Packit Service 646995
        MOUNTOPTIONS = os.getenv('MOUNTOPTIONS').split(' ')
Packit Service 646995
    else:
Packit Service 646995
        MOUNTOPTIONS = []
Packit Service 646995
    MOUNTOPTIONS += ['-t', FSTYPE]
Packit Service 646995
    MKFSCMD = [os.getenv('MKFSCMD', 'mkfs.' + FSTYPE)]
Packit Service 646995
    if os.getenv('MKFSOPTS'):
Packit Service 646995
        MKFSCMD += os.getenv('MKFSOPTS').split(' ')
Packit Service 646995
    BONNIEPARAMS = os.getenv('BONNIEPARAMS', '-r0 -n10:0:0 -s16 -uroot -f -q').split(' ')
Packit Service 646995
    verbosity = 1
Packit Service 646995
    debug = False
Packit Service 646995
    # the target (e.g. "iqn.*")
Packit Service 646995
    target = None
Packit Service 646995
    # the IP and optional port (e.g. "linux-system", "192.168.10.1:3260")
Packit Service 646995
    ipnr = None
Packit Service 646995
    # the device that will be created when our target is connected
Packit Service 646995
    device = None
Packit Service 646995
    # the first and only partition on said device
Packit Service 646995
    partition = None
Packit Service 646995
    # optional override for fio disk testing block size(s)
Packit Service 646995
    blocksize = None
Packit Service 646995
    # subtests to run -- by default, all of them
Packit Service 646995
    # XXX we should really look how many subtests there are, but there's
Packit Service 646995
    # no good way to detect that.
Packit Service 646995
    subtest_list = [i+1 for i in range(16)]
Packit Service 646995
Packit Service 646995
Packit Service 646995
def dprint(*args):
Packit Service 646995
    """
Packit Service 646995
    Print a debug message if in debug mode
Packit Service 646995
    """
Packit Service 646995
    if Global.debug:
Packit Service 646995
        print('DEBUG: ', file=sys.stderr, end='')
Packit Service 646995
        for arg in args:
Packit Service 646995
            print(arg, file=sys.stderr, end='')
Packit Service 646995
        print('', file=sys.stderr)
Packit Service 646995
Packit Service 646995
def vprint(*args):
Packit Service 646995
    """
Packit Service 646995
    Print a verbose message
Packit Service 646995
    """
Packit Service 646995
    if Global.verbosity > 1 and args:
Packit Service 646995
        for arg in args:
Packit Service 646995
            print(arg, end='')
Packit Service 646995
        print('')
Packit Service 646995
Packit Service 646995
def run_cmd(cmd, output_save_file=None):
Packit Service 646995
    """
Packit Service 646995
    run specified command, waiting for and returning result
Packit Service 646995
    """
Packit Service 646995
    if Global.debug:
Packit Service 646995
        cmd_str = ' '.join(cmd)
Packit Service 646995
        if output_save_file:
Packit Service 646995
            cmd_str += ' >& %s' % output_save_file
Packit Service 646995
        dprint(cmd_str)
Packit Service 646995
    pid = os.fork()
Packit Service 646995
    if pid < 0:
Packit Service 646995
        print("Error: cannot fork!", flie=sys.stderr)
Packit Service 646995
        sys.exit(1)
Packit Service 646995
    if pid == 0:
Packit Service 646995
        # the child
Packit Service 646995
        if output_save_file or not Global.debug:
Packit Service 646995
            stdout_fileno = sys.stdout.fileno()
Packit Service 646995
            stderr_fileno = sys.stderr.fileno()
Packit Service 646995
            if output_save_file:
Packit Service 646995
                new_stdout = os.open(output_save_file, os.O_WRONLY|os.O_CREAT|os.O_TRUNC,
Packit Service 646995
                                     mode=0o664)
Packit Service 646995
            else:
Packit Service 646995
                new_stdout = os.open('/dev/null', os.O_WRONLY)
Packit Service 646995
            os.dup2(new_stdout, stdout_fileno)
Packit Service 646995
            os.dup2(new_stdout, stderr_fileno)
Packit Service 646995
        os.execvp(cmd[0], cmd)
Packit Service 646995
        # not reached
Packit Service 646995
        sys.exit(1)
Packit Service 646995
Packit Service 646995
    # the parent
Packit Service 646995
    wpid, wstat = os.waitpid(pid, 0)
Packit Service 646995
    if wstat != 0:
Packit Service 646995
        dprint("exit status: (%d) %d" % (wstat, os.WEXITSTATUS(wstat)))
Packit Service 646995
    return os.WEXITSTATUS(wstat)
Packit Service 646995
Packit Service 646995
def new_initArgParsers(self):
Packit Service 646995
    """
Packit Service 646995
    Add  some options to the normal unittest main options
Packit Service 646995
    """
Packit Service 646995
    global old_initArgParsers
Packit Service 646995
Packit Service 646995
    old_initArgParsers(self)
Packit Service 646995
    self._main_parser.add_argument('-d', '--debug', dest='debug',
Packit Service 646995
            action='store_true',
Packit Service 646995
            help='Enable developer debugging')
Packit Service 646995
    self._main_parser.add_argument('-t', '--target', dest='target',
Packit Service 646995
            action='store',
Packit Service 646995
            help='Required: target name')
Packit Service 646995
    self._main_parser.add_argument('-i', '--ipnr', dest='ipnr',
Packit Service 646995
            action='store',
Packit Service 646995
            help='Required: name-or-ip[:port]')
Packit Service 646995
    self._main_parser.add_argument('-D', '--device', dest='device',
Packit Service 646995
            action='store',
Packit Service 646995
            help='Required: device')
Packit Service 646995
    self._main_parser.add_argument('-B', '--blocksize', dest='blocksize',
Packit Service 646995
            action='store',
Packit Service 646995
            help='block size (defaults to an assortment of sizes)')
Packit Service 646995
    self._main_parser.add_argument('-V', '--version', dest='version_request',
Packit Service 646995
            action='store_true',
Packit Service 646995
            help='Display Version info and exit')
Packit Service 646995
    self._main_parser.add_argument('-l', '--list', dest='list_tests',
Packit Service 646995
            action='store_true',
Packit Service 646995
            help='List test cases and exit')
Packit Service 646995
    self._main_parser.add_argument('-s', '--subtests', dest='subtest_list',
Packit Service 646995
            action='store',
Packit Service 646995
            help='Subtests to execute [default all, i.e. "1-16"]')
Packit Service 646995
Packit Service 646995
def print_suite(suite):
Packit Service 646995
    """Print a list of tests from a test suite"""
Packit Service 646995
    dprint("print_suite: entering for", suite)
Packit Service 646995
    if hasattr(suite, '__iter__'):
Packit Service 646995
        for x in suite:
Packit Service 646995
            print_suite(x)
Packit Service 646995
    else:
Packit Service 646995
        print(suite)
Packit Service 646995
Packit Service 646995
def new_parseArgs(self, argv):
Packit Service 646995
    """
Packit Service 646995
    Gather globals from unittest main for local consumption -- this
Packit Service 646995
    called to parse then validate the arguments, inside each TestCase
Packit Service 646995
    instance.
Packit Service 646995
    """
Packit Service 646995
    global old_parseArgs, prog_name, parent_version, lib_version
Packit Service 646995
Packit Service 646995
    # actually parse the arguments
Packit Service 646995
    old_parseArgs(self, argv)
Packit Service 646995
Packit Service 646995
    # now validate stuff
Packit Service 646995
    if self.version_request:
Packit Service 646995
        print('%s Version %s, harnes version %s' % \
Packit Service 646995
              (prog_name, parent_version, lib_version))
Packit Service 646995
        sys.exit(0)
Packit Service 646995
    Global.verbosity = self.verbosity
Packit Service 646995
    Global.debug = self.debug
Packit Service 646995
    if self.list_tests:
Packit Service 646995
        print_suite(unittest.defaultTestLoader.discover('.'))
Packit Service 646995
        sys.exit(0)
Packit Service 646995
    for v in ['target', 'ipnr', 'device']:
Packit Service 646995
        if getattr(self, v) is None:
Packit Service 646995
            print('Error: "%s" required' % v.upper())
Packit Service 646995
            sys.exit(1)
Packit Service 646995
        setattr(Global, v, getattr(self, v))
Packit Service 646995
    Global.blocksize = self.blocksize
Packit Service 646995
    dprint("found: verbosity=%d, target=%s, ipnr=%s, device=%s, bs=%s" % \
Packit Service 646995
            (Global.verbosity, Global.target, Global.ipnr, Global.device, Global.blocksize))
Packit Service 646995
    # get partition from path
Packit Service 646995
    device_dir = os.path.dirname(Global.device)
Packit Service 646995
    if device_dir == '/dev':
Packit Service 646995
        Global.partition = '%s1' % Global.device
Packit Service 646995
    elif device_dir in ['/dev/disk/by-id', '/dev/disk/by-path']:
Packit Service 646995
        Global.partition = '%s-part1' % Global.device
Packit Service 646995
    else:
Packit Service 646995
        print('Error: must start with "/dev" or "/dev/disk/by-{id,path}": %s' % \
Packit Service 646995
                Global.device, file=sys.sttderr)
Packit Service 646995
        sys.exit(1)
Packit Service 646995
    if self.subtest_list:
Packit Service 646995
        if not user_spec_to_list(self.subtest_list):
Packit Service 646995
            self._print_help()
Packit Service 646995
            sys.exit(1)
Packit Service 646995
Packit Service 646995
def user_spec_to_list(user_spec):
Packit Service 646995
    """
Packit Service 646995
    We have 16 subtests. By default, we run them all, but if
Packit Service 646995
    the user has specified a subset, like 'N' or 'N-M', then
Packit Service 646995
    a list of the indicies they requested.
Packit Service 646995
Packit Service 646995
    XXX: expand to handle groups, e.g. 1,3-4,12 ???
Packit Service 646995
Packit Service 646995
    XXX: should we validate that the range will work, or just
Packit Service 646995
    let an exception happen in that case?
Packit Service 646995
    """
Packit Service 646995
    pat_single = re.compile(r'(\d+)$')
Packit Service 646995
    pat_range = re.compile(r'(\d+)-(\d+)$')
Packit Service 646995
    found = False
Packit Service 646995
    start_idx = None
Packit Service 646995
    end_idx = None
Packit Service 646995
    res = pat_range.match(user_spec)
Packit Service 646995
    if res:
Packit Service 646995
        # user wants just one subtest
Packit Service 646995
        start_idx = int(res.group(1)) - 1
Packit Service 646995
        end_idx = int(res.group(2))
Packit Service 646995
        dprint("Found request for range: %d-%d" % (start_idx, end_idx))
Packit Service 646995
        found = True
Packit Service 646995
    else:
Packit Service 646995
        res = pat_single.match(user_spec)
Packit Service 646995
        if res:
Packit Service 646995
            start_idx = int(res.group(1)) - 1
Packit Service 646995
            end_idx = start_idx + 1
Packit Service 646995
            dprint("Found request for single: %d-%d" % (start_idx, end_idx))
Packit Service 646995
            found = True
Packit Service 646995
    if not found:
Packit Service 646995
        print('Error: subtest spec does not match N or N-M: %s' % user_spec)
Packit Service 646995
    else:
Packit Service 646995
        dprint("subtest_list before:", Global.subtest_list)
Packit Service 646995
        Global.subtest_list = Global.subtest_list[start_idx:end_idx]
Packit Service 646995
        dprint("subtest_list after:", Global.subtest_list)
Packit Service 646995
    return found
Packit Service 646995
    
Packit Service 646995
def setup_testProgram_overrides(version_str, name):
Packit Service 646995
    """
Packit Service 646995
    Add in special handling for a couple of the methods in TestProgram (main)
Packit Service 646995
    so that we can add parameters and detect some globals we care about
Packit Service 646995
    """
Packit Service 646995
    global old_parseArgs, old_initArgParsers, parent_version, prog_name
Packit Service 646995
Packit Service 646995
    old_initArgParsers = unittest.TestProgram._initArgParsers
Packit Service 646995
    unittest.TestProgram._initArgParsers = new_initArgParsers
Packit Service 646995
    old_parseArgs = unittest.TestProgram.parseArgs
Packit Service 646995
    unittest.TestProgram.parseArgs = new_parseArgs
Packit Service 646995
    parent_version = version_str
Packit Service 646995
    prog_name = name
Packit Service 646995
Packit Service 646995
def verify_needed_commands_exist(cmd_list):
Packit Service 646995
    """
Packit Service 646995
    Verify that the commands in the supplied list are in our path
Packit Service 646995
    """
Packit Service 646995
    path_list = os.getenv('PATH').split(':')
Packit Service 646995
    any_cmd_not_found = False
Packit Service 646995
    for cmd in cmd_list:
Packit Service 646995
        found = False
Packit Service 646995
        for a_path in path_list:
Packit Service 646995
            if os.path.exists('%s/%s' % (a_path, cmd)):
Packit Service 646995
                found = True
Packit Service 646995
                break
Packit Service 646995
        if not found:
Packit Service 646995
            print('Error: %s must be in your PATH' % cmd)
Packit Service 646995
            any_cmd_not_found = True
Packit Service 646995
    if any_cmd_not_found:
Packit Service 646995
        sys.exit(1)
Packit Service 646995
Packit Service 646995
Packit Service 646995
def run_fio():
Packit Service 646995
    """
Packit Service 646995
    Run the fio benchmark for various block sizes.
Packit Service 646995
    
Packit Service 646995
    Return zero for success.
Packit Service 646995
    Return non-zero for failure and a failure reason.
Packit Service 646995
Packit Service 646995
    Uses Globals: device, blocksize
Packit Service 646995
    """
Packit Service 646995
    if Global.blocksize is not None:
Packit Service 646995
        dprint('Found a block size passed in: %s' % Global.blocksize)
Packit Service 646995
        blocksizes = Global.blocksize.split(' ')
Packit Service 646995
    else:
Packit Service 646995
        dprint('NO Global block size pass in?')
Packit Service 646995
        blocksizes = ['512', '1k', '2k', '4k', '8k',
Packit Service 646995
                '16k', '32k', '75536', '128k', '1000000']
Packit Service 646995
    # for each block size, do a read test, then a write test
Packit Service 646995
    for bs in blocksizes:
Packit Service 646995
        vprint('Running "fio" read test: 8 threads, bs=%s' % bs)
Packit Service 646995
        # only support direct IO with aligned reads
Packit Service 646995
        if bs.endswith('k'):
Packit Service 646995
            direct=1
Packit Service 646995
        else:
Packit Service 646995
            direct=0
Packit Service 646995
        res = run_cmd(['fio', '--name=read-test', '--readwrite=randread',
Packit Service 646995
            '--runtime=2s', '--numjobs=8', '--blocksize=%s' % bs, 
Packit Service 646995
            '--direct=%d' % direct, '--filename=%s' % Global.device])
Packit Service 646995
        if res != 0:
Packit Service 646995
            return (res, 'fio failed')
Packit Service 646995
        vprint('Running "fio" write test: 8 threads, bs=%s' % bs)
Packit Service 646995
        res = run_cmd(['fio', '--name=write-test', '--readwrite=randwrite',
Packit Service 646995
            '--runtime=2s', '--numjobs=8', '--blocksize=%s' % bs, 
Packit Service 646995
            '--direct=%d' % direct, '--filename=%s' % Global.device])
Packit Service 646995
        if res != 0:
Packit Service 646995
            return (res, 'fio failed')
Packit Service 646995
        vprint('Running "fio" verify test: 1 thread, bs=%s' % bs)
Packit Service 646995
        res = run_cmd(['fio', '--name=verify-test', '--readwrite=randwrite',
Packit Service 646995
            '--runtime=2s', '--numjobs=1', '--blocksize=%s' % bs, 
Packit Service 646995
            '--direct=%d' % direct, '--filename=%s' % Global.device,
Packit Service 646995
            '--verify=md5', '--verify_state_save=0'])
Packit Service 646995
        if res != 0:
Packit Service 646995
            return (res, 'fio failed')
Packit Service 646995
    return (0, 'Success')
Packit Service 646995
Packit Service 646995
def wait_for_path(path, present=True, amt=10):
Packit Service 646995
    """Wait until a path exists or is gone"""
Packit Service 646995
    dprint("Looking for path=%s, present=%s" % (path, present))
Packit Service 646995
    for i in range(amt):
Packit Service 646995
        time.sleep(1)
Packit Service 646995
        if os.path.exists(path) == present:
Packit Service 646995
            dprint("We are Happy :) present=%s, cnt=%d" % (present, i))
Packit Service 646995
            return True
Packit Service 646995
    dprint("We are not happy :( present=%s actual=%s after %d seconds" % \
Packit Service 646995
           (present, os.path.exists(path), amt))
Packit Service 646995
    return False
Packit Service 646995
Packit Service 646995
def wipe_disc():
Packit Service 646995
    """
Packit Service 646995
    Wipe the label and partition table from the disc drive -- the sleep-s
Packit Service 646995
    are needed to give the async OS and udev a chance to notice the partition
Packit Service 646995
    table has been erased
Packit Service 646995
    """
Packit Service 646995
    # zero out the label and parition table
Packit Service 646995
    vprint('Running "sgdisk" and "dd" to wipe disc label, partitions, and filesystem')
Packit Service 646995
    time.sleep(1)
Packit Service 646995
    res = run_cmd(['sgdisk', '-Z', Global.device])
Packit Service 646995
    if res != 0:
Packit Service 646995
        return (res, '%s: could not zero out label: %d' % (Global.device, res))
Packit Service 646995
    res = run_cmd(['dd', 'if=/dev/zero', 'of=%s' % Global.device, 'bs=256k', 'count=20', 'oflag=direct'])
Packit Service 646995
    if res != 0:
Packit Service 646995
        return (res, '%s: could not zero out filesystem: %d' % (Global.device, res))
Packit Service 646995
    return (0, 'Success')
Packit Service 646995
    
Packit Service 646995
def run_parted():
Packit Service 646995
    """
Packit Service 646995
    Run the parted program to ensure there is one partition,
Packit Service 646995
    and that it covers the whole disk
Packit Service 646995
Packit Service 646995
    Return zero for success and the device pathname.
Packit Service 646995
    Return non-zero for failure and a failure reason.
Packit Service 646995
Packit Service 646995
    Uses Globals: device, partition
Packit Service 646995
    """
Packit Service 646995
    (res, reason) = wipe_disc()
Packit Service 646995
    if res != 0:
Packit Service 646995
        return (res, resason)
Packit Service 646995
    # ensure our partition file is not there, to be safe
Packit Service 646995
    if not wait_for_path(Global.partition, present=False, amt=30):
Packit Service 646995
        return (1, '%s: Partition already exists?' % Global.partition)
Packit Service 646995
    # make a label, then a partition table with one partition
Packit Service 646995
    vprint('Running "parted" to create a label and partition table')
Packit Service 646995
    res = run_cmd(['parted', Global.device, 'mklabel', 'gpt'])
Packit Service 646995
    if res != 0:
Packit Service 646995
        return (res, '%s: Could not create a GPT label' % Global.device)
Packit Service 646995
    res = run_cmd(['parted', '-a', 'none', Global.device, 'mkpart', 'primary', '0', '100%'])
Packit Service 646995
    if res != 0:
Packit Service 646995
        return (res, '%s: Could not create a primary partition' % Global.device)
Packit Service 646995
    # wait for the partition to show up
Packit Service 646995
    if not wait_for_path(Global.partition):
Packit Service 646995
        return (1, '%s: Partition never showed up?' % Global.partition)
Packit Service 646995
    # success
Packit Service 646995
    return (0, 'Success')
Packit Service 646995
Packit Service 646995
def run_mkfs():
Packit Service 646995
    vprint('Running "mkfs" to to create filesystem')
Packit Service 646995
    res = run_cmd(Global.MKFSCMD + [ Global.partition ] )
Packit Service 646995
    if res != 0:
Packit Service 646995
        return (res, '%s: mkfs failed (%d)' % (Global.partition, res))
Packit Service 646995
    return (0, 'Success')
Packit Service 646995
Packit Service 646995
def run_bonnie():
Packit Service 646995
    # make a temp dir and mount the device there
Packit Service 646995
    with tempfile.TemporaryDirectory() as tmp_dir:
Packit Service 646995
        vprint('Mounting the filesystem')
Packit Service 646995
        res = run_cmd(['mount'] + Global.MOUNTOPTIONS + [Global.partition, tmp_dir])
Packit Service 646995
        if res != 0:
Packit Service 646995
            return (res, '%s: mount failed (%d)' % (Global.partition, res))
Packit Service 646995
        # run bonnie++ on the new directory
Packit Service 646995
        vprint('Running "bonnie++" on the filesystem')
Packit Service 646995
        res = run_cmd(['bonnie++'] + Global.BONNIEPARAMS + ['-d', tmp_dir])
Packit Service 646995
        if res != 0:
Packit Service 646995
            return (res, '%s: umount failed (%d)' % (tmp_dir, res))
Packit Service 646995
        # unmount the device and remove the temp dir
Packit Service 646995
        res = run_cmd(['umount', tmp_dir])
Packit Service 646995
        if res != 0:
Packit Service 646995
            return (res, '%s: umount failed (%d)' % (tmp_dir, res))
Packit Service 646995
    return (0, 'Success')