1*03a970bbSNir Soffer#!/usr/bin/env python3 2*03a970bbSNir Soffer# 3*03a970bbSNir Soffer# Tests converting qcow2 compressed to NBD 4*03a970bbSNir Soffer# 5*03a970bbSNir Soffer# Copyright (c) 2020 Nir Soffer <nirsof@gmail.com> 6*03a970bbSNir Soffer# 7*03a970bbSNir Soffer# This program is free software; you can redistribute it and/or modify 8*03a970bbSNir Soffer# it under the terms of the GNU General Public License as published by 9*03a970bbSNir Soffer# the Free Software Foundation; either version 2 of the License, or 10*03a970bbSNir Soffer# (at your option) any later version. 11*03a970bbSNir Soffer# 12*03a970bbSNir Soffer# This program is distributed in the hope that it will be useful, 13*03a970bbSNir Soffer# but WITHOUT ANY WARRANTY; without even the implied warranty of 14*03a970bbSNir Soffer# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15*03a970bbSNir Soffer# GNU General Public License for more details. 16*03a970bbSNir Soffer# 17*03a970bbSNir Soffer# You should have received a copy of the GNU General Public License 18*03a970bbSNir Soffer# along with this program. If not, see <http://www.gnu.org/licenses/>. 19*03a970bbSNir Soffer# 20*03a970bbSNir Soffer# owner=nirsof@gmail.com 21*03a970bbSNir Soffer 22*03a970bbSNir Sofferimport io 23*03a970bbSNir Sofferimport tarfile 24*03a970bbSNir Soffer 25*03a970bbSNir Sofferimport iotests 26*03a970bbSNir Soffer 27*03a970bbSNir Sofferfrom iotests import ( 28*03a970bbSNir Soffer file_path, 29*03a970bbSNir Soffer qemu_img, 30*03a970bbSNir Soffer qemu_img_check, 31*03a970bbSNir Soffer qemu_img_create, 32*03a970bbSNir Soffer qemu_img_log, 33*03a970bbSNir Soffer qemu_img_measure, 34*03a970bbSNir Soffer qemu_io, 35*03a970bbSNir Soffer qemu_nbd_popen, 36*03a970bbSNir Soffer) 37*03a970bbSNir Soffer 38*03a970bbSNir Sofferiotests.script_initialize(supported_fmts=["qcow2"]) 39*03a970bbSNir Soffer 40*03a970bbSNir Soffer# Create source disk. Using qcow2 to enable strict comparing later, and 41*03a970bbSNir Soffer# avoid issues with random filesystem on CI environment. 42*03a970bbSNir Soffersrc_disk = file_path("disk.qcow2") 43*03a970bbSNir Sofferqemu_img_create("-f", iotests.imgfmt, src_disk, "1g") 44*03a970bbSNir Sofferqemu_io("-f", iotests.imgfmt, "-c", "write 1m 64k", src_disk) 45*03a970bbSNir Soffer 46*03a970bbSNir Soffer# The use case is writing qcow2 image directly into an ova file, which 47*03a970bbSNir Soffer# is a tar file with specific layout. This is tricky since we don't know the 48*03a970bbSNir Soffer# size of the image before compressing, so we have to do: 49*03a970bbSNir Soffer# 1. Add an ovf file. 50*03a970bbSNir Soffer# 2. Find the offset of the next member data. 51*03a970bbSNir Soffer# 3. Make room for image data, allocating for the worst case. 52*03a970bbSNir Soffer# 4. Write compressed image data into the tar. 53*03a970bbSNir Soffer# 5. Add a tar entry with the actual image size. 54*03a970bbSNir Soffer# 6. Shrink the tar to the actual size, aligned to 512 bytes. 55*03a970bbSNir Soffer 56*03a970bbSNir Soffertar_file = file_path("test.ova") 57*03a970bbSNir Soffer 58*03a970bbSNir Sofferwith tarfile.open(tar_file, "w") as tar: 59*03a970bbSNir Soffer 60*03a970bbSNir Soffer # 1. Add an ovf file. 61*03a970bbSNir Soffer 62*03a970bbSNir Soffer ovf_data = b"<xml/>" 63*03a970bbSNir Soffer ovf = tarfile.TarInfo("vm.ovf") 64*03a970bbSNir Soffer ovf.size = len(ovf_data) 65*03a970bbSNir Soffer tar.addfile(ovf, io.BytesIO(ovf_data)) 66*03a970bbSNir Soffer 67*03a970bbSNir Soffer # 2. Find the offset of the next member data. 68*03a970bbSNir Soffer 69*03a970bbSNir Soffer offset = tar.fileobj.tell() + 512 70*03a970bbSNir Soffer 71*03a970bbSNir Soffer # 3. Make room for image data, allocating for the worst case. 72*03a970bbSNir Soffer 73*03a970bbSNir Soffer measure = qemu_img_measure("-O", "qcow2", src_disk) 74*03a970bbSNir Soffer tar.fileobj.truncate(offset + measure["required"]) 75*03a970bbSNir Soffer 76*03a970bbSNir Soffer # 4. Write compressed image data into the tar. 77*03a970bbSNir Soffer 78*03a970bbSNir Soffer nbd_sock = file_path("nbd-sock", base_dir=iotests.sock_dir) 79*03a970bbSNir Soffer nbd_uri = "nbd+unix:///exp?socket=" + nbd_sock 80*03a970bbSNir Soffer 81*03a970bbSNir Soffer # Use raw format to allow creating qcow2 directly into tar file. 82*03a970bbSNir Soffer with qemu_nbd_popen( 83*03a970bbSNir Soffer "--socket", nbd_sock, 84*03a970bbSNir Soffer "--export-name", "exp", 85*03a970bbSNir Soffer "--format", "raw", 86*03a970bbSNir Soffer "--offset", str(offset), 87*03a970bbSNir Soffer tar_file): 88*03a970bbSNir Soffer 89*03a970bbSNir Soffer iotests.log("=== Target image info ===") 90*03a970bbSNir Soffer qemu_img_log("info", nbd_uri) 91*03a970bbSNir Soffer 92*03a970bbSNir Soffer qemu_img( 93*03a970bbSNir Soffer "convert", 94*03a970bbSNir Soffer "-f", iotests.imgfmt, 95*03a970bbSNir Soffer "-O", "qcow2", 96*03a970bbSNir Soffer "-c", 97*03a970bbSNir Soffer src_disk, 98*03a970bbSNir Soffer nbd_uri) 99*03a970bbSNir Soffer 100*03a970bbSNir Soffer iotests.log("=== Converted image info ===") 101*03a970bbSNir Soffer qemu_img_log("info", nbd_uri) 102*03a970bbSNir Soffer 103*03a970bbSNir Soffer iotests.log("=== Converted image check ===") 104*03a970bbSNir Soffer qemu_img_log("check", nbd_uri) 105*03a970bbSNir Soffer 106*03a970bbSNir Soffer iotests.log("=== Comparing to source disk ===") 107*03a970bbSNir Soffer qemu_img_log("compare", src_disk, nbd_uri) 108*03a970bbSNir Soffer 109*03a970bbSNir Soffer actual_size = qemu_img_check(nbd_uri)["image-end-offset"] 110*03a970bbSNir Soffer 111*03a970bbSNir Soffer # 5. Add a tar entry with the actual image size. 112*03a970bbSNir Soffer 113*03a970bbSNir Soffer disk = tarfile.TarInfo("disk") 114*03a970bbSNir Soffer disk.size = actual_size 115*03a970bbSNir Soffer tar.addfile(disk) 116*03a970bbSNir Soffer 117*03a970bbSNir Soffer # 6. Shrink the tar to the actual size, aligned to 512 bytes. 118*03a970bbSNir Soffer 119*03a970bbSNir Soffer tar_size = offset + (disk.size + 511) & ~511 120*03a970bbSNir Soffer tar.fileobj.seek(tar_size) 121*03a970bbSNir Soffer tar.fileobj.truncate(tar_size) 122*03a970bbSNir Soffer 123*03a970bbSNir Sofferwith tarfile.open(tar_file) as tar: 124*03a970bbSNir Soffer members = [{"name": m.name, "size": m.size, "offset": m.offset_data} 125*03a970bbSNir Soffer for m in tar] 126*03a970bbSNir Soffer iotests.log("=== OVA file contents ===") 127*03a970bbSNir Soffer iotests.log(members) 128