Browse Source
We have good coverage of the normal I/O paths now, but what remains is a test that tests some more special cases: Exporting an image on itself (thus turning a formatted image into a raw one), some error cases, and non-writable and non-growable exports. Signed-off-by: Max Reitz <mreitz@redhat.com> Reviewed-by: Kevin Wolf <kwolf@redhat.com> Message-Id: <20201027190600.192171-21-mreitz@redhat.com> Signed-off-by: Kevin Wolf <kwolf@redhat.com>pull/104/head
committed by
Kevin Wolf
3 changed files with 437 additions and 0 deletions
@ -0,0 +1,339 @@ |
|||
#!/usr/bin/env bash |
|||
# |
|||
# Test FUSE exports (in ways that are not captured by the generic |
|||
# tests) |
|||
# |
|||
# Copyright (C) 2020 Red Hat, Inc. |
|||
# |
|||
# 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/>. |
|||
# |
|||
|
|||
seq=$(basename "$0") |
|||
echo "QA output created by $seq" |
|||
|
|||
status=1 # failure is the default! |
|||
|
|||
_cleanup() |
|||
{ |
|||
_cleanup_qemu |
|||
_cleanup_test_img |
|||
rmdir "$EXT_MP" 2>/dev/null |
|||
rm -f "$EXT_MP" |
|||
rm -f "$COPIED_IMG" |
|||
} |
|||
trap "_cleanup; exit \$status" 0 1 2 3 15 |
|||
|
|||
# get standard environment, filters and checks |
|||
. ./common.rc |
|||
. ./common.filter |
|||
. ./common.qemu |
|||
|
|||
# Generic format, but needs a plain filename |
|||
_supported_fmt generic |
|||
if [ "$IMGOPTSSYNTAX" = "true" ]; then |
|||
_unsupported_fmt $IMGFMT |
|||
fi |
|||
# We need the image to have exactly the specified size, and VPC does |
|||
# not allow that by default |
|||
_unsupported_fmt vpc |
|||
|
|||
_supported_proto file # We create the FUSE export manually |
|||
_supported_os Linux # We need /dev/urandom |
|||
|
|||
# $1: Export ID |
|||
# $2: Options (beyond the node-name and ID) |
|||
# $3: Expected return value (defaults to 'return') |
|||
# $4: Node to export (defaults to 'node-format') |
|||
fuse_export_add() |
|||
{ |
|||
_send_qemu_cmd $QEMU_HANDLE \ |
|||
"{'execute': 'block-export-add', |
|||
'arguments': { |
|||
'type': 'fuse', |
|||
'id': '$1', |
|||
'node-name': '${4:-node-format}', |
|||
$2 |
|||
} }" \ |
|||
"${3:-return}" \ |
|||
| _filter_imgfmt |
|||
} |
|||
|
|||
# $1: Export ID |
|||
fuse_export_del() |
|||
{ |
|||
_send_qemu_cmd $QEMU_HANDLE \ |
|||
"{'execute': 'block-export-del', |
|||
'arguments': { |
|||
'id': '$1' |
|||
} }" \ |
|||
'return' |
|||
|
|||
_send_qemu_cmd $QEMU_HANDLE \ |
|||
'' \ |
|||
'BLOCK_EXPORT_DELETED' |
|||
} |
|||
|
|||
# Return the length of the protocol file |
|||
# $1: Protocol node export mount point |
|||
# $2: Original file (to compare) |
|||
get_proto_len() |
|||
{ |
|||
len1=$(stat -c '%s' "$1") |
|||
len2=$(stat -c '%s' "$2") |
|||
|
|||
if [ "$len1" != "$len2" ]; then |
|||
echo 'ERROR: Length of export and original differ:' >&2 |
|||
echo "$len1 != $len2" >&2 |
|||
else |
|||
echo '(OK: Lengths of export and original are the same)' >&2 |
|||
fi |
|||
|
|||
echo "$len1" |
|||
} |
|||
|
|||
COPIED_IMG="$TEST_IMG.copy" |
|||
EXT_MP="$TEST_IMG.fuse" |
|||
|
|||
echo '=== Set up ===' |
|||
|
|||
# Create image with random data |
|||
_make_test_img 64M |
|||
$QEMU_IO -c 'write -s /dev/urandom 0 64M' "$TEST_IMG" | _filter_qemu_io |
|||
|
|||
_launch_qemu |
|||
_send_qemu_cmd $QEMU_HANDLE \ |
|||
"{'execute': 'qmp_capabilities'}" \ |
|||
'return' |
|||
|
|||
# Separate blockdev-add calls for format and protocol so we can remove |
|||
# the format layer later on |
|||
_send_qemu_cmd $QEMU_HANDLE \ |
|||
"{'execute': 'blockdev-add', |
|||
'arguments': { |
|||
'driver': 'file', |
|||
'node-name': 'node-protocol', |
|||
'filename': '$TEST_IMG' |
|||
} }" \ |
|||
'return' |
|||
|
|||
_send_qemu_cmd $QEMU_HANDLE \ |
|||
"{'execute': 'blockdev-add', |
|||
'arguments': { |
|||
'driver': '$IMGFMT', |
|||
'node-name': 'node-format', |
|||
'file': 'node-protocol' |
|||
} }" \ |
|||
'return' |
|||
|
|||
echo |
|||
echo '=== Mountpoint not present ===' |
|||
|
|||
rmdir "$EXT_MP" 2>/dev/null |
|||
rm -f "$EXT_MP" |
|||
output=$(fuse_export_add 'export-err' "'mountpoint': '$EXT_MP'" error) |
|||
|
|||
if echo "$output" | grep -q "Invalid parameter 'fuse'"; then |
|||
_notrun 'No FUSE support' |
|||
fi |
|||
|
|||
echo "$output" |
|||
|
|||
echo |
|||
echo '=== Mountpoint is a directory ===' |
|||
|
|||
mkdir "$EXT_MP" |
|||
fuse_export_add 'export-err' "'mountpoint': '$EXT_MP'" error |
|||
rmdir "$EXT_MP" |
|||
|
|||
echo |
|||
echo '=== Mountpoint is a regular file ===' |
|||
|
|||
touch "$EXT_MP" |
|||
fuse_export_add 'export-mp' "'mountpoint': '$EXT_MP'" |
|||
|
|||
# Check that the export presents the same data as the original image |
|||
$QEMU_IMG compare -f raw -F $IMGFMT -U "$EXT_MP" "$TEST_IMG" |
|||
|
|||
echo |
|||
echo '=== Mount over existing file ===' |
|||
|
|||
# This is the coolest feature of FUSE exports: You can transparently |
|||
# make images in any format appear as raw images |
|||
fuse_export_add 'export-img' "'mountpoint': '$TEST_IMG'" |
|||
|
|||
# Accesses both exports at the same time, so we get a concurrency test |
|||
$QEMU_IMG compare -f raw -F raw -U "$EXT_MP" "$TEST_IMG" |
|||
|
|||
# Just to be sure, we later want to compare the data offline. Also, |
|||
# this allows us to see that cp works without complaining. |
|||
# (This is not a given, because cp will expect a short read at EOF. |
|||
# Internally, qemu does not allow short reads, so we have to check |
|||
# whether the FUSE export driver lets them work.) |
|||
cp "$TEST_IMG" "$COPIED_IMG" |
|||
|
|||
# $TEST_IMG will be in mode 0400 because it is read-only; we are going |
|||
# to write to the copy, so make it writable |
|||
chmod 0600 "$COPIED_IMG" |
|||
|
|||
echo |
|||
echo '=== Double export ===' |
|||
|
|||
# We have already seen that exporting a node twice works fine, but you |
|||
# cannot export anything twice on the same mount point. The reason is |
|||
# that qemu has to stat the given mount point, and this would have to |
|||
# be answered by the same qemu instance if it already has an export |
|||
# there. However, it cannot answer the stat because it is itself |
|||
# caught up in that same stat. |
|||
fuse_export_add 'export-err' "'mountpoint': '$EXT_MP'" error |
|||
|
|||
echo |
|||
echo '=== Remove export ===' |
|||
|
|||
# Double-check that $EXT_MP appears as a non-empty file (the raw image) |
|||
$QEMU_IMG info -f raw "$EXT_MP" | grep 'virtual size' |
|||
|
|||
fuse_export_del 'export-mp' |
|||
|
|||
# See that the file appears empty again |
|||
$QEMU_IMG info -f raw "$EXT_MP" | grep 'virtual size' |
|||
|
|||
echo |
|||
echo '=== Writable export ===' |
|||
|
|||
fuse_export_add 'export-mp' "'mountpoint': '$EXT_MP', 'writable': true" |
|||
|
|||
# Check that writing to the read-only export fails |
|||
$QEMU_IO -f raw -c 'write -P 42 1M 64k' "$TEST_IMG" | _filter_qemu_io |
|||
|
|||
# But here it should work |
|||
$QEMU_IO -f raw -c 'write -P 42 1M 64k' "$EXT_MP" | _filter_qemu_io |
|||
|
|||
# (Adjust the copy, too) |
|||
$QEMU_IO -f raw -c 'write -P 42 1M 64k' "$COPIED_IMG" | _filter_qemu_io |
|||
|
|||
echo |
|||
echo '=== Resizing exports ===' |
|||
|
|||
# Here, we need to export the protocol node -- the format layer may |
|||
# not be growable, simply because the format does not support it. |
|||
|
|||
# Remove all exports and the format node first so permissions will not |
|||
# get in the way |
|||
fuse_export_del 'export-mp' |
|||
fuse_export_del 'export-img' |
|||
|
|||
_send_qemu_cmd $QEMU_HANDLE \ |
|||
"{'execute': 'blockdev-del', |
|||
'arguments': { |
|||
'node-name': 'node-format' |
|||
} }" \ |
|||
'return' |
|||
|
|||
# Now export the protocol node |
|||
fuse_export_add \ |
|||
'export-mp' \ |
|||
"'mountpoint': '$EXT_MP', 'writable': true" \ |
|||
'return' \ |
|||
'node-protocol' |
|||
|
|||
echo |
|||
echo '--- Try growing non-growable export ---' |
|||
|
|||
# Get the current size so we can write beyond the EOF |
|||
orig_len=$(get_proto_len "$EXT_MP" "$TEST_IMG") |
|||
orig_disk_usage=$(stat -c '%b' "$TEST_IMG") |
|||
|
|||
# Should fail (exports are non-growable by default) |
|||
# (Note that qemu-io can never write beyond the EOF, so we have to use |
|||
# dd here) |
|||
dd if=/dev/zero of="$EXT_MP" bs=1 count=64k seek=$orig_len 2>&1 \ |
|||
| _filter_testdir | _filter_imgfmt |
|||
|
|||
echo |
|||
echo '--- Resize export ---' |
|||
|
|||
# But we can truncate it explicitly; even with fallocate |
|||
fallocate -o "$orig_len" -l 64k "$EXT_MP" |
|||
|
|||
new_len=$(get_proto_len "$EXT_MP" "$TEST_IMG") |
|||
if [ "$new_len" != "$((orig_len + 65536))" ]; then |
|||
echo 'ERROR: Unexpected post-truncate image size:' |
|||
echo "$new_len != $((orig_len + 65536))" |
|||
else |
|||
echo 'OK: Post-truncate image size is as expected' |
|||
fi |
|||
|
|||
new_disk_usage=$(stat -c '%b' "$TEST_IMG") |
|||
if [ "$new_disk_usage" -gt "$orig_disk_usage" ]; then |
|||
echo 'OK: Disk usage grew with fallocate' |
|||
else |
|||
echo 'ERROR: Disk usage did not grow despite fallocate:' |
|||
echo "$orig_disk_usage => $new_disk_usage" |
|||
fi |
|||
|
|||
echo |
|||
echo '--- Try growing growable export ---' |
|||
|
|||
# Now export as growable |
|||
fuse_export_del 'export-mp' |
|||
fuse_export_add \ |
|||
'export-mp' \ |
|||
"'mountpoint': '$EXT_MP', 'writable': true, 'growable': true" \ |
|||
'return' \ |
|||
'node-protocol' |
|||
|
|||
# Now we should be able to write beyond the EOF |
|||
dd if=/dev/zero of="$EXT_MP" bs=1 count=64k seek=$new_len 2>&1 \ |
|||
| _filter_testdir | _filter_imgfmt |
|||
|
|||
new_len=$(get_proto_len "$EXT_MP" "$TEST_IMG") |
|||
if [ "$new_len" != "$((orig_len + 131072))" ]; then |
|||
echo 'ERROR: Unexpected post-grow image size:' |
|||
echo "$new_len != $((orig_len + 131072))" |
|||
else |
|||
echo 'OK: Post-grow image size is as expected' |
|||
fi |
|||
|
|||
echo |
|||
echo '--- Shrink export ---' |
|||
|
|||
# Now go back to the original size |
|||
truncate -s "$orig_len" "$EXT_MP" |
|||
|
|||
new_len=$(get_proto_len "$EXT_MP" "$TEST_IMG") |
|||
if [ "$new_len" != "$orig_len" ]; then |
|||
echo 'ERROR: Unexpected post-truncate image size:' |
|||
echo "$new_len != $orig_len" |
|||
else |
|||
echo 'OK: Post-truncate image size is as expected' |
|||
fi |
|||
|
|||
echo |
|||
echo '=== Tear down ===' |
|||
|
|||
_send_qemu_cmd $QEMU_HANDLE \ |
|||
"{'execute': 'quit'}" \ |
|||
'return' |
|||
|
|||
wait=yes _cleanup_qemu |
|||
|
|||
echo |
|||
echo '=== Compare copy with original ===' |
|||
|
|||
$QEMU_IMG compare -f raw -F $IMGFMT "$COPIED_IMG" "$TEST_IMG" |
|||
|
|||
# success, all done |
|||
echo "*** done" |
|||
rm -f $seq.full |
|||
status=0 |
|||
@ -0,0 +1,97 @@ |
|||
QA output created by 308 |
|||
=== Set up === |
|||
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 |
|||
wrote 67108864/67108864 bytes at offset 0 |
|||
64 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) |
|||
{'execute': 'qmp_capabilities'} |
|||
{"return": {}} |
|||
{'execute': 'blockdev-add', 'arguments': { 'driver': 'file', 'node-name': 'node-protocol', 'filename': 'TEST_DIR/t.IMGFMT' } } |
|||
{"return": {}} |
|||
{'execute': 'blockdev-add', 'arguments': { 'driver': 'IMGFMT', 'node-name': 'node-format', 'file': 'node-protocol' } } |
|||
{"return": {}} |
|||
|
|||
=== Mountpoint not present === |
|||
{'execute': 'block-export-add', 'arguments': { 'type': 'fuse', 'id': 'export-err', 'node-name': 'node-format', 'mountpoint': 'TEST_DIR/t.IMGFMT.fuse' } } |
|||
{"error": {"class": "GenericError", "desc": "Failed to stat 'TEST_DIR/t.IMGFMT.fuse': No such file or directory"}} |
|||
|
|||
=== Mountpoint is a directory === |
|||
{'execute': 'block-export-add', 'arguments': { 'type': 'fuse', 'id': 'export-err', 'node-name': 'node-format', 'mountpoint': 'TEST_DIR/t.IMGFMT.fuse' } } |
|||
{"error": {"class": "GenericError", "desc": "'TEST_DIR/t.IMGFMT.fuse' is not a regular file"}} |
|||
|
|||
=== Mountpoint is a regular file === |
|||
{'execute': 'block-export-add', 'arguments': { 'type': 'fuse', 'id': 'export-mp', 'node-name': 'node-format', 'mountpoint': 'TEST_DIR/t.IMGFMT.fuse' } } |
|||
{"return": {}} |
|||
Images are identical. |
|||
|
|||
=== Mount over existing file === |
|||
{'execute': 'block-export-add', 'arguments': { 'type': 'fuse', 'id': 'export-img', 'node-name': 'node-format', 'mountpoint': 'TEST_DIR/t.IMGFMT' } } |
|||
{"return": {}} |
|||
Images are identical. |
|||
|
|||
=== Double export === |
|||
{'execute': 'block-export-add', 'arguments': { 'type': 'fuse', 'id': 'export-err', 'node-name': 'node-format', 'mountpoint': 'TEST_DIR/t.IMGFMT.fuse' } } |
|||
{"error": {"class": "GenericError", "desc": "There already is a FUSE export on 'TEST_DIR/t.IMGFMT.fuse'"}} |
|||
|
|||
=== Remove export === |
|||
virtual size: 64 MiB (67108864 bytes) |
|||
{'execute': 'block-export-del', 'arguments': { 'id': 'export-mp' } } |
|||
{"return": {}} |
|||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_EXPORT_DELETED", "data": {"id": "export-mp"}} |
|||
virtual size: 0 B (0 bytes) |
|||
|
|||
=== Writable export === |
|||
{'execute': 'block-export-add', 'arguments': { 'type': 'fuse', 'id': 'export-mp', 'node-name': 'node-format', 'mountpoint': 'TEST_DIR/t.IMGFMT.fuse', 'writable': true } } |
|||
{"return": {}} |
|||
write failed: Permission denied |
|||
wrote 65536/65536 bytes at offset 1048576 |
|||
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) |
|||
wrote 65536/65536 bytes at offset 1048576 |
|||
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) |
|||
|
|||
=== Resizing exports === |
|||
{'execute': 'block-export-del', 'arguments': { 'id': 'export-mp' } } |
|||
{"return": {}} |
|||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_EXPORT_DELETED", "data": {"id": "export-mp"}} |
|||
{'execute': 'block-export-del', 'arguments': { 'id': 'export-img' } } |
|||
{"return": {}} |
|||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_EXPORT_DELETED", "data": {"id": "export-img"}} |
|||
{'execute': 'blockdev-del', 'arguments': { 'node-name': 'node-format' } } |
|||
{"return": {}} |
|||
{'execute': 'block-export-add', 'arguments': { 'type': 'fuse', 'id': 'export-mp', 'node-name': 'node-protocol', 'mountpoint': 'TEST_DIR/t.IMGFMT.fuse', 'writable': true } } |
|||
{"return": {}} |
|||
|
|||
--- Try growing non-growable export --- |
|||
(OK: Lengths of export and original are the same) |
|||
dd: error writing 'TEST_DIR/t.IMGFMT.fuse': Input/output error |
|||
1+0 records in |
|||
0+0 records out |
|||
|
|||
--- Resize export --- |
|||
(OK: Lengths of export and original are the same) |
|||
OK: Post-truncate image size is as expected |
|||
OK: Disk usage grew with fallocate |
|||
|
|||
--- Try growing growable export --- |
|||
{'execute': 'block-export-del', 'arguments': { 'id': 'export-mp' } } |
|||
{"return": {}} |
|||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_EXPORT_DELETED", "data": {"id": "export-mp"}} |
|||
{'execute': 'block-export-add', 'arguments': { 'type': 'fuse', 'id': 'export-mp', 'node-name': 'node-protocol', 'mountpoint': 'TEST_DIR/t.IMGFMT.fuse', 'writable': true, 'growable': true } } |
|||
{"return": {}} |
|||
65536+0 records in |
|||
65536+0 records out |
|||
(OK: Lengths of export and original are the same) |
|||
OK: Post-grow image size is as expected |
|||
|
|||
--- Shrink export --- |
|||
(OK: Lengths of export and original are the same) |
|||
OK: Post-truncate image size is as expected |
|||
|
|||
=== Tear down === |
|||
{'execute': 'quit'} |
|||
{"return": {}} |
|||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false, "reason": "host-qmp-quit"}} |
|||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_EXPORT_DELETED", "data": {"id": "export-mp"}} |
|||
|
|||
=== Compare copy with original === |
|||
Images are identical. |
|||
*** done |
|||
Loading…
Reference in new issue