103a970bbSNir Soffer#!/usr/bin/env python3 29dd003a9SVladimir Sementsov-Ogievskiy# group: quick 303a970bbSNir Soffer# 403a970bbSNir Soffer# Tests converting qcow2 compressed to NBD 503a970bbSNir Soffer# 603a970bbSNir Soffer# Copyright (c) 2020 Nir Soffer <nirsof@gmail.com> 703a970bbSNir Soffer# 803a970bbSNir Soffer# This program is free software; you can redistribute it and/or modify 903a970bbSNir Soffer# it under the terms of the GNU General Public License as published by 1003a970bbSNir Soffer# the Free Software Foundation; either version 2 of the License, or 1103a970bbSNir Soffer# (at your option) any later version. 1203a970bbSNir Soffer# 1303a970bbSNir Soffer# This program is distributed in the hope that it will be useful, 1403a970bbSNir Soffer# but WITHOUT ANY WARRANTY; without even the implied warranty of 1503a970bbSNir Soffer# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 1603a970bbSNir Soffer# GNU General Public License for more details. 1703a970bbSNir Soffer# 1803a970bbSNir Soffer# You should have received a copy of the GNU General Public License 1903a970bbSNir Soffer# along with this program. If not, see <http://www.gnu.org/licenses/>. 2003a970bbSNir Soffer# 2103a970bbSNir Soffer# owner=nirsof@gmail.com 2203a970bbSNir Soffer 2303a970bbSNir Sofferimport io 2403a970bbSNir Sofferimport tarfile 2503a970bbSNir Soffer 2603a970bbSNir Sofferimport iotests 2703a970bbSNir Soffer 2803a970bbSNir Sofferfrom iotests import ( 2903a970bbSNir Soffer file_path, 3003a970bbSNir Soffer qemu_img, 3103a970bbSNir Soffer qemu_img_check, 3203a970bbSNir Soffer qemu_img_create, 3303a970bbSNir Soffer qemu_img_log, 3403a970bbSNir Soffer qemu_img_measure, 3503a970bbSNir Soffer qemu_io, 3603a970bbSNir Soffer qemu_nbd_popen, 37*c30175d6SVladimir Sementsov-Ogievskiy img_info_log, 3803a970bbSNir Soffer) 3903a970bbSNir Soffer 4003a970bbSNir Sofferiotests.script_initialize(supported_fmts=["qcow2"]) 4103a970bbSNir Soffer 4203a970bbSNir Soffer# Create source disk. Using qcow2 to enable strict comparing later, and 4303a970bbSNir Soffer# avoid issues with random filesystem on CI environment. 4403a970bbSNir Soffersrc_disk = file_path("disk.qcow2") 4503a970bbSNir Sofferqemu_img_create("-f", iotests.imgfmt, src_disk, "1g") 4603a970bbSNir Sofferqemu_io("-f", iotests.imgfmt, "-c", "write 1m 64k", src_disk) 4703a970bbSNir Soffer 4803a970bbSNir Soffer# The use case is writing qcow2 image directly into an ova file, which 4903a970bbSNir Soffer# is a tar file with specific layout. This is tricky since we don't know the 5003a970bbSNir Soffer# size of the image before compressing, so we have to do: 5103a970bbSNir Soffer# 1. Add an ovf file. 5203a970bbSNir Soffer# 2. Find the offset of the next member data. 5303a970bbSNir Soffer# 3. Make room for image data, allocating for the worst case. 5403a970bbSNir Soffer# 4. Write compressed image data into the tar. 5503a970bbSNir Soffer# 5. Add a tar entry with the actual image size. 5603a970bbSNir Soffer# 6. Shrink the tar to the actual size, aligned to 512 bytes. 5703a970bbSNir Soffer 5803a970bbSNir Soffertar_file = file_path("test.ova") 5903a970bbSNir Soffer 6003a970bbSNir Sofferwith tarfile.open(tar_file, "w") as tar: 6103a970bbSNir Soffer 6203a970bbSNir Soffer # 1. Add an ovf file. 6303a970bbSNir Soffer 6403a970bbSNir Soffer ovf_data = b"<xml/>" 6503a970bbSNir Soffer ovf = tarfile.TarInfo("vm.ovf") 6603a970bbSNir Soffer ovf.size = len(ovf_data) 6703a970bbSNir Soffer tar.addfile(ovf, io.BytesIO(ovf_data)) 6803a970bbSNir Soffer 6903a970bbSNir Soffer # 2. Find the offset of the next member data. 7003a970bbSNir Soffer 7103a970bbSNir Soffer offset = tar.fileobj.tell() + 512 7203a970bbSNir Soffer 7303a970bbSNir Soffer # 3. Make room for image data, allocating for the worst case. 7403a970bbSNir Soffer 7503a970bbSNir Soffer measure = qemu_img_measure("-O", "qcow2", src_disk) 7603a970bbSNir Soffer tar.fileobj.truncate(offset + measure["required"]) 7703a970bbSNir Soffer 7803a970bbSNir Soffer # 4. Write compressed image data into the tar. 7903a970bbSNir Soffer 8003a970bbSNir Soffer nbd_sock = file_path("nbd-sock", base_dir=iotests.sock_dir) 8103a970bbSNir Soffer nbd_uri = "nbd+unix:///exp?socket=" + nbd_sock 8203a970bbSNir Soffer 8303a970bbSNir Soffer # Use raw format to allow creating qcow2 directly into tar file. 8403a970bbSNir Soffer with qemu_nbd_popen( 8503a970bbSNir Soffer "--socket", nbd_sock, 8603a970bbSNir Soffer "--export-name", "exp", 8703a970bbSNir Soffer "--format", "raw", 8803a970bbSNir Soffer "--offset", str(offset), 8903a970bbSNir Soffer tar_file): 9003a970bbSNir Soffer 9103a970bbSNir Soffer iotests.log("=== Target image info ===") 92*c30175d6SVladimir Sementsov-Ogievskiy # Not img_info_log as it enforces imgfmt, but now we print info on raw 9303a970bbSNir Soffer qemu_img_log("info", nbd_uri) 9403a970bbSNir Soffer 9503a970bbSNir Soffer qemu_img( 9603a970bbSNir Soffer "convert", 9703a970bbSNir Soffer "-f", iotests.imgfmt, 9803a970bbSNir Soffer "-O", "qcow2", 9903a970bbSNir Soffer "-c", 10003a970bbSNir Soffer src_disk, 10103a970bbSNir Soffer nbd_uri) 10203a970bbSNir Soffer 10303a970bbSNir Soffer iotests.log("=== Converted image info ===") 104*c30175d6SVladimir Sementsov-Ogievskiy img_info_log(nbd_uri) 10503a970bbSNir Soffer 10603a970bbSNir Soffer iotests.log("=== Converted image check ===") 10703a970bbSNir Soffer qemu_img_log("check", nbd_uri) 10803a970bbSNir Soffer 10903a970bbSNir Soffer iotests.log("=== Comparing to source disk ===") 11003a970bbSNir Soffer qemu_img_log("compare", src_disk, nbd_uri) 11103a970bbSNir Soffer 11203a970bbSNir Soffer actual_size = qemu_img_check(nbd_uri)["image-end-offset"] 11303a970bbSNir Soffer 11403a970bbSNir Soffer # 5. Add a tar entry with the actual image size. 11503a970bbSNir Soffer 11603a970bbSNir Soffer disk = tarfile.TarInfo("disk") 11703a970bbSNir Soffer disk.size = actual_size 11803a970bbSNir Soffer tar.addfile(disk) 11903a970bbSNir Soffer 12003a970bbSNir Soffer # 6. Shrink the tar to the actual size, aligned to 512 bytes. 12103a970bbSNir Soffer 12203a970bbSNir Soffer tar_size = offset + (disk.size + 511) & ~511 12303a970bbSNir Soffer tar.fileobj.seek(tar_size) 12403a970bbSNir Soffer tar.fileobj.truncate(tar_size) 12503a970bbSNir Soffer 12603a970bbSNir Sofferwith tarfile.open(tar_file) as tar: 12703a970bbSNir Soffer members = [{"name": m.name, "size": m.size, "offset": m.offset_data} 12803a970bbSNir Soffer for m in tar] 12903a970bbSNir Soffer iotests.log("=== OVA file contents ===") 13003a970bbSNir Soffer iotests.log(members) 131