|  | #!/usr/bin/env bash | 
|  | # group: rw | 
|  | # | 
|  | # Test preallocated growth of qcow2 images | 
|  | # | 
|  | # Copyright (C) 2017 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/>. | 
|  | # | 
|  |  | 
|  | # creator | 
|  | owner=hreitz@redhat.com | 
|  |  | 
|  | seq=$(basename $0) | 
|  | echo "QA output created by $seq" | 
|  |  | 
|  | status=1	# failure is the default! | 
|  |  | 
|  | _cleanup() | 
|  | { | 
|  | _cleanup_test_img | 
|  | } | 
|  | trap "_cleanup; exit \$status" 0 1 2 3 15 | 
|  |  | 
|  | get_image_size_on_host() | 
|  | { | 
|  | echo $(($(stat -c '%b * %B' "$TEST_IMG_FILE"))) | 
|  | } | 
|  |  | 
|  | # get standard environment and filters | 
|  | . ./common.rc | 
|  | . ./common.filter | 
|  |  | 
|  | _supported_fmt qcow2 | 
|  | _supported_proto file | 
|  | # Growing a file with a backing file (without preallocation=full or | 
|  | # =falloc) requires zeroing the newly added area, which is impossible | 
|  | # to do quickly for v2 images, and hence is unsupported. | 
|  | _unsupported_imgopts 'compat=0.10' | 
|  |  | 
|  | if [ -z "$TEST_IMG_FILE" ]; then | 
|  | TEST_IMG_FILE=$TEST_IMG | 
|  | fi | 
|  |  | 
|  | # Test whether we are running on a broken XFS version.  There is this | 
|  | # bug: | 
|  |  | 
|  | # $ rm -f foo | 
|  | # $ touch foo | 
|  | # $ block_size=4096 # Your FS's block size | 
|  | # $ fallocate -o $((block_size / 2)) -l $block_size foo | 
|  | # $ LANG=C xfs_bmap foo | grep hole | 
|  | #         1: [8..15]: hole | 
|  | # | 
|  | # The problem is that the XFS driver rounds down the offset and | 
|  | # rounds up the length to the block size, but independently.  As | 
|  | # such, it only allocates the first block in the example above, | 
|  | # even though it should allocate the first two blocks (because our | 
|  | # request is to fallocate something that touches both the first | 
|  | # two blocks). | 
|  | # | 
|  | # This means that when you then write to the beginning of the | 
|  | # second block, the disk usage of the first two blocks grows. | 
|  | # | 
|  | # That is precisely what fallocate() promises, though: That when you | 
|  | # write to an area that you have fallocated, no new blocks will have | 
|  | # to be allocated. | 
|  |  | 
|  | touch "$TEST_IMG_FILE" | 
|  | # Assuming there is no FS with a block size greater than 64k | 
|  | fallocate -o 65535 -l 2 "$TEST_IMG_FILE" | 
|  | len0=$(get_image_size_on_host) | 
|  |  | 
|  | # Write to something that in theory we have just fallocated | 
|  | # (Thus, the on-disk size should not increase) | 
|  | poke_file "$TEST_IMG_FILE" 65536 42 | 
|  | len1=$(get_image_size_on_host) | 
|  |  | 
|  | if [ $len1 -gt $len0 ]; then | 
|  | _notrun "the test filesystem's fallocate() is broken" | 
|  | fi | 
|  |  | 
|  | rm -f "$TEST_IMG_FILE" | 
|  |  | 
|  | # Generally, we create some image with or without existing preallocation and | 
|  | # then resize it. Then we write some data into the image and verify that its | 
|  | # size does not change if we have used preallocation. | 
|  |  | 
|  | # With a cluster size of 512 B, one L2 table covers 64 * 512 B = 32 kB. | 
|  | # One cluster of the L1 table covers 64 * 32 kB = 2 MB. | 
|  | # There are multiple cases we want to test: | 
|  | # (1) Grow an image without having to allocate a new L2 table. | 
|  | # (2) Grow an image, having to allocate a new L2 table. | 
|  | # (3) Grow an image, having to grow the L1 table. | 
|  | # Therefore, we create an image that is 48 kB below 2 MB. Then: | 
|  | # (1) We resize it to 2 MB - 32 kB. (+ 16 kB) | 
|  | # (2) We resize it to 2 MB.         (+ 48 kB) | 
|  | # (3) We resize it to 2 MB + 32 kB. (+ 80 kB) | 
|  |  | 
|  | # in B | 
|  | CREATION_SIZE=$((2 * 1024 * 1024 - 48 * 1024)) | 
|  |  | 
|  | # 512 is the actual test -- but it's good to test 64k as well, just to be sure. | 
|  | for cluster_size in 512 64k; do | 
|  | # in kB | 
|  | for GROWTH_SIZE in 16 48 80; do | 
|  | for create_mode in off metadata falloc full; do | 
|  | for growth_mode in off metadata falloc full; do | 
|  | echo "--- cluster_size=$cluster_size growth_size=$GROWTH_SIZE create_mode=$create_mode growth_mode=$growth_mode ---" | 
|  |  | 
|  | _make_test_img -o "preallocation=$create_mode,cluster_size=$cluster_size" ${CREATION_SIZE} | 
|  | $QEMU_IMG resize -f "$IMGFMT" --preallocation=$growth_mode "$TEST_IMG" +${GROWTH_SIZE}K | 
|  |  | 
|  | host_size_0=$(get_image_size_on_host) | 
|  | file_length_0=$(stat -c '%s' "$TEST_IMG_FILE") | 
|  |  | 
|  | $QEMU_IO -c "write 0 $CREATION_SIZE" "$TEST_IMG" | _filter_qemu_io | 
|  |  | 
|  | host_size_1=$(get_image_size_on_host) | 
|  | file_length_1=$(stat -c '%s' "$TEST_IMG_FILE") | 
|  |  | 
|  | $QEMU_IO -c "write $CREATION_SIZE ${GROWTH_SIZE}K" "$TEST_IMG" | _filter_qemu_io | 
|  |  | 
|  | host_size_2=$(get_image_size_on_host) | 
|  | file_length_2=$(stat -c '%s' "$TEST_IMG_FILE") | 
|  |  | 
|  | # Test creation preallocation: Compare #0 against #1 | 
|  | if [ $create_mode != off ]; then | 
|  | # The image length should not have grown | 
|  | if [ $file_length_1 -gt $file_length_0 ]; then | 
|  | echo "ERROR (create): Image length has grown from $file_length_0 to $file_length_1" | 
|  | fi | 
|  | if [ $create_mode != metadata ]; then | 
|  | # The host size should not have grown either | 
|  | if [ $host_size_1 -gt $host_size_0 ]; then | 
|  | echo "ERROR (create): Host size has grown from $host_size_0 to $host_size_1" | 
|  | fi | 
|  | fi | 
|  | fi | 
|  |  | 
|  | # Test resize preallocation: Compare #2 against #1 | 
|  | if [ $growth_mode != off ]; then | 
|  | # The image length should not have grown | 
|  | if [ $file_length_2 -gt $file_length_1 ]; then | 
|  | echo "ERROR (grow): Image length has grown from $file_length_1 to $file_length_2" | 
|  | fi | 
|  | if [ $growth_mode != metadata ]; then | 
|  | # The host size should not have grown either | 
|  | if [ $host_size_2 -gt $host_size_1 ]; then | 
|  | echo "ERROR (grow): Host size has grown from $host_size_1 to $host_size_2" | 
|  | fi | 
|  | fi | 
|  | fi | 
|  |  | 
|  | echo | 
|  | done | 
|  | done | 
|  | done | 
|  | done | 
|  |  | 
|  | # Test image resizing using preallocation and unaligned offsets | 
|  | $QEMU_IMG create -f raw "$TEST_IMG.base" 128k | _filter_img_create | 
|  | $QEMU_IO -c 'write -q -P 1 0 128k' -f raw "$TEST_IMG.base" | 
|  | for orig_size in 31k 33k; do | 
|  | for dst_size in 96k 128k; do | 
|  | for prealloc in metadata full; do | 
|  | echo "--- Resizing image from $orig_size to $dst_size (preallocation=$prealloc) ---" | 
|  | _make_test_img -F raw -b "$TEST_IMG.base" -o cluster_size=64k "$orig_size" | 
|  | $QEMU_IMG resize -f "$IMGFMT" --preallocation="$prealloc" "$TEST_IMG" "$dst_size" | 
|  | # The first part of the image should contain data from the backing file | 
|  | $QEMU_IO -c "read -q -P 1 0 ${orig_size}" "$TEST_IMG" | 
|  | # The resized part of the image should contain zeroes | 
|  | $QEMU_IO -c "read -q -P 0 ${orig_size} 63k" "$TEST_IMG" | 
|  | # If the image does not have an external data file we can also verify its | 
|  | # actual size. The resized image should have 7 clusters: | 
|  | # header, L1 table, L2 table, refcount table, refcount block, 2 data clusters | 
|  | if ! _get_data_file "$TEST_IMG" > /dev/null; then | 
|  | expected_file_length=$((65536 * 7)) | 
|  | file_length=$(stat -c '%s' "$TEST_IMG_FILE") | 
|  | if [ "$file_length" != "$expected_file_length" ]; then | 
|  | echo "ERROR: file length $file_length (expected $expected_file_length)" | 
|  | fi | 
|  | fi | 
|  | echo | 
|  | done | 
|  | done | 
|  | done | 
|  |  | 
|  | # success, all done | 
|  | echo '*** done' | 
|  | rm -f $seq.full | 
|  | status=0 |