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