mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-03 05:51:15 +02:00
Merge branch '2.2' into 3.0
# Conflicts: # gns3server/compute/qemu/qemu_vm.py # gns3server/handlers/api/compute/server_handler.py # gns3server/utils/path.py # gns3server/version.py # requirements.txt # tests/compute/qemu/test_qemu_vm.py # tests/compute/test_manager.py
This commit is contained in:
commit
a324459152
571 files changed
+3964
-13577
No files matched your search
@@ -1832,23 +1832,16 @@ class QemuVM(BaseNode):
|
||||
def _get_qemu_img(self):
|
||||
"""
|
||||
Search the qemu-img binary in the same binary of the qemu binary
|
||||
for avoiding version incompatibility.
|
||||
to avoid version incompatibility.
|
||||
|
||||
:returns: qemu-img path or raise an error
|
||||
"""
|
||||
qemu_img_path = ""
|
||||
|
||||
qemu_path_dir = os.path.dirname(self.qemu_path)
|
||||
try:
|
||||
for f in os.listdir(qemu_path_dir):
|
||||
if f.startswith("qemu-img"):
|
||||
qemu_img_path = os.path.join(qemu_path_dir, f)
|
||||
except OSError as e:
|
||||
raise QemuError(f"Error while looking for qemu-img in {qemu_path_dir}: {e}")
|
||||
|
||||
if not qemu_img_path:
|
||||
raise QemuError(f"Could not find qemu-img in {qemu_path_dir}")
|
||||
|
||||
return qemu_img_path
|
||||
qemu_image_path = shutil.which("qemu-img", path=qemu_path_dir)
|
||||
if qemu_image_path:
|
||||
return qemu_image_path
|
||||
raise QemuError(f"Could not find qemu-img in {qemu_path_dir}")
|
||||
|
||||
async def _qemu_img_exec(self, command):
|
||||
|
||||
@@ -1864,27 +1857,36 @@ class QemuVM(BaseNode):
|
||||
log.info(f"{self._get_qemu_img()} returned with {retcode}")
|
||||
return retcode
|
||||
|
||||
async def _find_disk_file_format(self, disk):
|
||||
|
||||
qemu_img_path = self._get_qemu_img()
|
||||
try:
|
||||
output = await subprocess_check_output(qemu_img_path, "info", "--output=json", disk)
|
||||
except subprocess.SubprocessError as e:
|
||||
raise QemuError(f"Error received while checking Qemu disk format: {e}")
|
||||
if output:
|
||||
try:
|
||||
json_data = json.loads(output)
|
||||
except ValueError as e:
|
||||
raise QemuError(f"Invalid JSON data returned by qemu-img: {e}")
|
||||
return json_data.get("format")
|
||||
|
||||
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", f"backing_file={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_file_format = await self._find_disk_file_format(disk_image)
|
||||
if not backing_file_format:
|
||||
raise QemuError(f"Could not detect format for disk image: {disk_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", backing_file_format, "-f", "qcow2", "-u", disk, str(base_qcow2.size)]
|
||||
else:
|
||||
command = [qemu_img_path, "create", "-o", "backing_file={}".format(disk_image),
|
||||
"-F", backing_file_format, "-f", "qcow2", disk]
|
||||
|
||||
retcode = await self._qemu_img_exec(command)
|
||||
if retcode:
|
||||
@@ -2068,19 +2070,14 @@ class QemuVM(BaseNode):
|
||||
if retcode == 3:
|
||||
# image has leaked clusters, but is not corrupted, let's try to fix it
|
||||
log.warning(f"Qemu image {disk_image} has leaked clusters")
|
||||
if (await self._qemu_img_exec([qemu_img_path, "check", "-r", "leaks", f"{disk_image}"])) == 3:
|
||||
self.project.emit(
|
||||
"log.warning",
|
||||
{"message": f"Qemu image '{disk_image}' has leaked clusters and could not be fixed"},
|
||||
)
|
||||
if await self._qemu_img_exec([qemu_img_path, "check", "-r", "leaks", "{}".format(disk_image)]) == 3:
|
||||
self.project.emit("log.warning", {"message": "Qemu image '{}' has leaked clusters and could not be fixed".format(disk_image)})
|
||||
elif retcode == 2:
|
||||
# image is corrupted, let's try to fix it
|
||||
log.warning(f"Qemu image {disk_image} is corrupted")
|
||||
if (await self._qemu_img_exec([qemu_img_path, "check", "-r", "all", f"{disk_image}"])) == 2:
|
||||
self.project.emit(
|
||||
"log.warning",
|
||||
{"message": f"Qemu image '{disk_image}' is corrupted and could not be fixed"},
|
||||
)
|
||||
if await self._qemu_img_exec([qemu_img_path, "check", "-r", "all", "{}".format(disk_image)]) == 2:
|
||||
self.project.emit("log.warning", {"message": "Qemu image '{}' is corrupted and could not be fixed".format(disk_image)})
|
||||
# ignore retcode == 1. One reason is that the image is encrypted and there is no encrypt.key-secret available
|
||||
except (OSError, subprocess.SubprocessError) as e:
|
||||
stdout = self.read_qemu_img_stdout()
|
||||
raise QemuError(f"Could not check '{disk_name}' disk image: {e}\n{stdout}")
|
||||
@@ -2091,10 +2088,16 @@ 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
|
||||
backing_file_format = await self._find_disk_file_format(disk_image)
|
||||
if not backing_file_format:
|
||||
raise QemuError("Could not detect format for disk image: {}".format(disk_image))
|
||||
# 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, backing_file_format)
|
||||
except (Qcow2Error, OSError) as e:
|
||||
raise QemuError(f"Could not use qcow2 disk image '{disk_image}' for {disk_name} {e}")
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import json
|
||||
import os
|
||||
import asyncio
|
||||
import struct
|
||||
@@ -88,31 +89,50 @@ class Qcow2:
|
||||
return None
|
||||
return path
|
||||
|
||||
async def rebase(self, qemu_img, base_image):
|
||||
@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, backing_file_format):
|
||||
"""
|
||||
Rebase a linked clone in order to use the correct disk
|
||||
|
||||
:param qemu_img: Path to the qemu-img binary
|
||||
:param base_image: Path to the base image
|
||||
:param backing_file_format: File format of the base image
|
||||
"""
|
||||
|
||||
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, "-F", backing_file_format, 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")
|
||||
Reference in new issue
Block a user