Support to create empty disk images on the controller

pull/2292/head
grossmj 8 months ago
parent c1507b4155
commit 1ae6d13022

@ -23,13 +23,15 @@ import logging
import urllib.parse
from fastapi import APIRouter, Request, Depends, status
from fastapi.encoders import jsonable_encoder
from starlette.requests import ClientDisconnect
from sqlalchemy.orm.exc import MultipleResultsFound
from typing import List, Optional
from gns3server import schemas
from gns3server.config import Config
from gns3server.utils.images import InvalidImageError, write_image
from gns3server.compute.qemu import Qemu
from gns3server.utils.images import InvalidImageError, write_image, read_image_info, default_images_directory
from gns3server.db.repositories.images import ImagesRepository
from gns3server.db.repositories.templates import TemplatesRepository
from gns3server.db.repositories.rbac import RbacRepository
@ -50,6 +52,53 @@ log = logging.getLogger(__name__)
router = APIRouter()
@router.post(
"/{image_path:path}",
response_model=schemas.Image,
status_code=status.HTTP_201_CREATED,
dependencies=[Depends(has_privilege("Image.Allocate"))]
)
async def create_image(
image_path: str,
image_data: schemas.QemuDiskImageCreate,
images_repo: ImagesRepository = Depends(get_repository(ImagesRepository)),
) -> schemas.Image:
"""
Create a new blank image.
Required privilege: Image.Allocate
"""
allow_raw_image = Config.instance().settings.Server.allow_raw_images
if image_data.format == schemas.QemuDiskImageFormat.raw and not allow_raw_image:
raise ControllerBadRequestError("Raw images are not allowed")
disk_image_path = urllib.parse.unquote(image_path)
image_dir, image_name = os.path.split(disk_image_path)
# check if the path is within the default images directory
base_images_directory = os.path.expanduser(Config.instance().settings.Server.images_path)
full_path = os.path.abspath(os.path.join(base_images_directory, image_dir, image_name))
if os.path.commonprefix([base_images_directory, full_path]) != base_images_directory:
raise ControllerForbiddenError(f"Cannot write disk image, '{disk_image_path}' is forbidden")
if not image_dir:
# put the image in the default images directory
directory = default_images_directory(image_type="qemu")
os.makedirs(directory, exist_ok=True)
disk_image_path = os.path.abspath(os.path.join(directory, disk_image_path))
if await images_repo.get_image(disk_image_path):
raise ControllerBadRequestError(f"Disk image '{disk_image_path}' already exists")
options = jsonable_encoder(image_data, exclude_unset=True)
# FIXME: should we have the create_disk_image in the compute code since
# this code is used to create images on the controller?
await Qemu.instance().create_disk_image(disk_image_path, options)
image_info = await read_image_info(disk_image_path, "qemu")
return await images_repo.add_image(**image_info)
@router.get(
"",
response_model=List[schemas.Image],

@ -21,6 +21,8 @@ Qemu server module.
import asyncio
import os
import platform
import shutil
import shlex
import sys
import re
import subprocess
@ -160,42 +162,61 @@ class Qemu(BaseManager):
return qemus
@staticmethod
async def get_qemu_version(qemu_path):
async def create_disk_image(disk_image_path, options):
"""
Gets the Qemu version.
Create a Qemu disk (used by the controller to create empty disk images)
:param qemu_path: path to Qemu executable.
:param disk_image_path: disk image path
:param options: disk creation options
"""
qemu_img_path = shutil.which("qemu-img")
if not qemu_img_path:
raise QemuError(f"Could not find qemu-img binary")
try:
output = await subprocess_check_output(qemu_path, "-version", "-nographic")
match = re.search(r"version\s+([0-9a-z\-\.]+)", output)
if match:
version = match.group(1)
return version
else:
raise QemuError(f"Could not determine the Qemu version for {qemu_path}")
if os.path.exists(disk_image_path):
raise QemuError(f"Could not create disk image '{disk_image_path}', file already exists")
except UnicodeEncodeError:
raise QemuError(
f"Could not create disk image '{disk_image_path}', "
"Disk image name contains characters not supported by the filesystem"
)
img_format = options.pop("format")
img_size = options.pop("size")
command = [qemu_img_path, "create", "-f", img_format]
for option in sorted(options.keys()):
command.extend(["-o", f"{option}={options[option]}"])
command.append(disk_image_path)
command.append(f"{img_size}M")
command_string = " ".join(shlex.quote(s) for s in command)
output = ""
try:
log.info(f"Executing qemu-img with: {command_string}")
output = await subprocess_check_output(*command, stderr=True)
log.info(f"Qemu disk image'{disk_image_path}' created")
except (OSError, subprocess.SubprocessError) as e:
raise QemuError(f"Error while looking for the Qemu version: {e}")
raise QemuError(f"Could not create '{disk_image_path}' disk image: {e}\n{output}")
@staticmethod
async def _get_qemu_img_version(qemu_img_path):
async def get_qemu_version(qemu_path):
"""
Gets the Qemu-img version.
Gets the Qemu version.
:param qemu_img_path: path to Qemu-img executable.
:param qemu_path: path to Qemu executable.
"""
try:
output = await subprocess_check_output(qemu_img_path, "--version")
output = await subprocess_check_output(qemu_path, "-version", "-nographic")
match = re.search(r"version\s+([0-9a-z\-\.]+)", output)
if match:
version = match.group(1)
return version
else:
raise QemuError("Could not determine the Qemu-img version for '{}'".format(qemu_img_path))
raise QemuError(f"Could not determine the Qemu version for {qemu_path}")
except (OSError, subprocess.SubprocessError) as e:
raise QemuError("Error while looking for the Qemu-img version: {}".format(e))
raise QemuError(f"Error while looking for the Qemu version: {e}")
@staticmethod
async def get_swtpm_version(swtpm_path):

@ -82,4 +82,4 @@ from .compute.vmware_nodes import VMwareCreate, VMwareUpdate, VMware
from .compute.virtualbox_nodes import VirtualBoxCreate, VirtualBoxUpdate, VirtualBox
# Schemas for both controller and compute
from .qemu_disk_image import QemuDiskImageCreate, QemuDiskImageUpdate
from .qemu_disk_image import QemuDiskImageFormat, QemuDiskImageCreate, QemuDiskImageUpdate

Loading…
Cancel
Save