multi_disk.py 14.1 KB
Newer Older
1 2 3
"""
multi_disk test for Autotest framework.

L
Lucas Meneghel Rodrigues 已提交
4
:copyright: 2011-2012 Red Hat Inc.
5
"""
L
Lucas Meneghel Rodrigues 已提交
6 7 8
import logging
import re
import random
9

10 11
from avocado.utils import astring

12
from virttest import env_process
13 14
from virttest import error_context
from virttest import qemu_qtree
15
from virttest import utils_misc
16 17 18 19 20 21

_RE_RANGE1 = re.compile(r'range\([ ]*([-]?\d+|n).*\)')
_RE_RANGE2 = re.compile(r',[ ]*([-]?\d+|n)')
_RE_BLANKS = re.compile(r'^([ ]*)')


22
@error_context.context_aware
23 24 25 26 27 28 29
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])
L
Lucas Meneghel Rodrigues 已提交
30
    :raise ValueError: In case incorrect values are given.
L
Lucas Meneghel Rodrigues 已提交
31
    :return: List of int values. In case it can't substitute 'n'
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
             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


76
@error_context.context_aware
77
def run(test, params, env):
78 79 80 81
    """
    Test multi disk suport of guest, this case will:
    1) Create disks image in configuration file.
    2) Start the guest with those disks.
82 83 84 85 86 87 88
    3) Checks qtree vs. test params. (Optional)
    4) Create partition on those disks.
    5) Get disk dev filenames in guest.
    6) Format those disks in guest.
    7) Copy file into / out of those disks.
    8) Compare the original file and the copied file using md5 or fc comand.
    9) Repeat steps 3-5 if needed.
89

L
Lucas Meneghel Rodrigues 已提交
90 91 92
    :param test: QEMU test object
    :param params: Dictionary with the test parameters
    :param env: Dictionary with test environment.
93 94 95 96 97 98 99 100 101
    """
    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 ''

102 103 104 105 106 107
    def _do_post_cmd(session):
        cmd = params.get("post_cmd")
        if cmd:
            session.cmd_status_output(cmd)
        session.close()

108 109 110 111 112 113 114 115 116 117 118 119 120 121
    def _get_disk_index(session, image_size, disk_indexs):
        list_disk_cmd = "echo list disk > disk && "
        list_disk_cmd += "echo exit >> disk && diskpart /s disk"
        disks = session.cmd_output(list_disk_cmd)
        size_type = image_size[-1] + "B"
        disk_size = ""

        if size_type == "MB":
            disk_size = image_size[:-1] + " MB"
        elif size_type == "GB" and int(image_size[:-1]) < 8:
            disk_size = str(int(image_size[:-1])*1024) + " MB"
        else:
            disk_size = image_size[:-1] + " GB"

X
Xu Han 已提交
122
        regex_str = r'Disk (\d+).*?%s.*?%s' % (disk_size, disk_size)
123 124 125 126 127 128
        for disk in disks.splitlines():
            if disk.startswith("  Disk"):
                o = re.findall(regex_str, disk, re.I | re.M)
                if o:
                    disk_indexs.append(o[0])

129
    error_context.context("Parsing test configuration", logging.info)
130 131 132 133 134
    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"))
135
    stg_params += _add_param("image_boot", params.get("stg_image_boot", "no"))
136
    stg_params += _add_param("drive_format", params.get("stg_drive_format"))
X
Xu Tian 已提交
137
    stg_params += _add_param("drive_cache", params.get("stg_drive_cache"))
138 139 140 141 142 143 144 145 146 147 148 149 150
    if params.get("stg_assign_index") != "no":
        # 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 not stg_params[i].strip():
            i += 1
            continue
        if stg_params[i][-1] == '\\':
            stg_params[i] = '%s %s' % (stg_params[i][:-1],
L
Lucas Meneghel Rodrigues 已提交
151
                                       stg_params.pop(i + 1))
152 153 154 155
        i += 1

    rerange = []
    has_name = False
X
Xu Han 已提交
156
    for i in range(len(stg_params)):
157 158 159 160 161 162 163
        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)
L
Lucas Meneghel Rodrigues 已提交
164
            if parm is False:
X
Xu Han 已提交
165 166 167
                test.error("Incorrect cfg: stg_params %s looks "
                           "like range(..) but doesn't contain "
                           "numbers." % cmd)
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
            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* are 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(_)

195
    stg_image_name = params.get('stg_image_name', 'images/%s')
X
Xu Han 已提交
196
    for i in range(stg_image_num):
197 198 199 200 201 202 203
        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))
X
Xu Han 已提交
204
        for parm in param_matrix.items():
205 206 207 208 209 210
            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") == 'yes':
        # Only print the test param_matrix and finish
        logging.info('Newly added disks:\n%s',
211
                     astring.tabular_output(param_table, param_table_header))
212 213
        return

214
    # Always recreate VMs and disks
215
    error_context.context("Start the guest with new disks", logging.info)
216 217
    for vm_name in params.objects("vms"):
        vm_params = params.object_params(vm_name)
218 219
        env_process.process_images(env_process.preprocess_image, test,
                                   vm_params)
220

221
    error_context.context("Start the guest with those disks", logging.info)
222
    vm = env.get_vm(params["main_vm"])
223
    vm.create(timeout=max(10, stg_image_num), params=params)
224 225 226
    session = vm.wait_for_login(timeout=int(params.get("login_timeout", 360)))

    n_repeat = int(params.get("n_repeat", "1"))
227
    file_system = [_.strip() for _ in params.get("file_system").split()]
228
    cmd_timeout = float(params.get("cmd_timeout", 360))
229 230
    re_str = params["re_str"]
    black_list = params["black_list"].split()
231 232
    stg_image_size = params.get("stg_image_size")
    disk_indexs = []
233

234
    have_qtree = True
235
    out = vm.monitor.human_monitor_cmd("info qtree", debug=False)
236 237 238
    if "unknown command" in str(out):
        have_qtree = False

239
    if (params.get("check_guest_proc_scsi") == "yes") and have_qtree:
240
        error_context.context("Verifying qtree vs. test params")
241 242 243 244
        err = 0
        qtree = qemu_qtree.QtreeContainer()
        qtree.parse_info_qtree(vm.monitor.info('qtree'))
        disks = qemu_qtree.QtreeDisksContainer(qtree.get_nodes())
245
        (tmp1, tmp2) = disks.parse_info_block(vm.monitor.info_block())
246 247 248 249
        err += tmp1 + tmp2
        err += disks.generate_params()
        err += disks.check_disk_params(params)
        (tmp1, tmp2, _, _) = disks.check_guests_proc_scsi(
L
Lucas Meneghel Rodrigues 已提交
250
            session.cmd_output('cat /proc/scsi/scsi'))
251
        err += tmp1 + tmp2
252

253
        if err:
X
Xu Han 已提交
254 255
            test.fail("%s errors occurred while verifying"
                      " qtree vs. params" % err)
256 257
        if params.get('multi_disk_only_qtree') == 'yes':
            return
258 259

    try:
260 261
        cmd = params.get("clean_cmd")
        if cmd:
262
            session.cmd_status_output(cmd)
263

264
        if params.get("os_type") == "windows":
265
            error_context.context("Create partition on those disks", logging.info)
266 267
            # Get the disk index
            _get_disk_index(session, stg_image_size, disk_indexs)
268 269 270
            if len(disk_indexs) < stg_image_num:
                err_msg = "Set disks num: %d" % stg_image_num
                err_msg += ", Get disks num in guest: %d" % len(disk_indexs)
X
Xu Han 已提交
271
                test.fail("Fail to list all the volumes, %s" % err_msg)
272 273 274 275

            # Random select one file system from file_system
            index = random.randint(0, (len(file_system) - 1))
            fs_type = file_system[index].strip()
X
Xu Han 已提交
276
            for i in range(stg_image_num):
277 278
                utils_misc.format_windows_disk(session, disk_indexs[i], None,
                                               None, fs_type)
279

280
        error_context.context("Get disks dev filenames in guest", logging.info)
281
        cmd = params["list_volume_command"]
282 283
        s, output = session.cmd_status_output(cmd, timeout=cmd_timeout)
        if s != 0:
X
Xu Han 已提交
284 285
            test.fail("List volume command failed with cmd '%s'.\n"
                      "Output is: %s\n" % (cmd, output))
286

287 288
        output = session.cmd_output(cmd, timeout=cmd_timeout)
        disks = re.findall(re_str, output)
X
Xu Han 已提交
289
        disks = [item.strip() for item in disks]
290
        disks.sort()
291 292 293
        logging.debug("Volume list that meet regular expressions: %s",
                      " ".join(disks))

294 295 296
        images = params.get("images").split()
        if len(disks) < len(images):
            logging.debug("disks: %s , images: %s", len(disks), len(images))
X
Xu Han 已提交
297
            test.fail("Fail to list all the volumes!")
298 299

        if params.get("os_type") == "linux":
300 301
            output = session.cmd_output("mount")
            li = re.findall(r"^/dev/(%s)\d*" % re_str, output, re.M)
302 303
            if li:
                black_list.extend(li)
304 305 306 307 308 309
        else:
            black_list.extend(utils_misc.get_winutils_vol(session))
        disks = set(disks)
        black_list = set(black_list)
        logging.info("No need to check volume '%s'", (disks & black_list))
        disks = disks - black_list
310 311 312
    except Exception:
        _do_post_cmd(session)
        raise
313

314
    try:
315 316
        for i in range(n_repeat):
            logging.info("iterations: %s", (i + 1))
317
            error_context.context("Format those disks in guest", logging.info)
318 319
            for disk in disks:
                disk = disk.strip()
320
                error_context.context("Preparing disk: %s..." % disk)
321 322

                # Random select one file system from file_system
323
                index = random.randint(0, (len(file_system) - 1))
324
                fs = file_system[index].strip()
325
                cmd = params["format_command"] % (fs, disk)
326
                error_context.context("formatting test disk")
327
                session.cmd(cmd, timeout=cmd_timeout)
328 329 330 331
                cmd = params.get("mount_command")
                if cmd:
                    cmd = cmd % (disk, disk, disk)
                    session.cmd(cmd)
332

333
            error_context.context("Cope file into / out of those disks", logging.info)
334 335 336
            for disk in disks:
                disk = disk.strip()

337
                error_context.context("Performing I/O on disk: %s..." % disk)
338
                cmd_list = params["cmd_list"].split()
339
                for cmd_l in cmd_list:
340 341 342
                    cmd = params.get(cmd_l)
                    if cmd:
                        session.cmd(cmd % disk, timeout=cmd_timeout)
343

344
                cmd = params["compare_command"]
345
                key_word = params["check_result_key_word"]
346
                output = session.cmd_output(cmd)
347
                if key_word not in output:
X
Xu Han 已提交
348
                    test.fail("Files on guest os root fs and disk differ")
349 350 351 352 353 354 355 356

            if params.get("umount_command"):
                cmd = params.get("show_mount_cmd")
                output = session.cmd_output(cmd)
                disks = re.findall(re_str, output)
                disks.sort()
                for disk in disks:
                    disk = disk.strip()
357
                    error_context.context("Unmounting disk: %s..." % disk)
358
                    cmd = params.get("umount_command") % (disk, disk)
359 360
                    session.cmd(cmd)
    finally:
361 362 363 364 365 366 367
        cmd = params.get("show_mount_cmd")
        if cmd:
            try:
                output = session.cmd_output(cmd)
                disks = re.findall(re_str, output)
                disks.sort()
                for disk in disks:
368
                    error_context.context("Unmounting disk: %s..." % disk)
369 370
                    cmd = params["umount_command"] % (disk, disk)
                    session.cmd(cmd)
X
Xu Han 已提交
371
            except Exception as err:
372 373 374
                logging.warn("Get error when cleanup, '%s'", err)

        _do_post_cmd(session)