1
0
mirror of https://github.com/GNS3/gns3-server synced 2024-11-24 17:28:08 +00:00

Merge pull request #388 from GNS3/boenrobot-addCapabilities

Add  qemu capabilities
This commit is contained in:
Julien Duponchelle 2016-01-04 16:34:33 +01:00
commit e73f81c9a3
6 changed files with 149 additions and 8 deletions

View File

@ -25,7 +25,9 @@ from ...schemas.nio import NIO_SCHEMA
from ...schemas.qemu import QEMU_CREATE_SCHEMA from ...schemas.qemu import QEMU_CREATE_SCHEMA
from ...schemas.qemu import QEMU_UPDATE_SCHEMA from ...schemas.qemu import QEMU_UPDATE_SCHEMA
from ...schemas.qemu import QEMU_OBJECT_SCHEMA from ...schemas.qemu import QEMU_OBJECT_SCHEMA
from ...schemas.qemu import QEMU_BINARY_FILTER_SCHEMA
from ...schemas.qemu import QEMU_BINARY_LIST_SCHEMA from ...schemas.qemu import QEMU_BINARY_LIST_SCHEMA
from ...schemas.qemu import QEMU_CAPABILITY_LIST_SCHEMA
from ...schemas.qemu import QEMU_IMAGE_CREATE_SCHEMA from ...schemas.qemu import QEMU_IMAGE_CREATE_SCHEMA
from ...schemas.vm import VM_LIST_IMAGES_SCHEMA from ...schemas.vm import VM_LIST_IMAGES_SCHEMA
from ...modules.qemu import Qemu from ...modules.qemu import Qemu
@ -300,10 +302,11 @@ class QEMUHandler:
404: "Instance doesn't exist" 404: "Instance doesn't exist"
}, },
description="Get a list of available Qemu binaries", description="Get a list of available Qemu binaries",
input=QEMU_BINARY_FILTER_SCHEMA,
output=QEMU_BINARY_LIST_SCHEMA) output=QEMU_BINARY_LIST_SCHEMA)
def list_binaries(request, response): def list_binaries(request, response):
binaries = yield from Qemu.binary_list() binaries = yield from Qemu.binary_list(request.json.get("archs", None))
response.json(binaries) response.json(binaries)
@classmethod @classmethod
@ -321,6 +324,21 @@ class QEMUHandler:
binaries = yield from Qemu.img_binary_list() binaries = yield from Qemu.img_binary_list()
response.json(binaries) response.json(binaries)
@Route.get(
r"/qemu/capabilities",
status_codes={
200: "Success"
},
description="Get a list of Qemu capabilities on this server",
output=QEMU_CAPABILITY_LIST_SCHEMA
)
def get_capabilities(request, response):
capabilities = {"kvm": []}
kvms = yield from Qemu.get_kvm_archs()
if kvms:
capabilities["kvm"] = kvms
response.json(capabilities)
@classmethod @classmethod
@Route.post( @Route.post(
r"/qemu/img", r"/qemu/img",

View File

@ -21,6 +21,7 @@ Qemu server module.
import asyncio import asyncio
import os import os
import platform
import sys import sys
import re import re
import subprocess import subprocess
@ -38,6 +39,33 @@ class Qemu(BaseManager):
_VM_CLASS = QemuVM _VM_CLASS = QemuVM
@staticmethod
@asyncio.coroutine
def get_kvm_archs():
"""
Gets a list of architectures for which KVM is available on this server.
:returns: List of architectures for which KVM is available on this server.
"""
kvm = []
try:
process = yield from asyncio.create_subprocess_exec("kvm-ok")
yield from process.wait()
except OSError:
return kvm
if process.returncode == 0:
arch = platform.machine()
if arch == "x86_64":
kvm.append("x86_64")
kvm.append("i386")
elif arch == "i386":
kvm.append("i386")
else:
kvm.append(platform.machine())
return kvm
@staticmethod @staticmethod
def paths_list(): def paths_list():
""" """
@ -82,7 +110,7 @@ class Qemu(BaseManager):
return paths return paths
@staticmethod @staticmethod
def binary_list(): def binary_list(archs=None):
""" """
Gets QEMU binaries list available on the host. Gets QEMU binaries list available on the host.
@ -96,9 +124,17 @@ class Qemu(BaseManager):
if (f.startswith("qemu-system") or f.startswith("qemu-kvm") or f == "qemu" or f == "qemu.exe") and \ if (f.startswith("qemu-system") or f.startswith("qemu-kvm") or f == "qemu" or f == "qemu.exe") and \
os.access(os.path.join(path, f), os.X_OK) and \ os.access(os.path.join(path, f), os.X_OK) and \
os.path.isfile(os.path.join(path, f)): os.path.isfile(os.path.join(path, f)):
if archs is not None:
for arch in archs:
if f.endswith(arch) or f.endswith("{}.exe".format(arch)) or f.endswith("{}w.exe".format(arch)):
qemu_path = os.path.join(path, f) qemu_path = os.path.join(path, f)
version = yield from Qemu.get_qemu_version(qemu_path) version = yield from Qemu.get_qemu_version(qemu_path)
qemus.append({"path": qemu_path, "version": version}) qemus.append({"path": qemu_path, "version": version})
else:
qemu_path = os.path.join(path, f)
version = yield from Qemu.get_qemu_version(qemu_path)
qemus.append({"path": qemu_path, "version": version})
except OSError: except OSError:
continue continue

View File

@ -601,6 +601,21 @@ QEMU_OBJECT_SCHEMA = {
"vm_directory"] "vm_directory"]
} }
QEMU_BINARY_FILTER_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Request validation for a list of qemu capabilities",
"properties": {
"archs": {
"description": "Architectures to filter binaries by",
"type": "array",
"items": {
"enum": QEMU_PLATFORMS
}
}
},
"additionalProperties": False,
}
QEMU_BINARY_LIST_SCHEMA = { QEMU_BINARY_LIST_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#", "$schema": "http://json-schema.org/draft-04/schema#",
"description": "Request validation for a list of qemu binaries", "description": "Request validation for a list of qemu binaries",
@ -626,6 +641,21 @@ QEMU_BINARY_LIST_SCHEMA = {
"additionalProperties": False, "additionalProperties": False,
} }
QEMU_CAPABILITY_LIST_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Request validation for a list of qemu capabilities",
"properties": {
"kvm": {
"description": "Architectures that KVM is enabled for",
"type": "array",
"items": {
"enum": QEMU_PLATFORMS
}
}
},
"additionalProperties": False,
}
QEMU_IMAGE_CREATE_SCHEMA = { QEMU_IMAGE_CREATE_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#", "$schema": "http://json-schema.org/draft-04/schema#",
"description": "Create a new qemu image. Options can be specific to a format. Read qemu-img manual for more information", "description": "Create a new qemu image. Options can be specific to a format. Read qemu-img manual for more information",

View File

@ -200,11 +200,24 @@ def test_qemu_list_binaries(server, vm):
{"path": "/tmp/2", "version": "2.1.0"}] {"path": "/tmp/2", "version": "2.1.0"}]
with asyncio_patch("gns3server.modules.qemu.Qemu.binary_list", return_value=ret) as mock: with asyncio_patch("gns3server.modules.qemu.Qemu.binary_list", return_value=ret) as mock:
response = server.get("/qemu/binaries".format(project_id=vm["project_id"]), example=True) response = server.get("/qemu/binaries".format(project_id=vm["project_id"]), example=True)
assert mock.called assert mock.called_with(None)
assert response.status == 200 assert response.status == 200
assert response.json == ret assert response.json == ret
def test_qemu_list_binaries_filter(server, vm):
ret = [
{"path": "/tmp/x86_64", "version": "2.2.0"},
{"path": "/tmp/alpha", "version": "2.1.0"},
{"path": "/tmp/i386", "version": "2.1.0"}
]
with asyncio_patch("gns3server.modules.qemu.Qemu.binary_list", return_value=ret) as mock:
response = server.get("/qemu/binaries".format(project_id=vm["project_id"]), body={"archs": ["i386"]}, example=True)
assert response.status == 200
assert mock.called_with(["i386"])
assert response.json == ret
def test_vms(server, tmpdir, fake_qemu_vm): def test_vms(server, tmpdir, fake_qemu_vm):
with patch("gns3server.modules.Qemu.get_images_directory", return_value=str(tmpdir), example=True): with patch("gns3server.modules.Qemu.get_images_directory", return_value=str(tmpdir), example=True):
@ -312,3 +325,9 @@ def test_create_img_absolute_local(server):
response = server.post("/qemu/img", body=body, example=True) response = server.post("/qemu/img", body=body, example=True)
assert response.status == 201 assert response.status == 201
def test_capabilities(server):
with asyncio_patch("gns3server.modules.Qemu.get_kvm_archs", return_value=["x86_64"]):
response = server.get("/qemu/capabilities", example=True)
assert response.json["kvm"] == ["x86_64"]

View File

@ -20,6 +20,7 @@ import stat
import asyncio import asyncio
import sys import sys
import pytest import pytest
import platform
from gns3server.modules.qemu import Qemu from gns3server.modules.qemu import Qemu
from gns3server.modules.qemu.qemu_error import QemuError from gns3server.modules.qemu.qemu_error import QemuError
@ -58,18 +59,32 @@ def test_binary_list(loop):
os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
with asyncio_patch("gns3server.modules.qemu.subprocess_check_output", return_value="QEMU emulator version 2.2.0, Copyright (c) 2003-2008 Fabrice Bellard") as mock: with asyncio_patch("gns3server.modules.qemu.subprocess_check_output", return_value="QEMU emulator version 2.2.0, Copyright (c) 2003-2008 Fabrice Bellard") as mock:
qemus = loop.run_until_complete(asyncio.async(Qemu.binary_list()))
if sys.platform.startswith("win"): if sys.platform.startswith("win"):
version = "" version = ""
else: else:
version = "2.2.0" version = "2.2.0"
qemus = loop.run_until_complete(asyncio.async(Qemu.binary_list()))
assert {"path": os.path.join(os.environ["PATH"], "qemu-system-x86"), "version": version} in qemus assert {"path": os.path.join(os.environ["PATH"], "qemu-system-x86"), "version": version} in qemus
assert {"path": os.path.join(os.environ["PATH"], "qemu-kvm"), "version": version} in qemus assert {"path": os.path.join(os.environ["PATH"], "qemu-kvm"), "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"], "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
qemus = loop.run_until_complete(asyncio.async(Qemu.binary_list(["x86"])))
assert {"path": os.path.join(os.environ["PATH"], "qemu-system-x86"), "version": version} in qemus
assert {"path": os.path.join(os.environ["PATH"], "qemu-kvm"), "version": version} not in qemus
assert {"path": os.path.join(os.environ["PATH"], "qemu-system-x42"), "version": version} not in qemus
assert {"path": os.path.join(os.environ["PATH"], "hello"), "version": version} not in qemus
qemus = loop.run_until_complete(asyncio.async(Qemu.binary_list(["x86", "x42"])))
assert {"path": os.path.join(os.environ["PATH"], "qemu-system-x86"), "version": version} in qemus
assert {"path": os.path.join(os.environ["PATH"], "qemu-kvm"), "version": version} not 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
def test_img_binary_list(loop): def test_img_binary_list(loop):
@ -160,3 +175,21 @@ def test_create_image_exist(loop, tmpdir, fake_qemu_img_binary):
with pytest.raises(QemuError): with pytest.raises(QemuError):
loop.run_until_complete(asyncio.async(Qemu.instance().create_disk(fake_qemu_img_binary, "hda.qcow2", options))) loop.run_until_complete(asyncio.async(Qemu.instance().create_disk(fake_qemu_img_binary, "hda.qcow2", options)))
assert not process.called assert not process.called
def test_get_kvm_archs_no_kvm(loop):
with asyncio_patch("asyncio.create_subprocess_exec", side_effect=FileNotFoundError('kvm-ok')):
archs = loop.run_until_complete(asyncio.async(Qemu.get_kvm_archs()))
assert archs == []
def test_get_kvm_archs_kvm_ok(loop):
process = MagicMock()
with asyncio_patch("asyncio.create_subprocess_exec", return_value=process):
process.returncode = 0
archs = loop.run_until_complete(asyncio.async(Qemu.get_kvm_archs()))
if platform.machine() == 'x86_64':
assert archs == ['x86_64', 'i386']
else:
assert archs == platform.machine()

View File

@ -50,6 +50,11 @@ class _asyncio_patch:
future = asyncio.Future() future = asyncio.Future()
if "return_value" in self.kwargs: if "return_value" in self.kwargs:
future.set_result(self.kwargs["return_value"]) future.set_result(self.kwargs["return_value"])
elif "side_effect" in self.kwargs:
if isinstance(self.kwargs["side_effect"], Exception):
future.set_exception(self.kwargs["side_effect"])
else:
raise NotImplementedError
else: else:
future.set_result(True) future.set_result(True)
return future return future