| 1 | #!/usr/bin/env bash |
| 2 | # group: rw |
| 3 | # |
| 4 | # Test preallocated growth of qcow2 images |
| 5 | # |
| 6 | # Copyright (C) 2017 Red Hat, Inc. |
| 7 | # |
| 8 | # This program is free software; you can redistribute it and/or modify |
| 9 | # it under the terms of the GNU General Public License as published by |
| 10 | # the Free Software Foundation; either version 2 of the License, or |
| 11 | # (at your option) any later version. |
| 12 | # |
| 13 | # This program is distributed in the hope that it will be useful, |
| 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 16 | # GNU General Public License for more details. |
| 17 | # |
| 18 | # You should have received a copy of the GNU General Public License |
| 19 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 20 | # |
| 21 | |
| 22 | # creator |
| 23 | owner=hreitz@redhat.com |
| 24 | |
| 25 | seq=$(basename $0) |
| 26 | echo "QA output created by $seq" |
| 27 | |
| 28 | status=1 # failure is the default! |
| 29 | |
| 30 | _cleanup() |
| 31 | { |
| 32 | _cleanup_test_img |
| 33 | } |
| 34 | trap "_cleanup; exit \$status" 0 1 2 3 15 |
| 35 | |
| 36 | get_image_size_on_host() |
| 37 | { |
| 38 | disk_usage "$TEST_IMG_FILE" |
| 39 | } |
| 40 | |
| 41 | # get standard environment and filters |
| 42 | . ./common.rc |
| 43 | . ./common.filter |
| 44 | |
| 45 | _supported_fmt qcow2 |
| 46 | _supported_proto file |
| 47 | # Growing a file with a backing file (without preallocation=full or |
| 48 | # =falloc) requires zeroing the newly added area, which is impossible |
| 49 | # to do quickly for v2 images, and hence is unsupported. |
| 50 | _unsupported_imgopts 'compat=0.10' |
| 51 | |
| 52 | if [ -z "$TEST_IMG_FILE" ]; then |
| 53 | TEST_IMG_FILE=$TEST_IMG |
| 54 | fi |
| 55 | |
| 56 | # Test whether we are running on a broken XFS version. There is this |
| 57 | # bug: |
| 58 | |
| 59 | # $ rm -f foo |
| 60 | # $ touch foo |
| 61 | # $ block_size=4096 # Your FS's block size |
| 62 | # $ fallocate -o $((block_size / 2)) -l $block_size foo |
| 63 | # $ LANG=C xfs_bmap foo | grep hole |
| 64 | # 1: [8..15]: hole |
| 65 | # |
| 66 | # The problem is that the XFS driver rounds down the offset and |
| 67 | # rounds up the length to the block size, but independently. As |
| 68 | # such, it only allocates the first block in the example above, |
| 69 | # even though it should allocate the first two blocks (because our |
| 70 | # request is to fallocate something that touches both the first |
| 71 | # two blocks). |
| 72 | # |
| 73 | # This means that when you then write to the beginning of the |
| 74 | # second block, the disk usage of the first two blocks grows. |
| 75 | # |
| 76 | # That is precisely what fallocate() promises, though: That when you |
| 77 | # write to an area that you have fallocated, no new blocks will have |
| 78 | # to be allocated. |
| 79 | |
| 80 | touch "$TEST_IMG_FILE" |
| 81 | # Assuming there is no FS with a block size greater than 64k |
| 82 | fallocate -o 65535 -l 2 "$TEST_IMG_FILE" |
| 83 | len0=$(get_image_size_on_host) |
| 84 | |
| 85 | # Write to something that in theory we have just fallocated |
| 86 | # (Thus, the on-disk size should not increase) |
| 87 | poke_file "$TEST_IMG_FILE" 65536 42 |
| 88 | len1=$(get_image_size_on_host) |
| 89 | |
| 90 | if [ $len1 -gt $len0 ]; then |
| 91 | _notrun "the test filesystem's fallocate() is broken" |
| 92 | fi |
| 93 | |
| 94 | rm -f "$TEST_IMG_FILE" |
| 95 | |
| 96 | # Generally, we create some image with or without existing preallocation and |
| 97 | # then resize it. Then we write some data into the image and verify that its |
| 98 | # size does not change if we have used preallocation. |
| 99 | |
| 100 | # With a cluster size of 512 B, one L2 table covers 64 * 512 B = 32 kB. |
| 101 | # One cluster of the L1 table covers 64 * 32 kB = 2 MB. |
| 102 | # There are multiple cases we want to test: |
| 103 | # (1) Grow an image without having to allocate a new L2 table. |
| 104 | # (2) Grow an image, having to allocate a new L2 table. |
| 105 | # (3) Grow an image, having to grow the L1 table. |
| 106 | # Therefore, we create an image that is 48 kB below 2 MB. Then: |
| 107 | # (1) We resize it to 2 MB - 32 kB. (+ 16 kB) |
| 108 | # (2) We resize it to 2 MB. (+ 48 kB) |
| 109 | # (3) We resize it to 2 MB + 32 kB. (+ 80 kB) |
| 110 | |
| 111 | # in B |
| 112 | CREATION_SIZE=$((2 * 1024 * 1024 - 48 * 1024)) |
| 113 | |
| 114 | # 512 is the actual test -- but it's good to test 64k as well, just to be sure. |
| 115 | for cluster_size in 512 64k; do |
| 116 | # in kB |
| 117 | for GROWTH_SIZE in 16 48 80; do |
| 118 | for create_mode in off metadata falloc full; do |
| 119 | for growth_mode in off metadata falloc full; do |
| 120 | echo "--- cluster_size=$cluster_size growth_size=$GROWTH_SIZE create_mode=$create_mode growth_mode=$growth_mode ---" |
| 121 | |
| 122 | _make_test_img -o "preallocation=$create_mode,cluster_size=$cluster_size" ${CREATION_SIZE} |
| 123 | $QEMU_IMG resize -f "$IMGFMT" --preallocation=$growth_mode "$TEST_IMG" +${GROWTH_SIZE}K |
| 124 | |
| 125 | host_size_0=$(get_image_size_on_host) |
| 126 | file_length_0=$(stat -c '%s' "$TEST_IMG_FILE") |
| 127 | |
| 128 | $QEMU_IO -c "write 0 $CREATION_SIZE" "$TEST_IMG" | _filter_qemu_io |
| 129 | |
| 130 | host_size_1=$(get_image_size_on_host) |
| 131 | file_length_1=$(stat -c '%s' "$TEST_IMG_FILE") |
| 132 | |
| 133 | $QEMU_IO -c "write $CREATION_SIZE ${GROWTH_SIZE}K" "$TEST_IMG" | _filter_qemu_io |
| 134 | |
| 135 | host_size_2=$(get_image_size_on_host) |
| 136 | file_length_2=$(stat -c '%s' "$TEST_IMG_FILE") |
| 137 | |
| 138 | # Test creation preallocation: Compare #0 against #1 |
| 139 | if [ $create_mode != off ]; then |
| 140 | # The image length should not have grown |
| 141 | if [ $file_length_1 -gt $file_length_0 ]; then |
| 142 | echo "ERROR (create): Image length has grown from $file_length_0 to $file_length_1" |
| 143 | fi |
| 144 | if [ $create_mode != metadata ]; then |
| 145 | # The host size should not have grown either |
| 146 | if [ $host_size_1 -gt $host_size_0 ]; then |
| 147 | echo "ERROR (create): Host size has grown from $host_size_0 to $host_size_1" |
| 148 | fi |
| 149 | fi |
| 150 | fi |
| 151 | |
| 152 | # Test resize preallocation: Compare #2 against #1 |
| 153 | if [ $growth_mode != off ]; then |
| 154 | # The image length should not have grown |
| 155 | if [ $file_length_2 -gt $file_length_1 ]; then |
| 156 | echo "ERROR (grow): Image length has grown from $file_length_1 to $file_length_2" |
| 157 | fi |
| 158 | if [ $growth_mode != metadata ]; then |
| 159 | # The host size should not have grown either |
| 160 | if [ $host_size_2 -gt $host_size_1 ]; then |
| 161 | echo "ERROR (grow): Host size has grown from $host_size_1 to $host_size_2" |
| 162 | fi |
| 163 | fi |
| 164 | fi |
| 165 | |
| 166 | echo |
| 167 | done |
| 168 | done |
| 169 | done |
| 170 | done |
| 171 | |
| 172 | # Test image resizing using preallocation and unaligned offsets |
| 173 | $QEMU_IMG create -f raw "$TEST_IMG.base" 128k | _filter_img_create |
| 174 | $QEMU_IO -c 'write -q -P 1 0 128k' -f raw "$TEST_IMG.base" |
| 175 | for orig_size in 31k 33k; do |
| 176 | for dst_size in 96k 128k; do |
| 177 | for prealloc in metadata full; do |
| 178 | echo "--- Resizing image from $orig_size to $dst_size (preallocation=$prealloc) ---" |
| 179 | _make_test_img -F raw -b "$TEST_IMG.base" -o cluster_size=64k "$orig_size" |
| 180 | $QEMU_IMG resize -f "$IMGFMT" --preallocation="$prealloc" "$TEST_IMG" "$dst_size" |
| 181 | # The first part of the image should contain data from the backing file |
| 182 | $QEMU_IO -c "read -q -P 1 0 ${orig_size}" "$TEST_IMG" |
| 183 | # The resized part of the image should contain zeroes |
| 184 | $QEMU_IO -c "read -q -P 0 ${orig_size} 63k" "$TEST_IMG" |
| 185 | # If the image does not have an external data file we can also verify its |
| 186 | # actual size. The resized image should have 7 clusters: |
| 187 | # header, L1 table, L2 table, refcount table, refcount block, 2 data clusters |
| 188 | if ! _get_data_file "$TEST_IMG" > /dev/null; then |
| 189 | expected_file_length=$((65536 * 7)) |
| 190 | file_length=$(stat -c '%s' "$TEST_IMG_FILE") |
| 191 | if [ "$file_length" != "$expected_file_length" ]; then |
| 192 | echo "ERROR: file length $file_length (expected $expected_file_length)" |
| 193 | fi |
| 194 | fi |
| 195 | echo |
| 196 | done |
| 197 | done |
| 198 | done |
| 199 | |
| 200 | # success, all done |
| 201 | echo '*** done' |
| 202 | rm -f $seq.full |
| 203 | status=0 |