mirror of
https://github.com/Motorhead1991/qemu.git
synced 2025-07-30 22:03:54 -06:00

This test depends on TarFile.addfile() to add tar member header without writing the member data, which we write ourself using qemu-nbd. Python 3.13 changed the function in a backward incompatible way[1] to require a file object for tarinfo with non-zero size, breaking the test: -[{"name": "vm.ovf", "offset": 512, "size": 6}, {"name": "disk", "offset": 1536, "size": 393216}] +Traceback (most recent call last): + File "/home/stefanha/qemu/tests/qemu-iotests/302", line 118, in <module> + tar.addfile(disk) + ~~~~~~~~~~~^^^^^^ + File "/usr/lib64/python3.13/tarfile.py", line 2262, in addfile + raise ValueError("fileobj not provided for non zero-size regular file") +ValueError: fileobj not provided for non zero-size regular file The new behavior makes sense for most users, but breaks our unusual usage. Fix the test to add the member header directly using public but undocumented attributes. This is more fragile but the test works again. This also fixes a bug in the previous code - when calling addfile() without a fileobject, tar.offset points to the start of the member data instead of the end. [1] https://github.com/python/cpython/pull/117988 Signed-off-by: Nir Soffer <nirsof@gmail.com> Message-ID: <20250228195708.48035-1-nirsof@gmail.com> Reviewed-by: Eric Blake <eblake@redhat.com> Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> Signed-off-by: Eric Blake <eblake@redhat.com>
139 lines
4.2 KiB
Python
Executable file
139 lines
4.2 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
# group: quick
|
|
#
|
|
# Tests converting qcow2 compressed to NBD
|
|
#
|
|
# Copyright (c) 2020 Nir Soffer <nirsof@gmail.com>
|
|
#
|
|
# 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/>.
|
|
#
|
|
# owner=nirsof@gmail.com
|
|
|
|
import io
|
|
import tarfile
|
|
|
|
import iotests
|
|
|
|
from iotests import (
|
|
file_path,
|
|
qemu_img,
|
|
qemu_img_check,
|
|
qemu_img_create,
|
|
qemu_img_log,
|
|
qemu_img_measure,
|
|
qemu_io,
|
|
qemu_nbd_popen,
|
|
img_info_log,
|
|
)
|
|
|
|
iotests.script_initialize(supported_fmts=["qcow2"])
|
|
|
|
# Create source disk. Using qcow2 to enable strict comparing later, and
|
|
# avoid issues with random filesystem on CI environment.
|
|
src_disk = file_path("disk.qcow2")
|
|
qemu_img_create("-f", iotests.imgfmt, src_disk, "1g")
|
|
qemu_io("-f", iotests.imgfmt, "-c", "write 1m 64k", src_disk)
|
|
|
|
# The use case is writing qcow2 image directly into an ova file, which
|
|
# is a tar file with specific layout. This is tricky since we don't know the
|
|
# size of the image before compressing, so we have to do:
|
|
# 1. Add an ovf file.
|
|
# 2. Find the offset of the next member data.
|
|
# 3. Make room for image data, allocating for the worst case.
|
|
# 4. Write compressed image data into the tar.
|
|
# 5. Add a tar entry with the actual image size.
|
|
# 6. Shrink the tar to the actual size, aligned to 512 bytes.
|
|
|
|
tar_file = file_path("test.ova")
|
|
|
|
with tarfile.open(tar_file, "w") as tar:
|
|
|
|
# 1. Add an ovf file.
|
|
|
|
ovf_data = b"<xml/>"
|
|
ovf = tarfile.TarInfo("vm.ovf")
|
|
ovf.size = len(ovf_data)
|
|
tar.addfile(ovf, io.BytesIO(ovf_data))
|
|
|
|
# 2. Find the offset of the next member data.
|
|
|
|
offset = tar.fileobj.tell() + 512
|
|
|
|
# 3. Make room for image data, allocating for the worst case.
|
|
|
|
measure = qemu_img_measure("-O", "qcow2", src_disk)
|
|
tar.fileobj.truncate(offset + measure["required"])
|
|
|
|
# 4. Write compressed image data into the tar.
|
|
|
|
nbd_sock = file_path("nbd-sock", base_dir=iotests.sock_dir)
|
|
nbd_uri = "nbd+unix:///exp?socket=" + nbd_sock
|
|
|
|
# Use raw format to allow creating qcow2 directly into tar file.
|
|
with qemu_nbd_popen(
|
|
"--socket", nbd_sock,
|
|
"--export-name", "exp",
|
|
"--format", "raw",
|
|
"--offset", str(offset),
|
|
tar_file):
|
|
|
|
iotests.log("=== Target image info ===")
|
|
# Not img_info_log as it enforces imgfmt, but now we print info on raw
|
|
qemu_img_log("info", nbd_uri)
|
|
|
|
qemu_img(
|
|
"convert",
|
|
"-f", iotests.imgfmt,
|
|
"-O", "qcow2",
|
|
"-c",
|
|
src_disk,
|
|
nbd_uri)
|
|
|
|
iotests.log("=== Converted image info ===")
|
|
img_info_log(nbd_uri)
|
|
|
|
iotests.log("=== Converted image check ===")
|
|
qemu_img_log("check", nbd_uri)
|
|
|
|
iotests.log("=== Comparing to source disk ===")
|
|
qemu_img_log("compare", src_disk, nbd_uri)
|
|
|
|
actual_size = qemu_img_check(nbd_uri)["image-end-offset"]
|
|
|
|
# 5. Add a tar entry with the actual image size.
|
|
|
|
disk = tarfile.TarInfo("disk")
|
|
disk.size = actual_size
|
|
|
|
# Since python 3.13 we cannot use addfile() to create the member header.
|
|
# Add the tarinfo directly using public but undocumented attributes.
|
|
|
|
buf = disk.tobuf(tar.format, tar.encoding, tar.errors)
|
|
tar.fileobj.write(buf)
|
|
tar.members.append(disk)
|
|
|
|
# Update the offset and position to the location of the next member.
|
|
|
|
tar.offset = offset + (disk.size + 511) & ~511
|
|
tar.fileobj.seek(tar.offset)
|
|
|
|
# 6. Shrink the tar to the actual size.
|
|
|
|
tar.fileobj.truncate(tar.offset)
|
|
|
|
with tarfile.open(tar_file) as tar:
|
|
members = [{"name": m.name, "size": m.size, "offset": m.offset_data}
|
|
for m in tar]
|
|
iotests.log("=== OVA file contents ===")
|
|
iotests.log(members)
|