diff --git a/gns3server/handlers/api/qemu_handler.py b/gns3server/handlers/api/qemu_handler.py index 426ca1a2..7b43d34e 100644 --- a/gns3server/handlers/api/qemu_handler.py +++ b/gns3server/handlers/api/qemu_handler.py @@ -51,9 +51,10 @@ class QEMUHandler: qemu = Qemu.instance() vm = yield from qemu.create_vm(request.json.pop("name"), request.match_info["project_id"], - request.json.get("vm_id"), - qemu_path=request.json.get("qemu_path"), - console=request.json.get("console")) + request.json.pop("vm_id", None), + qemu_path=request.json.pop("qemu_path", None), + platform=request.json.pop("platform", None), + console=request.json.pop("console", None)) for name, value in request.json.items(): if hasattr(vm, name) and getattr(vm, name) != value: diff --git a/gns3server/modules/qemu/qemu_vm.py b/gns3server/modules/qemu/qemu_vm.py index 69aea17e..32c8f145 100644 --- a/gns3server/modules/qemu/qemu_vm.py +++ b/gns3server/modules/qemu/qemu_vm.py @@ -22,6 +22,7 @@ order to run a QEMU VM. import sys import os +import re import shutil import subprocess import shlex @@ -35,13 +36,16 @@ from ..nios.nio_udp import NIOUDP from ..nios.nio_tap import NIOTAP from ..nios.nio_nat import NIONAT from ..base_vm import BaseVM -from ...schemas.qemu import QEMU_OBJECT_SCHEMA +from ...schemas.qemu import QEMU_OBJECT_SCHEMA, QEMU_PLATFORMS from ...utils.asyncio import monitor_process import logging log = logging.getLogger(__name__) + + + class QemuVM(BaseVM): module_name = 'qemu' @@ -54,10 +58,11 @@ class QemuVM(BaseVM): :param manager: Manager instance :param console: TCP console port :param qemu_path: path to the QEMU binary + :param platform: Platform to emulate :param console: TCP console port """ - def __init__(self, name, vm_id, project, manager, qemu_path=None, console=None): + def __init__(self, name, vm_id, project, manager, qemu_path=None, console=None, platform=None): super().__init__(name, vm_id, project, manager, console=console) server_config = manager.config.get_section_config("Server") @@ -70,7 +75,18 @@ class QemuVM(BaseVM): self._stdout_file = "" # QEMU VM settings - self.qemu_path = qemu_path + + if qemu_path: + try: + self.qemu_path = qemu_path + except QemuError as e: + if platform: + self.platform = platform + else: + raise e + else: + self.platform = platform + self._hda_disk_image = "" self._hdb_disk_image = "" self._hdc_disk_image = "" @@ -124,6 +140,20 @@ class QemuVM(BaseVM): if qemu_path and os.pathsep not in qemu_path: qemu_path = shutil.which(qemu_path) + self._check_qemu_path(qemu_path) + self._qemu_path = qemu_path + self._platform = os.path.basename(qemu_path) + if self._platform == "qemu-kvm": + self._platform = "x86_64" + else: + self._platform = re.sub(r'^qemu-system-(.*)(w.exe)?$', r'\1', os.path.basename(qemu_path), re.IGNORECASE) + if self._platform not in QEMU_PLATFORMS: + raise QemuError("Platform {} is unknown".format(self._platform)) + log.info('QEMU VM "{name}" [{id}] has set the QEMU path to {qemu_path}'.format(name=self._name, + id=self._id, + qemu_path=qemu_path)) + + def _check_qemu_path(self, qemu_path): if qemu_path is None: raise QemuError("QEMU binary path is not set or not found in the path") if not os.path.exists(qemu_path): @@ -131,10 +161,20 @@ class QemuVM(BaseVM): if not os.access(qemu_path, os.X_OK): raise QemuError("QEMU binary '{}' is not executable".format(qemu_path)) - self._qemu_path = qemu_path - log.info('QEMU VM "{name}" [{id}] has set the QEMU path to {qemu_path}'.format(name=self._name, - id=self._id, - qemu_path=qemu_path)) + @property + def platform(self): + """ + Return the current platform + """ + return self._platform + + @platform.setter + def platform(self, platform): + self._platform = platform + if sys.platform.startswith("win"): + self.qemu_path = "qemu-system-{}w.exe".format(platform) + else: + self.qemu_path = "qemu-system-{}".format(platform) @property def hda_disk_image(self): diff --git a/gns3server/schemas/qemu.py b/gns3server/schemas/qemu.py index 45a6dce7..40cd4f3b 100644 --- a/gns3server/schemas/qemu.py +++ b/gns3server/schemas/qemu.py @@ -15,6 +15,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +QEMU_PLATFORMS = ["aarch64", "alpha", "arm", "cris", "i386", "lm32", "m68k", "microblaze", "microblazeel", "mips", "mips64", "mips64el", "mipsel", "moxie", "or32", "ppc", "ppc64", "ppcemb", "s390x", "sh4", "sh4eb", "sparc", "sparc64", "tricore", "unicore32", "x86_64", "xtensa", "xtensaeb"] + QEMU_CREATE_SCHEMA = { "$schema": "http://json-schema.org/draft-04/schema#", @@ -38,9 +40,13 @@ QEMU_CREATE_SCHEMA = { }, "qemu_path": { "description": "Path to QEMU", - "type": "string", + "type": ["string", "null"], "minLength": 1, }, + "platform": { + "description": "Platform to emulate", + "enum": QEMU_PLATFORMS + ["null"] + }, "console": { "description": "console TCP port", "minimum": 1, @@ -130,7 +136,7 @@ QEMU_CREATE_SCHEMA = { }, }, "additionalProperties": False, - "required": ["name", "qemu_path"], + "required": ["name"], } QEMU_UPDATE_SCHEMA = { @@ -148,6 +154,10 @@ QEMU_UPDATE_SCHEMA = { "type": ["string", "null"], "minLength": 1, }, + "platform": { + "description": "Platform to emulate", + "enum": QEMU_PLATFORMS + ["null"] + }, "console": { "description": "console TCP port", "minimum": 1, @@ -264,6 +274,10 @@ QEMU_OBJECT_SCHEMA = { "type": "string", "minLength": 1, }, + "platform": { + "description": "Platform to emulate", + "enum": QEMU_PLATFORMS + }, "hda_disk_image": { "description": "QEMU hda disk image path", "type": "string", @@ -352,7 +366,7 @@ QEMU_OBJECT_SCHEMA = { }, }, "additionalProperties": False, - "required": ["vm_id", "project_id", "name", "qemu_path", "hda_disk_image", "hdb_disk_image", + "required": ["vm_id", "project_id", "name", "qemu_path", "platform", "hda_disk_image", "hdb_disk_image", "hdc_disk_image", "hdd_disk_image", "ram", "adapters", "adapter_type", "mac_address", "console", "initrd", "kernel_image", "kernel_command_line", "legacy_networking", "acpi_shutdown", "kvm", "cpu_throttling", "process_priority", "options"] diff --git a/tests/handlers/api/test_qemu.py b/tests/handlers/api/test_qemu.py index cf08992a..c91fa792 100644 --- a/tests/handlers/api/test_qemu.py +++ b/tests/handlers/api/test_qemu.py @@ -25,7 +25,7 @@ from unittest.mock import patch @pytest.fixture def fake_qemu_bin(): - bin_path = os.path.join(os.environ["PATH"], "qemu_x42") + bin_path = os.path.join(os.environ["PATH"], "qemu_x86_64") with open(bin_path, "w+") as f: f.write("1") os.chmod(bin_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) @@ -51,7 +51,7 @@ def base_params(tmpdir, fake_qemu_bin): @pytest.fixture def fake_qemu_bin(): - bin_path = os.path.join(os.environ["PATH"], "qemu_x42") + bin_path = os.path.join(os.environ["PATH"], "qemu-system-x86_64") with open(bin_path, "w+") as f: f.write("1") os.chmod(bin_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) @@ -65,12 +65,27 @@ def vm(server, project, base_params): return response.json -def test_qemu_create(server, project, base_params): +def test_qemu_create(server, project, base_params, fake_qemu_bin): response = server.post("/projects/{project_id}/qemu/vms".format(project_id=project.id), base_params) assert response.status == 201 assert response.route == "/projects/{project_id}/qemu/vms" assert response.json["name"] == "PC TEST 1" assert response.json["project_id"] == project.id + assert response.json["qemu_path"] == fake_qemu_bin + assert response.json["platform"] == "x86_64" + + +def test_qemu_create_platform(server, project, base_params, fake_qemu_bin): + base_params["qemu_path"] = None + base_params["platform"] = "x86_64" + + response = server.post("/projects/{project_id}/qemu/vms".format(project_id=project.id), base_params) + assert response.status == 201 + assert response.route == "/projects/{project_id}/qemu/vms" + assert response.json["name"] == "PC TEST 1" + assert response.json["project_id"] == project.id + assert response.json["qemu_path"] == fake_qemu_bin + assert response.json["platform"] == "x86_64" def test_qemu_create_with_params(server, project, base_params): diff --git a/tests/modules/qemu/test_qemu_vm.py b/tests/modules/qemu/test_qemu_vm.py index 01b9b6f6..818782c8 100644 --- a/tests/modules/qemu/test_qemu_vm.py +++ b/tests/modules/qemu/test_qemu_vm.py @@ -52,9 +52,9 @@ def fake_qemu_img_binary(): def fake_qemu_binary(): if sys.platform.startswith("win"): - bin_path = os.path.join(os.environ["PATH"], "qemu_x42.EXE") + bin_path = os.path.join(os.environ["PATH"], "qemu-system-x86_64.EXE") else: - bin_path = os.path.join(os.environ["PATH"], "qemu_x42") + bin_path = os.path.join(os.environ["PATH"], "qemu-system-x86_64") with open(bin_path, "w+") as f: f.write("1") os.chmod(bin_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) @@ -171,7 +171,9 @@ def test_set_qemu_path(vm, tmpdir, fake_qemu_binary): vm.qemu_path = None # Should not crash with unicode characters - path = str(tmpdir / "bla\u62FF") + path = str(tmpdir / "\u62FF" / "qemu-system-mips") + + os.makedirs( str(tmpdir / "\u62FF") ) # Raise because file doesn't exists with pytest.raises(QemuError): @@ -189,14 +191,45 @@ def test_set_qemu_path(vm, tmpdir, fake_qemu_binary): vm.qemu_path = path assert vm.qemu_path == path + assert vm.platform == "mips" def test_set_qemu_path_environ(vm, tmpdir, fake_qemu_binary): # It should find the binary in the path - vm.qemu_path = "qemu_x42" + vm.qemu_path = "qemu-system-x86_64" assert vm.qemu_path == fake_qemu_binary + assert vm.platform == "x86_64" + + +@pytest.mark.skipif(sys.platform.startswith("linux") is False, reason="Supported only on linux") +def test_set_qemu_path_kvm_binary(vm, tmpdir, fake_qemu_binary): + + bin_path = os.path.join(os.environ["PATH"], "qemu-kvm") + 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 + + # It should find the binary in the path + vm.qemu_path = "qemu-kvm" + + assert vm.qemu_path == fake_qemu_binary + assert vm.platform == "x86_64" + + +def test_set_platform(project, manager): + + with patch("shutil.which", return_value="/bin/qemu-system-x86_64") as which_mock: + with patch("gns3server.modules.qemu.QemuVM._check_qemu_path"): + vm = QemuVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", project, manager, platform="x86_64") + if sys.platform.startswith("win"): + which_mock.assert_called_with("qemu-system-x86_64w.exe") + else: + which_mock.assert_called_with("qemu-system-x86_64") + assert vm.platform == "x86_64" + assert vm.qemu_path == "/bin/qemu-system-x86_64" def test_disk_options(vm, loop, fake_qemu_img_binary):