aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorAnthony Liguori <aliguori@us.ibm.com>2012-08-07 09:46:24 -0500
committerAnthony Liguori <aliguori@us.ibm.com>2012-08-07 09:46:24 -0500
commitb262fce11a7409d71be84f68637cf4005e995822 (patch)
treeab8fcdcd608718e38ce9772ccdc4f54cafa15b05 /tests
parent0b8db8fe15d17a529a5ea90614c11e9f031dfee8 (diff)
parent20caf0f766b48fbbf718eb20aaddb7ffa356a21f (diff)
downloadqemu-b262fce11a7409d71be84f68637cf4005e995822.zip
qemu-b262fce11a7409d71be84f68637cf4005e995822.tar.gz
qemu-b262fce11a7409d71be84f68637cf4005e995822.tar.bz2
Merge remote-tracking branch 'kwolf/for-anthony' into staging
* kwolf/for-anthony: qemu-img: use QemuOpts instead of QEMUOptionParameter in resize function qemu-iotests: Be more flexible with image creation options qemu-iotests: add 039 qcow2 lazy refcounts test qemu-io: add "abort" command to simulate program crash qcow2: implement lazy refcounts qemu-iotests: ignore qemu-img create lazy_refcounts output docs: add lazy refcounts bit to qcow2 specification qcow2: introduce dirty bit docs: add dirty bit to qcow2 specification qemu-iotests: add qed.py image manipulation utility qapi: generalize documentation of streaming commands ide scsi: Mess with geometry only for hard disk devices
Diffstat (limited to 'tests')
-rw-r--r--tests/qemu-iotests/031.out20
-rw-r--r--tests/qemu-iotests/036.out4
-rwxr-xr-xtests/qemu-iotests/039136
-rw-r--r--tests/qemu-iotests/039.out53
-rw-r--r--tests/qemu-iotests/common.rc7
-rw-r--r--tests/qemu-iotests/group1
-rwxr-xr-xtests/qemu-iotests/qed.py235
7 files changed, 441 insertions, 15 deletions
diff --git a/tests/qemu-iotests/031.out b/tests/qemu-iotests/031.out
index d3cab30..796c993 100644
--- a/tests/qemu-iotests/031.out
+++ b/tests/qemu-iotests/031.out
@@ -54,8 +54,8 @@ header_length 72
Header extension:
magic 0x6803f857
-length 0
-data ''
+length 96
+data <binary>
Header extension:
magic 0x12345678
@@ -68,7 +68,7 @@ No errors were found on the image.
magic 0x514649fb
version 2
-backing_file_offset 0x98
+backing_file_offset 0xf8
backing_file_size 0x17
cluster_bits 16
size 67108864
@@ -92,8 +92,8 @@ data 'host_device'
Header extension:
magic 0x6803f857
-length 0
-data ''
+length 96
+data <binary>
Header extension:
magic 0x12345678
@@ -155,8 +155,8 @@ header_length 104
Header extension:
magic 0x6803f857
-length 0
-data ''
+length 96
+data <binary>
Header extension:
magic 0x12345678
@@ -169,7 +169,7 @@ No errors were found on the image.
magic 0x514649fb
version 3
-backing_file_offset 0xb8
+backing_file_offset 0x118
backing_file_size 0x17
cluster_bits 16
size 67108864
@@ -193,8 +193,8 @@ data 'host_device'
Header extension:
magic 0x6803f857
-length 0
-data ''
+length 96
+data <binary>
Header extension:
magic 0x12345678
diff --git a/tests/qemu-iotests/036.out b/tests/qemu-iotests/036.out
index 6953e37..063ca22 100644
--- a/tests/qemu-iotests/036.out
+++ b/tests/qemu-iotests/036.out
@@ -46,7 +46,7 @@ header_length 104
Header extension:
magic 0x6803f857
-length 0
-data ''
+length 96
+data <binary>
*** done
diff --git a/tests/qemu-iotests/039 b/tests/qemu-iotests/039
new file mode 100755
index 0000000..a749fcf
--- /dev/null
+++ b/tests/qemu-iotests/039
@@ -0,0 +1,136 @@
+#!/bin/bash
+#
+# Test qcow2 lazy refcounts
+#
+# Copyright (C) 2012 Red Hat, Inc.
+# Copyright IBM, Corp. 2010
+#
+# Based on test 038.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+# creator
+owner=stefanha@linux.vnet.ibm.com
+
+seq=`basename $0`
+echo "QA output created by $seq"
+
+here=`pwd`
+tmp=/tmp/$$
+status=1 # failure is the default!
+
+_cleanup()
+{
+ _cleanup_test_img
+}
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+# get standard environment, filters and checks
+. ./common.rc
+. ./common.filter
+
+_supported_fmt qcow2
+_supported_proto generic
+_supported_os Linux
+
+size=128M
+
+echo
+echo "== Checking that image is clean on shutdown =="
+
+IMGOPTS="compat=1.1,lazy_refcounts=on"
+_make_test_img $size
+
+$QEMU_IO -c "write -P 0x5a 0 512" $TEST_IMG | _filter_qemu_io
+
+# The dirty bit must not be set
+./qcow2.py $TEST_IMG dump-header | grep incompatible_features
+_check_test_img
+
+echo
+echo "== Creating a dirty image file =="
+
+IMGOPTS="compat=1.1,lazy_refcounts=on"
+_make_test_img $size
+
+old_ulimit=$(ulimit -c)
+ulimit -c 0 # do not produce a core dump on abort(3)
+$QEMU_IO -c "write -P 0x5a 0 512" -c "abort" $TEST_IMG | _filter_qemu_io
+ulimit -c "$old_ulimit"
+
+# The dirty bit must be set
+./qcow2.py $TEST_IMG dump-header | grep incompatible_features
+_check_test_img
+
+echo
+echo "== Read-only access must still work =="
+
+$QEMU_IO -r -c "read -P 0x5a 0 512" $TEST_IMG | _filter_qemu_io
+
+# The dirty bit must be set
+./qcow2.py $TEST_IMG dump-header | grep incompatible_features
+
+echo
+echo "== Repairing the image file must succeed =="
+
+$QEMU_IMG check -r all $TEST_IMG
+
+# The dirty bit must not be set
+./qcow2.py $TEST_IMG dump-header | grep incompatible_features
+
+echo
+echo "== Data should still be accessible after repair =="
+
+$QEMU_IO -c "read -P 0x5a 0 512" $TEST_IMG | _filter_qemu_io
+
+echo
+echo "== Opening a dirty image read/write should repair it =="
+
+IMGOPTS="compat=1.1,lazy_refcounts=on"
+_make_test_img $size
+
+old_ulimit=$(ulimit -c)
+ulimit -c 0 # do not produce a core dump on abort(3)
+$QEMU_IO -c "write -P 0x5a 0 512" -c "abort" $TEST_IMG | _filter_qemu_io
+ulimit -c "$old_ulimit"
+
+# The dirty bit must be set
+./qcow2.py $TEST_IMG dump-header | grep incompatible_features
+
+$QEMU_IO -c "write 0 512" $TEST_IMG | _filter_qemu_io
+
+# The dirty bit must not be set
+./qcow2.py $TEST_IMG dump-header | grep incompatible_features
+
+echo
+echo "== Creating an image file with lazy_refcounts=off =="
+
+IMGOPTS="compat=1.1,lazy_refcounts=off"
+_make_test_img $size
+
+old_ulimit=$(ulimit -c)
+ulimit -c 0 # do not produce a core dump on abort(3)
+$QEMU_IO -c "write -P 0x5a 0 512" -c "abort" $TEST_IMG | _filter_qemu_io
+ulimit -c "$old_ulimit"
+
+# The dirty bit must not be set since lazy_refcounts=off
+./qcow2.py $TEST_IMG dump-header | grep incompatible_features
+_check_test_img
+
+# success, all done
+echo "*** done"
+rm -f $seq.full
+status=0
+
diff --git a/tests/qemu-iotests/039.out b/tests/qemu-iotests/039.out
new file mode 100644
index 0000000..155a05e
--- /dev/null
+++ b/tests/qemu-iotests/039.out
@@ -0,0 +1,53 @@
+QA output created by 039
+
+== Checking that image is clean on shutdown ==
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728
+wrote 512/512 bytes at offset 0
+512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+incompatible_features 0x0
+No errors were found on the image.
+
+== Creating a dirty image file ==
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728
+wrote 512/512 bytes at offset 0
+512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+incompatible_features 0x1
+ERROR OFLAG_COPIED: offset=8000000000050000 refcount=0
+ERROR cluster 5 refcount=0 reference=1
+
+2 errors were found on the image.
+Data may be corrupted, or further writes to the image may corrupt it.
+
+== Read-only access must still work ==
+read 512/512 bytes at offset 0
+512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+incompatible_features 0x1
+
+== Repairing the image file must succeed ==
+ERROR OFLAG_COPIED: offset=8000000000050000 refcount=0
+Repairing cluster 5 refcount=0 reference=1
+No errors were found on the image.
+incompatible_features 0x0
+
+== Data should still be accessible after repair ==
+read 512/512 bytes at offset 0
+512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+
+== Opening a dirty image read/write should repair it ==
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728
+wrote 512/512 bytes at offset 0
+512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+incompatible_features 0x1
+ERROR OFLAG_COPIED: offset=8000000000050000 refcount=0
+Repairing cluster 5 refcount=0 reference=1
+wrote 512/512 bytes at offset 0
+512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+incompatible_features 0x0
+
+== Creating an image file with lazy_refcounts=off ==
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728
+wrote 512/512 bytes at offset 0
+512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+incompatible_features 0x0
+No errors were found on the image.
+*** done
diff --git a/tests/qemu-iotests/common.rc b/tests/qemu-iotests/common.rc
index 5e3a524..7782808 100644
--- a/tests/qemu-iotests/common.rc
+++ b/tests/qemu-iotests/common.rc
@@ -110,10 +110,11 @@ _make_test_img()
sed -e "s#$IMGFMT#IMGFMT#g" | \
sed -e "s# encryption=off##g" | \
sed -e "s# cluster_size=[0-9]\\+##g" | \
- sed -e "s# table_size=0##g" | \
+ sed -e "s# table_size=[0-9]\\+##g" | \
sed -e "s# compat='[^']*'##g" | \
- sed -e "s# compat6=off##g" | \
- sed -e "s# static=off##g"
+ sed -e "s# compat6=\\(on\\|off\\)##g" | \
+ sed -e "s# static=\\(on\\|off\\)##g" | \
+ sed -e "s# lazy_refcounts=\\(on\\|off\\)##g"
}
_cleanup_test_img()
diff --git a/tests/qemu-iotests/group b/tests/qemu-iotests/group
index 7a2c92b..ebb5ca4 100644
--- a/tests/qemu-iotests/group
+++ b/tests/qemu-iotests/group
@@ -45,3 +45,4 @@
036 rw auto quick
037 rw auto backing
038 rw auto backing
+039 rw auto
diff --git a/tests/qemu-iotests/qed.py b/tests/qemu-iotests/qed.py
new file mode 100755
index 0000000..52ff845
--- /dev/null
+++ b/tests/qemu-iotests/qed.py
@@ -0,0 +1,235 @@
+#!/usr/bin/env python
+#
+# Tool to manipulate QED image files
+#
+# Copyright (C) 2010 IBM, Corp.
+#
+# Authors:
+# Stefan Hajnoczi <stefanha@linux.vnet.ibm.com>
+#
+# This work is licensed under the terms of the GNU GPL, version 2 or later.
+# See the COPYING file in the top-level directory.
+
+import sys
+import struct
+import random
+import optparse
+
+# This can be used as a module
+__all__ = ['QED_F_NEED_CHECK', 'QED']
+
+QED_F_NEED_CHECK = 0x02
+
+header_fmt = '<IIIIQQQQQII'
+header_size = struct.calcsize(header_fmt)
+field_names = ['magic', 'cluster_size', 'table_size',
+ 'header_size', 'features', 'compat_features',
+ 'autoclear_features', 'l1_table_offset', 'image_size',
+ 'backing_filename_offset', 'backing_filename_size']
+table_elem_fmt = '<Q'
+table_elem_size = struct.calcsize(table_elem_fmt)
+
+def err(msg):
+ sys.stderr.write(msg + '\n')
+ sys.exit(1)
+
+def unpack_header(s):
+ fields = struct.unpack(header_fmt, s)
+ return dict((field_names[idx], val) for idx, val in enumerate(fields))
+
+def pack_header(header):
+ fields = tuple(header[x] for x in field_names)
+ return struct.pack(header_fmt, *fields)
+
+def unpack_table_elem(s):
+ return struct.unpack(table_elem_fmt, s)[0]
+
+def pack_table_elem(elem):
+ return struct.pack(table_elem_fmt, elem)
+
+class QED(object):
+ def __init__(self, f):
+ self.f = f
+
+ self.f.seek(0, 2)
+ self.filesize = f.tell()
+
+ self.load_header()
+ self.load_l1_table()
+
+ def raw_pread(self, offset, size):
+ self.f.seek(offset)
+ return self.f.read(size)
+
+ def raw_pwrite(self, offset, data):
+ self.f.seek(offset)
+ return self.f.write(data)
+
+ def load_header(self):
+ self.header = unpack_header(self.raw_pread(0, header_size))
+
+ def store_header(self):
+ self.raw_pwrite(0, pack_header(self.header))
+
+ def read_table(self, offset):
+ size = self.header['table_size'] * self.header['cluster_size']
+ s = self.raw_pread(offset, size)
+ table = [unpack_table_elem(s[i:i + table_elem_size]) for i in xrange(0, size, table_elem_size)]
+ return table
+
+ def load_l1_table(self):
+ self.l1_table = self.read_table(self.header['l1_table_offset'])
+ self.table_nelems = self.header['table_size'] * self.header['cluster_size'] / table_elem_size
+
+ def write_table(self, offset, table):
+ s = ''.join(pack_table_elem(x) for x in table)
+ self.raw_pwrite(offset, s)
+
+def random_table_item(table):
+ vals = [(index, offset) for index, offset in enumerate(table) if offset != 0]
+ if not vals:
+ err('cannot pick random item because table is empty')
+ return random.choice(vals)
+
+def corrupt_table_duplicate(table):
+ '''Corrupt a table by introducing a duplicate offset'''
+ victim_idx, victim_val = random_table_item(table)
+ unique_vals = set(table)
+ if len(unique_vals) == 1:
+ err('no duplication corruption possible in table')
+ dup_val = random.choice(list(unique_vals.difference([victim_val])))
+ table[victim_idx] = dup_val
+
+def corrupt_table_invalidate(qed, table):
+ '''Corrupt a table by introducing an invalid offset'''
+ index, _ = random_table_item(table)
+ table[index] = qed.filesize + random.randint(0, 100 * 1024 * 1024 * 1024 * 1024)
+
+def cmd_show(qed, *args):
+ '''show [header|l1|l2 <offset>]- Show header or l1/l2 tables'''
+ if not args or args[0] == 'header':
+ print qed.header
+ elif args[0] == 'l1':
+ print qed.l1_table
+ elif len(args) == 2 and args[0] == 'l2':
+ offset = int(args[1])
+ print qed.read_table(offset)
+ else:
+ err('unrecognized sub-command')
+
+def cmd_duplicate(qed, table_level):
+ '''duplicate l1|l2 - Duplicate a random table element'''
+ if table_level == 'l1':
+ offset = qed.header['l1_table_offset']
+ table = qed.l1_table
+ elif table_level == 'l2':
+ _, offset = random_table_item(qed.l1_table)
+ table = qed.read_table(offset)
+ else:
+ err('unrecognized sub-command')
+ corrupt_table_duplicate(table)
+ qed.write_table(offset, table)
+
+def cmd_invalidate(qed, table_level):
+ '''invalidate l1|l2 - Plant an invalid table element at random'''
+ if table_level == 'l1':
+ offset = qed.header['l1_table_offset']
+ table = qed.l1_table
+ elif table_level == 'l2':
+ _, offset = random_table_item(qed.l1_table)
+ table = qed.read_table(offset)
+ else:
+ err('unrecognized sub-command')
+ corrupt_table_invalidate(qed, table)
+ qed.write_table(offset, table)
+
+def cmd_need_check(qed, *args):
+ '''need-check [on|off] - Test, set, or clear the QED_F_NEED_CHECK header bit'''
+ if not args:
+ print bool(qed.header['features'] & QED_F_NEED_CHECK)
+ return
+
+ if args[0] == 'on':
+ qed.header['features'] |= QED_F_NEED_CHECK
+ elif args[0] == 'off':
+ qed.header['features'] &= ~QED_F_NEED_CHECK
+ else:
+ err('unrecognized sub-command')
+ qed.store_header()
+
+def cmd_zero_cluster(qed, pos, *args):
+ '''zero-cluster <pos> [<n>] - Zero data clusters'''
+ pos, n = int(pos), 1
+ if args:
+ if len(args) != 1:
+ err('expected one argument')
+ n = int(args[0])
+
+ for i in xrange(n):
+ l1_index = pos / qed.header['cluster_size'] / len(qed.l1_table)
+ if qed.l1_table[l1_index] == 0:
+ err('no l2 table allocated')
+
+ l2_offset = qed.l1_table[l1_index]
+ l2_table = qed.read_table(l2_offset)
+
+ l2_index = (pos / qed.header['cluster_size']) % len(qed.l1_table)
+ l2_table[l2_index] = 1 # zero the data cluster
+ qed.write_table(l2_offset, l2_table)
+ pos += qed.header['cluster_size']
+
+def cmd_copy_metadata(qed, outfile):
+ '''copy-metadata <outfile> - Copy metadata only (for scrubbing corrupted images)'''
+ out = open(outfile, 'wb')
+
+ # Match file size
+ out.seek(qed.filesize - 1)
+ out.write('\0')
+
+ # Copy header clusters
+ out.seek(0)
+ header_size_bytes = qed.header['header_size'] * qed.header['cluster_size']
+ out.write(qed.raw_pread(0, header_size_bytes))
+
+ # Copy L1 table
+ out.seek(qed.header['l1_table_offset'])
+ s = ''.join(pack_table_elem(x) for x in qed.l1_table)
+ out.write(s)
+
+ # Copy L2 tables
+ for l2_offset in qed.l1_table:
+ if l2_offset == 0:
+ continue
+ l2_table = qed.read_table(l2_offset)
+ out.seek(l2_offset)
+ s = ''.join(pack_table_elem(x) for x in l2_table)
+ out.write(s)
+
+ out.close()
+
+def usage():
+ print 'Usage: %s <file> <cmd> [<arg>, ...]' % sys.argv[0]
+ print
+ print 'Supported commands:'
+ for cmd in sorted(x for x in globals() if x.startswith('cmd_')):
+ print globals()[cmd].__doc__
+ sys.exit(1)
+
+def main():
+ if len(sys.argv) < 3:
+ usage()
+ filename, cmd = sys.argv[1:3]
+
+ cmd = 'cmd_' + cmd.replace('-', '_')
+ if cmd not in globals():
+ usage()
+
+ qed = QED(open(filename, 'r+b'))
+ try:
+ globals()[cmd](qed, *sys.argv[3:])
+ except TypeError, e:
+ sys.stderr.write(globals()[cmd].__doc__ + '\n')
+ sys.exit(1)
+
+if __name__ == '__main__':
+ main()