diff --git a/gns3server/handlers/__init__.py b/gns3server/handlers/__init__.py
index 03e6dcab..5d558245 100644
--- a/gns3server/handlers/__init__.py
+++ b/gns3server/handlers/__init__.py
@@ -5,4 +5,5 @@ __all__ = ["version_handler",
"virtualbox_handler",
"dynamips_vm_handler",
"dynamips_device_handler",
- "iou_handler"]
+ "iou_handler",
+ "qemu_handler"]
diff --git a/gns3server/handlers/qemu_handler.py b/gns3server/handlers/qemu_handler.py
new file mode 100644
index 00000000..a40bfd78
--- /dev/null
+++ b/gns3server/handlers/qemu_handler.py
@@ -0,0 +1,254 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2015 GNS3 Technologies Inc.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+import os
+
+
+from ..web.route import Route
+from ..modules.port_manager import PortManager
+from ..schemas.qemu import QEMU_CREATE_SCHEMA
+from ..schemas.qemu import QEMU_UPDATE_SCHEMA
+from ..schemas.qemu import QEMU_OBJECT_SCHEMA
+from ..schemas.qemu import QEMU_NIO_SCHEMA
+from ..modules.qemu import Qemu
+
+
+class QEMUHandler:
+
+ """
+ API entry points for QEMU.
+ """
+
+ @classmethod
+ @Route.post(
+ r"/projects/{project_id}/qemu/vms",
+ parameters={
+ "project_id": "UUID for the project"
+ },
+ status_codes={
+ 201: "Instance created",
+ 400: "Invalid request",
+ 409: "Conflict"
+ },
+ description="Create a new Qemu.instance",
+ input=QEMU_CREATE_SCHEMA,
+ output=QEMU_OBJECT_SCHEMA)
+ def create(request, response):
+
+ qemu = Qemu.instance()
+ vm = yield from qemu.create_vm(request.json["name"],
+ request.match_info["project_id"],
+ request.json.get("vm_id"),
+ qemu_path=request.json.get("qemu_path"),
+ console=request.json.get("console"),
+ monitor=request.json.get("monitor"),
+ console_host=PortManager.instance().console_host,
+ monitor_host=PortManager.instance().console_host,
+ )
+ response.set_status(201)
+ response.json(vm)
+
+ @classmethod
+ @Route.get(
+ r"/projects/{project_id}/qemu/vms/{vm_id}",
+ parameters={
+ "project_id": "UUID for the project",
+ "vm_id": "UUID for the instance"
+ },
+ status_codes={
+ 200: "Success",
+ 400: "Invalid request",
+ 404: "Instance doesn't exist"
+ },
+ description="Get a Qemu.instance",
+ output=QEMU_OBJECT_SCHEMA)
+ def show(request, response):
+
+ qemu_manager = Qemu.instance()
+ vm = qemu_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
+ response.json(vm)
+
+ @classmethod
+ @Route.put(
+ r"/projects/{project_id}/qemu/vms/{vm_id}",
+ parameters={
+ "project_id": "UUID for the project",
+ "vm_id": "UUID for the instance"
+ },
+ status_codes={
+ 200: "Instance updated",
+ 400: "Invalid request",
+ 404: "Instance doesn't exist",
+ 409: "Conflict"
+ },
+ description="Update a Qemu.instance",
+ input=QEMU_UPDATE_SCHEMA,
+ output=QEMU_OBJECT_SCHEMA)
+ def update(request, response):
+
+ qemu_manager = Qemu.instance()
+ vm = qemu_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
+ vm.name = request.json.get("name", vm.name)
+ vm.console = request.json.get("console", vm.console)
+ vm.qemu_path = request.json.get("qemu_path", vm.qemu_path)
+
+ response.json(vm)
+
+ @classmethod
+ @Route.delete(
+ r"/projects/{project_id}/qemu/vms/{vm_id}",
+ parameters={
+ "project_id": "UUID for the project",
+ "vm_id": "UUID for the instance"
+ },
+ status_codes={
+ 204: "Instance deleted",
+ 400: "Invalid request",
+ 404: "Instance doesn't exist"
+ },
+ description="Delete a Qemu.instance")
+ def delete(request, response):
+
+ yield from Qemu.instance().delete_vm(request.match_info["vm_id"])
+ response.set_status(204)
+
+ @classmethod
+ @Route.post(
+ r"/projects/{project_id}/qemu/vms/{vm_id}/start",
+ parameters={
+ "project_id": "UUID for the project",
+ "vm_id": "UUID for the instance"
+ },
+ status_codes={
+ 204: "Instance started",
+ 400: "Invalid request",
+ 404: "Instance doesn't exist"
+ },
+ description="Start a Qemu.instance")
+ def start(request, response):
+
+ qemu_manager = Qemu.instance()
+ vm = qemu_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
+ yield from vm.start()
+ response.set_status(204)
+
+ @classmethod
+ @Route.post(
+ r"/projects/{project_id}/qemu/vms/{vm_id}/stop",
+ parameters={
+ "project_id": "UUID for the project",
+ "vm_id": "UUID for the instance"
+ },
+ status_codes={
+ 204: "Instance stopped",
+ 400: "Invalid request",
+ 404: "Instance doesn't exist"
+ },
+ description="Stop a Qemu.instance")
+ def stop(request, response):
+
+ qemu_manager = Qemu.instance()
+ vm = qemu_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
+ yield from vm.stop()
+ response.set_status(204)
+
+ @classmethod
+ @Route.post(
+ r"/projects/{project_id}/qemu/vms/{vm_id}/reload",
+ parameters={
+ "project_id": "UUID for the project",
+ "vm_id": "UUID for the instance",
+ },
+ status_codes={
+ 204: "Instance reloaded",
+ 400: "Invalid request",
+ 404: "Instance doesn't exist"
+ },
+ description="Reload a Qemu.instance")
+ def reload(request, response):
+
+ qemu_manager = Qemu.instance()
+ vm = qemu_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
+ yield from vm.reload()
+ response.set_status(204)
+
+ @classmethod
+ @Route.post(
+ r"/projects/{project_id}/qemu/vms/{vm_id}/suspend",
+ parameters={
+ "project_id": "UUID for the project",
+ "vm_id": "UUID for the instance",
+ },
+ status_codes={
+ 204: "Instance suspended",
+ 400: "Invalid request",
+ 404: "Instance doesn't exist"
+ },
+ description="Reload a Qemu.instance")
+ def suspend(request, response):
+
+ qemu_manager = Qemu.instance()
+ vm = qemu_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
+ yield from vm.suspend()
+ response.set_status(204)
+
+ @Route.post(
+ r"/projects/{project_id}/qemu/vms/{vm_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
+ parameters={
+ "project_id": "UUID for the project",
+ "vm_id": "UUID for the instance",
+ "adapter_number": "Network adapter where the nio is located",
+ "port_number": "Port where the nio should be added"
+ },
+ status_codes={
+ 201: "NIO created",
+ 400: "Invalid request",
+ 404: "Instance doesn't exist"
+ },
+ description="Add a NIO to a Qemu.instance",
+ input=QEMU_NIO_SCHEMA,
+ output=QEMU_NIO_SCHEMA)
+ def create_nio(request, response):
+
+ qemu_manager = Qemu.instance()
+ vm = qemu_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
+ nio = qemu_manager.create_nio(vm.qemu_path, request.json)
+ vm.adapter_add_nio_binding(int(request.match_info["adapter_number"]), int(request.match_info["port_number"]), nio)
+ response.set_status(201)
+ response.json(nio)
+
+ @classmethod
+ @Route.delete(
+ r"/projects/{project_id}/qemu/vms/{vm_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
+ parameters={
+ "project_id": "UUID for the project",
+ "vm_id": "UUID for the instance",
+ "adapter_number": "Network adapter where the nio is located",
+ "port_number": "Port from where the nio should be removed"
+ },
+ status_codes={
+ 204: "NIO deleted",
+ 400: "Invalid request",
+ 404: "Instance doesn't exist"
+ },
+ description="Remove a NIO from a Qemu.instance")
+ def delete_nio(request, response):
+
+ qemu_manager = Qemu.instance()
+ vm = qemu_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
+ vm.adapter_remove_nio_binding(int(request.match_info["adapter_number"]), int(request.match_info["port_number"]))
+ response.set_status(204)
diff --git a/gns3server/modules/__init__.py b/gns3server/modules/__init__.py
index 25c1012f..0f55e396 100644
--- a/gns3server/modules/__init__.py
+++ b/gns3server/modules/__init__.py
@@ -19,5 +19,6 @@ from .vpcs import VPCS
from .virtualbox import VirtualBox
from .dynamips import Dynamips
from .iou import IOU
+from .qemu import Qemu
-MODULES = [VPCS, VirtualBox, Dynamips, IOU]
+MODULES = [VPCS, VirtualBox, Dynamips, IOU, Qemu]
diff --git a/gns3server/modules/base_vm.py b/gns3server/modules/base_vm.py
index c5b39405..6a3f29c1 100644
--- a/gns3server/modules/base_vm.py
+++ b/gns3server/modules/base_vm.py
@@ -51,9 +51,12 @@ class BaseVM:
else:
self._console = self._manager.port_manager.get_free_console_port()
- log.debug("{module}: {name} [{id}] initialized".format(module=self.manager.module_name,
- name=self.name,
- id=self.id))
+ log.debug("{module}: {name} [{id}] initialized. Console port {console}".format(
+ module=self.manager.module_name,
+ name=self.name,
+ id=self.id,
+ console=self._console
+ ))
def __del__(self):
diff --git a/gns3server/modules/qemu/qemu_vm.py b/gns3server/modules/qemu/qemu_vm.py
index 9cb5960e..01b36e47 100644
--- a/gns3server/modules/qemu/qemu_vm.py
+++ b/gns3server/modules/qemu/qemu_vm.py
@@ -59,12 +59,8 @@ class QemuVM(BaseVM):
:param qemu_id: QEMU VM instance ID
:param console: TCP console port
:param console_host: IP address to bind for console connections
- :param console_start_port_range: TCP console port range start
- :param console_end_port_range: TCP console port range end
:param monitor: TCP monitor port
:param monitor_host: IP address to bind for monitor connections
- :param monitor_start_port_range: TCP monitor port range start
- :param monitor_end_port_range: TCP monitor port range end
"""
def __init__(self,
@@ -76,27 +72,19 @@ class QemuVM(BaseVM):
host="127.0.0.1",
console=None,
console_host="0.0.0.0",
- console_start_port_range=5001,
- console_end_port_range=5500,
monitor=None,
- monitor_host="0.0.0.0",
- monitor_start_port_range=5501,
- monitor_end_port_range=6000):
+ monitor_host="0.0.0.0"):
super().__init__(name, vm_id, project, manager, console=console)
self._host = host
+ self._console_host = console_host
self._command = []
self._started = False
self._process = None
self._cpulimit_process = None
self._stdout_file = ""
- self._console_host = console_host
- self._console_start_port_range = console_start_port_range
- self._console_end_port_range = console_end_port_range
self._monitor_host = monitor_host
- self._monitor_start_port_range = monitor_start_port_range
- self._monitor_end_port_range = monitor_end_port_range
# QEMU settings
self.qemu_path = qemu_path
@@ -104,7 +92,6 @@ class QemuVM(BaseVM):
self._hdb_disk_image = ""
self._options = ""
self._ram = 256
- self._console = console
self._monitor = monitor
self._ethernet_adapters = []
self._adapter_type = "e1000"
@@ -629,6 +616,7 @@ class QemuVM(BaseVM):
Executes a command with QEMU monitor when this VM is running.
:param command: QEMU monitor command (e.g. info status, stop etc.)
+ :params expected: An array with the string attended (Default None)
:param timeout: how long to wait for QEMU monitor
:returns: result of the command (Match object or None)
@@ -721,11 +709,12 @@ class QemuVM(BaseVM):
log.info("QEMU VM is not paused to be resumed, current status is {}".format(vm_status))
@asyncio.coroutine
- def port_add_nio_binding(self, adapter_id, nio):
+ def adapter_add_nio_binding(self, adapter_id, port_id, nio):
"""
Adds a port NIO binding.
:param adapter_id: adapter ID
+ :param port_id: port ID
:param nio: NIO instance to add to the slot/port
"""
@@ -761,11 +750,12 @@ class QemuVM(BaseVM):
adapter_id=adapter_id))
@asyncio.coroutine
- def port_remove_nio_binding(self, adapter_id):
+ def adapter_remove_nio_binding(self, adapter_id, port_id):
"""
Removes a port NIO binding.
:param adapter_id: adapter ID
+ :param port_id: port ID
:returns: NIO instance
"""
@@ -981,3 +971,12 @@ class QemuVM(BaseVM):
command.extend(shlex.split(additional_options))
command.extend(self._network_options())
return command
+
+ def __json__(self):
+ return {
+ "vm_id": self.id,
+ "project_id": self.project.id,
+ "name": self.name,
+ "console": self.console,
+ "monitor": self.monitor
+ }
diff --git a/gns3server/schemas/qemu.py b/gns3server/schemas/qemu.py
new file mode 100644
index 00000000..bbd2aabf
--- /dev/null
+++ b/gns3server/schemas/qemu.py
@@ -0,0 +1,332 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2014 GNS3 Technologies Inc.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+
+QEMU_CREATE_SCHEMA = {
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "description": "Request validation to create a new QEMU VM instance",
+ "type": "object",
+ "properties": {
+ "name": {
+ "description": "QEMU VM instance name",
+ "type": "string",
+ "minLength": 1,
+ },
+ "qemu_path": {
+ "description": "Path to QEMU",
+ "type": "string",
+ "minLength": 1,
+ },
+ "console": {
+ "description": "console TCP port",
+ "minimum": 1,
+ "maximum": 65535,
+ "type": "integer"
+ },
+ "monitor": {
+ "description": "monitor TCP port",
+ "minimum": 1,
+ "maximum": 65535,
+ "type": "integer"
+ },
+ },
+ "additionalProperties": False,
+ "required": ["name", "qemu_path"],
+}
+
+QEMU_UPDATE_SCHEMA = {
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "description": "Request validation to update a QEMU VM instance",
+ "type": "object",
+ "properties": {
+ "name": {
+ "description": "QEMU VM instance name",
+ "type": "string",
+ "minLength": 1,
+ },
+ "qemu_path": {
+ "description": "path to QEMU",
+ "type": "string",
+ "minLength": 1,
+ },
+ "hda_disk_image": {
+ "description": "QEMU hda disk image path",
+ "type": "string",
+ },
+ "hdb_disk_image": {
+ "description": "QEMU hdb disk image path",
+ "type": "string",
+ },
+ "ram": {
+ "description": "amount of RAM in MB",
+ "type": "integer"
+ },
+ "adapters": {
+ "description": "number of adapters",
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 32,
+ },
+ "adapter_type": {
+ "description": "QEMU adapter type",
+ "type": "string",
+ "minLength": 1,
+ },
+ "console": {
+ "description": "console TCP port",
+ "minimum": 1,
+ "maximum": 65535,
+ "type": "integer"
+ },
+ "monitor": {
+ "description": "monitor TCP port",
+ "minimum": 1,
+ "maximum": 65535,
+ "type": "integer"
+ },
+ "initrd": {
+ "description": "QEMU initrd path",
+ "type": "string",
+ },
+ "kernel_image": {
+ "description": "QEMU kernel image path",
+ "type": "string",
+ },
+ "kernel_command_line": {
+ "description": "QEMU kernel command line",
+ "type": "string",
+ },
+ "cloud_path": {
+ "description": "Path to the image in the cloud object store",
+ "type": "string",
+ },
+ "legacy_networking": {
+ "description": "Use QEMU legagy networking commands (-net syntax)",
+ "type": "boolean",
+ },
+ "cpu_throttling": {
+ "description": "Percentage of CPU allowed for QEMU",
+ "minimum": 0,
+ "maximum": 800,
+ "type": "integer",
+ },
+ "process_priority": {
+ "description": "Process priority for QEMU",
+ "enum": ["realtime",
+ "very high",
+ "high",
+ "normal",
+ "low",
+ "very low"]
+ },
+ "options": {
+ "description": "Additional QEMU options",
+ "type": "string",
+ },
+ },
+ "additionalProperties": False,
+}
+
+QEMU_START_SCHEMA = {
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "description": "Request validation to start a QEMU VM instance",
+ "type": "object",
+ "properties": {
+ "id": {
+ "description": "QEMU VM instance ID",
+ "type": "integer"
+ },
+ },
+ "additionalProperties": False,
+ "required": ["id"]
+}
+
+QEMU_NIO_SCHEMA = {
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "description": "Request validation to add a NIO for a VPCS instance",
+ "type": "object",
+ "definitions": {
+ "UDP": {
+ "description": "UDP Network Input/Output",
+ "properties": {
+ "type": {
+ "enum": ["nio_udp"]
+ },
+ "lport": {
+ "description": "Local port",
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 65535
+ },
+ "rhost": {
+ "description": "Remote host",
+ "type": "string",
+ "minLength": 1
+ },
+ "rport": {
+ "description": "Remote port",
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 65535
+ }
+ },
+ "required": ["type", "lport", "rhost", "rport"],
+ "additionalProperties": False
+ },
+ "Ethernet": {
+ "description": "Generic Ethernet Network Input/Output",
+ "properties": {
+ "type": {
+ "enum": ["nio_generic_ethernet"]
+ },
+ "ethernet_device": {
+ "description": "Ethernet device name e.g. eth0",
+ "type": "string",
+ "minLength": 1
+ },
+ },
+ "required": ["type", "ethernet_device"],
+ "additionalProperties": False
+ },
+ "TAP": {
+ "description": "TAP Network Input/Output",
+ "properties": {
+ "type": {
+ "enum": ["nio_tap"]
+ },
+ "tap_device": {
+ "description": "TAP device name e.g. tap0",
+ "type": "string",
+ "minLength": 1
+ },
+ },
+ "required": ["type", "tap_device"],
+ "additionalProperties": False
+ },
+ },
+ "oneOf": [
+ {"$ref": "#/definitions/UDP"},
+ {"$ref": "#/definitions/Ethernet"},
+ {"$ref": "#/definitions/TAP"},
+ ],
+ "additionalProperties": True,
+ "required": ["type"]
+}
+
+QEMU_OBJECT_SCHEMA = {
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "description": "Request validation for a QEMU VM instance",
+ "type": "object",
+ "properties": {
+ "vm_id": {
+ "description": "QEMU VM uuid",
+ "type": "string",
+ "minLength": 1,
+ },
+ "project_id": {
+ "description": "Project uuid",
+ "type": "string",
+ "minLength": 1,
+ },
+ "name": {
+ "description": "QEMU VM instance name",
+ "type": "string",
+ "minLength": 1,
+ },
+ "qemu_path": {
+ "description": "path to QEMU",
+ "type": "string",
+ "minLength": 1,
+ },
+ "hda_disk_image": {
+ "description": "QEMU hda disk image path",
+ "type": "string",
+ },
+ "hdb_disk_image": {
+ "description": "QEMU hdb disk image path",
+ "type": "string",
+ },
+ "ram": {
+ "description": "amount of RAM in MB",
+ "type": "integer"
+ },
+ "adapters": {
+ "description": "number of adapters",
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 32,
+ },
+ "adapter_type": {
+ "description": "QEMU adapter type",
+ "type": "string",
+ "minLength": 1,
+ },
+ "console": {
+ "description": "console TCP port",
+ "minimum": 1,
+ "maximum": 65535,
+ "type": "integer"
+ },
+ "monitor": {
+ "description": "monitor TCP port",
+ "minimum": 1,
+ "maximum": 65535,
+ "type": "integer"
+ },
+ "initrd": {
+ "description": "QEMU initrd path",
+ "type": "string",
+ },
+ "kernel_image": {
+ "description": "QEMU kernel image path",
+ "type": "string",
+ },
+ "kernel_command_line": {
+ "description": "QEMU kernel command line",
+ "type": "string",
+ },
+ "cloud_path": {
+ "description": "Path to the image in the cloud object store",
+ "type": "string",
+ },
+ "legacy_networking": {
+ "description": "Use QEMU legagy networking commands (-net syntax)",
+ "type": "boolean",
+ },
+ "cpu_throttling": {
+ "description": "Percentage of CPU allowed for QEMU",
+ "minimum": 0,
+ "maximum": 800,
+ "type": "integer",
+ },
+ "process_priority": {
+ "description": "Process priority for QEMU",
+ "enum": ["realtime",
+ "very high",
+ "high",
+ "normal",
+ "low",
+ "very low"]
+ },
+ "options": {
+ "description": "Additional QEMU options",
+ "type": "string",
+ },
+ },
+ "additionalProperties": False,
+ "required": ["vm_id"]
+}
diff --git a/tests/api/test_qemu.py b/tests/api/test_qemu.py
new file mode 100644
index 00000000..25cc9f9b
--- /dev/null
+++ b/tests/api/test_qemu.py
@@ -0,0 +1,169 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2015 GNS3 Technologies Inc.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+import pytest
+import os
+import stat
+from tests.utils import asyncio_patch
+from unittest.mock import patch
+
+
+@pytest.fixture
+def fake_qemu_bin():
+
+ bin_path = os.path.join(os.environ["PATH"], "qemu_x42")
+ 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
+
+
+@pytest.fixture
+def base_params(tmpdir, fake_qemu_bin):
+ """Return standard parameters"""
+ return {"name": "PC TEST 1", "qemu_path": fake_qemu_bin}
+
+
+@pytest.fixture
+def vm(server, project, base_params):
+ response = server.post("/projects/{project_id}/qemu/vms".format(project_id=project.id), base_params)
+ assert response.status == 201
+ return response.json
+
+
+def test_qemu_create(server, project, base_params):
+ 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
+
+
+def test_qemu_create_with_params(server, project, base_params):
+ params = base_params
+
+ response = server.post("/projects/{project_id}/qemu/vms".format(project_id=project.id), params, example=True)
+ 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
+
+
+def test_qemu_get(server, project, vm):
+ response = server.get("/projects/{project_id}/qemu/vms/{vm_id}".format(project_id=vm["project_id"], vm_id=vm["vm_id"]), example=True)
+ assert response.status == 200
+ assert response.route == "/projects/{project_id}/qemu/vms/{vm_id}"
+ assert response.json["name"] == "PC TEST 1"
+ assert response.json["project_id"] == project.id
+
+
+def test_qemu_start(server, vm):
+ with asyncio_patch("gns3server.modules.qemu.qemu_vm.QemuVM.start", return_value=True) as mock:
+ response = server.post("/projects/{project_id}/qemu/vms/{vm_id}/start".format(project_id=vm["project_id"], vm_id=vm["vm_id"]))
+ assert mock.called
+ assert response.status == 204
+
+
+def test_qemu_stop(server, vm):
+ with asyncio_patch("gns3server.modules.qemu.qemu_vm.QemuVM.stop", return_value=True) as mock:
+ response = server.post("/projects/{project_id}/qemu/vms/{vm_id}/stop".format(project_id=vm["project_id"], vm_id=vm["vm_id"]))
+ assert mock.called
+ assert response.status == 204
+
+
+def test_qemu_reload(server, vm):
+ with asyncio_patch("gns3server.modules.qemu.qemu_vm.QemuVM.reload", return_value=True) as mock:
+ response = server.post("/projects/{project_id}/qemu/vms/{vm_id}/reload".format(project_id=vm["project_id"], vm_id=vm["vm_id"]))
+ assert mock.called
+ assert response.status == 204
+
+
+def test_qemu_suspend(server, vm):
+ with asyncio_patch("gns3server.modules.qemu.qemu_vm.QemuVM.suspend", return_value=True) as mock:
+ response = server.post("/projects/{project_id}/qemu/vms/{vm_id}/suspend".format(project_id=vm["project_id"], vm_id=vm["vm_id"]))
+ assert mock.called
+ assert response.status == 204
+
+
+def test_qemu_delete(server, vm):
+ with asyncio_patch("gns3server.modules.qemu.Qemu.delete_vm", return_value=True) as mock:
+ response = server.delete("/projects/{project_id}/qemu/vms/{vm_id}".format(project_id=vm["project_id"], vm_id=vm["vm_id"]))
+ assert mock.called
+ assert response.status == 204
+
+
+def test_qemu_update(server, vm, tmpdir, free_console_port, project):
+ params = {
+ "name": "test",
+ "console": free_console_port,
+ }
+ response = server.put("/projects/{project_id}/qemu/vms/{vm_id}".format(project_id=vm["project_id"], vm_id=vm["vm_id"]), params)
+ assert response.status == 200
+ assert response.json["name"] == "test"
+ assert response.json["console"] == free_console_port
+
+
+def test_qemu_nio_create_udp(server, vm):
+ response = server.post("/projects/{project_id}/qemu/vms/{vm_id}/adapters/1/ports/0/nio".format(project_id=vm["project_id"], vm_id=vm["vm_id"]), {"type": "nio_udp",
+ "lport": 4242,
+ "rport": 4343,
+ "rhost": "127.0.0.1"},
+ example=True)
+ assert response.status == 201
+ assert response.route == "/projects/{project_id}/qemu/vms/{vm_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio"
+ assert response.json["type"] == "nio_udp"
+
+
+def test_qemu_nio_create_ethernet(server, vm):
+ response = server.post("/projects/{project_id}/qemu/vms/{vm_id}/adapters/1/ports/0/nio".format(project_id=vm["project_id"], vm_id=vm["vm_id"]), {"type": "nio_generic_ethernet",
+ "ethernet_device": "eth0",
+ },
+ example=True)
+ assert response.status == 201
+ assert response.route == "/projects/{project_id}/qemu/vms/{vm_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio"
+ assert response.json["type"] == "nio_generic_ethernet"
+ assert response.json["ethernet_device"] == "eth0"
+
+
+def test_qemu_nio_create_ethernet_different_port(server, vm):
+ response = server.post("/projects/{project_id}/qemu/vms/{vm_id}/adapters/0/ports/3/nio".format(project_id=vm["project_id"], vm_id=vm["vm_id"]), {"type": "nio_generic_ethernet",
+ "ethernet_device": "eth0",
+ },
+ example=False)
+ assert response.status == 201
+ assert response.route == "/projects/{project_id}/qemu/vms/{vm_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio"
+ assert response.json["type"] == "nio_generic_ethernet"
+ assert response.json["ethernet_device"] == "eth0"
+
+
+def test_qemu_nio_create_tap(server, vm):
+ with patch("gns3server.modules.base_manager.BaseManager._has_privileged_access", return_value=True):
+ response = server.post("/projects/{project_id}/qemu/vms/{vm_id}/adapters/1/ports/0/nio".format(project_id=vm["project_id"], vm_id=vm["vm_id"]), {"type": "nio_tap",
+ "tap_device": "test"})
+ assert response.status == 201
+ assert response.route == "/projects/{project_id}/qemu/vms/{vm_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio"
+ assert response.json["type"] == "nio_tap"
+
+
+def test_qemu_delete_nio(server, vm):
+ server.post("/projects/{project_id}/qemu/vms/{vm_id}/adapters/1/ports/0/nio".format(project_id=vm["project_id"], vm_id=vm["vm_id"]), {"type": "nio_udp",
+ "lport": 4242,
+ "rport": 4343,
+ "rhost": "127.0.0.1"})
+ response = server.delete("/projects/{project_id}/qemu/vms/{vm_id}/adapters/1/ports/0/nio".format(project_id=vm["project_id"], vm_id=vm["vm_id"]), example=True)
+ assert response.status == 204
+ assert response.route == "/projects/{project_id}/qemu/vms/{vm_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio"
diff --git a/tests/modules/qemu/test_qemu_vm.py b/tests/modules/qemu/test_qemu_vm.py
index 238520d7..63b89572 100644
--- a/tests/modules/qemu/test_qemu_vm.py
+++ b/tests/modules/qemu/test_qemu_vm.py
@@ -20,6 +20,7 @@ import aiohttp
import asyncio
import os
import stat
+import re
from tests.utils import asyncio_patch
@@ -83,7 +84,7 @@ def test_stop(loop, vm):
with asyncio_patch("asyncio.create_subprocess_exec", return_value=process):
nio = Qemu.instance().create_nio(vm.qemu_path, {"type": "nio_udp", "lport": 4242, "rport": 4243, "rhost": "127.0.0.1"})
- vm.port_add_nio_binding(0, nio)
+ vm.adapter_add_nio_binding(0, 0, nio)
loop.run_until_complete(asyncio.async(vm.start()))
assert vm.is_running()
loop.run_until_complete(asyncio.async(vm.stop()))
@@ -98,23 +99,32 @@ def test_reload(loop, vm):
assert mock.called_with("system_reset")
+def test_suspend(loop, vm):
+
+ control_vm_result = MagicMock()
+ control_vm_result.match.group.decode.return_value = "running"
+ with asyncio_patch("gns3server.modules.qemu.QemuVM._control_vm", return_value=control_vm_result) as mock:
+ loop.run_until_complete(asyncio.async(vm.suspend()))
+ assert mock.called_with("system_reset")
+
+
def test_add_nio_binding_udp(vm, loop):
nio = Qemu.instance().create_nio(vm.qemu_path, {"type": "nio_udp", "lport": 4242, "rport": 4243, "rhost": "127.0.0.1"})
- loop.run_until_complete(asyncio.async(vm.port_add_nio_binding(0, nio)))
+ loop.run_until_complete(asyncio.async(vm.adapter_add_nio_binding(0, 0, nio)))
assert nio.lport == 4242
-def test_add_nio_binding_tap(vm, loop):
+def test_add_nio_binding_ethernet(vm, loop):
with patch("gns3server.modules.base_manager.BaseManager._has_privileged_access", return_value=True):
- nio = Qemu.instance().create_nio(vm.qemu_path, {"type": "nio_tap", "tap_device": "test"})
- loop.run_until_complete(asyncio.async(vm.port_add_nio_binding(0, nio)))
- assert nio.tap_device == "test"
+ nio = Qemu.instance().create_nio(vm.qemu_path, {"type": "nio_generic_ethernet", "ethernet_device": "eth0"})
+ loop.run_until_complete(asyncio.async(vm.adapter_add_nio_binding(0, 0, nio)))
+ assert nio.ethernet_device == "eth0"
def test_port_remove_nio_binding(vm, loop):
nio = Qemu.instance().create_nio(vm.qemu_path, {"type": "nio_udp", "lport": 4242, "rport": 4243, "rhost": "127.0.0.1"})
- loop.run_until_complete(asyncio.async(vm.port_add_nio_binding(0, nio)))
- loop.run_until_complete(asyncio.async(vm.port_remove_nio_binding(0)))
+ loop.run_until_complete(asyncio.async(vm.adapter_add_nio_binding(0, 0, nio)))
+ loop.run_until_complete(asyncio.async(vm.adapter_remove_nio_binding(0, 0)))
assert vm._ethernet_adapters[0].ports[0] is None