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)]