diff --git a/gns3server/compute/qemu/qemu_vm.py b/gns3server/compute/qemu/qemu_vm.py index b5ae36e3..078ef674 100644 --- a/gns3server/compute/qemu/qemu_vm.py +++ b/gns3server/compute/qemu/qemu_vm.py @@ -1680,24 +1680,14 @@ class QemuVM(BaseNode): async def _create_linked_clone(self, disk_name, disk_image, disk): try: qemu_img_path = self._get_qemu_img() - command = [qemu_img_path, "create", "-o", "backing_file={}".format(disk_image), "-f", "qcow2", disk] - try: - base_qcow2 = Qcow2(disk_image) - if base_qcow2.crypt_method: - # Workaround for https://gitlab.com/qemu-project/qemu/-/issues/441 - # Also embed a secret name so it doesn't have to be passed to qemu -drive ... - options = { - "encrypt.key-secret": os.path.basename(disk_image), - "driver": "qcow2", - "file": { - "driver": "file", - "filename": disk_image, - }, - } - command = [qemu_img_path, "create", "-b", "json:"+json.dumps(options, separators=(',', ':')), - "-f", "qcow2", "-u", disk, str(base_qcow2.size)] - except Qcow2Error: - pass # non-qcow2 base images are acceptable (e.g. vmdk, raw image) + backing_options, base_qcow2 = Qcow2.backing_options(disk_image) + if base_qcow2 and base_qcow2.crypt_method: + # Workaround for https://gitlab.com/qemu-project/qemu/-/issues/441 + # (we have to pass -u and the size). Also embed secret name. + command = [qemu_img_path, "create", "-b", backing_options, + "-f", "qcow2", "-u", disk, str(base_qcow2.size)] + else: + command = [qemu_img_path, "create", "-o", "backing_file={}".format(disk_image), "-f", "qcow2", disk] retcode = await self._qemu_img_exec(command) if retcode: @@ -1880,10 +1870,13 @@ class QemuVM(BaseNode): # create the disk await self._create_linked_clone(disk_name, disk_image, disk) else: - # The disk exists we check if the clone works + # Rebase the image. This is in case the base image moved to a different directory, + # which will be the case if we imported a portable project. This uses + # get_abs_image_path(hdX_disk_image) and ignores the old base path embedded + # in the qcow2 file itself. try: qcow2 = Qcow2(disk) - await qcow2.validate(qemu_img_path) + await qcow2.rebase(qemu_img_path, disk_image) except (Qcow2Error, OSError) as e: raise QemuError("Could not use qcow2 disk image '{}' for {}: {}".format(disk_image, disk_name, e)) diff --git a/gns3server/compute/qemu/utils/qcow2.py b/gns3server/compute/qemu/utils/qcow2.py index 5a4d7979..719face0 100644 --- a/gns3server/compute/qemu/utils/qcow2.py +++ b/gns3server/compute/qemu/utils/qcow2.py @@ -15,6 +15,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import json import os import asyncio import struct @@ -88,6 +89,35 @@ class Qcow2: return None return path + @staticmethod + def backing_options(base_image): + """ + If the base_image is encrypted qcow2, return options for the upper layer + which include a secret name (equal to the basename) + + :param base_image: Path to the base file (which may or may not be qcow2) + + :returns: (base image string, Qcow2 object representing base image or None) + """ + + try: + base_qcow2 = Qcow2(base_image) + if base_qcow2.crypt_method: + # Embed a secret name so it doesn't have to be passed to qemu -drive ... + options = { + "encrypt.key-secret": os.path.basename(base_image), + "driver": "qcow2", + "file": { + "driver": "file", + "filename": base_image, + }, + } + return ("json:"+json.dumps(options, separators=(',', ':')), base_qcow2) + else: + return (base_image, base_qcow2) + except Qcow2Error: + return (base_image, None) # non-qcow2 base images are acceptable (e.g. vmdk, raw image) + async def rebase(self, qemu_img, base_image): """ Rebase a linked clone in order to use the correct disk @@ -98,21 +128,10 @@ class Qcow2: if not os.path.exists(base_image): raise FileNotFoundError(base_image) - command = [qemu_img, "rebase", "-u", "-b", base_image, self._path] + backing_options, _ = Qcow2.backing_options(base_image) + command = [qemu_img, "rebase", "-u", "-b", backing_options, self._path] process = await asyncio.create_subprocess_exec(*command) retcode = await process.wait() if retcode != 0: raise Qcow2Error("Could not rebase the image") self._reload() - - async def validate(self, qemu_img): - """ - Run qemu-img info to validate the file and its backing images - - :param qemu_img: Path to the qemu-img binary - """ - command = [qemu_img, "info", "--backing-chain", self._path] - process = await asyncio.create_subprocess_exec(*command) - retcode = await process.wait() - if retcode != 0: - raise Qcow2Error("Could not validate the image")