[PATCH] kvm test: Parametrisation of multi_disk test

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]


This patch makes multi_disk test more flexible. You can use
stg_params variable to set all kinds of setups. As example
I added few tests for virtio_scsi disks and all_drive_format test.

stg_params is a space separated set of parameters of newly added
disks. Every parameter have to have a name and after ':'
it can be ',' separated list of words or 'range(...)'.

range() is similar to python range(), but it supports 4th argument,
which multiplies occurrence of each value:
range(0,2,1,2) => [0,0,1,1]
Also range() supports 'n' which is substituted later to fit the
number of disks. See the code for details.

Signed-off-by: Lukas Doktor <ldoktor@xxxxxxxxxx>
---
 client/tests/kvm/tests/multi_disk.py |  346 ++++++++++++++++++++++++++++++----
 client/virt/guest-os.cfg.sample      |    6 +-
 client/virt/subtests.cfg.sample      |   48 +++++-
 3 files changed, 363 insertions(+), 37 deletions(-)

diff --git a/client/tests/kvm/tests/multi_disk.py b/client/tests/kvm/tests/multi_disk.py
index 5062fc3..4590b94 100644
--- a/client/tests/kvm/tests/multi_disk.py
+++ b/client/tests/kvm/tests/multi_disk.py
@@ -1,52 +1,324 @@
-import logging, re, random
+import logging
+import re
+import random
+import os
 from autotest.client.shared import error
-from autotest.client.virt import virt_env_process
+from autotest.client.virt.virt_env_process import preprocess
+from autotest.client.shared.base_utils import matrix_to_string
+from autotest.client.virt.virt_vm import get_image_filename
+
+re_range1 = re.compile(r'range\([ ]*([-]?\d+|n).*\)')
+re_range2 = re.compile(r',[ ]*([-]?\d+|n)')
+re_blanks = re.compile(r'^([ ]*)')
 
 
 @error.context_aware
+def _range(buf, n=None):
+    """
+    Converts 'range(..)' string to range. It supports 1-4 args. It supports
+    'n' as correct input, which is substituted to return the correct range.
+    range1-3 ... ordinary python range()
+    range4   ... multiplies the occurrence of each value
+                (range(0,4,1,2) => [0,0,1,1,2,2,3,3])
+    @raise ValueError: In case incorrect values are given.
+    @return: List of int values. In case it can't substitute 'n'
+             it returns the original string.
+    """
+    out = re_range1.match(buf)
+    if not out: return False
+    out = [out.groups()[0]]
+    out.extend(re_range2.findall(buf))
+    if 'n' in out:
+        if n is None:
+            # Don't know what to substitute, return the original
+            return buf
+        else:
+            # Doesn't cover all cases and also it works it's way...
+            n = int(n)
+            if out[0] == 'n':
+                out[0] = int(n)
+            if len(out) > 1 and out[1] == 'n':
+                out[1] = int(out[0]) + n
+            if len(out) > 2 and out[2] == 'n':
+                out[2] = (int(out[1]) - int(out[0])) / n
+            if len(out) > 3 and out[3] == 'n':
+                _len = len(range(int(out[0]), int(out[1]), int(out[2])))
+                out[3] = n / _len
+                if n % _len:
+                    out[3] += 1
+    for i in range(len(out)):
+        out[i] = int(out[i])
+    if len(out) == 1:
+        out = range(out[0])
+    elif len(out) == 2:
+        out = range(out[0], out[1])
+    elif len(out) == 3:
+        out = range(out[0], out[1], out[2])
+    elif len(out) == 4:
+        # arg4 * range
+        _out = []
+        for _ in range(out[0], out[1], out[2]):
+            _out.extend([_] * out[3])
+        out = _out
+    else:
+        raise ValueError("More than 4 parameters in _range()")
+    return out
+
+@error.context_aware
+def _qtree_check(vm, session, params, root_dir):
+    """
+    Tries to find differences between qemu qtree+info vs. /proc/scsi/scsi and
+    vm params.
+    
+    @param vm: VM object
+    @param session: ssh session to VM
+    @param params: Dictionary with the test parameters
+    @param root_dir: vm's root_dir (for get_image_name function)
+    """
+    err = 0
+    # check [params_names, qtree search pattern]
+    check = [['name', 'channel', 'scsiid', 'lun'],
+             ['dev-prop: drive = ', 'bus-prop: channel = ',
+              'bus-prop: scsi-id = ', 'bus-prop: lun = ']]
+    drive_formats = ['ide', 'scsi', 'virtio-blk-pci']
+
+    # Info about disks gathered from guest
+    disks = {}
+    no_virtio_disks = 0 # virtio-blk-pci disks are not in /proc/scsi/scsi
+
+    error.context("Gather info from 'info qtree'")
+    info = vm.monitor.info('qtree').split('\n')
+    current = None
+    line = info.pop(0)
+    offset = None
+    while len(info) > 0:
+        if current is not None:     # Get all info about disk
+            _offset = len(re_blanks.match(line).group(0))
+            if offset == None:
+                offset = _offset
+            elif offset != _offset:
+                # This line is about next device, store current and prepare
+                # for next one.
+                name = current.get('name')
+                if not name:
+                    logging.error("Skipping disk without a name: %s", current)
+                    err += 1
+                elif name in disks:
+                    logging.error("Disk %s present multiple times in qtree",
+                                   current)
+                else:
+                    if current['drive_format'] == 'virtio-blk-pci':
+                        no_virtio_disks += 1
+                    disks[name] = current
+                current = None
+                continue    # this line have to be proceeded in next round
+            line = line[offset:]
+            for i in xrange(len(check[1])):
+                if line.startswith(check[1][i]):
+                    current[check[0][i]] = line[len(check[1][i]):].strip()
+        else:       # Look for block with disk specification
+            line = line.strip()
+            for fmt in drive_formats:
+                if line.startswith('dev: %s' % fmt):
+                    current = {'drive_format': fmt}
+                    offset = None
+        line = info.pop(0)  # Next line
+
+    error.context("Gather info from 'info block'")
+    info = vm.monitor.info("block").split('\n')
+    for line in info:
+        if not line: continue
+        line = line.split(':', 1)
+        name = line[0].strip()
+        if name not in disks:
+            logging.error("disk %s is in block but not in qtree", name)
+            err += 1
+            continue
+        item = {}
+        for _ in line[1].strip().split(' '):
+            _ = _.split('=')
+            item[_[0]] = _[1]
+        if item.get('backing_file'):
+            disks[name]['snapshot'] = 'yes'
+            disks[name]['image_name'] = os.path.realpath(
+                                                    item.get('backing_file'))
+        elif item.get('file'):
+            disks[name]['image_name'] = os.path.realpath(item.get('file'))
+        else:
+            logging.error("Can'T get info about %s disk file.", name)
+            err += 1
+        if item.get('ro') and item.get('ro') != '0':
+            disks[name]['readonly'] = 'yes'
+
+    error.context("Verify info from guest's /proc/scsi/scsi")
+    # host, channel, id, lun, vendor
+    scsis = re.findall(r'Host:\s+(\w+)\s+Channel:\s+(\d+)\s+Id:\s+(\d+)\s+'
+                        'Lun:\s+(\d+)\n\s+Vendor:\s+([a-zA-Z0-9_-]+)\s+Model: ',
+                        session.cmd_output('cat /proc/scsi/scsi'))
+    if len(scsis) + no_virtio_disks != len(disks):
+        logging.error("The number of disks in qtree and /proc/scsi/scsi is not"
+                      " equal.")
+        err += 1
+    _disks = {}
+    # Check only scsi disks
+    for disk in disks.copy().iteritems():
+        if disk[1]['drive_format'].startswith('scsi'):
+            _disks[disk[0]] = disk[1]
+    _ = []
+    for scsi in scsis:
+        if scsi[4].startswith('QEMU'):
+            _.append("%d-%d-%d" % (int(scsi[1]), int(scsi[2]), int(scsi[3])))
+    scsis = _
+    # Check only channel, id and lun for now
+    for disk in _disks.itervalues():
+        name = '%d-%d-%d' % (int(disk['channel']), int(disk['scsiid']),
+                             int(disk['lun']))
+        if name not in scsis:
+            logging.error('Disk %s is in qtree but not in /proc/scsi/scsi.',
+                          disk)
+            err += 1
+        scsis.remove(name)
+
+    error.context("Verify the info from qtree+block vs. params.")
+    _disks = disks.copy()
+    for name in params.objects('images'):
+        current = None
+        image_params = params.object_params(name)
+        image_name = os.path.realpath(get_image_filename(image_params,
+                                                         root_dir))
+        for disk in disks.itervalues():
+            if disk.get('image_name') == image_name:
+                current = disk
+                qname = current.get('name')
+                current.pop('name')
+                break
+        if not current:
+            logging.error("Disk %s is not in qtree but is in params.", name)
+            err += 1
+            continue
+        for prop in check[0]:
+            if (image_params.get(prop) and current.get(prop) and
+                    image_params.get(prop) != current.get(prop)):
+                logging.error("Disk %s's property %s=%s doesn't math params %s",
+                              qname, prop, current.get(prop),
+                               image_params.get(prop))
+                err += 1
+        _disks.pop(qname)
+    if _disks:
+        logging.error('Some disks were in qtree but not in autotest params: %s',
+                      _disks)
+        err += 1
+
+    return err
+
+@error.context_aware
 def run_multi_disk(test, params, env):
     """
     Test multi disk suport of guest, this case will:
     1) Create disks image in configuration file.
     2) Start the guest with those disks.
-    3) Format those disks.
-    4) Copy file into / out of those disks.
-    5) Compare the original file and the copied file using md5 or fc comand.
-    6) Repeat steps 3-5 if needed.
+    3) Checks qtree vs. test params.
+    4) Format those disks.
+    5) Copy file into / out of those disks.
+    6) Compare the original file and the copied file using md5 or fc comand.
+    7) Repeat steps 3-5 if needed.
 
     @param test: kvm test object
     @param params: Dictionary with the test parameters
     @param env: Dictionary with test environment.
     """
-    stg_image_num = int(params.get("stg_image_num", 0))
-    stg_image_size = params.get("stg_image_size")
-    stg_image_format = params.get("stg_image_format")
-    stg_image_boot = params.get("stg_image_boot")
-    stg_drive_format = params.get("stg_drive_format")
-    stg_assign_index = params.get("stg_assign_index") == "yes"
-    for num in xrange(stg_image_num):
-        name = "stg%d" % num
-        params["images"] = params.get("images") + " %s" % name
-        params["image_name_%s" % name] = name
-        if stg_image_size:
-            params["image_size_%s" % name] = stg_image_size
-        if stg_image_format:
-            params["image_format_%s" % name] = stg_image_format
-        if stg_image_boot:
-            params["image_boot_%s" % name] = stg_image_boot
-        if stg_drive_format:
-            params["drive_format_%s" % name] = stg_drive_format
-        if stg_assign_index:
-            params["drive_index_%s" % name] = num
-        stg_params = params.object_params(name)
-        virt_env_process.preprocess_image(test, stg_params)
+    def _add_param(name, value):
+        """ Converts name+value to stg_params string """
+        if value:
+            value = re.sub(' ', '\\ ', value)
+            return "%s:%s " % (name, value)
+        else:
+            return ''
+
+    stg_image_num = 0
+    stg_params = params.get("stg_params", "")
+    # Compatibility
+    stg_params += _add_param("image_size", params.get("stg_image_size"))
+    stg_params += _add_param("image_format", params.get("stg_image_format"))
+    stg_params += _add_param("image_boot", params.get("stg_image_boot"))
+    stg_params += _add_param("drive_format", params.get("stg_drive_format"))
+    if params.get("stg_assign_index") == "yes":
+        # Assume 0 and 1 are already occupied (hd0 and cdrom)
+        stg_params += _add_param("drive_index", 'range(2,n)')
+    param_matrix = {}
+
+    stg_params = stg_params.split(' ')
+    i = 0
+    while i < len(stg_params) - 1:
+        if stg_params[i][-1] == '\\':
+            stg_params[i] = '%s %s' % (stg_params[i][:-1],
+                                          stg_params.pop(i + 1))
+        i += 1
 
+    rerange = []
+    has_name = False
+    for i in xrange(len(stg_params)):
+        if not stg_params[i].strip(): continue
+        (cmd, parm) = stg_params[i].split(':', 1)
+        if cmd == "image_name": has_name = True
+        if re_range1.match(parm):
+            parm = _range(parm)
+            if parm == False:
+                raise error.TestError("Incorrect cfg: stg_params %s looks "
+                                      "like range(..) but doesn't contain "
+                                      "numbers." % cmd)
+            param_matrix[cmd] = parm
+            if type(parm) is str:
+                # When we know the stg_image_num, substitute it.
+                rerange.append(cmd)
+                continue
+        else:
+            # ',' separated list of values
+            parm = parm.split(',')
+            j = 0
+            while j < len(parm) - 1:
+                if parm[j][-1] == '\\':
+                    parm[j] = '%s,%s' % (parm[j][:-1], parm.pop(j + 1))
+                j += 1
+            param_matrix[cmd] = parm
+        stg_image_num = max(stg_image_num, len(parm))
+
+    stg_image_num = int(params.get('stg_image_num', stg_image_num))
+    for cmd in rerange:
+        param_matrix[cmd] = _range(param_matrix[cmd], stg_image_num)
+    # param_table is for pretty print of param_matrix
+    param_table = []
+    param_table_header = ['name']
+    if not has_name: param_table_header.append('image_name')
+    for _ in param_matrix:
+        param_table_header.append(_)
+
+    stg_image_name = params.get('stg_image_name', '%s')
+    for i in xrange(stg_image_num):
+        name = "stg%d" % i
+        params['images'] += " %s" % name
+        param_table.append([])
+        param_table[-1].append(name)
+        if not has_name:
+            params["image_name_%s" % name] = stg_image_name % name
+            param_table[-1].append(params.get("image_name_%s" % name))
+        for parm in param_matrix.iteritems():
+            params['%s_%s' % (parm[0], name)] = str(parm[1][i % len(parm[1])])
+            param_table[-1].append(params.get('%s_%s' % (parm[0], name)))
+
+
+    if params.get("multi_disk_params_only"):
+        # Only print the test param_matrix and finish
+        logging.info('Newly added disks:\n%s',
+                     matrix_to_string(param_table, param_table_header))
+        return
+
+    # Always recreate VM (disks are already marked for deletion
+    preprocess(test, params, env)
+    logging.debug(params)
     vm = env.get_vm(params["main_vm"])
-    # stg_image_num is greater than 0 means there is some disk(s) added in
-    # this case. and guest must be created explicitly.
-    if stg_image_num > 0:
-        vm.create(params=params)
-    vm.verify_alive()
+    vm.create(timeout=max(10, stg_image_num))
+    #time.sleep(stg_image_num)  # Add some extra time
     session = vm.wait_for_login(timeout=int(params.get("login_timeout", 360)))
 
     images = params.get("images").split()
@@ -58,6 +330,14 @@ def run_multi_disk(test, params, env):
     re_str = params.get("re_str")
     block_list = params.get("block_list").split()
 
+    error.context("verifying qtree vs. test params")
+    _ = _qtree_check(vm, session, params, vm.root_dir)
+    if _:
+        raise error.TestFail("%s errors occurred while verifying qtree vs. "
+                             "params" % _)
+    if params.get('multi_disk_only_qtree') == 'yes':
+        return
+
     try:
         if params.get("clean_cmd"):
             cmd = params.get("clean_cmd")
diff --git a/client/virt/guest-os.cfg.sample b/client/virt/guest-os.cfg.sample
index fbbd01f..3aea5a5 100644
--- a/client/virt/guest-os.cfg.sample
+++ b/client/virt/guest-os.cfg.sample
@@ -47,8 +47,8 @@ variants:
             file_system = "ext3 ext2"
             mount_command = mkdir /mnt/%s && mount /dev/%s /mnt/%s
             umount_command = umount /dev/%s && rmdir /mnt/%s
-            list_volume_command = cd /dev && \ls [vhs]d?
-            re_str = "[vhs]d[a-z]"
+            list_volume_command = cd /dev && \ls [vhs]d*
+            re_str = "[vhs]d[a-z]*"
             format_command = echo y | mkfs -t %s /dev/%s
             copy_to_command = \cp -rf /bin/ls /mnt/%s
             copy_from_command = \cp -rf /mnt/%s/ls /tmp/ls
@@ -57,7 +57,7 @@ variants:
             max_disk:
                  stg_image_num = 27
                  list_volume_command = cd /dev && \ls vd*
-                 re_str = "[vhs]d[a-z][^0-9]"
+                 re_str = "[vhs]d[a-z]*[^0-9]"
         usb_multi_disk:
             show_mount_cmd = mount|gawk '/mnt/{print $1}'
             clean_cmd = "\rm -rf /mnt/*"
diff --git a/client/virt/subtests.cfg.sample b/client/virt/subtests.cfg.sample
index bf189e0..3a08f70 100644
--- a/client/virt/subtests.cfg.sample
+++ b/client/virt/subtests.cfg.sample
@@ -1598,10 +1598,56 @@ variants:
                 stg_image_num = 23
                 stg_image_size = 1G
                 stg_image_boot = no
+                stg_assign_index = yes
                 # other variants.
                 # stg_image_format = qcow2
                 # stg_drive_format = virtio
-                # stg_assign_index = yes
+            - all_drive_format_types:
+                stg_params = "drive_format:ide,scsi,virtio,scsi-hd,usb2"
+                usbs += " default-ehci"
+                usb_type_default-ehci = usb-ehci
+            - virtio_scsi_variants:
+                # Decrease length of the command
+                stg_image_name = '/tmp/%s'
+                stg_image_size = 1M
+                stg_assign_index = yes
+                stg_params = "drive_format:scsi-disk "
+                variants:
+                    - @passthrough:
+                        # We need to unload scsi_debug modules used by VM
+                        kill_vm = yes
+                        pre_command = "modprobe scsi_debug && echo 9 > /sys/bus/pseudo/drivers/scsi_debug/add_host"
+                        post_command = "rmmod scsi_debug"
+                        stg_params += "image_raw_device:yes "
+                        stg_params += "image_format:raw "
+                        stg_params += "indirect_image_select:range(-9,0) "
+                        variants:
+                            - block:
+                                stg_params += "image_name:/dev/sd* "
+                            - generic:
+                                stg_params += "drive_format:scsi-generic "
+                                stg_params += "image_name:/dev/sg* "
+                    - multi_lun:
+                        stg_params += "drive_lun:range(256) "
+                    - multi_scsiid_lun:
+                        stg_params += "drive_scsiid:range(15) "
+                        stg_params += "drive_lun:range(0,256,15,15) "
+                    - multi_bus_scsiid_lun:
+                        stg_params += "drive_bus:range(0,15,1,9) "
+                        stg_params += "drive_scsiid:range(0,11,5,3) "
+                        stg_params += "drive_lun:range(0,256,127) "
+            - debug_params:
+                # Remove this to execute this test-params-devel test
+                no multi_disk
+                # Dont run the actual test, only show the disk setup
+                multi_disk_params_only = yes
+                stg_image_name = '/tmp/%s'
+                stg_image_size = 1M
+                stg_params += "list_params:item1,item2,item3 "
+                stg_params += "simplerange:range(55) "
+                stg_params += "fullrange:range(first,last,step,multiple_items) "
+                stg_params += "range_0-all_disk:range(n) "
+
 
     - clock_getres: install setup image_copy unattended_install.cdrom
         only Linux
-- 
1.7.7.6

--
To unsubscribe from this list: send the line "unsubscribe kvm" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[KVM ARM]     [KVM ia64]     [KVM ppc]     [Spice Development]     [Libvirt]     [Libvirt Users]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Photo]     [Yosemite News]    [Yosemite Photos]    [Linux Kernel]     [Linux SCSI]     [XFree86]

Add to Google Powered by Linux