diff --git a/gns3server/handlers/__init__.py b/gns3server/handlers/__init__.py
index 727b4053..78bb3ca6 100644
--- a/gns3server/handlers/__init__.py
+++ b/gns3server/handlers/__init__.py
@@ -3,4 +3,5 @@ __all__ = ["version_handler",
"vpcs_handler",
"project_handler",
"virtualbox_handler",
- "dynamips_handler"]
+ "dynamips_handler",
+ "iou_handler"]
diff --git a/gns3server/handlers/iou_handler.py b/gns3server/handlers/iou_handler.py
new file mode 100644
index 00000000..eddb85bf
--- /dev/null
+++ b/gns3server/handlers/iou_handler.py
@@ -0,0 +1,235 @@
+# -*- 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 .
+
+from ..web.route import Route
+from ..schemas.iou import IOU_CREATE_SCHEMA
+from ..schemas.iou import IOU_UPDATE_SCHEMA
+from ..schemas.iou import IOU_OBJECT_SCHEMA
+from ..schemas.iou import IOU_NIO_SCHEMA
+from ..modules.iou import IOU
+
+
+class IOUHandler:
+
+ """
+ API entry points for IOU.
+ """
+
+ @classmethod
+ @Route.post(
+ r"/projects/{project_id}/iou/vms",
+ parameters={
+ "project_id": "UUID for the project"
+ },
+ status_codes={
+ 201: "Instance created",
+ 400: "Invalid request",
+ 409: "Conflict"
+ },
+ description="Create a new IOU instance",
+ input=IOU_CREATE_SCHEMA,
+ output=IOU_OBJECT_SCHEMA)
+ def create(request, response):
+
+ iou = IOU.instance()
+ vm = yield from iou.create_vm(request.json["name"],
+ request.match_info["project_id"],
+ request.json.get("vm_id"),
+ console=request.json.get("console"),
+ serial_adapters=request.json.get("serial_adapters"),
+ ethernet_adapters=request.json.get("ethernet_adapters"),
+ ram=request.json.get("ram"),
+ nvram=request.json.get("nvram")
+ )
+ vm.path = request.json.get("path", vm.path)
+ vm.iourc_path = request.json.get("iourc_path", vm.iourc_path)
+ response.set_status(201)
+ response.json(vm)
+
+ @classmethod
+ @Route.get(
+ r"/projects/{project_id}/iou/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 IOU instance",
+ output=IOU_OBJECT_SCHEMA)
+ def show(request, response):
+
+ iou_manager = IOU.instance()
+ vm = iou_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}/iou/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 IOU instance",
+ input=IOU_UPDATE_SCHEMA,
+ output=IOU_OBJECT_SCHEMA)
+ def update(request, response):
+
+ iou_manager = IOU.instance()
+ vm = iou_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.path = request.json.get("path", vm.path)
+ vm.iourc_path = request.json.get("iourc_path", vm.iourc_path)
+ vm.ethernet_adapters = request.json.get("ethernet_adapters", vm.ethernet_adapters)
+ vm.serial_adapters = request.json.get("serial_adapters", vm.serial_adapters)
+ vm.ram = request.json.get("ram", vm.ram)
+ vm.nvram = request.json.get("nvram", vm.nvram)
+
+ response.json(vm)
+
+ @classmethod
+ @Route.delete(
+ r"/projects/{project_id}/iou/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 IOU instance")
+ def delete(request, response):
+
+ yield from IOU.instance().delete_vm(request.match_info["vm_id"])
+ response.set_status(204)
+
+ @classmethod
+ @Route.post(
+ r"/projects/{project_id}/iou/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 IOU instance")
+ def start(request, response):
+
+ iou_manager = IOU.instance()
+ vm = iou_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}/iou/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 IOU instance")
+ def stop(request, response):
+
+ iou_manager = IOU.instance()
+ vm = iou_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}/iou/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 IOU instance")
+ def reload(request, response):
+
+ iou_manager = IOU.instance()
+ vm = iou_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
+ yield from vm.reload()
+ response.set_status(204)
+
+ @Route.post(
+ r"/projects/{project_id}/iou/vms/{vm_id}/ports/{port_number:\d+}/nio",
+ parameters={
+ "project_id": "UUID for the project",
+ "vm_id": "UUID for the instance",
+ "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 IOU instance",
+ input=IOU_NIO_SCHEMA,
+ output=IOU_NIO_SCHEMA)
+ def create_nio(request, response):
+
+ iou_manager = IOU.instance()
+ vm = iou_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
+ nio = iou_manager.create_nio(vm.iouyap_path, request.json)
+ vm.slot_add_nio_binding(0, int(request.match_info["port_number"]), nio)
+ response.set_status(201)
+ response.json(nio)
+
+ @classmethod
+ @Route.delete(
+ r"/projects/{project_id}/iou/vms/{vm_id}/ports/{port_number:\d+}/nio",
+ parameters={
+ "project_id": "UUID for the project",
+ "vm_id": "UUID for the instance",
+ "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 IOU instance")
+ def delete_nio(request, response):
+
+ iou_manager = IOU.instance()
+ vm = iou_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
+ vm.slot_remove_nio_binding(0, int(request.match_info["port_number"]))
+ response.set_status(204)
diff --git a/gns3server/main.py b/gns3server/main.py
index 4810fd68..f25ce6d9 100644
--- a/gns3server/main.py
+++ b/gns3server/main.py
@@ -91,6 +91,7 @@ def parse_arguments(argv, config):
"allow": config.getboolean("allow_remote_console", False),
"quiet": config.getboolean("quiet", False),
"debug": config.getboolean("debug", False),
+ "live": config.getboolean("live", False),
}
parser = argparse.ArgumentParser(description="GNS3 server version {}".format(__version__))
@@ -104,7 +105,8 @@ def parse_arguments(argv, config):
parser.add_argument("-L", "--local", action="store_true", help="local mode (allows some insecure operations)")
parser.add_argument("-A", "--allow", action="store_true", help="allow remote connections to local console ports")
parser.add_argument("-q", "--quiet", action="store_true", help="do not show logs on stdout")
- parser.add_argument("-d", "--debug", action="store_true", help="show debug logs and enable code live reload")
+ parser.add_argument("-d", "--debug", action="store_true", help="show debug logs")
+ parser.add_argument("--live", action="store_true", help="enable code live reload")
return parser.parse_args(argv)
diff --git a/gns3server/modules/__init__.py b/gns3server/modules/__init__.py
index 4e2f51bb..25c1012f 100644
--- a/gns3server/modules/__init__.py
+++ b/gns3server/modules/__init__.py
@@ -18,5 +18,6 @@
from .vpcs import VPCS
from .virtualbox import VirtualBox
from .dynamips import Dynamips
+from .iou import IOU
-MODULES = [VPCS, VirtualBox, Dynamips]
+MODULES = [VPCS, VirtualBox, Dynamips, IOU]
diff --git a/gns3server/modules/adapters/ethernet_adapter.py b/gns3server/modules/adapters/ethernet_adapter.py
index 9d3ee003..f1a06c63 100644
--- a/gns3server/modules/adapters/ethernet_adapter.py
+++ b/gns3server/modules/adapters/ethernet_adapter.py
@@ -29,4 +29,4 @@ class EthernetAdapter(Adapter):
def __str__(self):
- return "VPCS Ethernet adapter"
+ return "Ethernet adapter"
diff --git a/gns3server/old_modules/iou/adapters/serial_adapter.py b/gns3server/modules/adapters/serial_adapter.py
similarity index 88%
rename from gns3server/old_modules/iou/adapters/serial_adapter.py
rename to gns3server/modules/adapters/serial_adapter.py
index ca7d3200..5bb00dc1 100644
--- a/gns3server/old_modules/iou/adapters/serial_adapter.py
+++ b/gns3server/modules/adapters/serial_adapter.py
@@ -21,12 +21,12 @@ from .adapter import Adapter
class SerialAdapter(Adapter):
"""
- IOU Serial adapter.
+ VPCS Ethernet adapter.
"""
def __init__(self):
- Adapter.__init__(self, interfaces=4)
+ Adapter.__init__(self, interfaces=1)
def __str__(self):
- return "IOU Serial adapter"
+ return "Serial adapter"
diff --git a/gns3server/modules/base_manager.py b/gns3server/modules/base_manager.py
index ec51f658..f38feacd 100644
--- a/gns3server/modules/base_manager.py
+++ b/gns3server/modules/base_manager.py
@@ -32,8 +32,8 @@ from ..config import Config
from ..utils.asyncio import wait_run_in_executor
from .project_manager import ProjectManager
-from .nios.nio_udp import NIOUDP
-from .nios.nio_tap import NIOTAP
+from .nios.nio_udp import NIO_UDP
+from .nios.nio_tap import NIO_TAP
class BaseManager:
@@ -274,11 +274,11 @@ class BaseManager:
sock.connect((rhost, rport))
except OSError as e:
raise aiohttp.web.HTTPInternalServerError(text="Could not create an UDP connection to {}:{}: {}".format(rhost, rport, e))
- nio = NIOUDP(lport, rhost, rport)
+ nio = NIO_UDP(lport, rhost, rport)
elif nio_settings["type"] == "nio_tap":
tap_device = nio_settings["tap_device"]
if not self._has_privileged_access(executable):
raise aiohttp.web.HTTPForbidden(text="{} has no privileged access to {}.".format(executable, tap_device))
- nio = NIOTAP(tap_device)
+ nio = NIO_TAP(tap_device)
assert nio is not None
return nio
diff --git a/gns3server/modules/dynamips/dynamips_error.py b/gns3server/modules/dynamips/dynamips_error.py
index 0f64dff6..265b22e6 100644
--- a/gns3server/modules/dynamips/dynamips_error.py
+++ b/gns3server/modules/dynamips/dynamips_error.py
@@ -24,18 +24,4 @@ from ..vm_error import VMError
class DynamipsError(VMError):
- 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
+ pass
diff --git a/gns3server/modules/iou/__init__.py b/gns3server/modules/iou/__init__.py
new file mode 100644
index 00000000..c3ba15b4
--- /dev/null
+++ b/gns3server/modules/iou/__init__.py
@@ -0,0 +1,64 @@
+# -*- 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 .
+
+"""
+IOU server module.
+"""
+
+import asyncio
+
+from ..base_manager import BaseManager
+from .iou_error import IOUError
+from .iou_vm import IOUVM
+
+
+class IOU(BaseManager):
+ _VM_CLASS = IOUVM
+
+ def __init__(self):
+ super().__init__()
+ self._free_application_ids = list(range(1, 512))
+ self._used_application_ids = {}
+
+ @asyncio.coroutine
+ def create_vm(self, *args, **kwargs):
+
+ vm = yield from super().create_vm(*args, **kwargs)
+ try:
+ self._used_application_ids[vm.id] = self._free_application_ids.pop(0)
+ except IndexError:
+ raise IOUError("No mac address available")
+ return vm
+
+ @asyncio.coroutine
+ def delete_vm(self, vm_id, *args, **kwargs):
+
+ vm = self.get_vm(vm_id)
+ i = self._used_application_ids[vm_id]
+ self._free_application_ids.insert(0, i)
+ del self._used_application_ids[vm_id]
+ yield from super().delete_vm(vm_id, *args, **kwargs)
+
+ def get_application_id(self, vm_id):
+ """
+ Get an unique IOU mac id
+
+ :param vm_id: ID of the IOU VM
+ :returns: IOU MAC id
+ """
+
+ return self._used_application_ids.get(vm_id, 1)
diff --git a/gns3server/old_modules/iou/adapters/ethernet_adapter.py b/gns3server/modules/iou/iou_error.py
similarity index 70%
rename from gns3server/old_modules/iou/adapters/ethernet_adapter.py
rename to gns3server/modules/iou/iou_error.py
index bf96362f..cd43bdb9 100644
--- a/gns3server/old_modules/iou/adapters/ethernet_adapter.py
+++ b/gns3server/modules/iou/iou_error.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
-# Copyright (C) 2014 GNS3 Technologies Inc.
+# 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
@@ -15,18 +15,12 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-from .adapter import Adapter
+"""
+Custom exceptions for IOU module.
+"""
+
+from ..vm_error import VMError
-class EthernetAdapter(Adapter):
-
- """
- IOU Ethernet adapter.
- """
-
- def __init__(self):
- Adapter.__init__(self, interfaces=4)
-
- def __str__(self):
-
- return "IOU Ethernet adapter"
+class IOUError(VMError):
+ pass
diff --git a/gns3server/old_modules/iou/iou_device.py b/gns3server/modules/iou/iou_vm.py
similarity index 50%
rename from gns3server/old_modules/iou/iou_device.py
rename to gns3server/modules/iou/iou_vm.py
index ff8ff2c3..d2387c7d 100644
--- a/gns3server/old_modules/iou/iou_device.py
+++ b/gns3server/modules/iou/iou_vm.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
-# Copyright (C) 2014 GNS3 Technologies Inc.
+# 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
@@ -16,310 +16,220 @@
# along with this program. If not, see .
"""
-IOU device management (creates command line, processes, files etc.) in
+IOU VM management (creates command line, processes, files etc.) in
order to run an IOU instance.
"""
import os
-import re
-import signal
+import sys
import subprocess
+import signal
+import re
+import asyncio
+import shutil
import argparse
import threading
import configparser
-import shutil
-from .ioucon import start_ioucon
+from pkg_resources import parse_version
from .iou_error import IOUError
-from .adapters.ethernet_adapter import EthernetAdapter
-from .adapters.serial_adapter import SerialAdapter
-from .nios.nio_udp import NIO_UDP
-from .nios.nio_tap import NIO_TAP
-from .nios.nio_generic_ethernet import NIO_GenericEthernet
-from ..attic import find_unused_port
+from ..adapters.ethernet_adapter import EthernetAdapter
+from ..adapters.serial_adapter import SerialAdapter
+from ..nios.nio_udp import NIO_UDP
+from ..nios.nio_tap import NIO_TAP
+from ..base_vm import BaseVM
+from .ioucon import start_ioucon
+
import logging
log = logging.getLogger(__name__)
-class IOUDevice(object):
+class IOUVM(BaseVM):
+ module_name = 'iou'
"""
- IOU device implementation.
+ IOU vm implementation.
- :param name: name of this IOU device
- :param path: path to IOU executable
- :param working_dir: path to a working directory
- :param iou_id: IOU instance ID
+ :param name: name of this IOU vm
+ :param vm_id: IOU instance identifier
+ :param project: Project instance
+ :param manager: parent VM Manager
: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
+ :params console_host: TCP console host IP
+ :params ethernet_adapters: Number of ethernet adapters
+ :params serial_adapters: Number of serial adapters
+ :params ram: Ram MB
+ :params nvram: Nvram KB
"""
- _instances = []
- _allocated_console_ports = []
-
- def __init__(self,
- name,
- path,
- working_dir,
- iou_id=None,
+ def __init__(self, name, vm_id, project, manager,
console=None,
console_host="0.0.0.0",
- console_start_port_range=4001,
- console_end_port_range=4512):
+ ram=None,
+ nvram=None,
+ ethernet_adapters=None,
+ serial_adapters=None):
- if not iou_id:
- # find an instance identifier if none is provided (0 < id <= 512)
- self._id = 0
- for identifier in range(1, 513):
- if identifier not in self._instances:
- self._id = identifier
- self._instances.append(self._id)
- break
+ super().__init__(name, vm_id, project, manager)
- if self._id == 0:
- raise IOUError("Maximum number of IOU instances reached")
- else:
- if iou_id in self._instances:
- raise IOUError("IOU identifier {} is already used by another IOU device".format(iou_id))
- self._id = iou_id
- self._instances.append(self._id)
-
- self._name = name
- self._path = path
- self._iourc = ""
- self._iouyap = ""
self._console = console
- self._working_dir = None
self._command = []
- self._process = None
self._iouyap_process = None
+ self._iou_process = None
self._iou_stdout_file = ""
- self._iouyap_stdout_file = ""
- self._ioucon_thead = None
- self._ioucon_thread_stop_event = None
self._started = False
+ self._path = None
+ self._iourc_path = None
+ self._ioucon_thread = None
self._console_host = console_host
- self._console_start_port_range = console_start_port_range
- self._console_end_port_range = console_end_port_range
# IOU settings
- self._ethernet_adapters = [EthernetAdapter(), EthernetAdapter()] # one adapter = 4 interfaces
- self._serial_adapters = [SerialAdapter(), SerialAdapter()] # one adapter = 4 interfaces
- self._slots = self._ethernet_adapters + self._serial_adapters
+ self._ethernet_adapters = []
+ self._serial_adapters = []
+ self.ethernet_adapters = 2 if ethernet_adapters is None else ethernet_adapters # one adapter = 4 interfaces
+ self.serial_adapters = 2 if serial_adapters is None else serial_adapters # one adapter = 4 interfaces
self._use_default_iou_values = True # for RAM & NVRAM values
- self._nvram = 128 # Kilobytes
+ self._nvram = 128 if nvram is None else nvram # Kilobytes
self._initial_config = ""
- self._ram = 256 # Megabytes
+ self._ram = 256 if ram is None else ram # Megabytes
self._l1_keepalives = False # used to overcome the always-up Ethernet interfaces (not supported by all IOSes).
- working_dir_path = os.path.join(working_dir, "iou", "device-{}".format(self._id))
+ if self._console is not None:
+ self._console = self._manager.port_manager.reserve_console_port(self._console)
+ else:
+ self._console = self._manager.port_manager.get_free_console_port()
- if iou_id and not os.path.isdir(working_dir_path):
- raise IOUError("Working directory {} doesn't exist".format(working_dir_path))
+ def close(self):
- # 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._console_host,
- ignore_ports=self._allocated_console_ports)
- except Exception as e:
- raise IOUError(e)
-
- if self._console in self._allocated_console_ports:
- raise IOUError("Console port {} is already in used another IOU device".format(console))
- self._allocated_console_ports.append(self._console)
-
- log.info("IOU device {name} [id={id}] has been created".format(name=self._name,
- id=self._id))
-
- def defaults(self):
- """
- Returns all the default attribute values for IOU.
-
- :returns: default values (dictionary)
- """
-
- iou_defaults = {"name": self._name,
- "path": self._path,
- "intial_config": self._initial_config,
- "use_default_iou_values": self._use_default_iou_values,
- "ram": self._ram,
- "nvram": self._nvram,
- "ethernet_adapters": len(self._ethernet_adapters),
- "serial_adapters": len(self._serial_adapters),
- "console": self._console,
- "l1_keepalives": self._l1_keepalives}
-
- return iou_defaults
-
- @property
- def id(self):
- """
- Returns the unique ID for this IOU device.
-
- :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 IOU device.
-
- :returns: name
- """
-
- return self._name
-
- @name.setter
- def name(self, new_name):
- """
- Sets the name of this IOU device.
-
- :param new_name: name
- """
-
- if self._initial_config:
- # update the initial-config
- config_path = os.path.join(self._working_dir, "initial-config.cfg")
- if os.path.isfile(config_path):
- try:
- with open(config_path, "r+", errors="replace") as f:
- old_config = f.read()
- new_config = old_config.replace(self._name, new_name)
- f.seek(0)
- f.write(new_config)
- except OSError as e:
- raise IOUError("Could not amend the configuration {}: {}".format(config_path, e))
-
- log.info("IOU {name} [id={id}]: renamed to {new_name}".format(name=self._name,
- id=self._id,
- new_name=new_name))
- self._name = new_name
+ if self._console:
+ self._manager.port_manager.release_console_port(self._console)
+ self._console = None
@property
def path(self):
- """
- Returns the path to the IOU executable.
-
- :returns: path to IOU
- """
+ """Path of the iou binary"""
return self._path
@path.setter
def path(self, path):
"""
- Sets the path to the IOU executable.
+ Path of the iou binary
- :param path: path to IOU
+ :params path: Path to the binary
"""
self._path = path
- log.info("IOU {name} [id={id}]: path changed to {path}".format(name=self._name,
- id=self._id,
- path=path))
+ if not os.path.isfile(self._path) or not os.path.exists(self._path):
+ if os.path.islink(self._path):
+ raise IOUError("IOU image '{}' linked to '{}' is not accessible".format(self._path, os.path.realpath(self._path)))
+ else:
+ raise IOUError("IOU image '{}' is not accessible".format(self._path))
+
+ try:
+ with open(self._path, "rb") as f:
+ # read the first 7 bytes of the file.
+ elf_header_start = f.read(7)
+ except OSError as e:
+ raise IOUError("Cannot read ELF header for IOU image '{}': {}".format(self._path, e))
+
+ # IOU images must start with the ELF magic number, be 32-bit, little endian
+ # and have an ELF version of 1 normal IOS image are big endian!
+ if elf_header_start != b'\x7fELF\x01\x01\x01':
+ raise IOUError("'{}' is not a valid IOU image".format(self._path))
+
+ if not os.access(self._path, os.X_OK):
+ raise IOUError("IOU image '{}' is not executable".format(self._path))
@property
- def iourc(self):
+ def iourc_path(self):
"""
Returns the path to the iourc file.
-
:returns: path to the iourc file
"""
- return self._iourc
+ return self._iourc_path
- @iourc.setter
- def iourc(self, iourc):
+ @iourc_path.setter
+ def iourc_path(self, path):
"""
- Sets the path to the iourc file.
-
- :param iourc: path to the iourc file.
+ Set path to IOURC file
"""
- self._iourc = iourc
+ self._iourc_path = path
log.info("IOU {name} [id={id}]: iourc file path set to {path}".format(name=self._name,
id=self._id,
- path=self._iourc))
+ path=self._iourc_path))
@property
- def iouyap(self):
+ def use_default_iou_values(self):
"""
- Returns the path to iouyap
-
- :returns: path to iouyap
+ Returns if this device uses the default IOU image values.
+ :returns: boolean
"""
- return self._iouyap
+ return self._use_default_iou_values
- @iouyap.setter
- def iouyap(self, iouyap):
+ @use_default_iou_values.setter
+ def use_default_iou_values(self, state):
"""
- Sets the path to iouyap.
-
- :param iouyap: path to iouyap
+ Sets if this device uses the default IOU image values.
+ :param state: boolean
"""
- self._iouyap = iouyap
- log.info("IOU {name} [id={id}]: iouyap path set to {path}".format(name=self._name,
- id=self._id,
- path=self._iouyap))
+ self._use_default_iou_values = state
+ if state:
+ log.info("IOU {name} [id={id}]: uses the default IOU image values".format(name=self._name, id=self._id))
+ else:
+ log.info("IOU {name} [id={id}]: does not use the default IOU image values".format(name=self._name, id=self._id))
+
+ def _check_requirements(self):
+ """
+ Check if IOUYAP is available
+ """
+ path = self.iouyap_path
+ if not path:
+ raise IOUError("No path to a IOU executable has been set")
+
+ if not os.path.isfile(path):
+ raise IOUError("IOU program '{}' is not accessible".format(path))
+
+ if not os.access(path, os.X_OK):
+ raise IOUError("IOU program '{}' is not executable".format(path))
+
+ def __json__(self):
+
+ return {"name": self.name,
+ "vm_id": self.id,
+ "console": self._console,
+ "project_id": self.project.id,
+ "path": self.path,
+ "ethernet_adapters": len(self._ethernet_adapters),
+ "serial_adapters": len(self._serial_adapters),
+ "ram": self._ram,
+ "nvram": self._nvram
+ }
@property
- def working_dir(self):
+ def iouyap_path(self):
"""
- Returns current working directory
+ Returns the IOUYAP executable path.
- :returns: path to the working directory
+ :returns: path to IOUYAP
"""
- return self._working_dir
-
- @working_dir.setter
- def working_dir(self, working_dir):
- """
- Sets the working directory for IOU.
-
- :param working_dir: path to the working directory
- """
-
- try:
- os.makedirs(working_dir)
- except FileExistsError:
- pass
- except OSError as e:
- raise IOUError("Could not create working directory {}: {}".format(working_dir, e))
-
- self._working_dir = working_dir
- log.info("IOU {name} [id={id}]: working directory changed to {wd}".format(name=self._name,
- id=self._id,
- wd=self._working_dir))
+ path = self._manager.config.get_section_config("IOU").get("iouyap_path", "iouyap")
+ if path == "iouyap":
+ path = shutil.which("iouyap")
+ return path
@property
def console(self):
"""
- Returns the TCP console port.
+ Returns the console port of this IOU vm.
- :returns: console port (integer)
+ :returns: console port
"""
return self._console
@@ -327,84 +237,168 @@ class IOUDevice(object):
@console.setter
def console(self, console):
"""
- Sets the TCP console port.
+ Change console port
- :param console: console port (integer)
+ :params console: Console port (integer)
"""
- if console in self._allocated_console_ports:
- raise IOUError("Console port {} is already used by another IOU device".format(console))
-
- self._allocated_console_ports.remove(self._console)
- self._console = console
- self._allocated_console_ports.append(self._console)
- log.info("IOU {name} [id={id}]: console port set to {port}".format(name=self._name,
- id=self._id,
- port=console))
-
- def command(self):
- """
- Returns the IOU command line.
-
- :returns: IOU command line (string)
- """
-
- return " ".join(self._build_command())
-
- def delete(self):
- """
- Deletes this IOU device.
- """
-
- 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("IOU device {name} [id={id}] has been deleted".format(name=self._name,
- id=self._id))
-
- def clean_delete(self):
- """
- Deletes this IOU device & all files (nvram, initial-config etc.)
- """
-
- 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 IOU device {name} [id={id}]: {error}".format(name=self._name,
- id=self._id,
- error=e))
+ if console == self._console:
return
-
- log.info("IOU device {name} [id={id}] has been deleted (including associated files)".format(name=self._name,
- id=self._id))
+ if self._console:
+ self._manager.port_manager.release_console_port(self._console)
+ self._console = self._manager.port_manager.reserve_console_port(console)
@property
- def started(self):
+ def ram(self):
"""
- Returns either this IOU device has been started or not.
-
- :returns: boolean
+ Returns the amount of RAM allocated to this IOU instance.
+ :returns: amount of RAM in Mbytes (integer)
"""
- return self._started
+ return self._ram
+
+ @ram.setter
+ def ram(self, ram):
+ """
+ Sets amount of RAM allocated to this IOU instance.
+ :param ram: amount of RAM in Mbytes (integer)
+ """
+
+ if self._ram == ram:
+ return
+
+ log.info("IOU {name} [id={id}]: RAM updated from {old_ram}MB to {new_ram}MB".format(name=self._name,
+ id=self._id,
+ old_ram=self._ram,
+ new_ram=ram))
+
+ self._ram = ram
+
+ @property
+ def nvram(self):
+ """
+ Returns the mount of NVRAM allocated to this IOU instance.
+ :returns: amount of NVRAM in Kbytes (integer)
+ """
+
+ return self._nvram
+
+ @nvram.setter
+ def nvram(self, nvram):
+ """
+ Sets amount of NVRAM allocated to this IOU instance.
+ :param nvram: amount of NVRAM in Kbytes (integer)
+ """
+
+ if self._nvram == nvram:
+ return
+
+ log.info("IOU {name} [id={id}]: NVRAM updated from {old_nvram}KB to {new_nvram}KB".format(name=self._name,
+ id=self._id,
+ old_nvram=self._nvram,
+ new_nvram=nvram))
+ self._nvram = nvram
+
+ @property
+ def application_id(self):
+ return self._manager.get_application_id(self.id)
+
+ # TODO: ASYNCIO
+ def _library_check(self):
+ """
+ Checks for missing shared library dependencies in the IOU image.
+ """
+
+ try:
+ output = subprocess.check_output(["ldd", self._path])
+ except (FileNotFoundError, subprocess.SubprocessError) as e:
+ log.warn("could not determine the shared library dependencies for {}: {}".format(self._path, e))
+ return
+
+ p = re.compile("([\.\w]+)\s=>\s+not found")
+ missing_libs = p.findall(output.decode("utf-8"))
+ if missing_libs:
+ raise IOUError("The following shared library dependencies cannot be found for IOU image {}: {}".format(self._path,
+ ", ".join(missing_libs)))
+
+ @asyncio.coroutine
+ def start(self):
+ """
+ Starts the IOU process.
+ """
+
+ self._check_requirements()
+ if not self.is_running():
+
+ # TODO: ASYNC
+ # self._library_check()
+
+ if self._iourc_path and not os.path.isfile(self._iourc_path):
+ raise IOUError("A valid iourc file is necessary to start IOU")
+
+ iouyap_path = self.iouyap_path
+ if not iouyap_path or not os.path.isfile(iouyap_path):
+ raise IOUError("iouyap is necessary to start IOU")
+
+ self._create_netmap_config()
+ # created a environment variable pointing to the iourc file.
+ env = os.environ.copy()
+ if self._iourc_path:
+ env["IOURC"] = self._iourc_path
+ self._command = self._build_command()
+ try:
+ log.info("Starting IOU: {}".format(self._command))
+ self._iou_stdout_file = os.path.join(self.working_dir, "iou.log")
+ log.info("Logging to {}".format(self._iou_stdout_file))
+ with open(self._iou_stdout_file, "w") as fd:
+ self._iou_process = yield from asyncio.create_subprocess_exec(*self._command,
+ stdout=fd,
+ stderr=subprocess.STDOUT,
+ cwd=self.working_dir,
+ env=env)
+ log.info("IOU instance {} started PID={}".format(self._id, self._iou_process.pid))
+ self._started = True
+ except FileNotFoundError as e:
+ raise IOUError("could not start IOU: {}: 32-bit binary support is probably not installed".format(e))
+ except (OSError, subprocess.SubprocessError) as e:
+ iou_stdout = self.read_iou_stdout()
+ log.error("could not start IOU {}: {}\n{}".format(self._path, e, iou_stdout))
+ raise IOUError("could not start IOU {}: {}\n{}".format(self._path, e, iou_stdout))
+
+ # start console support
+ self._start_ioucon()
+ # connections support
+ self._start_iouyap()
+
+ def _start_iouyap(self):
+ """
+ Starts iouyap (handles connections to and from this IOU device).
+ """
+
+ try:
+ self._update_iouyap_config()
+ command = [self.iouyap_path, "-q", str(self.application_id + 512)] # iouyap has always IOU ID + 512
+ log.info("starting iouyap: {}".format(command))
+ self._iouyap_stdout_file = os.path.join(self.working_dir, "iouyap.log")
+ log.info("logging to {}".format(self._iouyap_stdout_file))
+ with open(self._iouyap_stdout_file, "w") as fd:
+ self._iouyap_process = subprocess.Popen(command,
+ stdout=fd,
+ stderr=subprocess.STDOUT,
+ cwd=self.working_dir)
+
+ log.info("iouyap started PID={}".format(self._iouyap_process.pid))
+ except (OSError, subprocess.SubprocessError) as e:
+ iouyap_stdout = self.read_iouyap_stdout()
+ log.error("could not start iouyap: {}\n{}".format(e, iouyap_stdout))
+ raise IOUError("Could not start iouyap: {}\n{}".format(e, iouyap_stdout))
def _update_iouyap_config(self):
"""
Updates the iouyap.ini file.
"""
- iouyap_ini = os.path.join(self._working_dir, "iouyap.ini")
+ iouyap_ini = os.path.join(self.working_dir, "iouyap.ini")
config = configparser.ConfigParser()
config["default"] = {"netmap": "NETMAP",
@@ -431,7 +425,7 @@ class IOUDevice(object):
connection = {"eth_dev": "{ethernet_device}".format(ethernet_device=nio.ethernet_device)}
if connection:
- interface = "{iouyap_id}:{bay}/{unit}".format(iouyap_id=str(self._id + 512), bay=bay_id, unit=unit_id)
+ interface = "{iouyap_id}:{bay}/{unit}".format(iouyap_id=str(self.application_id + 512), bay=bay_id, unit=unit_id)
config[interface] = connection
if nio.capturing:
@@ -460,211 +454,71 @@ class IOUDevice(object):
except OSError as e:
raise IOUError("Could not create {}: {}".format(iouyap_ini, e))
- def _create_netmap_config(self):
- """
- Creates the NETMAP file.
- """
-
- netmap_path = os.path.join(self._working_dir, "NETMAP")
- try:
- with open(netmap_path, "w") as f:
- for bay in range(0, 16):
- for unit in range(0, 4):
- f.write("{iouyap_id}:{bay}/{unit}{iou_id:>5d}:{bay}/{unit}\n".format(iouyap_id=str(self._id + 512),
- bay=bay,
- unit=unit,
- iou_id=self._id))
- log.info("IOU {name} [id={id}]: NETMAP file created".format(name=self._name,
- id=self._id))
- except OSError as e:
- raise IOUError("Could not create {}: {}".format(netmap_path, e))
-
- def _start_ioucon(self):
- """
- Starts ioucon thread (for console connections).
- """
-
- if not self._ioucon_thead:
- telnet_server = "{}:{}".format(self._console_host, self.console)
- log.info("starting ioucon for IOU instance {} to accept Telnet connections on {}".format(self._name, telnet_server))
- args = argparse.Namespace(appl_id=str(self._id), debug=False, escape='^^', telnet_limit=0, telnet_server=telnet_server)
- self._ioucon_thread_stop_event = threading.Event()
- self._ioucon_thead = threading.Thread(target=start_ioucon, args=(args, self._ioucon_thread_stop_event))
- self._ioucon_thead.start()
-
- def _start_iouyap(self):
- """
- Starts iouyap (handles connections to and from this IOU device).
- """
-
- try:
- self._update_iouyap_config()
- command = [self._iouyap, "-q", str(self._id + 512)] # iouyap has always IOU ID + 512
- log.info("starting iouyap: {}".format(command))
- self._iouyap_stdout_file = os.path.join(self._working_dir, "iouyap.log")
- log.info("logging to {}".format(self._iouyap_stdout_file))
- with open(self._iouyap_stdout_file, "w") as fd:
- self._iouyap_process = subprocess.Popen(command,
- stdout=fd,
- stderr=subprocess.STDOUT,
- cwd=self._working_dir)
-
- log.info("iouyap started PID={}".format(self._iouyap_process.pid))
- except (OSError, subprocess.SubprocessError) as e:
- iouyap_stdout = self.read_iouyap_stdout()
- log.error("could not start iouyap: {}\n{}".format(e, iouyap_stdout))
- raise IOUError("Could not start iouyap: {}\n{}".format(e, iouyap_stdout))
-
- def _library_check(self):
- """
- Checks for missing shared library dependencies in the IOU image.
- """
-
- try:
- output = subprocess.check_output(["ldd", self._path])
- except (FileNotFoundError, subprocess.SubprocessError) as e:
- log.warn("could not determine the shared library dependencies for {}: {}".format(self._path, e))
- return
-
- p = re.compile("([\.\w]+)\s=>\s+not found")
- missing_libs = p.findall(output.decode("utf-8"))
- if missing_libs:
- raise IOUError("The following shared library dependencies cannot be found for IOU image {}: {}".format(self._path,
- ", ".join(missing_libs)))
-
- def start(self):
- """
- Starts the IOU process.
- """
-
- if not self.is_running():
-
- if not os.path.isfile(self._path) or not os.path.exists(self._path):
- if os.path.islink(self._path):
- raise IOUError("IOU image '{}' linked to '{}' is not accessible".format(self._path, os.path.realpath(self._path)))
- else:
- raise IOUError("IOU image '{}' is not accessible".format(self._path))
-
- try:
- with open(self._path, "rb") as f:
- # read the first 7 bytes of the file.
- elf_header_start = f.read(7)
- except OSError as e:
- raise IOUError("Cannot read ELF header for IOU image '{}': {}".format(self._path, e))
-
- # IOU images must start with the ELF magic number, be 32-bit, little endian
- # and have an ELF version of 1 normal IOS image are big endian!
- if elf_header_start != b'\x7fELF\x01\x01\x01':
- raise IOUError("'{}' is not a valid IOU image".format(self._path))
-
- if not os.access(self._path, os.X_OK):
- raise IOUError("IOU image '{}' is not executable".format(self._path))
-
- self._library_check()
-
- if not self._iourc or not os.path.isfile(self._iourc):
- raise IOUError("A valid iourc file is necessary to start IOU")
-
- if not self._iouyap or not os.path.isfile(self._iouyap):
- raise IOUError("iouyap is necessary to start IOU")
-
- self._create_netmap_config()
- # created a environment variable pointing to the iourc file.
- env = os.environ.copy()
- env["IOURC"] = self._iourc
- self._command = self._build_command()
- try:
- log.info("starting IOU: {}".format(self._command))
- self._iou_stdout_file = os.path.join(self._working_dir, "iou.log")
- log.info("logging to {}".format(self._iou_stdout_file))
- with open(self._iou_stdout_file, "w") as fd:
- self._process = subprocess.Popen(self._command,
- stdout=fd,
- stderr=subprocess.STDOUT,
- cwd=self._working_dir,
- env=env)
- log.info("IOU instance {} started PID={}".format(self._id, self._process.pid))
- self._started = True
- except FileNotFoundError as e:
- raise IOUError("could not start IOU: {}: 32-bit binary support is probably not installed".format(e))
- except (OSError, subprocess.SubprocessError) as e:
- iou_stdout = self.read_iou_stdout()
- log.error("could not start IOU {}: {}\n{}".format(self._path, e, iou_stdout))
- raise IOUError("could not start IOU {}: {}\n{}".format(self._path, e, iou_stdout))
-
- # start console support
- self._start_ioucon()
- # connections support
- self._start_iouyap()
-
+ @asyncio.coroutine
def stop(self):
"""
Stops the IOU process.
"""
# stop console support
- if self._ioucon_thead:
+ if self._ioucon_thread:
self._ioucon_thread_stop_event.set()
- if self._ioucon_thead.is_alive():
- self._ioucon_thead.join(timeout=3.0) # wait for the thread to free the console port
- self._ioucon_thead = None
+ if self._ioucon_thread.is_alive():
+ self._ioucon_thread.join(timeout=3.0) # wait for the thread to free the console port
+ self._ioucon_thread = None
- # stop iouyap
- if self.is_iouyap_running():
- log.info("stopping iouyap PID={} for IOU instance {}".format(self._iouyap_process.pid, self._id))
+ if self.is_running():
+ self._terminate_process_iou()
+ try:
+ yield from asyncio.wait_for(self._iou_process.wait(), timeout=3)
+ except asyncio.TimeoutError:
+ self._iou_process.kill()
+ if self._iou_process.returncode is None:
+ log.warn("IOU process {} is still running".format(self._iou_process.pid))
+
+ self._iou_process = None
+
+ if self._iouyap_process is not None:
+ self._terminate_process_iouyap()
+ try:
+ yield from asyncio.wait_for(self._iouyap_process.wait(), timeout=3)
+ except asyncio.TimeoutError:
+ self._iou_process.kill()
+ if self._iouyap_process.returncode is None:
+ log.warn("IOUYAP process {} is still running".format(self._iou_process.pid))
+
+ self._started = False
+
+ def _terminate_process_iouyap(self):
+ """Terminate the process if running"""
+
+ if self._iou_process:
+ log.info("Stopping IOUYAP instance {} PID={}".format(self.name, self._iouyap_process.pid))
try:
self._iouyap_process.terminate()
- self._iouyap_process.wait(1)
- except subprocess.TimeoutExpired:
- self._iouyap_process.kill()
- if self._iouyap_process.poll() is None:
- log.warn("iouyap PID={} for IOU instance {} is still running".format(self._iouyap_process.pid,
- self._id))
- self._iouyap_process = None
+ # Sometime the process can already be dead when we garbage collect
+ except ProcessLookupError:
+ pass
- # stop the IOU process
- if self.is_running():
- log.info("stopping IOU instance {} PID={}".format(self._id, self._process.pid))
+ def _terminate_process_iou(self):
+ """Terminate the process if running"""
+
+ if self._iou_process:
+ log.info("Stopping IOU instance {} PID={}".format(self.name, self._iou_process.pid))
try:
- self._process.terminate()
- self._process.wait(1)
- except subprocess.TimeoutExpired:
- self._process.kill()
- if self._process.poll() is None:
- log.warn("IOU instance {} PID={} is still running".format(self._id,
- self._process.pid))
- self._process = None
- self._started = False
+ self._iou_process.terminate()
+ # Sometime the process can already be dead when we garbage collect
+ except ProcessLookupError:
+ pass
- def read_iou_stdout(self):
+ @asyncio.coroutine
+ def reload(self):
"""
- Reads the standard output of the IOU process.
- Only use when the process has been stopped or has crashed.
+ Reload the IOU process. (Stop / Start)
"""
- output = ""
- if self._iou_stdout_file:
- try:
- with open(self._iou_stdout_file, errors="replace") as file:
- output = file.read()
- except OSError as e:
- log.warn("could not read {}: {}".format(self._iou_stdout_file, e))
- return output
-
- def read_iouyap_stdout(self):
- """
- Reads the standard output of the iouyap process.
- Only use when the process has been stopped or has crashed.
- """
-
- output = ""
- if self._iouyap_stdout_file:
- try:
- with open(self._iouyap_stdout_file, errors="replace") as file:
- output = file.read()
- except OSError as e:
- log.warn("could not read {}: {}".format(self._iouyap_stdout_file, e))
- return output
+ yield from self.stop()
+ yield from self.start()
def is_running(self):
"""
@@ -673,106 +527,44 @@ class IOUDevice(object):
:returns: True or False
"""
- if self._process and self._process.poll() is None:
+ if self._iou_process:
return True
return False
def is_iouyap_running(self):
"""
- Checks if the iouyap process is running
+ Checks if the IOUYAP process is running
:returns: True or False
"""
- if self._iouyap_process and self._iouyap_process.poll() is None:
+ if self._iouyap_process:
return True
return False
- def slot_add_nio_binding(self, slot_id, port_id, nio):
+ def _create_netmap_config(self):
"""
- Adds a slot NIO binding.
-
- :param slot_id: slot ID
- :param port_id: port ID
- :param nio: NIO instance to add to the slot/port
+ Creates the NETMAP file.
"""
+ netmap_path = os.path.join(self.working_dir, "NETMAP")
try:
- adapter = self._slots[slot_id]
- except IndexError:
- raise IOUError("Slot {slot_id} doesn't exist on IOU {name}".format(name=self._name,
- slot_id=slot_id))
-
- if not adapter.port_exists(port_id):
- raise IOUError("Port {port_id} doesn't exist in adapter {adapter}".format(adapter=adapter,
- port_id=port_id))
-
- adapter.add_nio(port_id, nio)
- log.info("IOU {name} [id={id}]: {nio} added to {slot_id}/{port_id}".format(name=self._name,
- id=self._id,
- nio=nio,
- slot_id=slot_id,
- port_id=port_id))
- if self.is_iouyap_running():
- self._update_iouyap_config()
- os.kill(self._iouyap_process.pid, signal.SIGHUP)
-
- def slot_remove_nio_binding(self, slot_id, port_id):
- """
- Removes a slot NIO binding.
-
- :param slot_id: slot ID
- :param port_id: port ID
-
- :returns: NIO instance
- """
-
- try:
- adapter = self._slots[slot_id]
- except IndexError:
- raise IOUError("Slot {slot_id} doesn't exist on IOU {name}".format(name=self._name,
- slot_id=slot_id))
-
- if not adapter.port_exists(port_id):
- raise IOUError("Port {port_id} doesn't exist in adapter {adapter}".format(adapter=adapter,
- port_id=port_id))
-
- nio = adapter.get_nio(port_id)
- adapter.remove_nio(port_id)
- log.info("IOU {name} [id={id}]: {nio} removed from {slot_id}/{port_id}".format(name=self._name,
- id=self._id,
- nio=nio,
- slot_id=slot_id,
- port_id=port_id))
- if self.is_iouyap_running():
- self._update_iouyap_config()
- os.kill(self._iouyap_process.pid, signal.SIGHUP)
-
- return nio
-
- def _enable_l1_keepalives(self, command):
- """
- Enables L1 keepalive messages if supported.
-
- :param command: command line
- """
-
- env = os.environ.copy()
- env["IOURC"] = self._iourc
- try:
- output = subprocess.check_output([self._path, "-h"], stderr=subprocess.STDOUT, cwd=self._working_dir, env=env)
- if re.search("-l\s+Enable Layer 1 keepalive messages", output.decode("utf-8")):
- command.extend(["-l"])
- else:
- raise IOUError("layer 1 keepalive messages are not supported by {}".format(os.path.basename(self._path)))
- except (OSError, subprocess.SubprocessError) as e:
- log.warn("could not determine if layer 1 keepalive messages are supported by {}: {}".format(os.path.basename(self._path), e))
+ with open(netmap_path, "w") as f:
+ for bay in range(0, 16):
+ for unit in range(0, 4):
+ f.write("{iouyap_id}:{bay}/{unit}{iou_id:>5d}:{bay}/{unit}\n".format(iouyap_id=str(self.application_id + 512),
+ bay=bay,
+ unit=unit,
+ iou_id=self.application_id))
+ log.info("IOU {name} [id={id}]: NETMAP file created".format(name=self._name,
+ id=self._id))
+ except OSError as e:
+ raise IOUError("Could not create {}: {}".format(netmap_path, e))
def _build_command(self):
"""
Command to start the IOU process.
(to be passed to subprocess.Popen())
-
IOU command line:
Usage: [options]
: unix-js-m | unix-is-m | unix-i-m | ...
@@ -811,140 +603,56 @@ class IOUDevice(object):
command.extend(["-c", self._initial_config])
if self._l1_keepalives:
self._enable_l1_keepalives(command)
- command.extend([str(self._id)])
+ command.extend([str(self.application_id)])
return command
- @property
- def use_default_iou_values(self):
+ def read_iou_stdout(self):
"""
- Returns if this device uses the default IOU image values.
-
- :returns: boolean
+ Reads the standard output of the IOU process.
+ Only use when the process has been stopped or has crashed.
"""
- return self._use_default_iou_values
+ output = ""
+ if self._iou_stdout_file:
+ try:
+ with open(self._iou_stdout_file, errors="replace") as file:
+ output = file.read()
+ except OSError as e:
+ log.warn("could not read {}: {}".format(self._iou_stdout_file, e))
+ return output
- @use_default_iou_values.setter
- def use_default_iou_values(self, state):
+ def read_iouyap_stdout(self):
"""
- Sets if this device uses the default IOU image values.
-
- :param state: boolean
+ Reads the standard output of the iouyap process.
+ Only use when the process has been stopped or has crashed.
"""
- self._use_default_iou_values = state
- if state:
- log.info("IOU {name} [id={id}]: uses the default IOU image values".format(name=self._name, id=self._id))
- else:
- log.info("IOU {name} [id={id}]: does not use the default IOU image values".format(name=self._name, id=self._id))
+ output = ""
+ if self._iouyap_stdout_file:
+ try:
+ with open(self._iouyap_stdout_file, errors="replace") as file:
+ output = file.read()
+ except OSError as e:
+ log.warn("could not read {}: {}".format(self._iouyap_stdout_file, e))
+ return output
- @property
- def l1_keepalives(self):
+ def _start_ioucon(self):
"""
- Returns either layer 1 keepalive messages option is enabled or disabled.
-
- :returns: boolean
+ Starts ioucon thread (for console connections).
"""
- return self._l1_keepalives
-
- @l1_keepalives.setter
- def l1_keepalives(self, state):
- """
- Enables or disables layer 1 keepalive messages.
-
- :param state: boolean
- """
-
- self._l1_keepalives = state
- if state:
- log.info("IOU {name} [id={id}]: has activated layer 1 keepalive messages".format(name=self._name, id=self._id))
- else:
- log.info("IOU {name} [id={id}]: has deactivated layer 1 keepalive messages".format(name=self._name, id=self._id))
-
- @property
- def ram(self):
- """
- Returns the amount of RAM allocated to this IOU instance.
-
- :returns: amount of RAM in Mbytes (integer)
- """
-
- return self._ram
-
- @ram.setter
- def ram(self, ram):
- """
- Sets amount of RAM allocated to this IOU instance.
-
- :param ram: amount of RAM in Mbytes (integer)
- """
-
- if self._ram == ram:
- return
-
- log.info("IOU {name} [id={id}]: RAM updated from {old_ram}MB to {new_ram}MB".format(name=self._name,
- id=self._id,
- old_ram=self._ram,
- new_ram=ram))
-
- self._ram = ram
-
- @property
- def nvram(self):
- """
- Returns the mount of NVRAM allocated to this IOU instance.
-
- :returns: amount of NVRAM in Kbytes (integer)
- """
-
- return self._nvram
-
- @nvram.setter
- def nvram(self, nvram):
- """
- Sets amount of NVRAM allocated to this IOU instance.
-
- :param nvram: amount of NVRAM in Kbytes (integer)
- """
-
- if self._nvram == nvram:
- return
-
- log.info("IOU {name} [id={id}]: NVRAM updated from {old_nvram}KB to {new_nvram}KB".format(name=self._name,
- id=self._id,
- old_nvram=self._nvram,
- new_nvram=nvram))
- self._nvram = nvram
-
- @property
- def initial_config(self):
- """
- Returns the initial-config for this IOU instance.
-
- :returns: path to initial-config file
- """
-
- return self._initial_config
-
- @initial_config.setter
- def initial_config(self, initial_config):
- """
- Sets the initial-config for this IOU instance.
-
- :param initial_config: path to initial-config file
- """
-
- self._initial_config = initial_config
- log.info("IOU {name} [id={id}]: initial_config set to {config}".format(name=self._name,
- id=self._id,
- config=self._initial_config))
+ if not self._ioucon_thread:
+ telnet_server = "{}:{}".format(self._console_host, self.console)
+ log.info("Starting ioucon for IOU instance {} to accept Telnet connections on {}".format(self._name, telnet_server))
+ args = argparse.Namespace(appl_id=str(self.application_id), debug=False, escape='^^', telnet_limit=0, telnet_server=telnet_server)
+ self._ioucon_thread_stop_event = threading.Event()
+ self._ioucon_thread = threading.Thread(target=start_ioucon, args=(args, self._ioucon_thread_stop_event))
+ self._ioucon_thread.start()
@property
def ethernet_adapters(self):
"""
Returns the number of Ethernet adapters for this IOU instance.
-
:returns: number of adapters
"""
@@ -954,7 +662,6 @@ class IOUDevice(object):
def ethernet_adapters(self, ethernet_adapters):
"""
Sets the number of Ethernet adapters for this IOU instance.
-
:param ethernet_adapters: number of adapters
"""
@@ -972,7 +679,6 @@ class IOUDevice(object):
def serial_adapters(self):
"""
Returns the number of Serial adapters for this IOU instance.
-
:returns: number of adapters
"""
@@ -982,7 +688,6 @@ class IOUDevice(object):
def serial_adapters(self, serial_adapters):
"""
Sets the number of Serial adapters for this IOU instance.
-
:param serial_adapters: number of adapters
"""
@@ -996,15 +701,40 @@ class IOUDevice(object):
self._slots = self._ethernet_adapters + self._serial_adapters
- def start_capture(self, slot_id, port_id, output_file, data_link_type="DLT_EN10MB"):
+ def slot_add_nio_binding(self, slot_id, port_id, nio):
"""
- Starts a packet capture.
-
+ Adds a slot NIO binding.
:param slot_id: slot ID
:param port_id: port ID
- :param port: allocated port
- :param output_file: PCAP destination file for the capture
- :param data_link_type: PCAP data link type (DLT_*), default is DLT_EN10MB
+ :param nio: NIO instance to add to the slot/port
+ """
+
+ try:
+ adapter = self._slots[slot_id]
+ except IndexError:
+ raise IOUError("Slot {slot_id} doesn't exist on IOU {name}".format(name=self._name,
+ slot_id=slot_id))
+
+ if not adapter.port_exists(port_id):
+ raise IOUError("Port {port_id} doesn't exist in adapter {adapter}".format(adapter=adapter,
+ port_id=port_id))
+
+ adapter.add_nio(port_id, nio)
+ log.info("IOU {name} [id={id}]: {nio} added to {slot_id}/{port_id}".format(name=self._name,
+ id=self._id,
+ nio=nio,
+ slot_id=slot_id,
+ port_id=port_id))
+ if self.is_iouyap_running():
+ self._update_iouyap_config()
+ os.kill(self._iouyap_process.pid, signal.SIGHUP)
+
+ def slot_remove_nio_binding(self, slot_id, port_id):
+ """
+ Removes a slot NIO binding.
+ :param slot_id: slot ID
+ :param port_id: port ID
+ :returns: NIO instance
"""
try:
@@ -1018,52 +748,14 @@ class IOUDevice(object):
port_id=port_id))
nio = adapter.get_nio(port_id)
- if nio.capturing:
- raise IOUError("Packet capture is already activated on {slot_id}/{port_id}".format(slot_id=slot_id,
- port_id=port_id))
-
- try:
- os.makedirs(os.path.dirname(output_file))
- except FileExistsError:
- pass
- except OSError as e:
- raise IOUError("Could not create captures directory {}".format(e))
-
- nio.startPacketCapture(output_file, data_link_type)
-
- log.info("IOU {name} [id={id}]: starting packet capture on {slot_id}/{port_id}".format(name=self._name,
- id=self._id,
- slot_id=slot_id,
- port_id=port_id))
-
+ adapter.remove_nio(port_id)
+ log.info("IOU {name} [id={id}]: {nio} removed from {slot_id}/{port_id}".format(name=self._name,
+ id=self._id,
+ nio=nio,
+ slot_id=slot_id,
+ port_id=port_id))
if self.is_iouyap_running():
self._update_iouyap_config()
os.kill(self._iouyap_process.pid, signal.SIGHUP)
- def stop_capture(self, slot_id, port_id):
- """
- Stops a packet capture.
-
- :param slot_id: slot ID
- :param port_id: port ID
- """
-
- try:
- adapter = self._slots[slot_id]
- except IndexError:
- raise IOUError("Slot {slot_id} doesn't exist on IOU {name}".format(name=self._name,
- slot_id=slot_id))
-
- if not adapter.port_exists(port_id):
- raise IOUError("Port {port_id} doesn't exist in adapter {adapter}".format(adapter=adapter,
- port_id=port_id))
-
- nio = adapter.get_nio(port_id)
- nio.stopPacketCapture()
- log.info("IOU {name} [id={id}]: stopping packet capture on {slot_id}/{port_id}".format(name=self._name,
- id=self._id,
- slot_id=slot_id,
- port_id=port_id))
- if self.is_iouyap_running():
- self._update_iouyap_config()
- os.kill(self._iouyap_process.pid, signal.SIGHUP)
+ return nio
diff --git a/gns3server/old_modules/iou/ioucon.py b/gns3server/modules/iou/ioucon.py
similarity index 99%
rename from gns3server/old_modules/iou/ioucon.py
rename to gns3server/modules/iou/ioucon.py
index 9a0e980e..6dbd782d 100644
--- a/gns3server/old_modules/iou/ioucon.py
+++ b/gns3server/modules/iou/ioucon.py
@@ -55,7 +55,7 @@ EXIT_ABORT = 2
# Mostly from:
# https://code.google.com/p/miniboa/source/browse/trunk/miniboa/telnet.py
-#--[ Telnet Commands ]---------------------------------------------------------
+# --[ Telnet Commands ]---------------------------------------------------------
SE = 240 # End of sub-negotiation parameters
NOP = 241 # No operation
DATMK = 242 # Data stream portion of a sync.
@@ -74,7 +74,7 @@ DONT = 254 # Don't = Demand or confirm option halt
IAC = 255 # Interpret as Command
SEND = 1 # Sub-process negotiation SEND command
IS = 0 # Sub-process negotiation IS command
-#--[ Telnet Options ]----------------------------------------------------------
+# --[ Telnet Options ]----------------------------------------------------------
BINARY = 0 # Transmit Binary
ECHO = 1 # Echo characters back to sender
RECON = 2 # Reconnection
diff --git a/gns3server/modules/nios/nio_tap.py b/gns3server/modules/nios/nio_tap.py
index 9f51ce13..a63a72c3 100644
--- a/gns3server/modules/nios/nio_tap.py
+++ b/gns3server/modules/nios/nio_tap.py
@@ -22,7 +22,7 @@ Interface for TAP NIOs (UNIX based OSes only).
from .nio import NIO
-class NIOTAP(NIO):
+class NIO_TAP(NIO):
"""
TAP NIO.
diff --git a/gns3server/modules/nios/nio_udp.py b/gns3server/modules/nios/nio_udp.py
index a87875fe..4af43cd6 100644
--- a/gns3server/modules/nios/nio_udp.py
+++ b/gns3server/modules/nios/nio_udp.py
@@ -22,7 +22,7 @@ Interface for UDP NIOs.
from .nio import NIO
-class NIOUDP(NIO):
+class NIO_UDP(NIO):
"""
UDP NIO.
diff --git a/gns3server/modules/virtualbox/virtualbox_error.py b/gns3server/modules/virtualbox/virtualbox_error.py
index ec05bfb6..df481c21 100644
--- a/gns3server/modules/virtualbox/virtualbox_error.py
+++ b/gns3server/modules/virtualbox/virtualbox_error.py
@@ -24,18 +24,4 @@ from ..vm_error import VMError
class VirtualBoxError(VMError):
- 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
+ pass
diff --git a/gns3server/modules/vm_error.py b/gns3server/modules/vm_error.py
index d7b71e14..55cfc4cf 100644
--- a/gns3server/modules/vm_error.py
+++ b/gns3server/modules/vm_error.py
@@ -17,4 +17,19 @@
class VMError(Exception):
- pass
+
+ 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/vpcs/vpcs_error.py b/gns3server/modules/vpcs/vpcs_error.py
index f32afdaa..b8e99d4b 100644
--- a/gns3server/modules/vpcs/vpcs_error.py
+++ b/gns3server/modules/vpcs/vpcs_error.py
@@ -24,18 +24,4 @@ from ..vm_error import VMError
class VPCSError(VMError):
- 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
+ pass
diff --git a/gns3server/old_modules/iou/__init__.py b/gns3server/old_modules/iou/__init__.py
deleted file mode 100644
index 04c7e4c0..00000000
--- a/gns3server/old_modules/iou/__init__.py
+++ /dev/null
@@ -1,843 +0,0 @@
-# -*- 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 .
-
-"""
-IOU server module.
-"""
-
-import os
-import base64
-import ntpath
-import stat
-import tempfile
-import socket
-import shutil
-
-from gns3server.modules import IModule
-from gns3server.config import Config
-from gns3dms.cloud.rackspace_ctrl import get_provider
-from .iou_device import IOUDevice
-from .iou_error import IOUError
-from .nios.nio_udp import NIO_UDP
-from .nios.nio_tap import NIO_TAP
-from .nios.nio_generic_ethernet import NIO_GenericEthernet
-from ..attic import find_unused_port
-from ..attic import has_privileged_access
-
-from .schemas import IOU_CREATE_SCHEMA
-from .schemas import IOU_DELETE_SCHEMA
-from .schemas import IOU_UPDATE_SCHEMA
-from .schemas import IOU_START_SCHEMA
-from .schemas import IOU_STOP_SCHEMA
-from .schemas import IOU_RELOAD_SCHEMA
-from .schemas import IOU_ALLOCATE_UDP_PORT_SCHEMA
-from .schemas import IOU_ADD_NIO_SCHEMA
-from .schemas import IOU_DELETE_NIO_SCHEMA
-from .schemas import IOU_START_CAPTURE_SCHEMA
-from .schemas import IOU_STOP_CAPTURE_SCHEMA
-from .schemas import IOU_EXPORT_CONFIG_SCHEMA
-
-import logging
-log = logging.getLogger(__name__)
-
-
-class IOU(IModule):
-
- """
- IOU 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 iouyap location
- config = Config.instance()
- iou_config = config.get_section_config(name.upper())
- self._iouyap = iou_config.get("iouyap_path")
- if not self._iouyap or not os.path.isfile(self._iouyap):
- paths = [os.getcwd()] + os.environ["PATH"].split(os.pathsep)
- # look for iouyap in the current working directory and $PATH
- for path in paths:
- try:
- if "iouyap" in os.listdir(path) and os.access(os.path.join(path, "iouyap"), os.X_OK):
- self._iouyap = os.path.join(path, "iouyap")
- break
- except OSError:
- continue
-
- if not self._iouyap:
- log.warning("iouyap binary couldn't be found!")
- elif not os.access(self._iouyap, os.X_OK):
- log.warning("iouyap is not executable")
-
- # a new process start when calling IModule
- IModule.__init__(self, name, *args, **kwargs)
- self._iou_instances = {}
- self._console_start_port_range = iou_config.get("console_start_port_range", 4001)
- self._console_end_port_range = iou_config.get("console_end_port_range", 4500)
- self._allocated_udp_ports = []
- self._udp_start_port_range = iou_config.get("udp_start_port_range", 30001)
- self._udp_end_port_range = iou_config.get("udp_end_port_range", 35000)
- self._host = iou_config.get("host", kwargs["host"])
- self._console_host = iou_config.get("console_host", kwargs["console_host"])
- self._projects_dir = kwargs["projects_dir"]
- self._tempdir = kwargs["temp_dir"]
- self._working_dir = self._projects_dir
- self._server_iourc_path = iou_config.get("iourc", "")
- self._iourc = ""
-
- # check every 5 seconds
- self._iou_callback = self.add_periodic_callback(self._check_iou_is_alive, 5000)
- self._iou_callback.start()
-
- def stop(self, signum=None):
- """
- Properly stops the module.
-
- :param signum: signal number (if called by the signal handler)
- """
-
- self._iou_callback.stop()
-
- # delete all IOU instances
- for iou_id in self._iou_instances:
- iou_instance = self._iou_instances[iou_id]
- iou_instance.delete()
-
- self.delete_iourc_file()
-
- IModule.stop(self, signum) # this will stop the I/O loop
-
- def _check_iou_is_alive(self):
- """
- Periodic callback to check if IOU and iouyap are alive
- for each IOU instance.
-
- Sends a notification to the client if not.
- """
-
- for iou_id in self._iou_instances:
- iou_instance = self._iou_instances[iou_id]
- if iou_instance.started and (not iou_instance.is_running() or not iou_instance.is_iouyap_running()):
- notification = {"module": self.name,
- "id": iou_id,
- "name": iou_instance.name}
- if not iou_instance.is_running():
- stdout = iou_instance.read_iou_stdout()
- notification["message"] = "IOU has stopped running"
- notification["details"] = stdout
- self.send_notification("{}.iou_stopped".format(self.name), notification)
- elif not iou_instance.is_iouyap_running():
- stdout = iou_instance.read_iouyap_stdout()
- notification["message"] = "iouyap has stopped running"
- notification["details"] = stdout
- self.send_notification("{}.iouyap_stopped".format(self.name), notification)
- iou_instance.stop()
-
- def get_iou_instance(self, iou_id):
- """
- Returns an IOU device instance.
-
- :param iou_id: IOU device identifier
-
- :returns: IOUDevice instance
- """
-
- if iou_id not in self._iou_instances:
- log.debug("IOU device ID {} doesn't exist".format(iou_id), exc_info=1)
- self.send_custom_error("IOU device ID {} doesn't exist".format(iou_id))
- return None
- return self._iou_instances[iou_id]
-
- def delete_iourc_file(self):
- """
- Deletes the IOURC file.
- """
-
- if self._iourc and os.path.isfile(self._iourc):
- try:
- log.info("deleting iourc file {}".format(self._iourc))
- os.remove(self._iourc)
- except OSError as e:
- log.warn("could not delete iourc file {}: {}".format(self._iourc, e))
-
- @IModule.route("iou.reset")
- def reset(self, request=None):
- """
- Resets the module (JSON-RPC notification).
-
- :param request: JSON request (not used)
- """
-
- # delete all IOU instances
- for iou_id in self._iou_instances:
- iou_instance = self._iou_instances[iou_id]
- iou_instance.delete()
-
- # resets the instance IDs
- IOUDevice.reset()
-
- self._iou_instances.clear()
- self._allocated_udp_ports.clear()
- self.delete_iourc_file()
-
- self._working_dir = self._projects_dir
- log.info("IOU module has been reset")
-
- @IModule.route("iou.settings")
- def settings(self, request):
- """
- Set or update settings.
-
- Mandatory request parameters:
- - iourc (base64 encoded iourc file)
-
- Optional request parameters:
- - iouyap (path to iouyap)
- - 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 "iourc" in request:
- iourc_content = base64.decodebytes(request["iourc"].encode("utf-8")).decode("utf-8")
- iourc_content = iourc_content.replace("\r\n", "\n") # dos2unix
- try:
- with tempfile.NamedTemporaryFile(mode="w", delete=False) as f:
- log.info("saving iourc file content to {}".format(f.name))
- f.write(iourc_content)
- self._iourc = f.name
- except OSError as e:
- raise IOUError("Could not create the iourc file: {}".format(e))
-
- if "iouyap" in request and request["iouyap"]:
- self._iouyap = request["iouyap"]
- log.info("iouyap path set to {}".format(self._iouyap))
-
- 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 iou_id in self._iou_instances:
- iou_instance = self._iou_instances[iou_id]
- iou_instance.working_dir = os.path.join(self._working_dir, "iou", "device-{}".format(iou_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("iou.create")
- def iou_create(self, request):
- """
- Creates a new IOU instance.
-
- Mandatory request parameters:
- - path (path to the IOU executable)
-
- Optional request parameters:
- - name (IOU name)
- - console (IOU console port)
-
- Response parameters:
- - id (IOU instance identifier)
- - name (IOU name)
- - default settings
-
- :param request: JSON request
- """
-
- # validate the request
- if not self.validate_request(request, IOU_CREATE_SCHEMA):
- return
-
- name = request["name"]
- iou_path = request["path"]
- console = request.get("console")
- iou_id = request.get("iou_id")
-
- updated_iou_path = os.path.join(self.images_directory, iou_path)
- if os.path.isfile(updated_iou_path):
- iou_path = updated_iou_path
- else:
- if not os.path.exists(self.images_directory):
- os.mkdir(self.images_directory)
- cloud_path = request.get("cloud_path", None)
- if cloud_path is not None:
- # Download the image from cloud files
- _, filename = ntpath.split(iou_path)
- src = '{}/{}'.format(cloud_path, filename)
- provider = get_provider(self._cloud_settings)
- log.debug("Downloading file from {} to {}...".format(src, updated_iou_path))
- provider.download_file(src, updated_iou_path)
- log.debug("Download of {} complete.".format(src))
- # Make file executable
- st = os.stat(updated_iou_path)
- os.chmod(updated_iou_path, st.st_mode | stat.S_IEXEC)
- iou_path = updated_iou_path
-
- try:
- iou_instance = IOUDevice(name,
- iou_path,
- self._working_dir,
- iou_id,
- console,
- self._console_host,
- self._console_start_port_range,
- self._console_end_port_range)
-
- except IOUError as e:
- self.send_custom_error(str(e))
- return
-
- response = {"name": iou_instance.name,
- "id": iou_instance.id}
-
- defaults = iou_instance.defaults()
- response.update(defaults)
- self._iou_instances[iou_instance.id] = iou_instance
- self.send_response(response)
-
- @IModule.route("iou.delete")
- def iou_delete(self, request):
- """
- Deletes an IOU instance.
-
- Mandatory request parameters:
- - id (IOU instance identifier)
-
- Response parameter:
- - True on success
-
- :param request: JSON request
- """
-
- # validate the request
- if not self.validate_request(request, IOU_DELETE_SCHEMA):
- return
-
- # get the instance
- iou_instance = self.get_iou_instance(request["id"])
- if not iou_instance:
- return
-
- try:
- iou_instance.clean_delete()
- del self._iou_instances[request["id"]]
- except IOUError as e:
- self.send_custom_error(str(e))
- return
-
- self.send_response(True)
-
- @IModule.route("iou.update")
- def iou_update(self, request):
- """
- Updates an IOU instance
-
- Mandatory request parameters:
- - id (IOU instance identifier)
-
- Optional request parameters:
- - any setting to update
- - initial_config_base64 (initial-config base64 encoded)
-
- Response parameters:
- - updated settings
-
- :param request: JSON request
- """
-
- # validate the request
- if not self.validate_request(request, IOU_UPDATE_SCHEMA):
- return
-
- # get the instance
- iou_instance = self.get_iou_instance(request["id"])
- if not iou_instance:
- return
-
- config_path = os.path.join(iou_instance.working_dir, "initial-config.cfg")
- try:
- if "initial_config_base64" in request:
- # a new initial-config has been pushed
- config = base64.decodebytes(request["initial_config_base64"].encode("utf-8")).decode("utf-8")
- config = "!\n" + config.replace("\r", "")
- config = config.replace('%h', iou_instance.name)
- try:
- with open(config_path, "w") as f:
- log.info("saving initial-config to {}".format(config_path))
- f.write(config)
- except OSError as e:
- raise IOUError("Could not save the configuration {}: {}".format(config_path, e))
- # update the request with the new local initial-config path
- request["initial_config"] = os.path.basename(config_path)
- elif "initial_config" in request:
- if os.path.isfile(request["initial_config"]) and request["initial_config"] != config_path:
- # this is a local file set in the GUI
- try:
- with open(request["initial_config"], "r", errors="replace") as f:
- config = f.read()
- with open(config_path, "w") as f:
- config = "!\n" + config.replace("\r", "")
- config = config.replace('%h', iou_instance.name)
- f.write(config)
- request["initial_config"] = os.path.basename(config_path)
- except OSError as e:
- raise IOUError("Could not save the configuration from {} to {}: {}".format(request["initial_config"], config_path, e))
- elif not os.path.isfile(config_path):
- raise IOUError("Startup-config {} could not be found on this server".format(request["initial_config"]))
- except IOUError as e:
- self.send_custom_error(str(e))
- return
-
- # update the IOU settings
- response = {}
- for name, value in request.items():
- if hasattr(iou_instance, name) and getattr(iou_instance, name) != value:
- try:
- setattr(iou_instance, name, value)
- response[name] = value
- except IOUError as e:
- self.send_custom_error(str(e))
- return
-
- self.send_response(response)
-
- @IModule.route("iou.start")
- def vm_start(self, request):
- """
- Starts an IOU instance.
-
- Mandatory request parameters:
- - id (IOU instance identifier)
-
- Response parameters:
- - True on success
-
- :param request: JSON request
- """
-
- # validate the request
- if not self.validate_request(request, IOU_START_SCHEMA):
- return
-
- # get the instance
- iou_instance = self.get_iou_instance(request["id"])
- if not iou_instance:
- return
-
- try:
- iou_instance.iouyap = self._iouyap
- if self._iourc:
- iou_instance.iourc = self._iourc
- else:
- # if there is no IOURC file pushed by the client then use the server IOURC file
- iou_instance.iourc = self._server_iourc_path
- iou_instance.start()
- except IOUError as e:
- self.send_custom_error(str(e))
- return
- self.send_response(True)
-
- @IModule.route("iou.stop")
- def vm_stop(self, request):
- """
- Stops an IOU instance.
-
- Mandatory request parameters:
- - id (IOU instance identifier)
-
- Response parameters:
- - True on success
-
- :param request: JSON request
- """
-
- # validate the request
- if not self.validate_request(request, IOU_STOP_SCHEMA):
- return
-
- # get the instance
- iou_instance = self.get_iou_instance(request["id"])
- if not iou_instance:
- return
-
- try:
- iou_instance.stop()
- except IOUError as e:
- self.send_custom_error(str(e))
- return
- self.send_response(True)
-
- @IModule.route("iou.reload")
- def vm_reload(self, request):
- """
- Reloads an IOU instance.
-
- Mandatory request parameters:
- - id (IOU identifier)
-
- Response parameters:
- - True on success
-
- :param request: JSON request
- """
-
- # validate the request
- if not self.validate_request(request, IOU_RELOAD_SCHEMA):
- return
-
- # get the instance
- iou_instance = self.get_iou_instance(request["id"])
- if not iou_instance:
- return
-
- try:
- if iou_instance.is_running():
- iou_instance.stop()
- iou_instance.start()
- except IOUError as e:
- self.send_custom_error(str(e))
- return
- self.send_response(True)
-
- @IModule.route("iou.allocate_udp_port")
- def allocate_udp_port(self, request):
- """
- Allocates a UDP port in order to create an UDP NIO.
-
- Mandatory request parameters:
- - id (IOU 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, IOU_ALLOCATE_UDP_PORT_SCHEMA):
- return
-
- # get the instance
- iou_instance = self.get_iou_instance(request["id"])
- if not iou_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(iou_instance.name,
- iou_instance.id,
- port,
- self._host))
- response = {"lport": port,
- "port_id": request["port_id"]}
- self.send_response(response)
-
- @IModule.route("iou.add_nio")
- def add_nio(self, request):
- """
- Adds an NIO (Network Input/Output) for an IOU instance.
-
- Mandatory request parameters:
- - id (IOU instance identifier)
- - slot (slot number)
- - 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)
- - type "nio_generic_ethernet"
- - ethernet_device (Ethernet device name e.g. eth0)
- - type "nio_tap"
- - tap_device (TAP device name e.g. tap0)
-
- Response parameters:
- - port_id (unique port identifier)
-
- :param request: JSON request
- """
-
- # validate the request
- if not self.validate_request(request, IOU_ADD_NIO_SCHEMA):
- return
-
- # get the instance
- iou_instance = self.get_iou_instance(request["id"])
- if not iou_instance:
- return
-
- slot = request["slot"]
- 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 IOUError("Could not create an UDP connection to {}:{}: {}".format(rhost, rport, e))
- nio = NIO_UDP(lport, rhost, rport)
- elif request["nio"]["type"] == "nio_tap":
- tap_device = request["nio"]["tap_device"]
- if not has_privileged_access(self._iouyap):
- raise IOUError("{} has no privileged access to {}.".format(self._iouyap, tap_device))
- nio = NIO_TAP(tap_device)
- elif request["nio"]["type"] == "nio_generic_ethernet":
- ethernet_device = request["nio"]["ethernet_device"]
- if not has_privileged_access(self._iouyap):
- raise IOUError("{} has no privileged access to {}.".format(self._iouyap, ethernet_device))
- nio = NIO_GenericEthernet(ethernet_device)
- if not nio:
- raise IOUError("Requested NIO does not exist or is not supported: {}".format(request["nio"]["type"]))
- except IOUError as e:
- self.send_custom_error(str(e))
- return
-
- try:
- iou_instance.slot_add_nio_binding(slot, port, nio)
- except IOUError as e:
- self.send_custom_error(str(e))
- return
-
- self.send_response({"port_id": request["port_id"]})
-
- @IModule.route("iou.delete_nio")
- def delete_nio(self, request):
- """
- Deletes an NIO (Network Input/Output).
-
- Mandatory request parameters:
- - id (IOU instance identifier)
- - slot (slot identifier)
- - port (port identifier)
-
- Response parameters:
- - True on success
-
- :param request: JSON request
- """
-
- # validate the request
- if not self.validate_request(request, IOU_DELETE_NIO_SCHEMA):
- return
-
- # get the instance
- iou_instance = self.get_iou_instance(request["id"])
- if not iou_instance:
- return
-
- slot = request["slot"]
- port = request["port"]
- try:
- nio = iou_instance.slot_remove_nio_binding(slot, port)
- if isinstance(nio, NIO_UDP) and nio.lport in self._allocated_udp_ports:
- self._allocated_udp_ports.remove(nio.lport)
- except IOUError as e:
- self.send_custom_error(str(e))
- return
-
- self.send_response(True)
-
- @IModule.route("iou.start_capture")
- def start_capture(self, request):
- """
- Starts a packet capture.
-
- Mandatory request parameters:
- - id (vm identifier)
- - slot (slot number)
- - port (port number)
- - port_id (port identifier)
- - capture_file_name
-
- Optional request parameters:
- - data_link_type (PCAP DLT_* value)
-
- Response parameters:
- - port_id (port identifier)
- - capture_file_path (path to the capture file)
-
- :param request: JSON request
- """
-
- # validate the request
- if not self.validate_request(request, IOU_START_CAPTURE_SCHEMA):
- return
-
- # get the instance
- iou_instance = self.get_iou_instance(request["id"])
- if not iou_instance:
- return
-
- slot = request["slot"]
- port = request["port"]
- capture_file_name = request["capture_file_name"]
- data_link_type = request.get("data_link_type")
-
- try:
- capture_file_path = os.path.join(self._working_dir, "captures", capture_file_name)
- iou_instance.start_capture(slot, port, capture_file_path, data_link_type)
- except IOUError as e:
- self.send_custom_error(str(e))
- return
-
- response = {"port_id": request["port_id"],
- "capture_file_path": capture_file_path}
- self.send_response(response)
-
- @IModule.route("iou.stop_capture")
- def stop_capture(self, request):
- """
- Stops a packet capture.
-
- Mandatory request parameters:
- - id (vm identifier)
- - slot (slot number)
- - port (port number)
- - port_id (port identifier)
-
- Response parameters:
- - port_id (port identifier)
-
- :param request: JSON request
- """
-
- # validate the request
- if not self.validate_request(request, IOU_STOP_CAPTURE_SCHEMA):
- return
-
- # get the instance
- iou_instance = self.get_iou_instance(request["id"])
- if not iou_instance:
- return
-
- slot = request["slot"]
- port = request["port"]
- try:
- iou_instance.stop_capture(slot, port)
- except IOUError as e:
- self.send_custom_error(str(e))
- return
-
- response = {"port_id": request["port_id"]}
- self.send_response(response)
-
- @IModule.route("iou.export_config")
- def export_config(self, request):
- """
- Exports the initial-config from an IOU instance.
-
- Mandatory request parameters:
- - id (vm identifier)
-
- Response parameters:
- - initial_config_base64 (initial-config base64 encoded)
- - False if no configuration can be exported
- """
-
- # validate the request
- if not self.validate_request(request, IOU_EXPORT_CONFIG_SCHEMA):
- return
-
- # get the instance
- iou_instance = self.get_iou_instance(request["id"])
- if not iou_instance:
- return
-
- if not iou_instance.initial_config:
- self.send_custom_error("unable to export the initial-config because it doesn't exist")
- return
-
- response = {}
- initial_config_path = os.path.join(iou_instance.working_dir, iou_instance.initial_config)
- try:
- with open(initial_config_path, "rb") as f:
- config = f.read()
- response["initial_config_base64"] = base64.encodebytes(config).decode("utf-8")
- except OSError as e:
- self.send_custom_error("unable to export the initial-config: {}".format(e))
- return
-
- if not response:
- self.send_response(False)
- else:
- self.send_response(response)
-
- @IModule.route("iou.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/old_modules/iou/adapters/__init__.py b/gns3server/old_modules/iou/adapters/__init__.py
deleted file mode 100644
index e69de29b..00000000
diff --git a/gns3server/old_modules/iou/adapters/adapter.py b/gns3server/old_modules/iou/adapters/adapter.py
deleted file mode 100644
index 06645e56..00000000
--- a/gns3server/old_modules/iou/adapters/adapter.py
+++ /dev/null
@@ -1,105 +0,0 @@
-# -*- 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=4):
-
- 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/old_modules/iou/iou_error.py b/gns3server/old_modules/iou/iou_error.py
deleted file mode 100644
index 8aac176f..00000000
--- a/gns3server/old_modules/iou/iou_error.py
+++ /dev/null
@@ -1,39 +0,0 @@
-# -*- 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 .
-
-"""
-Custom exceptions for IOU module.
-"""
-
-
-class IOUError(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/old_modules/iou/nios/__init__.py b/gns3server/old_modules/iou/nios/__init__.py
deleted file mode 100644
index e69de29b..00000000
diff --git a/gns3server/old_modules/iou/nios/nio.py b/gns3server/old_modules/iou/nios/nio.py
deleted file mode 100644
index 0c8e610e..00000000
--- a/gns3server/old_modules/iou/nios/nio.py
+++ /dev/null
@@ -1,80 +0,0 @@
-# -*- 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 = ""
- self._pcap_data_link_type = ""
-
- def startPacketCapture(self, pcap_output_file, pcap_data_link_type="DLT_EN10MB"):
- """
-
- :param pcap_output_file: PCAP destination file for the capture
- :param pcap_data_link_type: PCAP data link type (DLT_*), default is DLT_EN10MB
- """
-
- self._capturing = True
- self._pcap_output_file = pcap_output_file
- self._pcap_data_link_type = pcap_data_link_type
-
- def stopPacketCapture(self):
-
- self._capturing = False
- self._pcap_output_file = ""
- self._pcap_data_link_type = ""
-
- @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
-
- @property
- def pcap_data_link_type(self):
- """
- Returns the PCAP data link type
-
- :returns: PCAP data link type (DLT_* value)
- """
-
- return self._pcap_data_link_type
diff --git a/gns3server/old_modules/iou/nios/nio_generic_ethernet.py b/gns3server/old_modules/iou/nios/nio_generic_ethernet.py
deleted file mode 100644
index 709e6474..00000000
--- a/gns3server/old_modules/iou/nios/nio_generic_ethernet.py
+++ /dev/null
@@ -1,50 +0,0 @@
-# -*- 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 generic Ethernet NIOs (PCAP library).
-"""
-
-from .nio import NIO
-
-
-class NIO_GenericEthernet(NIO):
-
- """
- Generic Ethernet NIO.
-
- :param ethernet_device: Ethernet device name (e.g. eth0)
- """
-
- def __init__(self, ethernet_device):
-
- NIO.__init__(self)
- self._ethernet_device = ethernet_device
-
- @property
- def ethernet_device(self):
- """
- Returns the Ethernet device used by this NIO.
-
- :returns: the Ethernet device name
- """
-
- return self._ethernet_device
-
- def __str__(self):
-
- return "NIO Ethernet"
diff --git a/gns3server/old_modules/iou/nios/nio_tap.py b/gns3server/old_modules/iou/nios/nio_tap.py
deleted file mode 100644
index f6b1663f..00000000
--- a/gns3server/old_modules/iou/nios/nio_tap.py
+++ /dev/null
@@ -1,50 +0,0 @@
-# -*- 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 TAP NIOs (UNIX based OSes only).
-"""
-
-from .nio import NIO
-
-
-class NIO_TAP(NIO):
-
- """
- TAP NIO.
-
- :param tap_device: TAP device name (e.g. tap0)
- """
-
- def __init__(self, tap_device):
-
- NIO.__init__(self)
- self._tap_device = tap_device
-
- @property
- def tap_device(self):
- """
- Returns the TAP device used by this NIO.
-
- :returns: the TAP device name
- """
-
- return self._tap_device
-
- def __str__(self):
-
- return "NIO TAP"
diff --git a/gns3server/old_modules/iou/nios/nio_udp.py b/gns3server/old_modules/iou/nios/nio_udp.py
deleted file mode 100644
index 3b25f0c4..00000000
--- a/gns3server/old_modules/iou/nios/nio_udp.py
+++ /dev/null
@@ -1,76 +0,0 @@
-# -*- 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/old_modules/iou/schemas.py b/gns3server/old_modules/iou/schemas.py
deleted file mode 100644
index f1315ec3..00000000
--- a/gns3server/old_modules/iou/schemas.py
+++ /dev/null
@@ -1,472 +0,0 @@
-# -*- 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 .
-
-
-IOU_CREATE_SCHEMA = {
- "$schema": "http://json-schema.org/draft-04/schema#",
- "description": "Request validation to create a new IOU instance",
- "type": "object",
- "properties": {
- "name": {
- "description": "IOU device name",
- "type": "string",
- "minLength": 1,
- },
- "iou_id": {
- "description": "IOU device instance ID",
- "type": "integer"
- },
- "console": {
- "description": "console TCP port",
- "minimum": 1,
- "maximum": 65535,
- "type": "integer"
- },
- "path": {
- "description": "path to the IOU executable",
- "type": "string",
- "minLength": 1,
- },
- "cloud_path": {
- "description": "Path to the image in the cloud object store",
- "type": "string",
- }
- },
- "additionalProperties": False,
- "required": ["name", "path"],
-}
-
-IOU_DELETE_SCHEMA = {
- "$schema": "http://json-schema.org/draft-04/schema#",
- "description": "Request validation to delete an IOU instance",
- "type": "object",
- "properties": {
- "id": {
- "description": "IOU device instance ID",
- "type": "integer"
- },
- },
- "additionalProperties": False,
- "required": ["id"]
-}
-
-IOU_UPDATE_SCHEMA = {
- "$schema": "http://json-schema.org/draft-04/schema#",
- "description": "Request validation to update an IOU instance",
- "type": "object",
- "properties": {
- "id": {
- "description": "IOU device instance ID",
- "type": "integer"
- },
- "name": {
- "description": "IOU device name",
- "type": "string",
- "minLength": 1,
- },
- "path": {
- "description": "path to the IOU executable",
- "type": "string",
- "minLength": 1,
- },
- "initial_config": {
- "description": "path to the IOU initial configuration file",
- "type": "string",
- "minLength": 1,
- },
- "ram": {
- "description": "amount of RAM in MB",
- "type": "integer"
- },
- "nvram": {
- "description": "amount of NVRAM in KB",
- "type": "integer"
- },
- "ethernet_adapters": {
- "description": "number of Ethernet adapters",
- "type": "integer",
- "minimum": 0,
- "maximum": 16,
- },
- "serial_adapters": {
- "description": "number of serial adapters",
- "type": "integer",
- "minimum": 0,
- "maximum": 16,
- },
- "console": {
- "description": "console TCP port",
- "minimum": 1,
- "maximum": 65535,
- "type": "integer"
- },
- "use_default_iou_values": {
- "description": "use the default IOU RAM & NVRAM values",
- "type": "boolean"
- },
- "l1_keepalives": {
- "description": "enable or disable layer 1 keepalive messages",
- "type": "boolean"
- },
- "initial_config_base64": {
- "description": "initial configuration base64 encoded",
- "type": "string"
- },
- },
- "additionalProperties": False,
- "required": ["id"]
-}
-
-IOU_START_SCHEMA = {
- "$schema": "http://json-schema.org/draft-04/schema#",
- "description": "Request validation to start an IOU instance",
- "type": "object",
- "properties": {
- "id": {
- "description": "IOU device instance ID",
- "type": "integer"
- },
- },
- "additionalProperties": False,
- "required": ["id"]
-}
-
-IOU_STOP_SCHEMA = {
- "$schema": "http://json-schema.org/draft-04/schema#",
- "description": "Request validation to stop an IOU instance",
- "type": "object",
- "properties": {
- "id": {
- "description": "IOU device instance ID",
- "type": "integer"
- },
- },
- "additionalProperties": False,
- "required": ["id"]
-}
-
-IOU_RELOAD_SCHEMA = {
- "$schema": "http://json-schema.org/draft-04/schema#",
- "description": "Request validation to reload an IOU instance",
- "type": "object",
- "properties": {
- "id": {
- "description": "IOU device instance ID",
- "type": "integer"
- },
- },
- "additionalProperties": False,
- "required": ["id"]
-}
-
-IOU_ALLOCATE_UDP_PORT_SCHEMA = {
- "$schema": "http://json-schema.org/draft-04/schema#",
- "description": "Request validation to allocate an UDP port for an IOU instance",
- "type": "object",
- "properties": {
- "id": {
- "description": "IOU device instance ID",
- "type": "integer"
- },
- "port_id": {
- "description": "Unique port identifier for the IOU instance",
- "type": "integer"
- },
- },
- "additionalProperties": False,
- "required": ["id", "port_id"]
-}
-
-IOU_ADD_NIO_SCHEMA = {
- "$schema": "http://json-schema.org/draft-04/schema#",
- "description": "Request validation to add a NIO for an IOU 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": "IOU device instance ID",
- "type": "integer"
- },
- "port_id": {
- "description": "Unique port identifier for the IOU instance",
- "type": "integer"
- },
- "slot": {
- "description": "Slot number",
- "type": "integer",
- "minimum": 0,
- "maximum": 15
- },
- "port": {
- "description": "Port number",
- "type": "integer",
- "minimum": 0,
- "maximum": 3
- },
- "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", "slot", "port", "nio"]
-}
-
-
-IOU_DELETE_NIO_SCHEMA = {
- "$schema": "http://json-schema.org/draft-04/schema#",
- "description": "Request validation to delete a NIO for an IOU instance",
- "type": "object",
- "properties": {
- "id": {
- "description": "IOU device instance ID",
- "type": "integer"
- },
- "slot": {
- "description": "Slot number",
- "type": "integer",
- "minimum": 0,
- "maximum": 15
- },
- "port": {
- "description": "Port number",
- "type": "integer",
- "minimum": 0,
- "maximum": 3
- },
- },
- "additionalProperties": False,
- "required": ["id", "slot", "port"]
-}
-
-IOU_START_CAPTURE_SCHEMA = {
- "$schema": "http://json-schema.org/draft-04/schema#",
- "description": "Request validation to start a packet capture on an IOU instance port",
- "type": "object",
- "properties": {
- "id": {
- "description": "IOU device instance ID",
- "type": "integer"
- },
- "slot": {
- "description": "Slot number",
- "type": "integer",
- "minimum": 0,
- "maximum": 15
- },
- "port": {
- "description": "Port number",
- "type": "integer",
- "minimum": 0,
- "maximum": 3
- },
- "port_id": {
- "description": "Unique port identifier for the IOU instance",
- "type": "integer"
- },
- "capture_file_name": {
- "description": "Capture file name",
- "type": "string",
- "minLength": 1,
- },
- "data_link_type": {
- "description": "PCAP data link type",
- "type": "string",
- "minLength": 1,
- },
- },
- "additionalProperties": False,
- "required": ["id", "slot", "port", "port_id", "capture_file_name"]
-}
-
-IOU_STOP_CAPTURE_SCHEMA = {
- "$schema": "http://json-schema.org/draft-04/schema#",
- "description": "Request validation to stop a packet capture on an IOU instance port",
- "type": "object",
- "properties": {
- "id": {
- "description": "IOU device instance ID",
- "type": "integer"
- },
- "slot": {
- "description": "Slot number",
- "type": "integer",
- "minimum": 0,
- "maximum": 15
- },
- "port": {
- "description": "Port number",
- "type": "integer",
- "minimum": 0,
- "maximum": 3
- },
- "port_id": {
- "description": "Unique port identifier for the IOU instance",
- "type": "integer"
- },
- },
- "additionalProperties": False,
- "required": ["id", "slot", "port", "port_id"]
-}
-
-IOU_EXPORT_CONFIG_SCHEMA = {
- "$schema": "http://json-schema.org/draft-04/schema#",
- "description": "Request validation to export an initial-config from an IOU instance",
- "type": "object",
- "properties": {
- "id": {
- "description": "IOU device instance ID",
- "type": "integer"
- },
- },
- "additionalProperties": False,
- "required": ["id"]
-}
diff --git a/gns3server/old_modules/qemu/__init__.py b/gns3server/old_modules/qemu/__init__.py
deleted file mode 100644
index 01b3c72e..00000000
--- a/gns3server/old_modules/qemu/__init__.py
+++ /dev/null
@@ -1,687 +0,0 @@
-# -*- 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 sys
-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
-
-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):
-
- # 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._monitor_start_port_range = qemu_config.get("monitor_start_port_range", 5501)
- self._monitor_end_port_range = qemu_config.get("monitor_end_port_range", 6000)
- 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._console_host = qemu_config.get("console_host", kwargs["console_host"])
- self._monitor_host = qemu_config.get("monitor_host", "127.0.0.1")
- 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()
-
- self._working_dir = self._projects_dir
- 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
- - monitor_start_port_range
- - monitor_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 "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 "monitor_start_port_range" in request and "monitor_end_port_range" in request:
- self._monitor_start_port_range = request["monitor_start_port_range"]
- self._monitor_end_port_range = request["monitor_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)
- - monitor (QEMU VM monitor 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")
- monitor = request.get("monitor")
- qemu_id = request.get("qemu_id")
-
- try:
- qemu_instance = QemuVM(name,
- qemu_path,
- self._working_dir,
- self._host,
- qemu_id,
- console,
- self._console_host,
- self._console_start_port_range,
- self._console_end_port_range,
- monitor,
- self._monitor_host,
- self._monitor_start_port_range,
- self._monitor_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
- """
-
- if sys.platform.startswith("win"):
- return ""
- try:
- output = subprocess.check_output([qemu_path, "-version"])
- match = re.search("version\s+([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 subprocess.SubprocessError 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:
- - List of Qemu binaries
- """
-
- qemus = []
- paths = [os.getcwd()] + os.environ["PATH"].split(os.pathsep)
- # look for Qemu binaries in the current working directory and $PATH
- if sys.platform.startswith("win"):
- # add specific Windows paths
- if hasattr(sys, "frozen"):
- # add any qemu dir in the same location as gns3server.exe to the list of paths
- exec_dir = os.path.dirname(os.path.abspath(sys.executable))
- for f in os.listdir(exec_dir):
- if f.lower().startswith("qemu"):
- paths.append(os.path.join(exec_dir, f))
-
- if "PROGRAMFILES(X86)" in os.environ and os.path.exists(os.environ["PROGRAMFILES(X86)"]):
- paths.append(os.path.join(os.environ["PROGRAMFILES(X86)"], "qemu"))
- if "PROGRAMFILES" in os.environ and os.path.exists(os.environ["PROGRAMFILES"]):
- paths.append(os.path.join(os.environ["PROGRAMFILES"], "qemu"))
- elif sys.platform.startswith("darwin"):
- # add specific locations on Mac OS X regardless of what's in $PATH
- paths.extend(["/usr/local/bin", "/opt/local/bin"])
- if hasattr(sys, "frozen"):
- paths.append(os.path.abspath(os.path.join(os.getcwd(), "../../../qemu/bin/")))
- for path in paths:
- try:
- for f in os.listdir(path):
- if (f.startswith("qemu-system") or f == "qemu" or f == "qemu.exe") and \
- os.access(os.path.join(path, f), os.X_OK) and \
- os.path.isfile(os.path.join(path, f)):
- qemu_path = os.path.join(path, f)
- version = self._get_qemu_version(qemu_path)
- qemus.append({"path": qemu_path, "version": version})
- except OSError:
- continue
-
- response = {"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/old_modules/qemu/adapters/__init__.py b/gns3server/old_modules/qemu/adapters/__init__.py
deleted file mode 100644
index e69de29b..00000000
diff --git a/gns3server/old_modules/qemu/adapters/adapter.py b/gns3server/old_modules/qemu/adapters/adapter.py
deleted file mode 100644
index ade660f9..00000000
--- a/gns3server/old_modules/qemu/adapters/adapter.py
+++ /dev/null
@@ -1,105 +0,0 @@
-# -*- 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/old_modules/qemu/adapters/ethernet_adapter.py b/gns3server/old_modules/qemu/adapters/ethernet_adapter.py
deleted file mode 100644
index 2064bb68..00000000
--- a/gns3server/old_modules/qemu/adapters/ethernet_adapter.py
+++ /dev/null
@@ -1,32 +0,0 @@
-# -*- 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/old_modules/qemu/nios/__init__.py b/gns3server/old_modules/qemu/nios/__init__.py
deleted file mode 100644
index e69de29b..00000000
diff --git a/gns3server/old_modules/qemu/nios/nio.py b/gns3server/old_modules/qemu/nios/nio.py
deleted file mode 100644
index 3c8a6b9e..00000000
--- a/gns3server/old_modules/qemu/nios/nio.py
+++ /dev/null
@@ -1,66 +0,0 @@
-# -*- 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/old_modules/qemu/nios/nio_udp.py b/gns3server/old_modules/qemu/nios/nio_udp.py
deleted file mode 100644
index 3b25f0c4..00000000
--- a/gns3server/old_modules/qemu/nios/nio_udp.py
+++ /dev/null
@@ -1,76 +0,0 @@
-# -*- 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/old_modules/qemu/qemu_error.py b/gns3server/old_modules/qemu/qemu_error.py
deleted file mode 100644
index 55135a34..00000000
--- a/gns3server/old_modules/qemu/qemu_error.py
+++ /dev/null
@@ -1,39 +0,0 @@
-# -*- 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/old_modules/qemu/qemu_vm.py b/gns3server/old_modules/qemu/qemu_vm.py
deleted file mode 100644
index a5ae107d..00000000
--- a/gns3server/old_modules/qemu/qemu_vm.py
+++ /dev/null
@@ -1,1244 +0,0 @@
-# -*- 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 sys
-import os
-import shutil
-import random
-import subprocess
-import shlex
-import ntpath
-import telnetlib
-import time
-import re
-
-from gns3server.config import Config
-from gns3dms.cloud.rackspace_ctrl import get_provider
-
-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 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_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
- """
-
- _instances = []
- _allocated_console_ports = []
- _allocated_monitor_ports = []
-
- def __init__(self,
- name,
- qemu_path,
- working_dir,
- host="127.0.0.1",
- qemu_id=None,
- 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):
-
- 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._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
- self._cloud_path = None
-
- # QEMU settings
- self._qemu_path = qemu_path
- self._hda_disk_image = ""
- self._hdb_disk_image = ""
- self._options = ""
- self._ram = 256
- self._console = console
- self._monitor = monitor
- self._ethernet_adapters = []
- self._adapter_type = "e1000"
- self._initrd = ""
- self._kernel_image = ""
- self._kernel_command_line = ""
- self._legacy_networking = False
- self._cpu_throttling = 0 # means no CPU throttling
- self._process_priority = "low"
-
- 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._console_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)
-
- if not self._monitor:
- # allocate a monitor port
- try:
- self._monitor = find_unused_port(self._monitor_start_port_range,
- self._monitor_end_port_range,
- self._monitor_host,
- ignore_ports=self._allocated_monitor_ports)
- except Exception as e:
- raise QemuError(e)
-
- if self._monitor in self._allocated_monitor_ports:
- raise QemuError("Monitor port {} is already used by another QEMU VM".format(monitor))
- self._allocated_monitor_ports.append(self._monitor)
-
- 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,
- "hda_disk_image": self._hda_disk_image,
- "hdb_disk_image": self._hdb_disk_image,
- "options": self._options,
- "adapters": self.adapters,
- "adapter_type": self._adapter_type,
- "console": self._console,
- "monitor": self._monitor,
- "initrd": self._initrd,
- "kernel_image": self._kernel_image,
- "kernel_command_line": self._kernel_command_line,
- "legacy_networking": self._legacy_networking,
- "cpu_throttling": self._cpu_throttling,
- "process_priority": self._process_priority
- }
-
- 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()
- cls._allocated_monitor_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))
-
- @property
- def monitor(self):
- """
- Returns the TCP monitor port.
-
- :returns: monitor port (integer)
- """
-
- return self._monitor
-
- @monitor.setter
- def monitor(self, monitor):
- """
- Sets the TCP monitor port.
-
- :param monitor: monitor port (integer)
- """
-
- if monitor in self._allocated_monitor_ports:
- raise QemuError("Monitor port {} is already used by another QEMU VM".format(monitor))
-
- self._allocated_monitor_ports.remove(self._monitor)
- self._monitor = monitor
- self._allocated_monitor_ports.append(self._monitor)
-
- log.info("QEMU VM {name} [id={id}]: monitor port set to {port}".format(name=self._name,
- id=self._id,
- port=monitor))
-
- 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)
-
- if self._monitor and self._monitor in self._allocated_monitor_ports:
- self._allocated_monitor_ports.remove(self._monitor)
-
- 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)
-
- if self._monitor:
- self._allocated_monitor_ports.remove(self._monitor)
-
- 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 cloud_path(self):
- """
- Returns the cloud path where images can be downloaded from.
-
- :returns: cloud path
- """
-
- return self._cloud_path
-
- @cloud_path.setter
- def cloud_path(self, cloud_path):
- """
- Sets the cloud path where images can be downloaded from.
-
- :param cloud_path:
- :return:
- """
-
- self._cloud_path = cloud_path
-
- @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 hda_disk_image(self):
- """
- Returns the hda disk image path for this QEMU VM.
-
- :returns: QEMU hda disk image path
- """
-
- return self._hda_disk_image
-
- @hda_disk_image.setter
- def hda_disk_image(self, hda_disk_image):
- """
- Sets the hda disk image for this QEMU VM.
-
- :param hda_disk_image: QEMU hda disk image path
- """
-
- log.info("QEMU VM {name} [id={id}] has set the QEMU hda disk image path to {disk_image}".format(name=self._name,
- id=self._id,
- disk_image=hda_disk_image))
- self._hda_disk_image = hda_disk_image
-
- @property
- def hdb_disk_image(self):
- """
- Returns the hdb disk image path for this QEMU VM.
-
- :returns: QEMU hdb disk image path
- """
-
- return self._hdb_disk_image
-
- @hdb_disk_image.setter
- def hdb_disk_image(self, hdb_disk_image):
- """
- Sets the hdb disk image for this QEMU VM.
-
- :param hdb_disk_image: QEMU hdb disk image path
- """
-
- log.info("QEMU VM {name} [id={id}] has set the QEMU hdb disk image path to {disk_image}".format(name=self._name,
- id=self._id,
- disk_image=hdb_disk_image))
- self._hdb_disk_image = hdb_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))
-
- @property
- def legacy_networking(self):
- """
- Returns either QEMU legacy networking commands are used.
-
- :returns: boolean
- """
-
- return self._legacy_networking
-
- @legacy_networking.setter
- def legacy_networking(self, legacy_networking):
- """
- Sets either QEMU legacy networking commands are used.
-
- :param legacy_networking: boolean
- """
-
- if legacy_networking:
- log.info("QEMU VM {name} [id={id}] has enabled legacy networking".format(name=self._name, id=self._id))
- else:
- log.info("QEMU VM {name} [id={id}] has disabled legacy networking".format(name=self._name, id=self._id))
- self._legacy_networking = legacy_networking
-
- @property
- def cpu_throttling(self):
- """
- Returns the percentage of CPU allowed.
-
- :returns: integer
- """
-
- return self._cpu_throttling
-
- @cpu_throttling.setter
- def cpu_throttling(self, cpu_throttling):
- """
- Sets the percentage of CPU allowed.
-
- :param cpu_throttling: integer
- """
-
- log.info("QEMU VM {name} [id={id}] has set the percentage of CPU allowed to {cpu}".format(name=self._name,
- id=self._id,
- cpu=cpu_throttling))
- self._cpu_throttling = cpu_throttling
- self._stop_cpulimit()
- if cpu_throttling:
- self._set_cpu_throttling()
-
- @property
- def process_priority(self):
- """
- Returns the process priority.
-
- :returns: string
- """
-
- return self._process_priority
-
- @process_priority.setter
- def process_priority(self, process_priority):
- """
- Sets the process priority.
-
- :param process_priority: string
- """
-
- log.info("QEMU VM {name} [id={id}] has set the process priority to {priority}".format(name=self._name,
- id=self._id,
- priority=process_priority))
- self._process_priority = process_priority
-
- @property
- def ram(self):
- """
- Returns the RAM amount for this QEMU VM.
-
- :returns: RAM amount in MB
- """
-
- return self._ram
-
- @ram.setter
- def ram(self, ram):
- """
- Sets the amount of RAM for this QEMU VM.
-
- :param ram: RAM amount in MB
- """
-
- log.info("QEMU VM {name} [id={id}] has set the RAM to {ram}".format(name=self._name,
- id=self._id,
- ram=ram))
- self._ram = ram
-
- @property
- def options(self):
- """
- Returns the options for this QEMU VM.
-
- :returns: QEMU options
- """
-
- return self._options
-
- @options.setter
- def options(self, options):
- """
- Sets the options for this QEMU VM.
-
- :param options: QEMU options
- """
-
- log.info("QEMU VM {name} [id={id}] has set the QEMU options to {options}".format(name=self._name,
- id=self._id,
- options=options))
- self._options = options
-
- @property
- def initrd(self):
- """
- Returns the initrd path for this QEMU VM.
-
- :returns: QEMU initrd path
- """
-
- return self._initrd
-
- @initrd.setter
- def initrd(self, initrd):
- """
- Sets the initrd path for this QEMU VM.
-
- :param initrd: QEMU initrd path
- """
-
- log.info("QEMU VM {name} [id={id}] has set the QEMU initrd path to {initrd}".format(name=self._name,
- id=self._id,
- initrd=initrd))
- self._initrd = initrd
-
- @property
- def kernel_image(self):
- """
- Returns the kernel image path for this QEMU VM.
-
- :returns: QEMU kernel image path
- """
-
- return self._kernel_image
-
- @kernel_image.setter
- def kernel_image(self, kernel_image):
- """
- Sets the kernel image path for this QEMU VM.
-
- :param kernel_image: QEMU kernel image path
- """
-
- log.info("QEMU VM {name} [id={id}] has set the QEMU kernel image path to {kernel_image}".format(name=self._name,
- id=self._id,
- kernel_image=kernel_image))
- self._kernel_image = kernel_image
-
- @property
- def kernel_command_line(self):
- """
- Returns the kernel command line for this QEMU VM.
-
- :returns: QEMU kernel command line
- """
-
- return self._kernel_command_line
-
- @kernel_command_line.setter
- def kernel_command_line(self, kernel_command_line):
- """
- Sets the kernel command line for this QEMU VM.
-
- :param kernel_command_line: QEMU kernel command line
- """
-
- log.info("QEMU VM {name} [id={id}] has set the QEMU kernel command line to {kernel_command_line}".format(name=self._name,
- id=self._id,
- kernel_command_line=kernel_command_line))
- self._kernel_command_line = kernel_command_line
-
- def _set_process_priority(self):
- """
- Changes the process priority
- """
-
- if sys.platform.startswith("win"):
- try:
- import win32api
- import win32con
- import win32process
- except ImportError:
- log.error("pywin32 must be installed to change the priority class for QEMU VM {}".format(self._name))
- else:
- log.info("setting QEMU VM {} priority class to BELOW_NORMAL".format(self._name))
- handle = win32api.OpenProcess(win32con.PROCESS_ALL_ACCESS, 0, self._process.pid)
- if self._process_priority == "realtime":
- priority = win32process.REALTIME_PRIORITY_CLASS
- elif self._process_priority == "very high":
- priority = win32process.HIGH_PRIORITY_CLASS
- elif self._process_priority == "high":
- priority = win32process.ABOVE_NORMAL_PRIORITY_CLASS
- elif self._process_priority == "low":
- priority = win32process.BELOW_NORMAL_PRIORITY_CLASS
- elif self._process_priority == "very low":
- priority = win32process.IDLE_PRIORITY_CLASS
- else:
- priority = win32process.NORMAL_PRIORITY_CLASS
- win32process.SetPriorityClass(handle, priority)
- else:
- if self._process_priority == "realtime":
- priority = -20
- elif self._process_priority == "very high":
- priority = -15
- elif self._process_priority == "high":
- priority = -5
- elif self._process_priority == "low":
- priority = 5
- elif self._process_priority == "very low":
- priority = 19
- else:
- priority = 0
- try:
- subprocess.call(['renice', '-n', str(priority), '-p', str(self._process.pid)])
- except (OSError, subprocess.SubprocessError) as e:
- log.error("could not change process priority for QEMU VM {}: {}".format(self._name, e))
-
- def _stop_cpulimit(self):
- """
- Stops the cpulimit process.
- """
-
- if self._cpulimit_process and self._cpulimit_process.poll() is None:
- self._cpulimit_process.kill()
- try:
- self._process.wait(3)
- except subprocess.TimeoutExpired:
- log.error("could not kill cpulimit process {}".format(self._cpulimit_process.pid))
-
- def _set_cpu_throttling(self):
- """
- Limits the CPU usage for current QEMU process.
- """
-
- if not self.is_running():
- return
-
- try:
- if sys.platform.startswith("win") and hasattr(sys, "frozen"):
- cpulimit_exec = os.path.join(os.path.dirname(os.path.abspath(sys.executable)), "cpulimit", "cpulimit.exe")
- else:
- cpulimit_exec = "cpulimit"
- subprocess.Popen([cpulimit_exec, "--lazy", "--pid={}".format(self._process.pid), "--limit={}".format(self._cpu_throttling)], cwd=self._working_dir)
- log.info("CPU throttled to {}%".format(self._cpu_throttling))
- except FileNotFoundError:
- raise QemuError("cpulimit could not be found, please install it or deactivate CPU throttling")
- except (OSError, subprocess.SubprocessError) as e:
- raise QemuError("Could not throttle CPU: {}".format(e))
-
- def start(self):
- """
- Starts this QEMU VM.
- """
-
- if self.is_running():
-
- # resume the VM if it is paused
- self.resume()
- return
-
- else:
-
- if not os.path.isfile(self._qemu_path) or not os.path.exists(self._qemu_path):
- found = False
- paths = [os.getcwd()] + os.environ["PATH"].split(os.pathsep)
- # look for the qemu binary in the current working directory and $PATH
- for path in paths:
- try:
- if self._qemu_path in os.listdir(path) and os.access(os.path.join(path, self._qemu_path), os.X_OK):
- self._qemu_path = os.path.join(path, self._qemu_path)
- found = True
- break
- except OSError:
- continue
-
- if not found:
- raise QemuError("QEMU binary '{}' is not accessible".format(self._qemu_path))
-
- if self.cloud_path is not None:
- # Download from Cloud Files
- if self.hda_disk_image != "":
- _, filename = ntpath.split(self.hda_disk_image)
- src = '{}/{}'.format(self.cloud_path, filename)
- dst = os.path.join(self.working_dir, filename)
- if not os.path.isfile(dst):
- cloud_settings = Config.instance().cloud_settings()
- provider = get_provider(cloud_settings)
- log.debug("Downloading file from {} to {}...".format(src, dst))
- provider.download_file(src, dst)
- log.debug("Download of {} complete.".format(src))
- self.hda_disk_image = dst
- if self.hdb_disk_image != "":
- _, filename = ntpath.split(self.hdb_disk_image)
- src = '{}/{}'.format(self.cloud_path, filename)
- dst = os.path.join(self.working_dir, filename)
- if not os.path.isfile(dst):
- cloud_settings = Config.instance().cloud_settings()
- provider = get_provider(cloud_settings)
- log.debug("Downloading file from {} to {}...".format(src, dst))
- provider.download_file(src, dst)
- log.debug("Download of {} complete.".format(src))
- self.hdb_disk_image = dst
-
- if self.initrd != "":
- _, filename = ntpath.split(self.initrd)
- src = '{}/{}'.format(self.cloud_path, filename)
- dst = os.path.join(self.working_dir, filename)
- if not os.path.isfile(dst):
- cloud_settings = Config.instance().cloud_settings()
- provider = get_provider(cloud_settings)
- log.debug("Downloading file from {} to {}...".format(src, dst))
- provider.download_file(src, dst)
- log.debug("Download of {} complete.".format(src))
- self.initrd = dst
- if self.kernel_image != "":
- _, filename = ntpath.split(self.kernel_image)
- src = '{}/{}'.format(self.cloud_path, filename)
- dst = os.path.join(self.working_dir, filename)
- if not os.path.isfile(dst):
- cloud_settings = Config.instance().cloud_settings()
- provider = get_provider(cloud_settings)
- log.debug("Downloading file from {} to {}...".format(src, dst))
- provider.download_file(src, dst)
- log.debug("Download of {} complete.".format(src))
- self.kernel_image = dst
-
- 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, subprocess.SubprocessError) 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))
-
- self._set_process_priority()
- if self._cpu_throttling:
- self._set_cpu_throttling()
-
- 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
- self._stop_cpulimit()
-
- def _control_vm(self, command, expected=None, timeout=30):
- """
- Executes a command with QEMU monitor when this VM is running.
-
- :param command: QEMU monitor command (e.g. info status, stop etc.)
- :param timeout: how long to wait for QEMU monitor
-
- :returns: result of the command (Match object or None)
- """
-
- result = None
- if self.is_running() and self._monitor:
- log.debug("Execute QEMU monitor command: {}".format(command))
- try:
- tn = telnetlib.Telnet(self._monitor_host, self._monitor, timeout=timeout)
- except OSError as e:
- log.warn("Could not connect to QEMU monitor: {}".format(e))
- return result
- try:
- tn.write(command.encode('ascii') + b"\n")
- time.sleep(0.1)
- except OSError as e:
- log.warn("Could not write to QEMU monitor: {}".format(e))
- tn.close()
- return result
- if expected:
- try:
- ind, match, dat = tn.expect(list=expected, timeout=timeout)
- if match:
- result = match
- except EOFError as e:
- log.warn("Could not read from QEMU monitor: {}".format(e))
- tn.close()
- return result
-
- def _get_vm_status(self):
- """
- Returns this VM suspend status (running|paused)
-
- :returns: status (string)
- """
-
- result = None
-
- match = self._control_vm("info status", [b"running", b"paused"])
- if match:
- result = match.group(0).decode('ascii')
- return result
-
- def suspend(self):
- """
- Suspends this QEMU VM.
- """
-
- vm_status = self._get_vm_status()
- if vm_status == "running":
- self._control_vm("stop")
- log.debug("QEMU VM has been suspended")
- else:
- log.info("QEMU VM is not running to be suspended, current status is {}".format(vm_status))
-
- def reload(self):
- """
- Reloads this QEMU VM.
- """
-
- self._control_vm("system_reset")
- log.debug("QEMU VM has been reset")
-
- def resume(self):
- """
- Resumes this QEMU VM.
- """
-
- vm_status = self._get_vm_status()
- if vm_status == "paused":
- self._control_vm("cont")
- log.debug("QEMU VM has been resumed")
- else:
- log.info("QEMU VM is not paused to be resumed, current status is {}".format(vm_status))
-
- 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))
-
- if self.is_running():
- # dynamically configure an UDP tunnel on the QEMU VM adapter
- if nio and isinstance(nio, NIO_UDP):
- if self._legacy_networking:
- self._control_vm("host_net_remove {} gns3-{}".format(adapter_id, adapter_id))
- self._control_vm("host_net_add udp vlan={},name=gns3-{},sport={},dport={},daddr={}".format(adapter_id,
- adapter_id,
- nio.lport,
- nio.rport,
- nio.rhost))
- else:
- # FIXME: does it work? very undocumented feature...
- self._control_vm("netdev_del gns3-{}".format(adapter_id))
- self._control_vm("netdev_add socket,id=gns3-{},udp={}:{},localaddr={}:{}".format(adapter_id,
- nio.rhost,
- nio.rport,
- self._host,
- nio.lport))
-
- 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))
-
- if self.is_running():
- # dynamically disable the QEMU VM adapter
- if self._legacy_networking:
- self._control_vm("host_net_remove {} gns3-{}".format(adapter_id, adapter_id))
- self._control_vm("host_net_add user vlan={},name=gns3-{}".format(adapter_id, adapter_id))
- else:
- # FIXME: does it work? very undocumented feature...
- self._control_vm("netdev_del gns3-{}".format(adapter_id))
- self._control_vm("netdev_add user,id=gns3-{}".format(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._console_host, self._console)]
- else:
- return []
-
- def _monitor_options(self):
-
- if self._monitor:
- return ["-monitor", "telnet:{}:{},server,nowait".format(self._monitor_host, self._monitor)]
- else:
- return []
-
- def _disk_options(self):
-
- options = []
- qemu_img_path = ""
- qemu_path_dir = os.path.dirname(self._qemu_path)
- try:
- for f in os.listdir(qemu_path_dir):
- if f.startswith("qemu-img"):
- qemu_img_path = os.path.join(qemu_path_dir, f)
- except OSError as e:
- raise QemuError("Error while looking for qemu-img in {}: {}".format(qemu_path_dir, e))
-
- if not qemu_img_path:
- raise QemuError("Could not find qemu-img in {}".format(qemu_path_dir))
-
- try:
- if self._hda_disk_image:
- if not os.path.isfile(self._hda_disk_image) or not os.path.exists(self._hda_disk_image):
- if os.path.islink(self._hda_disk_image):
- raise QemuError("hda disk image '{}' linked to '{}' is not accessible".format(self._hda_disk_image, os.path.realpath(self._hda_disk_image)))
- else:
- raise QemuError("hda disk image '{}' is not accessible".format(self._hda_disk_image))
- hda_disk = os.path.join(self._working_dir, "hda_disk.qcow2")
- if not os.path.exists(hda_disk):
- retcode = subprocess.call([qemu_img_path, "create", "-o",
- "backing_file={}".format(self._hda_disk_image),
- "-f", "qcow2", hda_disk])
- log.info("{} returned with {}".format(qemu_img_path, retcode))
- else:
- # create a "FLASH" with 256MB if no disk image has been specified
- hda_disk = os.path.join(self._working_dir, "flash.qcow2")
- if not os.path.exists(hda_disk):
- retcode = subprocess.call([qemu_img_path, "create", "-f", "qcow2", hda_disk, "128M"])
- log.info("{} returned with {}".format(qemu_img_path, retcode))
-
- except (OSError, subprocess.SubprocessError) as e:
- raise QemuError("Could not create disk image {}".format(e))
-
- options.extend(["-hda", hda_disk])
- if self._hdb_disk_image:
- if not os.path.isfile(self._hdb_disk_image) or not os.path.exists(self._hdb_disk_image):
- if os.path.islink(self._hdb_disk_image):
- raise QemuError("hdb disk image '{}' linked to '{}' is not accessible".format(self._hdb_disk_image, os.path.realpath(self._hdb_disk_image)))
- else:
- raise QemuError("hdb disk image '{}' is not accessible".format(self._hdb_disk_image))
- hdb_disk = os.path.join(self._working_dir, "hdb_disk.qcow2")
- if not os.path.exists(hdb_disk):
- try:
- retcode = subprocess.call([qemu_img_path, "create", "-o",
- "backing_file={}".format(self._hdb_disk_image),
- "-f", "qcow2", hdb_disk])
- log.info("{} returned with {}".format(qemu_img_path, retcode))
- except (OSError, subprocess.SubprocessError) as e:
- raise QemuError("Could not create disk image {}".format(e))
- options.extend(["-hdb", hdb_disk])
-
- return options
-
- def _linux_boot_options(self):
-
- options = []
- if self._initrd:
- if not os.path.isfile(self._initrd) or not os.path.exists(self._initrd):
- if os.path.islink(self._initrd):
- raise QemuError("initrd file '{}' linked to '{}' is not accessible".format(self._initrd, os.path.realpath(self._initrd)))
- else:
- raise QemuError("initrd file '{}' is not accessible".format(self._initrd))
- options.extend(["-initrd", self._initrd])
- if self._kernel_image:
- if not os.path.isfile(self._kernel_image) or not os.path.exists(self._kernel_image):
- if os.path.islink(self._kernel_image):
- raise QemuError("kernel image '{}' linked to '{}' is not accessible".format(self._kernel_image, os.path.realpath(self._kernel_image)))
- else:
- raise QemuError("kernel image '{}' is not accessible".format(self._kernel_image))
- options.extend(["-kernel", self._kernel_image])
- if self._kernel_command_line:
- options.extend(["-append", self._kernel_command_line])
-
- return options
-
- def _network_options(self):
-
- network_options = []
- adapter_id = 0
- for adapter in self._ethernet_adapters:
- # TODO: let users specify a base mac address
- mac = "00:00:ab:%02x:%02x:%02d" % (random.randint(0x00, 0xff), random.randint(0x00, 0xff), adapter_id)
- if self._legacy_networking:
- network_options.extend(["-net", "nic,vlan={},macaddr={},model={}".format(adapter_id, mac, self._adapter_type)])
- else:
- network_options.extend(["-device", "{},mac={},netdev=gns3-{}".format(self._adapter_type, mac, adapter_id)])
-
- nio = adapter.get_nio(0)
- if nio and isinstance(nio, NIO_UDP):
- if self._legacy_networking:
- network_options.extend(["-net", "udp,vlan={},name=gns3-{},sport={},dport={},daddr={}".format(adapter_id,
- adapter_id,
- nio.lport,
- nio.rport,
- nio.rhost)])
- else:
- network_options.extend(["-netdev", "socket,id=gns3-{},udp={}:{},localaddr={}:{}".format(adapter_id,
- nio.rhost,
- nio.rport,
- self._host,
- nio.lport)])
- else:
- if self._legacy_networking:
- network_options.extend(["-net", "user,vlan={},name=gns3-{}".format(adapter_id, adapter_id)])
- else:
- network_options.extend(["-netdev", "user,id=gns3-{}".format(adapter_id)])
- 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._linux_boot_options())
- command.extend(self._serial_options())
- command.extend(self._monitor_options())
- additional_options = self._options.strip()
- if additional_options:
- command.extend(shlex.split(additional_options))
- command.extend(self._network_options())
- return command
diff --git a/gns3server/old_modules/qemu/schemas.py b/gns3server/old_modules/qemu/schemas.py
deleted file mode 100644
index 32b09664..00000000
--- a/gns3server/old_modules/qemu/schemas.py
+++ /dev/null
@@ -1,423 +0,0 @@
-# -*- 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"
- },
- "monitor": {
- "description": "monitor 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,
- },
- "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": ["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": 32
- },
- "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": 32
- },
- },
- "additionalProperties": False,
- "required": ["id", "port"]
-}
diff --git a/gns3server/schemas/iou.py b/gns3server/schemas/iou.py
new file mode 100644
index 00000000..6d304a19
--- /dev/null
+++ b/gns3server/schemas/iou.py
@@ -0,0 +1,247 @@
+# -*- 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 .
+
+
+IOU_CREATE_SCHEMA = {
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "description": "Request validation to create a new IOU instance",
+ "type": "object",
+ "properties": {
+ "name": {
+ "description": "IOU VM name",
+ "type": "string",
+ "minLength": 1,
+ },
+ "vm_id": {
+ "description": "IOU VM identifier",
+ "oneOf": [
+ {"type": "string",
+ "minLength": 36,
+ "maxLength": 36,
+ "pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$"},
+ {"type": "integer"} # for legacy projects
+ ]
+ },
+ "console": {
+ "description": "console TCP port",
+ "minimum": 1,
+ "maximum": 65535,
+ "type": ["integer", "null"]
+ },
+ "path": {
+ "description": "Path of iou binary",
+ "type": "string"
+ },
+ "iourc_path": {
+ "description": "Path of iourc",
+ "type": "string"
+ },
+ "serial_adapters": {
+ "description": "How many serial adapters are connected to the IOU",
+ "type": "integer"
+ },
+ "ethernet_adapters": {
+ "description": "How many ethernet adapters are connected to the IOU",
+ "type": "integer"
+ },
+ "ram": {
+ "description": "Allocated RAM MB",
+ "type": ["integer", "null"]
+ },
+ "nvram": {
+ "description": "Allocated NVRAM KB",
+ "type": ["integer", "null"]
+ }
+ },
+ "additionalProperties": False,
+ "required": ["name", "path"]
+}
+
+IOU_UPDATE_SCHEMA = {
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "description": "Request validation to update a IOU instance",
+ "type": "object",
+ "properties": {
+ "name": {
+ "description": "IOU VM name",
+ "type": ["string", "null"],
+ "minLength": 1,
+ },
+ "console": {
+ "description": "console TCP port",
+ "minimum": 1,
+ "maximum": 65535,
+ "type": ["integer", "null"]
+ },
+ "path": {
+ "description": "Path of iou binary",
+ "type": ["string", "null"]
+ },
+ "iourc_path": {
+ "description": "Path of iourc",
+ "type": ["string", "null"]
+ },
+ "initial_config": {
+ "description": "Initial configuration path",
+ "type": ["string", "null"]
+ },
+ "serial_adapters": {
+ "description": "How many serial adapters are connected to the IOU",
+ "type": ["integer", "null"]
+ },
+ "ethernet_adapters": {
+ "description": "How many ethernet adapters are connected to the IOU",
+ "type": ["integer", "null"]
+ },
+ "ram": {
+ "description": "Allocated RAM MB",
+ "type": ["integer", "null"]
+ },
+ "nvram": {
+ "description": "Allocated NVRAM KB",
+ "type": ["integer", "null"]
+ }
+ },
+ "additionalProperties": False,
+}
+
+IOU_OBJECT_SCHEMA = {
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "description": "IOU instance",
+ "type": "object",
+ "properties": {
+ "name": {
+ "description": "IOU VM name",
+ "type": "string",
+ "minLength": 1,
+ },
+ "vm_id": {
+ "description": "IOU VM UUID",
+ "type": "string",
+ "minLength": 36,
+ "maxLength": 36,
+ "pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$"
+ },
+ "console": {
+ "description": "console TCP port",
+ "minimum": 1,
+ "maximum": 65535,
+ "type": "integer"
+ },
+ "project_id": {
+ "description": "Project UUID",
+ "type": "string",
+ "minLength": 36,
+ "maxLength": 36,
+ "pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$"
+ },
+ "path": {
+ "description": "Path of iou binary",
+ "type": "string"
+ },
+ "serial_adapters": {
+ "description": "How many serial adapters are connected to the IOU",
+ "type": "integer"
+ },
+ "ethernet_adapters": {
+ "description": "How many ethernet adapters are connected to the IOU",
+ "type": "integer"
+ },
+ "ram": {
+ "description": "Allocated RAM MB",
+ "type": "integer"
+ },
+ "nvram": {
+ "description": "Allocated NVRAM KB",
+ "type": "integer"
+ }
+ },
+ "additionalProperties": False,
+ "required": ["name", "vm_id", "console", "project_id", "path", "serial_adapters", "ethernet_adapters", "ram", "nvram"]
+}
+
+IOU_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"]
+}
diff --git a/gns3server/server.py b/gns3server/server.py
index 349880e5..baf7e1a9 100644
--- a/gns3server/server.py
+++ b/gns3server/server.py
@@ -173,7 +173,7 @@ class Server:
self._loop.run_until_complete(self._run_application(app, ssl_context))
self._signal_handling()
- if server_config.getboolean("debug"):
+ if server_config.getboolean("live"):
log.info("Code live reload is enabled, watching for file changes")
self._loop.call_later(1, self._reload_hook)
self._loop.run_forever()
diff --git a/gns3server/web/response.py b/gns3server/web/response.py
index 9ae3c1a6..9241d0c9 100644
--- a/gns3server/web/response.py
+++ b/gns3server/web/response.py
@@ -62,8 +62,6 @@ class Response(aiohttp.web.Response):
try:
jsonschema.validate(answer, self._output_schema)
except jsonschema.ValidationError as e:
- log.error("Invalid output schema {} '{}' in schema: {}".format(e.validator,
- e.validator_value,
- json.dumps(e.schema)))
+ log.error("Invalid output query. JSON schema error: {}".format(e.message))
raise aiohttp.web.HTTPBadRequest(text="{}".format(e))
self.body = json.dumps(answer, indent=4, sort_keys=True).encode('utf-8')
diff --git a/gns3server/web/route.py b/gns3server/web/route.py
index 10c28205..4de33da0 100644
--- a/gns3server/web/route.py
+++ b/gns3server/web/route.py
@@ -42,12 +42,9 @@ def parse_request(request, input_schema):
try:
jsonschema.validate(request.json, input_schema)
except jsonschema.ValidationError as e:
- log.error("Invalid input schema {} '{}' in schema: {}".format(e.validator,
- e.validator_value,
- json.dumps(e.schema)))
- raise aiohttp.web.HTTPBadRequest(text="Request is not {} '{}' in schema: {}".format(
- e.validator,
- e.validator_value,
+ log.error("Invalid input query. JSON schema error: {}".format(e.message))
+ raise aiohttp.web.HTTPBadRequest(text="Invalid JSON: {} in schema: {}".format(
+ e.message,
json.dumps(e.schema)))
return request
diff --git a/tests/api/test_iou.py b/tests/api/test_iou.py
new file mode 100644
index 00000000..61837160
--- /dev/null
+++ b/tests/api/test_iou.py
@@ -0,0 +1,165 @@
+# -*- 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_iou_bin(tmpdir):
+ """Create a fake IOU image on disk"""
+
+ path = str(tmpdir / "iou.bin")
+ with open(path, "w+") as f:
+ f.write('\x7fELF\x01\x01\x01')
+ os.chmod(path, stat.S_IREAD | stat.S_IEXEC)
+ return path
+
+
+@pytest.fixture
+def base_params(tmpdir, fake_iou_bin):
+ """Return standard parameters"""
+ return {"name": "PC TEST 1", "path": fake_iou_bin, "iourc_path": str(tmpdir / "iourc")}
+
+
+@pytest.fixture
+def vm(server, project, base_params):
+ response = server.post("/projects/{project_id}/iou/vms".format(project_id=project.id), base_params)
+ assert response.status == 201
+ return response.json
+
+
+def test_iou_create(server, project, base_params):
+ response = server.post("/projects/{project_id}/iou/vms".format(project_id=project.id), base_params)
+ assert response.status == 201
+ assert response.route == "/projects/{project_id}/iou/vms"
+ assert response.json["name"] == "PC TEST 1"
+ assert response.json["project_id"] == project.id
+ assert response.json["serial_adapters"] == 2
+ assert response.json["ethernet_adapters"] == 2
+ assert response.json["ram"] == 256
+ assert response.json["nvram"] == 128
+
+
+def test_iou_create_with_params(server, project, base_params):
+ params = base_params
+ params["ram"] = 1024
+ params["nvram"] = 512
+ params["serial_adapters"] = 4
+ params["ethernet_adapters"] = 0
+
+ response = server.post("/projects/{project_id}/iou/vms".format(project_id=project.id), params, example=True)
+ assert response.status == 201
+ assert response.route == "/projects/{project_id}/iou/vms"
+ assert response.json["name"] == "PC TEST 1"
+ assert response.json["project_id"] == project.id
+ assert response.json["serial_adapters"] == 4
+ assert response.json["ethernet_adapters"] == 0
+ assert response.json["ram"] == 1024
+ assert response.json["nvram"] == 512
+
+
+def test_iou_get(server, project, vm):
+ response = server.get("/projects/{project_id}/iou/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}/iou/vms/{vm_id}"
+ assert response.json["name"] == "PC TEST 1"
+ assert response.json["project_id"] == project.id
+ assert response.json["serial_adapters"] == 2
+ assert response.json["ethernet_adapters"] == 2
+ assert response.json["ram"] == 256
+ assert response.json["nvram"] == 128
+
+
+def test_iou_start(server, vm):
+ with asyncio_patch("gns3server.modules.iou.iou_vm.IOUVM.start", return_value=True) as mock:
+ response = server.post("/projects/{project_id}/iou/vms/{vm_id}/start".format(project_id=vm["project_id"], vm_id=vm["vm_id"]))
+ assert mock.called
+ assert response.status == 204
+
+
+def test_iou_stop(server, vm):
+ with asyncio_patch("gns3server.modules.iou.iou_vm.IOUVM.stop", return_value=True) as mock:
+ response = server.post("/projects/{project_id}/iou/vms/{vm_id}/stop".format(project_id=vm["project_id"], vm_id=vm["vm_id"]))
+ assert mock.called
+ assert response.status == 204
+
+
+def test_iou_reload(server, vm):
+ with asyncio_patch("gns3server.modules.iou.iou_vm.IOUVM.reload", return_value=True) as mock:
+ response = server.post("/projects/{project_id}/iou/vms/{vm_id}/reload".format(project_id=vm["project_id"], vm_id=vm["vm_id"]))
+ assert mock.called
+ assert response.status == 204
+
+
+def test_iou_delete(server, vm):
+ with asyncio_patch("gns3server.modules.iou.IOU.delete_vm", return_value=True) as mock:
+ response = server.delete("/projects/{project_id}/iou/vms/{vm_id}".format(project_id=vm["project_id"], vm_id=vm["vm_id"]))
+ assert mock.called
+ assert response.status == 204
+
+
+def test_iou_update(server, vm, tmpdir, free_console_port):
+ params = {
+ "name": "test",
+ "console": free_console_port,
+ "ram": 512,
+ "nvram": 2048,
+ "ethernet_adapters": 4,
+ "serial_adapters": 0
+ }
+ response = server.put("/projects/{project_id}/iou/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
+ assert response.json["ethernet_adapters"] == 4
+ assert response.json["serial_adapters"] == 0
+ assert response.json["ram"] == 512
+ assert response.json["nvram"] == 2048
+
+
+def test_iou_nio_create_udp(server, vm):
+ response = server.post("/projects/{project_id}/iou/vms/{vm_id}/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}/iou/vms/{vm_id}/ports/{port_number:\d+}/nio"
+ assert response.json["type"] == "nio_udp"
+
+
+def test_iou_nio_create_tap(server, vm):
+ with patch("gns3server.modules.base_manager.BaseManager._has_privileged_access", return_value=True):
+ response = server.post("/projects/{project_id}/iou/vms/{vm_id}/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}/iou/vms/{vm_id}/ports/{port_number:\d+}/nio"
+ assert response.json["type"] == "nio_tap"
+
+
+def test_iou_delete_nio(server, vm):
+ server.post("/projects/{project_id}/iou/vms/{vm_id}/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}/iou/vms/{vm_id}/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}/iou/vms/{vm_id}/ports/{port_number:\d+}/nio"
diff --git a/tests/modules/iou/test_iou_manager.py b/tests/modules/iou/test_iou_manager.py
new file mode 100644
index 00000000..7817a297
--- /dev/null
+++ b/tests/modules/iou/test_iou_manager.py
@@ -0,0 +1,73 @@
+# -*- 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 uuid
+
+
+from gns3server.modules.iou import IOU
+from gns3server.modules.iou.iou_error import IOUError
+from gns3server.modules.project_manager import ProjectManager
+
+
+def test_get_application_id(loop, project, port_manager):
+ # Cleanup the IOU object
+ IOU._instance = None
+ iou = IOU.instance()
+ iou.port_manager = port_manager
+ vm1_id = str(uuid.uuid4())
+ vm2_id = str(uuid.uuid4())
+ vm3_id = str(uuid.uuid4())
+ loop.run_until_complete(iou.create_vm("PC 1", project.id, vm1_id))
+ loop.run_until_complete(iou.create_vm("PC 2", project.id, vm2_id))
+ assert iou.get_application_id(vm1_id) == 1
+ assert iou.get_application_id(vm1_id) == 1
+ assert iou.get_application_id(vm2_id) == 2
+ loop.run_until_complete(iou.delete_vm(vm1_id))
+ loop.run_until_complete(iou.create_vm("PC 3", project.id, vm3_id))
+ assert iou.get_application_id(vm3_id) == 1
+
+
+def test_get_application_id_multiple_project(loop, port_manager):
+ # Cleanup the IOU object
+ IOU._instance = None
+ iou = IOU.instance()
+ iou.port_manager = port_manager
+ vm1_id = str(uuid.uuid4())
+ vm2_id = str(uuid.uuid4())
+ vm3_id = str(uuid.uuid4())
+ project1 = ProjectManager.instance().create_project()
+ project2 = ProjectManager.instance().create_project()
+ loop.run_until_complete(iou.create_vm("PC 1", project1.id, vm1_id))
+ loop.run_until_complete(iou.create_vm("PC 2", project1.id, vm2_id))
+ loop.run_until_complete(iou.create_vm("PC 2", project2.id, vm3_id))
+ assert iou.get_application_id(vm1_id) == 1
+ assert iou.get_application_id(vm2_id) == 2
+ assert iou.get_application_id(vm3_id) == 3
+
+
+def test_get_application_id_no_id_available(loop, project, port_manager):
+ # Cleanup the IOU object
+ IOU._instance = None
+ iou = IOU.instance()
+ iou.port_manager = port_manager
+ with pytest.raises(IOUError):
+ for i in range(1, 513):
+ vm_id = str(uuid.uuid4())
+ loop.run_until_complete(iou.create_vm("PC {}".format(i), project.id, vm_id))
+ assert iou.get_application_id(vm_id) == i
diff --git a/tests/modules/iou/test_iou_vm.py b/tests/modules/iou/test_iou_vm.py
new file mode 100644
index 00000000..8f8a0181
--- /dev/null
+++ b/tests/modules/iou/test_iou_vm.py
@@ -0,0 +1,170 @@
+# -*- 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 aiohttp
+import asyncio
+import os
+import stat
+from tests.utils import asyncio_patch
+
+
+from unittest.mock import patch, MagicMock
+from gns3server.modules.iou.iou_vm import IOUVM
+from gns3server.modules.iou.iou_error import IOUError
+from gns3server.modules.iou import IOU
+
+
+@pytest.fixture(scope="module")
+def manager(port_manager):
+ m = IOU.instance()
+ m.port_manager = port_manager
+ return m
+
+
+@pytest.fixture(scope="function")
+def vm(project, manager, tmpdir, fake_iou_bin):
+ fake_file = str(tmpdir / "iourc")
+ with open(fake_file, "w+") as f:
+ f.write("1")
+
+ vm = IOUVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", project, manager)
+ config = manager.config.get_section_config("IOU")
+ config["iouyap_path"] = fake_file
+ manager.config.set_section_config("IOU", config)
+
+ vm.path = fake_iou_bin
+ vm.iourc_path = fake_file
+ return vm
+
+
+@pytest.fixture
+def fake_iou_bin(tmpdir):
+ """Create a fake IOU image on disk"""
+
+ path = str(tmpdir / "iou.bin")
+ with open(path, "w+") as f:
+ f.write('\x7fELF\x01\x01\x01')
+ os.chmod(path, stat.S_IREAD | stat.S_IEXEC)
+ return path
+
+
+def test_vm(project, manager):
+ vm = IOUVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", project, manager)
+ assert vm.name == "test"
+ assert vm.id == "00010203-0405-0607-0809-0a0b0c0d0e0f"
+
+
+@patch("gns3server.config.Config.get_section_config", return_value={"iouyap_path": "/bin/test_fake"})
+def test_vm_invalid_iouyap_path(project, manager, loop):
+ with pytest.raises(IOUError):
+ vm = IOUVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0e", project, manager)
+ loop.run_until_complete(asyncio.async(vm.start()))
+
+
+def test_start(loop, vm, monkeypatch):
+ with asyncio_patch("gns3server.modules.iou.iou_vm.IOUVM._check_requirements", return_value=True):
+ with asyncio_patch("gns3server.modules.iou.iou_vm.IOUVM._start_ioucon", return_value=True):
+ with asyncio_patch("gns3server.modules.iou.iou_vm.IOUVM._start_iouyap", return_value=True):
+ with asyncio_patch("asyncio.create_subprocess_exec", return_value=MagicMock()):
+ loop.run_until_complete(asyncio.async(vm.start()))
+ assert vm.is_running()
+
+
+def test_stop(loop, vm):
+ process = MagicMock()
+
+ # Wait process kill success
+ future = asyncio.Future()
+ future.set_result(True)
+ process.wait.return_value = future
+
+ with asyncio_patch("gns3server.modules.iou.iou_vm.IOUVM._check_requirements", return_value=True):
+ with asyncio_patch("gns3server.modules.iou.iou_vm.IOUVM._start_ioucon", return_value=True):
+ with asyncio_patch("gns3server.modules.iou.iou_vm.IOUVM._start_iouyap", return_value=True):
+ with asyncio_patch("asyncio.create_subprocess_exec", return_value=process):
+ loop.run_until_complete(asyncio.async(vm.start()))
+ assert vm.is_running()
+ loop.run_until_complete(asyncio.async(vm.stop()))
+ assert vm.is_running() is False
+ process.terminate.assert_called_with()
+
+
+def test_reload(loop, vm, fake_iou_bin):
+ process = MagicMock()
+
+ # Wait process kill success
+ future = asyncio.Future()
+ future.set_result(True)
+ process.wait.return_value = future
+
+ with asyncio_patch("gns3server.modules.iou.iou_vm.IOUVM._check_requirements", return_value=True):
+ with asyncio_patch("gns3server.modules.iou.iou_vm.IOUVM._start_ioucon", return_value=True):
+ with asyncio_patch("gns3server.modules.iou.iou_vm.IOUVM._start_iouyap", return_value=True):
+ with asyncio_patch("asyncio.create_subprocess_exec", return_value=process):
+ loop.run_until_complete(asyncio.async(vm.start()))
+ assert vm.is_running()
+ loop.run_until_complete(asyncio.async(vm.reload()))
+ assert vm.is_running() is True
+ process.terminate.assert_called_with()
+
+
+def test_close(vm, port_manager):
+ with asyncio_patch("gns3server.modules.iou.iou_vm.IOUVM._check_requirements", return_value=True):
+ with asyncio_patch("asyncio.create_subprocess_exec", return_value=MagicMock()):
+ vm.start()
+ port = vm.console
+ vm.close()
+ # Raise an exception if the port is not free
+ port_manager.reserve_console_port(port)
+ assert vm.is_running() is False
+
+
+def test_path(vm, fake_iou_bin):
+
+ vm.path = fake_iou_bin
+ assert vm.path == fake_iou_bin
+
+
+def test_path_invalid_bin(vm, tmpdir):
+
+ path = str(tmpdir / "test.bin")
+ with pytest.raises(IOUError):
+ vm.path = path
+
+ with open(path, "w+") as f:
+ f.write("BUG")
+
+ with pytest.raises(IOUError):
+ vm.path = path
+
+
+def test_create_netmap_config(vm):
+
+ vm._create_netmap_config()
+ netmap_path = os.path.join(vm.working_dir, "NETMAP")
+
+ with open(netmap_path) as f:
+ content = f.read()
+
+ assert "513:0/0 1:0/0" in content
+ assert "513:15/3 1:15/3" in content
+
+
+def test_build_command(vm):
+
+ assert vm._build_command() == [vm.path, '-L', str(vm.application_id)]