mirror of
https://github.com/GNS3/gns3-server
synced 2024-11-28 11:18:11 +00:00
API for creating a qemu disk image
This commit is contained in:
parent
5d8c90d138
commit
5b0c36c0d6
@ -316,6 +316,21 @@ class QEMUHandler:
|
|||||||
binaries = yield from Qemu.img_binary_list()
|
binaries = yield from Qemu.img_binary_list()
|
||||||
response.json(binaries)
|
response.json(binaries)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
@Route.post(
|
||||||
|
r"/qemu/img",
|
||||||
|
status_codes={
|
||||||
|
201: "Image created",
|
||||||
|
},
|
||||||
|
description="Create a Qemu image"
|
||||||
|
)
|
||||||
|
def create_img(request, response):
|
||||||
|
|
||||||
|
qemu_img = request.json.pop("qemu_img")
|
||||||
|
path = request.json.pop("path")
|
||||||
|
yield from Qemu.instance().create_disk(qemu_img, path, request.json)
|
||||||
|
response.set_status(201)
|
||||||
|
|
||||||
@Route.get(
|
@Route.get(
|
||||||
r"/qemu/vms",
|
r"/qemu/vms",
|
||||||
status_codes={
|
status_codes={
|
||||||
|
@ -187,3 +187,32 @@ class Qemu(BaseManager):
|
|||||||
"""
|
"""
|
||||||
return os.path.join(os.path.expanduser(self.config.get_section_config("Server").get("images_path", "~/GNS3/images")), "QEMU")
|
return os.path.join(os.path.expanduser(self.config.get_section_config("Server").get("images_path", "~/GNS3/images")), "QEMU")
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def create_disk(self, qemu_img, path, options):
|
||||||
|
"""
|
||||||
|
Create a qemu disk with qemu-img
|
||||||
|
|
||||||
|
:param qemu_img: qemu-img binary path
|
||||||
|
:param path: Image path
|
||||||
|
:param options: Disk image creation options
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
img_format = options.pop("format")
|
||||||
|
img_size = options.pop("size")
|
||||||
|
|
||||||
|
if not os.path.isabs(path):
|
||||||
|
directory = self.get_images_directory()
|
||||||
|
os.makedirs(directory, exist_ok=True)
|
||||||
|
path = os.path.join(directory, os.path.basename(path))
|
||||||
|
|
||||||
|
command = [qemu_img, "create", "-f", img_format]
|
||||||
|
for option in sorted(options.keys()):
|
||||||
|
command.extend(["-o", "{}={}".format(option, options[option])])
|
||||||
|
command.append(path)
|
||||||
|
command.append("{}M".format(img_size))
|
||||||
|
|
||||||
|
process = yield from asyncio.create_subprocess_exec(*command)
|
||||||
|
yield from process.wait()
|
||||||
|
except (OSError, subprocess.SubprocessError) as e:
|
||||||
|
raise QemuError("Could create disk image {}:{}".format(path, e))
|
||||||
|
@ -189,7 +189,6 @@ class QemuVM(BaseVM):
|
|||||||
|
|
||||||
:param hda_disk_image: QEMU hda disk image path
|
:param hda_disk_image: QEMU hda disk image path
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self._hda_disk_image = self.manager.get_abs_image_path(hda_disk_image)
|
self._hda_disk_image = self.manager.get_abs_image_path(hda_disk_image)
|
||||||
log.info('QEMU VM "{name}" [{id}] has set the QEMU hda disk image path to {disk_image}'.format(name=self._name,
|
log.info('QEMU VM "{name}" [{id}] has set the QEMU hda disk image path to {disk_image}'.format(name=self._name,
|
||||||
id=self._id,
|
id=self._id,
|
||||||
@ -1242,31 +1241,3 @@ class QemuVM(BaseVM):
|
|||||||
answer["kernel_image_md5sum"] = md5sum(self._kernel_image)
|
answer["kernel_image_md5sum"] = md5sum(self._kernel_image)
|
||||||
|
|
||||||
return answer
|
return answer
|
||||||
|
|
||||||
@asyncio.coroutine
|
|
||||||
def _create_disk(self, name, options):
|
|
||||||
"""
|
|
||||||
Create a qemu disk with qemu-img
|
|
||||||
|
|
||||||
:param name: Image name without the extension
|
|
||||||
:param options: Disk image creation options
|
|
||||||
:returns: Image name with the extensions
|
|
||||||
"""
|
|
||||||
|
|
||||||
img_format = options.pop("format")
|
|
||||||
img_size = options.pop("size")
|
|
||||||
img_name = "{}.{}".format(name, img_format)
|
|
||||||
|
|
||||||
qemu_img = self._get_qemu_img()
|
|
||||||
command = [qemu_img, "create", "-f", img_format]
|
|
||||||
for option in sorted(options.keys()):
|
|
||||||
command.extend(["-o", "{}={}".format(option, options[option])])
|
|
||||||
command.append(os.path.join(self.working_dir, img_name))
|
|
||||||
command.append("{}M".format(img_size))
|
|
||||||
try:
|
|
||||||
process = yield from asyncio.create_subprocess_exec(*command)
|
|
||||||
yield from process.wait()
|
|
||||||
except (OSError, subprocess.SubprocessError) as e:
|
|
||||||
raise QemuError("Could create disk image {}:{}".format(name, e))
|
|
||||||
|
|
||||||
return img_name
|
|
||||||
|
@ -239,3 +239,20 @@ def test_upload_vm_permission_denied(server, tmpdir):
|
|||||||
with patch("gns3server.modules.Qemu.get_images_directory", return_value=str(tmpdir),):
|
with patch("gns3server.modules.Qemu.get_images_directory", return_value=str(tmpdir),):
|
||||||
response = server.post("/qemu/vms/test2", body="TEST", raw=True)
|
response = server.post("/qemu/vms/test2", body="TEST", raw=True)
|
||||||
assert response.status == 409
|
assert response.status == 409
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_img(server):
|
||||||
|
body = {
|
||||||
|
"qemu_img": "/tmp/qemu-img",
|
||||||
|
"path": "hda.qcow2",
|
||||||
|
"format": "qcow2",
|
||||||
|
"preallocation": "metadata",
|
||||||
|
"cluster_size": 64,
|
||||||
|
"refcount_bits": 12,
|
||||||
|
"lazy_refcounts": "off",
|
||||||
|
"size": 100
|
||||||
|
}
|
||||||
|
with asyncio_patch("gns3server.modules.Qemu.create_disk"):
|
||||||
|
response = server.post("/qemu/img", body=body, example=True)
|
||||||
|
|
||||||
|
assert response.status == 201
|
||||||
|
@ -19,9 +19,21 @@ import os
|
|||||||
import stat
|
import stat
|
||||||
import asyncio
|
import asyncio
|
||||||
import sys
|
import sys
|
||||||
|
import pytest
|
||||||
|
|
||||||
from gns3server.modules.qemu import Qemu
|
from gns3server.modules.qemu import Qemu
|
||||||
from tests.utils import asyncio_patch
|
from tests.utils import asyncio_patch
|
||||||
|
from unittest.mock import patch, MagicMock
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def fake_qemu_img_binary(tmpdir):
|
||||||
|
|
||||||
|
bin_path = str(tmpdir / "qemu-img")
|
||||||
|
with open(bin_path, "w+") as f:
|
||||||
|
f.write("1")
|
||||||
|
os.chmod(bin_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
|
||||||
|
return bin_path
|
||||||
|
|
||||||
|
|
||||||
def test_get_qemu_version(loop):
|
def test_get_qemu_version(loop):
|
||||||
@ -57,6 +69,7 @@ def test_binary_list(loop):
|
|||||||
assert {"path": os.path.join(os.environ["PATH"], "qemu-system-x42"), "version": version} in qemus
|
assert {"path": os.path.join(os.environ["PATH"], "qemu-system-x42"), "version": version} in qemus
|
||||||
assert {"path": os.path.join(os.environ["PATH"], "hello"), "version": version} not in qemus
|
assert {"path": os.path.join(os.environ["PATH"], "hello"), "version": version} not in qemus
|
||||||
|
|
||||||
|
|
||||||
def test_img_binary_list(loop):
|
def test_img_binary_list(loop):
|
||||||
|
|
||||||
files_to_create = ["qemu-img", "qemu-io", "qemu-system-x86", "qemu-system-x42", "qemu-kvm", "hello"]
|
files_to_create = ["qemu-img", "qemu-io", "qemu-system-x86", "qemu-system-x42", "qemu-kvm", "hello"]
|
||||||
@ -83,3 +96,52 @@ def test_img_binary_list(loop):
|
|||||||
def test_get_legacy_vm_workdir():
|
def test_get_legacy_vm_workdir():
|
||||||
|
|
||||||
assert Qemu.get_legacy_vm_workdir(42, "bla") == os.path.join("qemu", "vm-42")
|
assert Qemu.get_legacy_vm_workdir(42, "bla") == os.path.join("qemu", "vm-42")
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_image_abs_path(loop, tmpdir, fake_qemu_img_binary):
|
||||||
|
options = {
|
||||||
|
"format": "qcow2",
|
||||||
|
"preallocation": "metadata",
|
||||||
|
"cluster_size": 64,
|
||||||
|
"refcount_bits": 12,
|
||||||
|
"lazy_refcounts": "off",
|
||||||
|
"size": 100
|
||||||
|
}
|
||||||
|
with asyncio_patch("asyncio.create_subprocess_exec", return_value=MagicMock()) as process:
|
||||||
|
loop.run_until_complete(asyncio.async(Qemu.instance().create_disk(fake_qemu_img_binary, str(tmpdir / "hda.qcow2"), options)))
|
||||||
|
args, kwargs = process.call_args
|
||||||
|
assert args == (
|
||||||
|
fake_qemu_img_binary,
|
||||||
|
"create",
|
||||||
|
"-f",
|
||||||
|
"qcow2",
|
||||||
|
"-o",
|
||||||
|
"cluster_size=64",
|
||||||
|
"-o",
|
||||||
|
"lazy_refcounts=off",
|
||||||
|
"-o",
|
||||||
|
"preallocation=metadata",
|
||||||
|
"-o",
|
||||||
|
"refcount_bits=12",
|
||||||
|
str(tmpdir / "hda.qcow2"),
|
||||||
|
"100M"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_image_relative_path(loop, tmpdir, fake_qemu_img_binary):
|
||||||
|
options = {
|
||||||
|
"format": "raw",
|
||||||
|
"size": 100
|
||||||
|
}
|
||||||
|
with asyncio_patch("asyncio.create_subprocess_exec", return_value=MagicMock()) as process:
|
||||||
|
with patch("gns3server.modules.qemu.Qemu.get_images_directory", return_value=str(tmpdir)):
|
||||||
|
loop.run_until_complete(asyncio.async(Qemu.instance().create_disk(fake_qemu_img_binary, "hda.qcow2", options)))
|
||||||
|
args, kwargs = process.call_args
|
||||||
|
assert args == (
|
||||||
|
fake_qemu_img_binary,
|
||||||
|
"create",
|
||||||
|
"-f",
|
||||||
|
"raw",
|
||||||
|
str(tmpdir / "hda.qcow2"),
|
||||||
|
"100M"
|
||||||
|
)
|
||||||
|
@ -424,34 +424,3 @@ def test_get_qemu_img_not_exist(vm, tmpdir):
|
|||||||
vm._qemu_path = str(tmpdir / "qemu-sytem-x86_64")
|
vm._qemu_path = str(tmpdir / "qemu-sytem-x86_64")
|
||||||
with pytest.raises(QemuError):
|
with pytest.raises(QemuError):
|
||||||
vm._get_qemu_img()
|
vm._get_qemu_img()
|
||||||
|
|
||||||
|
|
||||||
def test_create_image(vm, loop, fake_qemu_img_binary):
|
|
||||||
options = {
|
|
||||||
"format": "qcow2",
|
|
||||||
"preallocation": "metadata",
|
|
||||||
"cluster_size": 64,
|
|
||||||
"refcount_bits": 12,
|
|
||||||
"lazy_refcounts": "off",
|
|
||||||
"size": 100
|
|
||||||
}
|
|
||||||
with asyncio_patch("asyncio.create_subprocess_exec", return_value=MagicMock()) as process:
|
|
||||||
filename = loop.run_until_complete(asyncio.async(vm._create_disk("hda", options)))
|
|
||||||
args, kwargs = process.call_args
|
|
||||||
assert args == (
|
|
||||||
fake_qemu_img_binary,
|
|
||||||
"create",
|
|
||||||
"-f",
|
|
||||||
"qcow2",
|
|
||||||
"-o",
|
|
||||||
"cluster_size=64",
|
|
||||||
"-o",
|
|
||||||
"lazy_refcounts=off",
|
|
||||||
"-o",
|
|
||||||
"preallocation=metadata",
|
|
||||||
"-o",
|
|
||||||
"refcount_bits=12",
|
|
||||||
os.path.join(vm.working_dir, "hda.qcow2"),
|
|
||||||
"100M"
|
|
||||||
)
|
|
||||||
assert filename == "hda.qcow2"
|
|
||||||
|
Loading…
Reference in New Issue
Block a user