diff --git a/gns3server/modules/__init__.py b/gns3server/modules/__init__.py
index 5bd4c110..dd628589 100644
--- a/gns3server/modules/__init__.py
+++ b/gns3server/modules/__init__.py
@@ -20,8 +20,9 @@ from .base import IModule
from .dynamips import Dynamips
from .vpcs import VPCS
from .virtualbox import VirtualBox
+from .qemu import Qemu
-MODULES = [Dynamips, VPCS, VirtualBox]
+MODULES = [Dynamips, VPCS, VirtualBox, Qemu]
if sys.platform.startswith("linux"):
# IOU runs only on Linux
diff --git a/gns3server/modules/iou/nios/nio.py b/gns3server/modules/iou/nios/nio.py
index 197d4817..059d56a3 100644
--- a/gns3server/modules/iou/nios/nio.py
+++ b/gns3server/modules/iou/nios/nio.py
@@ -22,7 +22,7 @@ Base interface for NIOs.
class NIO(object):
"""
- IOU NIO.
+ Network Input/Output.
"""
def __init__(self):
diff --git a/gns3server/modules/iou/nios/nio_generic_ethernet.py b/gns3server/modules/iou/nios/nio_generic_ethernet.py
index 45c89b4e..068e9fc3 100644
--- a/gns3server/modules/iou/nios/nio_generic_ethernet.py
+++ b/gns3server/modules/iou/nios/nio_generic_ethernet.py
@@ -24,7 +24,7 @@ from .nio import NIO
class NIO_GenericEthernet(NIO):
"""
- NIO generic Ethernet NIO.
+ Generic Ethernet NIO.
:param ethernet_device: Ethernet device name (e.g. eth0)
"""
diff --git a/gns3server/modules/iou/nios/nio_tap.py b/gns3server/modules/iou/nios/nio_tap.py
index 3164e933..95ec631d 100644
--- a/gns3server/modules/iou/nios/nio_tap.py
+++ b/gns3server/modules/iou/nios/nio_tap.py
@@ -24,7 +24,7 @@ from .nio import NIO
class NIO_TAP(NIO):
"""
- IOU TAP NIO.
+ TAP NIO.
:param tap_device: TAP device name (e.g. tap0)
"""
diff --git a/gns3server/modules/iou/nios/nio_udp.py b/gns3server/modules/iou/nios/nio_udp.py
index 41ffbc4f..2c850351 100644
--- a/gns3server/modules/iou/nios/nio_udp.py
+++ b/gns3server/modules/iou/nios/nio_udp.py
@@ -24,7 +24,7 @@ from .nio import NIO
class NIO_UDP(NIO):
"""
- IOU UDP NIO.
+ UDP NIO.
:param lport: local port number
:param rhost: remote address/host
diff --git a/gns3server/modules/qemu/__init__.py b/gns3server/modules/qemu/__init__.py
new file mode 100644
index 00000000..1935b10c
--- /dev/null
+++ b/gns3server/modules/qemu/__init__.py
@@ -0,0 +1,684 @@
+# -*- 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 server module.
+"""
+
+import os
+import socket
+import shutil
+import subprocess
+import re
+
+from gns3server.modules import IModule
+from gns3server.config import Config
+from .qemu_vm import QemuVM
+from .qemu_error import QemuError
+from .nios.nio_udp import NIO_UDP
+from ..attic import find_unused_port
+
+from .schemas import QEMU_CREATE_SCHEMA
+from .schemas import QEMU_DELETE_SCHEMA
+from .schemas import QEMU_UPDATE_SCHEMA
+from .schemas import QEMU_START_SCHEMA
+from .schemas import QEMU_STOP_SCHEMA
+from .schemas import QEMU_SUSPEND_SCHEMA
+from .schemas import QEMU_RELOAD_SCHEMA
+from .schemas import QEMU_ALLOCATE_UDP_PORT_SCHEMA
+from .schemas import QEMU_ADD_NIO_SCHEMA
+from .schemas import QEMU_DELETE_NIO_SCHEMA
+
+QEMU_BINARIES = ["qemu.exe",
+ "qemu-system-arm",
+ "qemu-system-mips",
+ "qemu-system-ppc",
+ "qemu-system-sparc",
+ "qemu-system-x86",
+ "qemu-system-i386",
+ "qemu-system-x86_64"]
+
+import logging
+log = logging.getLogger(__name__)
+
+
+class Qemu(IModule):
+ """
+ QEMU module.
+
+ :param name: module name
+ :param args: arguments for the module
+ :param kwargs: named arguments for the module
+ """
+
+ def __init__(self, name, *args, **kwargs):
+
+ # get the qemu-img location
+ config = Config.instance()
+ qemu_config = config.get_section_config(name.upper())
+ self._qemu_img_path = qemu_config.get("qemu_img_path")
+ if not self._qemu_img_path or not os.path.isfile(self._qemu_img_path):
+ paths = [os.getcwd()] + os.environ["PATH"].split(":")
+ # look for qemu-img in the current working directory and $PATH
+ for path in paths:
+ try:
+ if "qemu-img" in os.listdir(path) and os.access(os.path.join(path, "qemu-img"), os.X_OK):
+ self._qemu_img_path = os.path.join(path, "qemu-img")
+ break
+ except OSError:
+ continue
+
+ if not self._qemu_img_path:
+ log.warning("qemu-img couldn't be found!")
+ elif not os.access(self._qemu_img_path, os.X_OK):
+ log.warning("qemu-img is not executable")
+
+ # a new process start when calling IModule
+ IModule.__init__(self, name, *args, **kwargs)
+ self._qemu_instances = {}
+
+ config = Config.instance()
+ qemu_config = config.get_section_config(name.upper())
+ self._console_start_port_range = qemu_config.get("console_start_port_range", 5001)
+ self._console_end_port_range = qemu_config.get("console_end_port_range", 5500)
+ self._allocated_udp_ports = []
+ self._udp_start_port_range = qemu_config.get("udp_start_port_range", 40001)
+ self._udp_end_port_range = qemu_config.get("udp_end_port_range", 45500)
+ self._host = qemu_config.get("host", kwargs["host"])
+ self._projects_dir = kwargs["projects_dir"]
+ self._tempdir = kwargs["temp_dir"]
+ self._working_dir = self._projects_dir
+
+ def stop(self, signum=None):
+ """
+ Properly stops the module.
+
+ :param signum: signal number (if called by the signal handler)
+ """
+
+ # delete all QEMU instances
+ for qemu_id in self._qemu_instances:
+ qemu_instance = self._qemu_instances[qemu_id]
+ qemu_instance.delete()
+
+ IModule.stop(self, signum) # this will stop the I/O loop
+
+ def get_qemu_instance(self, qemu_id):
+ """
+ Returns a QEMU VM instance.
+
+ :param qemu_id: QEMU VM identifier
+
+ :returns: QemuVM instance
+ """
+
+ if qemu_id not in self._qemu_instances:
+ log.debug("QEMU VM ID {} doesn't exist".format(qemu_id), exc_info=1)
+ self.send_custom_error("QEMU VM ID {} doesn't exist".format(qemu_id))
+ return None
+ return self._qemu_instances[qemu_id]
+
+ @IModule.route("qemu.reset")
+ def reset(self, request):
+ """
+ Resets the module.
+
+ :param request: JSON request
+ """
+
+ # delete all QEMU instances
+ for qemu_id in self._qemu_instances:
+ qemu_instance = self._qemu_instances[qemu_id]
+ qemu_instance.delete()
+
+ # resets the instance IDs
+ QemuVM.reset()
+
+ self._qemu_instances.clear()
+ self._allocated_udp_ports.clear()
+
+ log.info("QEMU module has been reset")
+
+ @IModule.route("qemu.settings")
+ def settings(self, request):
+ """
+ Set or update settings.
+
+ Optional request parameters:
+ - working_dir (path to a working directory)
+ - project_name
+ - console_start_port_range
+ - console_end_port_range
+ - udp_start_port_range
+ - udp_end_port_range
+
+ :param request: JSON request
+ """
+
+ if request is None:
+ self.send_param_error()
+ return
+
+ if "qemu_img_path" in request and request["qemu_img_path"]:
+ self._qemu_img_path = request["qemu_img_path"]
+ log.info("QEMU image utility path set to {}".format(self._qemu_img_path))
+ for qemu_id in self._qemu_instances:
+ qemu_instance = self._qemu_instances[qemu_id]
+ qemu_instance.qemu_img_path = self._qemu_img_path
+
+ if "working_dir" in request:
+ new_working_dir = request["working_dir"]
+ log.info("this server is local with working directory path to {}".format(new_working_dir))
+ else:
+ new_working_dir = os.path.join(self._projects_dir, request["project_name"])
+ log.info("this server is remote with working directory path to {}".format(new_working_dir))
+ if self._projects_dir != self._working_dir != new_working_dir:
+ if not os.path.isdir(new_working_dir):
+ try:
+ shutil.move(self._working_dir, new_working_dir)
+ except OSError as e:
+ log.error("could not move working directory from {} to {}: {}".format(self._working_dir,
+ new_working_dir,
+ e))
+ return
+
+ # update the working directory if it has changed
+ if self._working_dir != new_working_dir:
+ self._working_dir = new_working_dir
+ for qemu_id in self._qemu_instances:
+ qemu_instance = self._qemu_instances[qemu_id]
+ qemu_instance.working_dir = os.path.join(self._working_dir, "qemu", "vm-{}".format(qemu_instance.id))
+
+ if "console_start_port_range" in request and "console_end_port_range" in request:
+ self._console_start_port_range = request["console_start_port_range"]
+ self._console_end_port_range = request["console_end_port_range"]
+
+ if "udp_start_port_range" in request and "udp_end_port_range" in request:
+ self._udp_start_port_range = request["udp_start_port_range"]
+ self._udp_end_port_range = request["udp_end_port_range"]
+
+ log.debug("received request {}".format(request))
+
+ @IModule.route("qemu.create")
+ def qemu_create(self, request):
+ """
+ Creates a new QEMU VM instance.
+
+ Mandatory request parameters:
+ - name (QEMU VM name)
+ - qemu_path (path to the Qemu binary)
+
+ Optional request parameters:
+ - console (QEMU VM console port)
+
+ Response parameters:
+ - id (QEMU VM instance identifier)
+ - name (QEMU VM name)
+ - default settings
+
+ :param request: JSON request
+ """
+
+ # validate the request
+ if not self.validate_request(request, QEMU_CREATE_SCHEMA):
+ return
+
+ name = request["name"]
+ qemu_path = request["qemu_path"]
+ console = request.get("console")
+ qemu_id = request.get("qemu_id")
+
+ try:
+ qemu_instance = QemuVM(name,
+ qemu_path,
+ self._qemu_img_path,
+ self._working_dir,
+ self._host,
+ qemu_id,
+ console,
+ self._console_start_port_range,
+ self._console_end_port_range)
+
+ except QemuError as e:
+ self.send_custom_error(str(e))
+ return
+
+ response = {"name": qemu_instance.name,
+ "id": qemu_instance.id}
+
+ defaults = qemu_instance.defaults()
+ response.update(defaults)
+ self._qemu_instances[qemu_instance.id] = qemu_instance
+ self.send_response(response)
+
+ @IModule.route("qemu.delete")
+ def qemu_delete(self, request):
+ """
+ Deletes a QEMU VM instance.
+
+ Mandatory request parameters:
+ - id (QEMU VM instance identifier)
+
+ Response parameter:
+ - True on success
+
+ :param request: JSON request
+ """
+
+ # validate the request
+ if not self.validate_request(request, QEMU_DELETE_SCHEMA):
+ return
+
+ # get the instance
+ qemu_instance = self.get_qemu_instance(request["id"])
+ if not qemu_instance:
+ return
+
+ try:
+ qemu_instance.clean_delete()
+ del self._qemu_instances[request["id"]]
+ except QemuError as e:
+ self.send_custom_error(str(e))
+ return
+
+ self.send_response(True)
+
+ @IModule.route("qemu.update")
+ def qemu_update(self, request):
+ """
+ Updates a QEMU VM instance
+
+ Mandatory request parameters:
+ - id (QEMU VM instance identifier)
+
+ Optional request parameters:
+ - any setting to update
+
+ Response parameters:
+ - updated settings
+
+ :param request: JSON request
+ """
+
+ # validate the request
+ if not self.validate_request(request, QEMU_UPDATE_SCHEMA):
+ return
+
+ # get the instance
+ qemu_instance = self.get_qemu_instance(request["id"])
+ if not qemu_instance:
+ return
+
+ # update the QEMU VM settings
+ response = {}
+ for name, value in request.items():
+ if hasattr(qemu_instance, name) and getattr(qemu_instance, name) != value:
+ try:
+ setattr(qemu_instance, name, value)
+ response[name] = value
+ except QemuError as e:
+ self.send_custom_error(str(e))
+ return
+
+ self.send_response(response)
+
+ @IModule.route("qemu.start")
+ def qemu_start(self, request):
+ """
+ Starts a QEMU VM instance.
+
+ Mandatory request parameters:
+ - id (QEMU VM instance identifier)
+
+ Response parameters:
+ - True on success
+
+ :param request: JSON request
+ """
+
+ # validate the request
+ if not self.validate_request(request, QEMU_START_SCHEMA):
+ return
+
+ # get the instance
+ qemu_instance = self.get_qemu_instance(request["id"])
+ if not qemu_instance:
+ return
+
+ try:
+ qemu_instance.start()
+ except QemuError as e:
+ self.send_custom_error(str(e))
+ return
+ self.send_response(True)
+
+ @IModule.route("qemu.stop")
+ def qemu_stop(self, request):
+ """
+ Stops a QEMU VM instance.
+
+ Mandatory request parameters:
+ - id (QEMU VM instance identifier)
+
+ Response parameters:
+ - True on success
+
+ :param request: JSON request
+ """
+
+ # validate the request
+ if not self.validate_request(request, QEMU_STOP_SCHEMA):
+ return
+
+ # get the instance
+ qemu_instance = self.get_qemu_instance(request["id"])
+ if not qemu_instance:
+ return
+
+ try:
+ qemu_instance.stop()
+ except QemuError as e:
+ self.send_custom_error(str(e))
+ return
+ self.send_response(True)
+
+ @IModule.route("qemu.reload")
+ def qemu_reload(self, request):
+ """
+ Reloads a QEMU VM instance.
+
+ Mandatory request parameters:
+ - id (QEMU VM identifier)
+
+ Response parameters:
+ - True on success
+
+ :param request: JSON request
+ """
+
+ # validate the request
+ if not self.validate_request(request, QEMU_RELOAD_SCHEMA):
+ return
+
+ # get the instance
+ qemu_instance = self.get_qemu_instance(request["id"])
+ if not qemu_instance:
+ return
+
+ try:
+ qemu_instance.reload()
+ except QemuError as e:
+ self.send_custom_error(str(e))
+ return
+ self.send_response(True)
+
+ @IModule.route("qemu.stop")
+ def qemu_stop(self, request):
+ """
+ Stops a QEMU VM instance.
+
+ Mandatory request parameters:
+ - id (QEMU VM instance identifier)
+
+ Response parameters:
+ - True on success
+
+ :param request: JSON request
+ """
+
+ # validate the request
+ if not self.validate_request(request, QEMU_STOP_SCHEMA):
+ return
+
+ # get the instance
+ qemu_instance = self.get_qemu_instance(request["id"])
+ if not qemu_instance:
+ return
+
+ try:
+ qemu_instance.stop()
+ except QemuError as e:
+ self.send_custom_error(str(e))
+ return
+ self.send_response(True)
+
+ @IModule.route("qemu.suspend")
+ def qemu_suspend(self, request):
+ """
+ Suspends a QEMU VM instance.
+
+ Mandatory request parameters:
+ - id (QEMU VM instance identifier)
+
+ Response parameters:
+ - True on success
+
+ :param request: JSON request
+ """
+
+ # validate the request
+ if not self.validate_request(request, QEMU_SUSPEND_SCHEMA):
+ return
+
+ # get the instance
+ qemu_instance = self.get_qemu_instance(request["id"])
+ if not qemu_instance:
+ return
+
+ try:
+ qemu_instance.suspend()
+ except QemuError as e:
+ self.send_custom_error(str(e))
+ return
+ self.send_response(True)
+
+ @IModule.route("qemu.allocate_udp_port")
+ def allocate_udp_port(self, request):
+ """
+ Allocates a UDP port in order to create an UDP NIO.
+
+ Mandatory request parameters:
+ - id (QEMU VM identifier)
+ - port_id (unique port identifier)
+
+ Response parameters:
+ - port_id (unique port identifier)
+ - lport (allocated local port)
+
+ :param request: JSON request
+ """
+
+ # validate the request
+ if not self.validate_request(request, QEMU_ALLOCATE_UDP_PORT_SCHEMA):
+ return
+
+ # get the instance
+ qemu_instance = self.get_qemu_instance(request["id"])
+ if not qemu_instance:
+ return
+
+ try:
+ port = find_unused_port(self._udp_start_port_range,
+ self._udp_end_port_range,
+ host=self._host,
+ socket_type="UDP",
+ ignore_ports=self._allocated_udp_ports)
+ except Exception as e:
+ self.send_custom_error(str(e))
+ return
+
+ self._allocated_udp_ports.append(port)
+ log.info("{} [id={}] has allocated UDP port {} with host {}".format(qemu_instance.name,
+ qemu_instance.id,
+ port,
+ self._host))
+
+ response = {"lport": port,
+ "port_id": request["port_id"]}
+ self.send_response(response)
+
+ @IModule.route("qemu.add_nio")
+ def add_nio(self, request):
+ """
+ Adds an NIO (Network Input/Output) for a QEMU VM instance.
+
+ Mandatory request parameters:
+ - id (QEMU VM instance identifier)
+ - port (port number)
+ - port_id (unique port identifier)
+ - nio (one of the following)
+ - type "nio_udp"
+ - lport (local port)
+ - rhost (remote host)
+ - rport (remote port)
+
+ Response parameters:
+ - port_id (unique port identifier)
+
+ :param request: JSON request
+ """
+
+ # validate the request
+ if not self.validate_request(request, QEMU_ADD_NIO_SCHEMA):
+ return
+
+ # get the instance
+ qemu_instance = self.get_qemu_instance(request["id"])
+ if not qemu_instance:
+ return
+
+ port = request["port"]
+ try:
+ nio = None
+ if request["nio"]["type"] == "nio_udp":
+ lport = request["nio"]["lport"]
+ rhost = request["nio"]["rhost"]
+ rport = request["nio"]["rport"]
+ try:
+ #TODO: handle IPv6
+ with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
+ sock.connect((rhost, rport))
+ except OSError as e:
+ raise QemuError("Could not create an UDP connection to {}:{}: {}".format(rhost, rport, e))
+ nio = NIO_UDP(lport, rhost, rport)
+ if not nio:
+ raise QemuError("Requested NIO does not exist or is not supported: {}".format(request["nio"]["type"]))
+ except QemuError as e:
+ self.send_custom_error(str(e))
+ return
+
+ try:
+ qemu_instance.port_add_nio_binding(port, nio)
+ except QemuError as e:
+ self.send_custom_error(str(e))
+ return
+
+ self.send_response({"port_id": request["port_id"]})
+
+ @IModule.route("qemu.delete_nio")
+ def delete_nio(self, request):
+ """
+ Deletes an NIO (Network Input/Output).
+
+ Mandatory request parameters:
+ - id (QEMU VM instance identifier)
+ - port (port identifier)
+
+ Response parameters:
+ - True on success
+
+ :param request: JSON request
+ """
+
+ # validate the request
+ if not self.validate_request(request, QEMU_DELETE_NIO_SCHEMA):
+ return
+
+ # get the instance
+ qemu_instance = self.get_qemu_instance(request["id"])
+ if not qemu_instance:
+ return
+
+ port = request["port"]
+ try:
+ nio = qemu_instance.port_remove_nio_binding(port)
+ if isinstance(nio, NIO_UDP) and nio.lport in self._allocated_udp_ports:
+ self._allocated_udp_ports.remove(nio.lport)
+ except QemuError as e:
+ self.send_custom_error(str(e))
+ return
+
+ self.send_response(True)
+
+ def _get_qemu_version(self, qemu_path):
+ """
+ Gets the Qemu version.
+
+ :param qemu_path: path to Qemu
+ """
+
+ try:
+ output = subprocess.check_output([qemu_path, "--version"])
+ match = re.search("QEMU emulator version ([0-9a-z\-\.]+)", output.decode("utf-8"))
+ if match:
+ version = match.group(1)
+ return version
+ else:
+ raise QemuError("Could not determine the Qemu version for {}".format(qemu_path))
+ except (OSError, subprocess.CalledProcessError) as e:
+ raise QemuError("Error while looking for the Qemu version: {}".format(e))
+
+ @IModule.route("qemu.qemu_list")
+ def qemu_list(self, request):
+ """
+ Gets QEMU binaries list.
+
+ Response parameters:
+ - Server address/host
+ - List of Qemu binaries
+ """
+
+ qemus = []
+ paths = [os.getcwd()] + os.environ["PATH"].split(":")
+ # look for Qemu binaries in the current working directory and $PATH
+ for path in paths:
+ for qemu_binary in QEMU_BINARIES:
+ try:
+ if qemu_binary in os.listdir(path) and os.access(os.path.join(path, qemu_binary), os.X_OK):
+ qemu_path = os.path.join(path, qemu_binary)
+ version = self._get_qemu_version(qemu_path)
+ qemus.append({"path": qemu_path, "version": version})
+ except OSError:
+ continue
+
+ response = {"server": self._host,
+ "qemus": qemus}
+ self.send_response(response)
+
+ @IModule.route("qemu.echo")
+ def echo(self, request):
+ """
+ Echo end point for testing purposes.
+
+ :param request: JSON request
+ """
+
+ if request is None:
+ self.send_param_error()
+ else:
+ log.debug("received request {}".format(request))
+ self.send_response(request)
diff --git a/gns3server/modules/qemu/adapters/__init__.py b/gns3server/modules/qemu/adapters/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/gns3server/modules/qemu/adapters/adapter.py b/gns3server/modules/qemu/adapters/adapter.py
new file mode 100644
index 00000000..cf439427
--- /dev/null
+++ b/gns3server/modules/qemu/adapters/adapter.py
@@ -0,0 +1,104 @@
+# -*- 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 .
+
+
+class Adapter(object):
+ """
+ Base class for adapters.
+
+ :param interfaces: number of interfaces supported by this adapter.
+ """
+
+ def __init__(self, interfaces=1):
+
+ self._interfaces = interfaces
+
+ self._ports = {}
+ for port_id in range(0, interfaces):
+ self._ports[port_id] = None
+
+ def removable(self):
+ """
+ Returns True if the adapter can be removed from a slot
+ and False if not.
+
+ :returns: boolean
+ """
+
+ return True
+
+ def port_exists(self, port_id):
+ """
+ Checks if a port exists on this adapter.
+
+ :returns: True is the port exists,
+ False otherwise.
+ """
+
+ if port_id in self._ports:
+ return True
+ return False
+
+ def add_nio(self, port_id, nio):
+ """
+ Adds a NIO to a port on this adapter.
+
+ :param port_id: port ID (integer)
+ :param nio: NIO instance
+ """
+
+ self._ports[port_id] = nio
+
+ def remove_nio(self, port_id):
+ """
+ Removes a NIO from a port on this adapter.
+
+ :param port_id: port ID (integer)
+ """
+
+ self._ports[port_id] = None
+
+ def get_nio(self, port_id):
+ """
+ Returns the NIO assigned to a port.
+
+ :params port_id: port ID (integer)
+
+ :returns: NIO instance
+ """
+
+ return self._ports[port_id]
+
+ @property
+ def ports(self):
+ """
+ Returns port to NIO mapping
+
+ :returns: dictionary port -> NIO
+ """
+
+ return self._ports
+
+ @property
+ def interfaces(self):
+ """
+ Returns the number of interfaces supported by this adapter.
+
+ :returns: number of interfaces
+ """
+
+ return self._interfaces
diff --git a/gns3server/modules/qemu/adapters/ethernet_adapter.py b/gns3server/modules/qemu/adapters/ethernet_adapter.py
new file mode 100644
index 00000000..27426ec2
--- /dev/null
+++ b/gns3server/modules/qemu/adapters/ethernet_adapter.py
@@ -0,0 +1,31 @@
+# -*- 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 .
+
+from .adapter import Adapter
+
+
+class EthernetAdapter(Adapter):
+ """
+ QEMU Ethernet adapter.
+ """
+
+ def __init__(self):
+ Adapter.__init__(self, interfaces=1)
+
+ def __str__(self):
+
+ return "QEMU Ethernet adapter"
diff --git a/gns3server/modules/qemu/nios/__init__.py b/gns3server/modules/qemu/nios/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/gns3server/modules/qemu/nios/nio.py b/gns3server/modules/qemu/nios/nio.py
new file mode 100644
index 00000000..eee5f1d5
--- /dev/null
+++ b/gns3server/modules/qemu/nios/nio.py
@@ -0,0 +1,65 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2013 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 .
+
+"""
+Base interface for NIOs.
+"""
+
+
+class NIO(object):
+ """
+ Network Input/Output.
+ """
+
+ def __init__(self):
+
+ self._capturing = False
+ self._pcap_output_file = ""
+
+ def startPacketCapture(self, pcap_output_file):
+ """
+
+ :param pcap_output_file: PCAP destination file for the capture
+ """
+
+ self._capturing = True
+ self._pcap_output_file = pcap_output_file
+
+ def stopPacketCapture(self):
+
+ self._capturing = False
+ self._pcap_output_file = ""
+
+ @property
+ def capturing(self):
+ """
+ Returns either a capture is configured on this NIO.
+
+ :returns: boolean
+ """
+
+ return self._capturing
+
+ @property
+ def pcap_output_file(self):
+ """
+ Returns the path to the PCAP output file.
+
+ :returns: path to the PCAP output file
+ """
+
+ return self._pcap_output_file
diff --git a/gns3server/modules/qemu/nios/nio_udp.py b/gns3server/modules/qemu/nios/nio_udp.py
new file mode 100644
index 00000000..2c850351
--- /dev/null
+++ b/gns3server/modules/qemu/nios/nio_udp.py
@@ -0,0 +1,75 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2013 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 .
+
+"""
+Interface for UDP NIOs.
+"""
+
+from .nio import NIO
+
+
+class NIO_UDP(NIO):
+ """
+ UDP NIO.
+
+ :param lport: local port number
+ :param rhost: remote address/host
+ :param rport: remote port number
+ """
+
+ _instance_count = 0
+
+ def __init__(self, lport, rhost, rport):
+
+ NIO.__init__(self)
+ self._lport = lport
+ self._rhost = rhost
+ self._rport = rport
+
+ @property
+ def lport(self):
+ """
+ Returns the local port
+
+ :returns: local port number
+ """
+
+ return self._lport
+
+ @property
+ def rhost(self):
+ """
+ Returns the remote host
+
+ :returns: remote address/host
+ """
+
+ return self._rhost
+
+ @property
+ def rport(self):
+ """
+ Returns the remote port
+
+ :returns: remote port number
+ """
+
+ return self._rport
+
+ def __str__(self):
+
+ return "NIO UDP"
diff --git a/gns3server/modules/qemu/qemu_error.py b/gns3server/modules/qemu/qemu_error.py
new file mode 100644
index 00000000..55135a34
--- /dev/null
+++ b/gns3server/modules/qemu/qemu_error.py
@@ -0,0 +1,39 @@
+# -*- 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 .
+
+"""
+Custom exceptions for QEMU module.
+"""
+
+
+class QemuError(Exception):
+
+ def __init__(self, message, original_exception=None):
+
+ Exception.__init__(self, message)
+ if isinstance(message, Exception):
+ message = str(message)
+ self._message = message
+ self._original_exception = original_exception
+
+ def __repr__(self):
+
+ return self._message
+
+ def __str__(self):
+
+ return self._message
diff --git a/gns3server/modules/qemu/qemu_vm.py b/gns3server/modules/qemu/qemu_vm.py
new file mode 100644
index 00000000..2a32b035
--- /dev/null
+++ b/gns3server/modules/qemu/qemu_vm.py
@@ -0,0 +1,616 @@
+# -*- 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 VM instance.
+"""
+
+import os
+import shutil
+import random
+import subprocess
+
+from .qemu_error import QemuError
+from .adapters.ethernet_adapter import EthernetAdapter
+from .nios.nio_udp import NIO_UDP
+from ..attic import find_unused_port
+
+import logging
+log = logging.getLogger(__name__)
+
+
+class QemuVM(object):
+ """
+ QEMU VM implementation.
+
+ :param name: name of this QEMU VM
+ :param qemu_path: path to the QEMU binary
+ :param qemu_img_path: path to the QEMU IMG binary
+ :param working_dir: path to a working directory
+ :param host: host/address to bind for console and UDP connections
+ :param qemu_id: QEMU VM instance ID
+ :param console: TCP console port
+ :param console_start_port_range: TCP console port range start
+ :param console_end_port_range: TCP console port range end
+ """
+
+ _instances = []
+ _allocated_console_ports = []
+
+ def __init__(self,
+ name,
+ qemu_path,
+ qemu_img_path,
+ working_dir,
+ host="127.0.0.1",
+ qemu_id=None,
+ console=None,
+ console_start_port_range=5001,
+ console_end_port_range=5500):
+
+ if not qemu_id:
+ self._id = 0
+ for identifier in range(1, 1024):
+ if identifier not in self._instances:
+ self._id = identifier
+ self._instances.append(self._id)
+ break
+
+ if self._id == 0:
+ raise QemuError("Maximum number of QEMU VM instances reached")
+ else:
+ if qemu_id in self._instances:
+ raise QemuError("QEMU identifier {} is already used by another QEMU VM instance".format(qemu_id))
+ self._id = qemu_id
+ self._instances.append(self._id)
+
+ self._name = name
+ self._working_dir = None
+ self._host = host
+ self._command = []
+ self._started = False
+ self._process = None
+ self._stdout_file = ""
+ self._qemu_img_path = qemu_img_path
+ self._console_start_port_range = console_start_port_range
+ self._console_end_port_range = console_end_port_range
+
+ # QEMU settings
+ self._qemu_path = qemu_path
+ self._disk_image = ""
+ self._options = ""
+ self._ram = 256
+ self._console = console
+ self._ethernet_adapters = []
+ self._adapter_type = "e1000"
+
+ working_dir_path = os.path.join(working_dir, "qemu", "vm-{}".format(self._id))
+
+ if qemu_id and not os.path.isdir(working_dir_path):
+ raise QemuError("Working directory {} doesn't exist".format(working_dir_path))
+
+ # create the device own working directory
+ self.working_dir = working_dir_path
+
+ if not self._console:
+ # allocate a console port
+ try:
+ self._console = find_unused_port(self._console_start_port_range,
+ self._console_end_port_range,
+ self._host,
+ ignore_ports=self._allocated_console_ports)
+ except Exception as e:
+ raise QemuError(e)
+
+ if self._console in self._allocated_console_ports:
+ raise QemuError("Console port {} is already used by another QEMU VM".format(console))
+ self._allocated_console_ports.append(self._console)
+
+ self.adapters = 1 # creates 1 adapter by default
+ log.info("QEMU VM {name} [id={id}] has been created".format(name=self._name,
+ id=self._id))
+
+ def defaults(self):
+ """
+ Returns all the default attribute values for this QEMU VM.
+
+ :returns: default values (dictionary)
+ """
+
+ qemu_defaults = {"name": self._name,
+ "qemu_path": self._qemu_path,
+ "ram": self._ram,
+ "disk_image": self._disk_image,
+ "options": self._options,
+ "adapters": self.adapters,
+ "adapter_type": self._adapter_type,
+ "console": self._console}
+
+ return qemu_defaults
+
+ @property
+ def id(self):
+ """
+ Returns the unique ID for this QEMU VM.
+
+ :returns: id (integer)
+ """
+
+ return self._id
+
+ @classmethod
+ def reset(cls):
+ """
+ Resets allocated instance list.
+ """
+
+ cls._instances.clear()
+ cls._allocated_console_ports.clear()
+
+ @property
+ def name(self):
+ """
+ Returns the name of this QEMU VM.
+
+ :returns: name
+ """
+
+ return self._name
+
+ @name.setter
+ def name(self, new_name):
+ """
+ Sets the name of this QEMU VM.
+
+ :param new_name: name
+ """
+
+ log.info("QEMU VM {name} [id={id}]: renamed to {new_name}".format(name=self._name,
+ id=self._id,
+ new_name=new_name))
+
+ self._name = new_name
+
+ @property
+ def working_dir(self):
+ """
+ Returns current working directory
+
+ :returns: path to the working directory
+ """
+
+ return self._working_dir
+
+ @working_dir.setter
+ def working_dir(self, working_dir):
+ """
+ Sets the working directory this QEMU VM.
+
+ :param working_dir: path to the working directory
+ """
+
+ try:
+ os.makedirs(working_dir)
+ except FileExistsError:
+ pass
+ except OSError as e:
+ raise QemuError("Could not create working directory {}: {}".format(working_dir, e))
+
+ self._working_dir = working_dir
+ log.info("QEMU VM {name} [id={id}]: working directory changed to {wd}".format(name=self._name,
+ id=self._id,
+ wd=self._working_dir))
+
+ @property
+ def console(self):
+ """
+ Returns the TCP console port.
+
+ :returns: console port (integer)
+ """
+
+ return self._console
+
+ @console.setter
+ def console(self, console):
+ """
+ Sets the TCP console port.
+
+ :param console: console port (integer)
+ """
+
+ if console in self._allocated_console_ports:
+ raise QemuError("Console port {} is already used by another QEMU VM".format(console))
+
+ self._allocated_console_ports.remove(self._console)
+ self._console = console
+ self._allocated_console_ports.append(self._console)
+
+ log.info("QEMU VM {name} [id={id}]: console port set to {port}".format(name=self._name,
+ id=self._id,
+ port=console))
+
+ def delete(self):
+ """
+ Deletes this QEMU VM.
+ """
+
+ self.stop()
+ if self._id in self._instances:
+ self._instances.remove(self._id)
+
+ if self.console and self.console in self._allocated_console_ports:
+ self._allocated_console_ports.remove(self.console)
+
+ log.info("QEMU VM {name} [id={id}] has been deleted".format(name=self._name,
+ id=self._id))
+
+ def clean_delete(self):
+ """
+ Deletes this QEMU VM & all files.
+ """
+
+ self.stop()
+ if self._id in self._instances:
+ self._instances.remove(self._id)
+
+ if self.console:
+ self._allocated_console_ports.remove(self.console)
+
+ try:
+ shutil.rmtree(self._working_dir)
+ except OSError as e:
+ log.error("could not delete QEMU VM {name} [id={id}]: {error}".format(name=self._name,
+ id=self._id,
+ error=e))
+ return
+
+ log.info("QEMU VM {name} [id={id}] has been deleted (including associated files)".format(name=self._name,
+ id=self._id))
+
+ @property
+ def qemu_path(self):
+ """
+ Returns the QEMU binary path for this QEMU VM.
+
+ :returns: QEMU path
+ """
+
+ return self._qemu_path
+
+ @qemu_path.setter
+ def qemu_path(self, qemu_path):
+ """
+ Sets the QEMU binary path this QEMU VM.
+
+ :param qemu_path: QEMU path
+ """
+
+ log.info("QEMU VM {name} [id={id}] has set the QEMU path to {qemu_path}".format(name=self._name,
+ id=self._id,
+ qemu_path=qemu_path))
+ self._qemu_path = qemu_path
+
+ @property
+ def qemu_img_path(self):
+ """
+ Returns the QEMU IMG binary path for this QEMU VM.
+
+ :returns: QEMU IMG path
+ """
+
+ return self._qemu_img_path
+
+ @qemu_img_path.setter
+ def qemu_img_path(self, qemu_img_path):
+ """
+ Sets the QEMU IMG binary path this QEMU VM.
+
+ :param qemu_img_path: QEMU IMG path
+ """
+
+ log.info("QEMU VM {name} [id={id}] has set the QEMU IMG path to {qemu_img_path}".format(name=self._name,
+ id=self._id,
+ qemu_img_path=qemu_img_path))
+ self._qemu_img_path = qemu_img_path
+
+ @property
+ def disk_image(self):
+ """
+ Returns the disk image path for this QEMU VM.
+
+ :returns: QEMU disk image path
+ """
+
+ return self._disk_image
+
+ @disk_image.setter
+ def disk_image(self, disk_image):
+ """
+ Sets the disk image for this QEMU VM.
+
+ :param disk_image: QEMU disk image path
+ """
+
+ log.info("QEMU VM {name} [id={id}] has set the QEMU disk image path to {disk_image}".format(name=self._name,
+ id=self._id,
+ disk_image=disk_image))
+ self._disk_image = disk_image
+
+ @property
+ def adapters(self):
+ """
+ Returns the number of Ethernet adapters for this QEMU VM instance.
+
+ :returns: number of adapters
+ """
+
+ return len(self._ethernet_adapters)
+
+ @adapters.setter
+ def adapters(self, adapters):
+ """
+ Sets the number of Ethernet adapters for this QEMU VM instance.
+
+ :param adapters: number of adapters
+ """
+
+ self._ethernet_adapters.clear()
+ for adapter_id in range(0, adapters):
+ self._ethernet_adapters.append(EthernetAdapter())
+
+ log.info("QEMU VM {name} [id={id}]: number of Ethernet adapters changed to {adapters}".format(name=self._name,
+ id=self._id,
+ adapters=adapters))
+
+ @property
+ def adapter_type(self):
+ """
+ Returns the adapter type for this QEMU VM instance.
+
+ :returns: adapter type (string)
+ """
+
+ return self._adapter_type
+
+ @adapter_type.setter
+ def adapter_type(self, adapter_type):
+ """
+ Sets the adapter type for this QEMU VM instance.
+
+ :param adapter_type: adapter type (string)
+ """
+
+ self._adapter_type = adapter_type
+
+ log.info("QEMU VM {name} [id={id}]: adapter type changed to {adapter_type}".format(name=self._name,
+ id=self._id,
+ adapter_type=adapter_type))
+
+ def start(self):
+ """
+ Starts this QEMU VM.
+ """
+
+ if not self.is_running():
+
+ if not os.path.isfile(self._qemu_path) or not os.path.exists(self._qemu_path):
+ raise QemuError("QEMU binary '{}' is not accessible".format(self._qemu_path))
+
+ #TODO: check binary image is valid?
+ self._command = self._build_command()
+
+ try:
+ log.info("starting QEMU: {}".format(self._command))
+ self._stdout_file = os.path.join(self._working_dir, "qemu.log")
+ log.info("logging to {}".format(self._stdout_file))
+ with open(self._stdout_file, "w") as fd:
+ self._process = subprocess.Popen(self._command,
+ stdout=fd,
+ stderr=subprocess.STDOUT,
+ cwd=self._working_dir)
+ log.info("QEMU VM instance {} started PID={}".format(self._id, self._process.pid))
+ self._started = True
+ except OSError as e:
+ stdout = self.read_stdout()
+ log.error("could not start QEMU {}: {}\n{}".format(self._qemu_path, e, stdout))
+ raise QemuError("could not start QEMU {}: {}\n{}".format(self._qemu_path, e, stdout))
+
+ def stop(self):
+ """
+ Stops this QEMU VM.
+ """
+
+ # stop the QEMU process
+ if self.is_running():
+ log.info("stopping QEMU VM instance {} PID={}".format(self._id, self._process.pid))
+ try:
+ self._process.terminate()
+ self._process.wait(1)
+ except subprocess.TimeoutExpired:
+ self._process.kill()
+ if self._process.poll() is None:
+ log.warn("QEMU VM instance {} PID={} is still running".format(self._id,
+ self._process.pid))
+ self._process = None
+ self._started = False
+
+ def suspend(self):
+ """
+ Suspends this QEMU VM.
+ """
+
+ pass
+
+ def reload(self):
+ """
+ Reloads this QEMU VM.
+ """
+
+ pass
+
+ def resume(self):
+ """
+ Resumes this QEMU VM.
+ """
+
+ pass
+
+ def port_add_nio_binding(self, adapter_id, nio):
+ """
+ Adds a port NIO binding.
+
+ :param adapter_id: adapter ID
+ :param nio: NIO instance to add to the slot/port
+ """
+
+ try:
+ adapter = self._ethernet_adapters[adapter_id]
+ except IndexError:
+ raise QemuError("Adapter {adapter_id} doesn't exist on QEMU VM {name}".format(name=self._name,
+ adapter_id=adapter_id))
+
+ adapter.add_nio(0, nio)
+ log.info("QEMU VM {name} [id={id}]: {nio} added to adapter {adapter_id}".format(name=self._name,
+ id=self._id,
+ nio=nio,
+ adapter_id=adapter_id))
+
+ def port_remove_nio_binding(self, adapter_id):
+ """
+ Removes a port NIO binding.
+
+ :param adapter_id: adapter ID
+
+ :returns: NIO instance
+ """
+
+ try:
+ adapter = self._ethernet_adapters[adapter_id]
+ except IndexError:
+ raise QemuError("Adapter {adapter_id} doesn't exist on QEMU VM {name}".format(name=self._name,
+ adapter_id=adapter_id))
+
+ nio = adapter.get_nio(0)
+ adapter.remove_nio(0)
+ log.info("QEMU VM {name} [id={id}]: {nio} removed from adapter {adapter_id}".format(name=self._name,
+ id=self._id,
+ nio=nio,
+ adapter_id=adapter_id))
+ return nio
+
+ @property
+ def started(self):
+ """
+ Returns either this QEMU VM has been started or not.
+
+ :returns: boolean
+ """
+
+ return self._started
+
+ def read_stdout(self):
+ """
+ Reads the standard output of the QEMU process.
+ Only use when the process has been stopped or has crashed.
+ """
+
+ output = ""
+ if self._stdout_file:
+ try:
+ with open(self._stdout_file, errors="replace") as file:
+ output = file.read()
+ except OSError as e:
+ log.warn("could not read {}: {}".format(self._stdout_file, e))
+ return output
+
+ def is_running(self):
+ """
+ Checks if the QEMU process is running
+
+ :returns: True or False
+ """
+
+ if self._process and self._process.poll() is None:
+ return True
+ return False
+
+ def command(self):
+ """
+ Returns the QEMU command line.
+
+ :returns: QEMU command line (string)
+ """
+
+ return " ".join(self._build_command())
+
+ def _serial_options(self):
+
+ if self._console:
+ return ["-serial", "telnet:{}:{},server,nowait".format(self._host, self._console)]
+ else:
+ return []
+
+ def _disk_options(self):
+
+ hda_disk = os.path.join(self._working_dir, "hda.disk")
+ if not os.path.exists(hda_disk):
+ try:
+ retcode = subprocess.call([self._qemu_img_path, "create", "-o",
+ "backing_file={}".format(self._disk_image),
+ "-f", "qcow2", hda_disk])
+ log.info("{} returned with {}".format(self._qemu_img_path, retcode))
+ except OSError as e:
+ raise QemuError("Could not create disk image {}".format(e))
+
+ return ["-hda", hda_disk]
+
+ def _network_options(self):
+
+ network_options = []
+ adapter_id = 0
+ for adapter in self._ethernet_adapters:
+ nio = adapter.get_nio(0)
+ if nio:
+ #TODO: let users specific the base mac address
+ mac = "00:00:ab:%02x:%02x:%02d" % (random.randint(0x00, 0xff), random.randint(0x00, 0xff), adapter_id)
+ network_options.extend(["-device", "{},mac={},netdev=gns3-{}".format(self._adapter_type, mac, adapter_id)])
+ if isinstance(nio, NIO_UDP):
+ network_options.extend(["-netdev", "socket,id=gns3-{},udp={}:{},localaddr={}:{}".format(adapter_id,
+ nio.rhost,
+ nio.rport,
+ self._host,
+ nio.lport)])
+ else:
+ network_options.extend(["-device", "{}".format(self._adapter_type)])
+ adapter_id += 1
+
+ return network_options
+
+ def _build_command(self):
+ """
+ Command to start the QEMU process.
+ (to be passed to subprocess.Popen())
+ """
+
+ command = [self._qemu_path]
+ command.extend(["-name", self._name])
+ command.extend(["-m", str(self._ram)])
+ command.extend(self._disk_options())
+ command.extend(self._serial_options())
+ command.extend(self._network_options())
+ return command
diff --git a/gns3server/modules/qemu/schemas.py b/gns3server/modules/qemu/schemas.py
new file mode 100644
index 00000000..bfcbfdff
--- /dev/null
+++ b/gns3server/modules/qemu/schemas.py
@@ -0,0 +1,373 @@
+# -*- 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,
+ },
+ "qemu_id": {
+ "description": "QEMU VM instance ID",
+ "type": "integer"
+ },
+ "console": {
+ "description": "console TCP port",
+ "minimum": 1,
+ "maximum": 65535,
+ "type": "integer"
+ },
+ },
+ "additionalProperties": False,
+ "required": ["name", "qemu_path"],
+}
+
+QEMU_DELETE_SCHEMA = {
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "description": "Request validation to delete a QEMU VM instance",
+ "type": "object",
+ "properties": {
+ "id": {
+ "description": "QEMU VM instance ID",
+ "type": "integer"
+ },
+ },
+ "additionalProperties": False,
+ "required": ["id"]
+}
+
+QEMU_UPDATE_SCHEMA = {
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "description": "Request validation to update a QEMU VM instance",
+ "type": "object",
+ "properties": {
+ "id": {
+ "description": "QEMU VM instance ID",
+ "type": "integer"
+ },
+ "name": {
+ "description": "QEMU VM instance name",
+ "type": "string",
+ "minLength": 1,
+ },
+ "qemu_path": {
+ "description": "path to QEMU",
+ "type": "string",
+ "minLength": 1,
+ },
+ "disk_image": {
+ "description": "QEMU disk image path",
+ "type": "string",
+ "minLength": 1,
+ },
+ "ram": {
+ "description": "amount of RAM in MB",
+ "type": "integer"
+ },
+ "adapters": {
+ "description": "number of adapters",
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 8,
+ },
+ "adapter_type": {
+ "description": "QEMU adapter type",
+ "type": "string",
+ "minLength": 1,
+ },
+ "console": {
+ "description": "console TCP port",
+ "minimum": 1,
+ "maximum": 65535,
+ "type": "integer"
+ },
+ "options": {
+ "description": "additional QEMU options",
+ "type": "string",
+ },
+ },
+ "additionalProperties": False,
+ "required": ["id"]
+}
+
+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_STOP_SCHEMA = {
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "description": "Request validation to stop a QEMU VM instance",
+ "type": "object",
+ "properties": {
+ "id": {
+ "description": "QEMU VM instance ID",
+ "type": "integer"
+ },
+ },
+ "additionalProperties": False,
+ "required": ["id"]
+}
+
+QEMU_SUSPEND_SCHEMA = {
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "description": "Request validation to suspend a QEMU VM instance",
+ "type": "object",
+ "properties": {
+ "id": {
+ "description": "QEMU VM instance ID",
+ "type": "integer"
+ },
+ },
+ "additionalProperties": False,
+ "required": ["id"]
+}
+
+QEMU_RELOAD_SCHEMA = {
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "description": "Request validation to reload a QEMU VM instance",
+ "type": "object",
+ "properties": {
+ "id": {
+ "description": "QEMU VM instance ID",
+ "type": "integer"
+ },
+ },
+ "additionalProperties": False,
+ "required": ["id"]
+}
+
+QEMU_ALLOCATE_UDP_PORT_SCHEMA = {
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "description": "Request validation to allocate an UDP port for a QEMU VM instance",
+ "type": "object",
+ "properties": {
+ "id": {
+ "description": "QEMU VM instance ID",
+ "type": "integer"
+ },
+ "port_id": {
+ "description": "Unique port identifier for the QEMU VM instance",
+ "type": "integer"
+ },
+ },
+ "additionalProperties": False,
+ "required": ["id", "port_id"]
+}
+
+QEMU_ADD_NIO_SCHEMA = {
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "description": "Request validation to add a NIO for a QEMU VM 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
+ },
+ "LinuxEthernet": {
+ "description": "Linux Ethernet Network Input/Output",
+ "properties": {
+ "type": {
+ "enum": ["nio_linux_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
+ },
+ "UNIX": {
+ "description": "UNIX Network Input/Output",
+ "properties": {
+ "type": {
+ "enum": ["nio_unix"]
+ },
+ "local_file": {
+ "description": "path to the UNIX socket file (local)",
+ "type": "string",
+ "minLength": 1
+ },
+ "remote_file": {
+ "description": "path to the UNIX socket file (remote)",
+ "type": "string",
+ "minLength": 1
+ },
+ },
+ "required": ["type", "local_file", "remote_file"],
+ "additionalProperties": False
+ },
+ "VDE": {
+ "description": "VDE Network Input/Output",
+ "properties": {
+ "type": {
+ "enum": ["nio_vde"]
+ },
+ "control_file": {
+ "description": "path to the VDE control file",
+ "type": "string",
+ "minLength": 1
+ },
+ "local_file": {
+ "description": "path to the VDE control file",
+ "type": "string",
+ "minLength": 1
+ },
+ },
+ "required": ["type", "control_file", "local_file"],
+ "additionalProperties": False
+ },
+ "NULL": {
+ "description": "NULL Network Input/Output",
+ "properties": {
+ "type": {
+ "enum": ["nio_null"]
+ },
+ },
+ "required": ["type"],
+ "additionalProperties": False
+ },
+ },
+
+ "properties": {
+ "id": {
+ "description": "QEMU VM instance ID",
+ "type": "integer"
+ },
+ "port_id": {
+ "description": "Unique port identifier for the QEMU VM instance",
+ "type": "integer"
+ },
+ "port": {
+ "description": "Port number",
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 8
+ },
+ "nio": {
+ "type": "object",
+ "description": "Network Input/Output",
+ "oneOf": [
+ {"$ref": "#/definitions/UDP"},
+ {"$ref": "#/definitions/Ethernet"},
+ {"$ref": "#/definitions/LinuxEthernet"},
+ {"$ref": "#/definitions/TAP"},
+ {"$ref": "#/definitions/UNIX"},
+ {"$ref": "#/definitions/VDE"},
+ {"$ref": "#/definitions/NULL"},
+ ]
+ },
+ },
+ "additionalProperties": False,
+ "required": ["id", "port_id", "port", "nio"]
+}
+
+
+QEMU_DELETE_NIO_SCHEMA = {
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "description": "Request validation to delete a NIO for a QEMU VM instance",
+ "type": "object",
+ "properties": {
+ "id": {
+ "description": "QEMU VM instance ID",
+ "type": "integer"
+ },
+ "port": {
+ "description": "Port number",
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 8
+ },
+ },
+ "additionalProperties": False,
+ "required": ["id", "port"]
+}
diff --git a/gns3server/modules/virtualbox/__init__.py b/gns3server/modules/virtualbox/__init__.py
index cfd8a6aa..d7af5c48 100644
--- a/gns3server/modules/virtualbox/__init__.py
+++ b/gns3server/modules/virtualbox/__init__.py
@@ -68,7 +68,7 @@ class VirtualBox(IModule):
self._vboxwrapper_path = vbox_config.get("vboxwrapper_path")
if not self._vboxwrapper_path or not os.path.isfile(self._vboxwrapper_path):
paths = [os.getcwd()] + os.environ["PATH"].split(":")
- # look for iouyap in the current working directory and $PATH
+ # look for vboxwrapper in the current working directory and $PATH
for path in paths:
try:
if "vboxwrapper" in os.listdir(path) and os.access(os.path.join(path, "vboxwrapper"), os.X_OK):
@@ -172,9 +172,9 @@ class VirtualBox(IModule):
"""
Returns a VirtualBox VM instance.
- :param vbox_id: VirtualBox device identifier
+ :param vbox_id: VirtualBox VM identifier
- :returns: VBoxDevice instance
+ :returns: VirtualBoxVM instance
"""
if vbox_id not in self._vbox_instances:
@@ -271,6 +271,7 @@ class VirtualBox(IModule):
Mandatory request parameters:
- name (VirtualBox VM name)
+ - vmname (VirtualBox VM name in VirtualBox)
Optional request parameters:
- console (VirtualBox VM console port)
@@ -653,7 +654,7 @@ class VirtualBox(IModule):
Deletes an NIO (Network Input/Output).
Mandatory request parameters:
- - id (VPCS instance identifier)
+ - id (VirtualBox instance identifier)
- port (port identifier)
Response parameters:
@@ -688,7 +689,7 @@ class VirtualBox(IModule):
Starts a packet capture.
Mandatory request parameters:
- - id (vm identifier)
+ - id (VirtualBox VM identifier)
- port (port number)
- port_id (port identifier)
- capture_file_name
@@ -729,7 +730,7 @@ class VirtualBox(IModule):
Stops a packet capture.
Mandatory request parameters:
- - id (vm identifier)
+ - id (VirtualBox VM identifier)
- port (port number)
- port_id (port identifier)
diff --git a/gns3server/modules/virtualbox/nios/nio.py b/gns3server/modules/virtualbox/nios/nio.py
index c85569bd..eee5f1d5 100644
--- a/gns3server/modules/virtualbox/nios/nio.py
+++ b/gns3server/modules/virtualbox/nios/nio.py
@@ -22,7 +22,7 @@ Base interface for NIOs.
class NIO(object):
"""
- IOU NIO.
+ Network Input/Output.
"""
def __init__(self):
diff --git a/gns3server/modules/virtualbox/nios/nio_udp.py b/gns3server/modules/virtualbox/nios/nio_udp.py
index 41ffbc4f..2c850351 100644
--- a/gns3server/modules/virtualbox/nios/nio_udp.py
+++ b/gns3server/modules/virtualbox/nios/nio_udp.py
@@ -24,7 +24,7 @@ from .nio import NIO
class NIO_UDP(NIO):
"""
- IOU UDP NIO.
+ UDP NIO.
:param lport: local port number
:param rhost: remote address/host
diff --git a/gns3server/modules/vpcs/nios/nio_tap.py b/gns3server/modules/vpcs/nios/nio_tap.py
index ee550e7b..4c3ed6b2 100644
--- a/gns3server/modules/vpcs/nios/nio_tap.py
+++ b/gns3server/modules/vpcs/nios/nio_tap.py
@@ -22,7 +22,7 @@ Interface for TAP NIOs (UNIX based OSes only).
class NIO_TAP(object):
"""
- IOU TAP NIO.
+ TAP NIO.
:param tap_device: TAP device name (e.g. tap0)
"""
diff --git a/gns3server/modules/vpcs/nios/nio_udp.py b/gns3server/modules/vpcs/nios/nio_udp.py
index 3142d70e..0527f675 100644
--- a/gns3server/modules/vpcs/nios/nio_udp.py
+++ b/gns3server/modules/vpcs/nios/nio_udp.py
@@ -22,7 +22,7 @@ Interface for UDP NIOs.
class NIO_UDP(object):
"""
- IOU UDP NIO.
+ UDP NIO.
:param lport: local port number
:param rhost: remote address/host
diff --git a/gns3server/modules/vpcs/vpcs_device.py b/gns3server/modules/vpcs/vpcs_device.py
index 33d5d8e0..d5ad8c09 100644
--- a/gns3server/modules/vpcs/vpcs_device.py
+++ b/gns3server/modules/vpcs/vpcs_device.py
@@ -338,11 +338,10 @@ class VPCSDevice(object):
"""
try:
- output = subprocess.check_output([self._path, "-v"], stderr=subprocess.STDOUT, cwd=self._working_dir)
+ output = subprocess.check_output([self._path, "-v"], cwd=self._working_dir)
match = re.search("Welcome to Virtual PC Simulator, version ([0-9a-z\.]+)", output.decode("utf-8"))
if match:
version = match.group(1)
- print(version)
if parse_version(version) < parse_version("0.5b1"):
raise VPCSError("VPCS executable version must be >= 0.5b1")
else: