diff --git a/docs/api/examples/post_virtualbox.txt b/docs/api/examples/post_virtualbox.txt index 0f7721fa..6be43ee6 100644 --- a/docs/api/examples/post_virtualbox.txt +++ b/docs/api/examples/post_virtualbox.txt @@ -19,5 +19,5 @@ X-ROUTE: /virtualbox { "name": "VM1", "project_uuid": "a1e920ca-338a-4e9f-b363-aa607b09dd80", - "uuid": "a028124a-9a69-4b06-b673-21f7eb3d034f" + "uuid": "3142e932-d316-40d7-bed3-7ef8e2d313b3" } diff --git a/gns3server/handlers/virtualbox_handler.py b/gns3server/handlers/virtualbox_handler.py index d6b944cc..55d9a53b 100644 --- a/gns3server/handlers/virtualbox_handler.py +++ b/gns3server/handlers/virtualbox_handler.py @@ -32,6 +32,7 @@ class VirtualBoxHandler: r"/virtualbox", status_codes={ 201: "VirtualBox VM instance created", + 400: "Invalid project UUID", 409: "Conflict" }, description="Create a new VirtualBox VM instance", @@ -40,8 +41,10 @@ class VirtualBoxHandler: def create(request, response): vbox_manager = VirtualBox.instance() - vm = yield from vbox_manager.create_vm(request.json["name"], request.json["project_uuid"], request.json.get("uuid")) - print(vm) + vm = yield from vbox_manager.create_vm(request.json["name"], + request.json["project_uuid"], + request.json.get("uuid"), + vmname=request.json["vmname"]) response.json({"name": vm.name, "uuid": vm.uuid, "project_uuid": vm.project.uuid}) diff --git a/gns3server/handlers/vpcs_handler.py b/gns3server/handlers/vpcs_handler.py index 54d32af2..5d4e9ac8 100644 --- a/gns3server/handlers/vpcs_handler.py +++ b/gns3server/handlers/vpcs_handler.py @@ -33,6 +33,7 @@ class VPCSHandler: r"/vpcs", status_codes={ 201: "VPCS instance created", + 400: "Invalid project UUID", 409: "Conflict" }, description="Create a new VPCS instance", @@ -43,7 +44,7 @@ class VPCSHandler: vpcs = VPCS.instance() vm = yield from vpcs.create_vm(request.json["name"], request.json["project_uuid"], - uuid=request.json.get("uuid"), + request.json.get("uuid"), console=request.json.get("console"), script_file=request.json.get("script_file")) response.json(vm) diff --git a/gns3server/modules/base_manager.py b/gns3server/modules/base_manager.py index 7d51cb9f..3dcbca25 100644 --- a/gns3server/modules/base_manager.py +++ b/gns3server/modules/base_manager.py @@ -20,6 +20,7 @@ import asyncio import aiohttp from uuid import UUID, uuid4 +from ..config import Config from .project_manager import ProjectManager @@ -34,6 +35,7 @@ class BaseManager: self._vms = {} self._port_manager = None + self._config = Config.instance() @classmethod def instance(cls): @@ -50,7 +52,7 @@ class BaseManager: @property def port_manager(self): """ - Returns the port_manager for this VMs + Returns the port manager. :returns: Port manager """ @@ -62,6 +64,16 @@ class BaseManager: self._port_manager = new_port_manager + @property + def config(self): + """ + Returns the server config. + + :returns: Config + """ + + return self._config + @classmethod @asyncio.coroutine # FIXME: why coroutine? def destroy(cls): @@ -87,25 +99,23 @@ class BaseManager: return self._vms[uuid] @asyncio.coroutine - def create_vm(self, name, project_identifier, uuid=None, **kwargs): + def create_vm(self, name, project_uuid, uuid, *args, **kwargs): """ Create a new VM - :param name VM name - :param project_identifier UUID of Project - :param uuid Force UUID force VM + :param name: VM name + :param project_uuid: UUID of Project + :param uuid: restore a VM UUID """ - project = ProjectManager.instance().get_project(project_identifier) + project = ProjectManager.instance().get_project(project_uuid) # TODO: support for old projects VM with normal IDs. - # TODO: supports specific args: pass kwargs to VM_CLASS? - if not uuid: uuid = str(uuid4()) - vm = self._VM_CLASS(name, uuid, project, self, **kwargs) + vm = self._VM_CLASS(name, uuid, project, self, *args, **kwargs) future = vm.create() if isinstance(future, asyncio.Future): yield from future diff --git a/gns3server/modules/base_vm.py b/gns3server/modules/base_vm.py index cecd8432..001d5b1e 100644 --- a/gns3server/modules/base_vm.py +++ b/gns3server/modules/base_vm.py @@ -15,7 +15,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from ..config import Config import logging log = logging.getLogger(__name__) @@ -29,13 +28,17 @@ class BaseVM: self._uuid = uuid self._project = project self._manager = manager - self._config = Config.instance() # TODO: When delete release console ports @property def project(self): - """Return VM current project""" + """ + Returns the VM current project. + + :returns: Project instance. + """ + return self._project @property diff --git a/gns3server/modules/port_manager.py b/gns3server/modules/port_manager.py index ad90d92a..31a38a4e 100644 --- a/gns3server/modules/port_manager.py +++ b/gns3server/modules/port_manager.py @@ -147,7 +147,7 @@ class PortManager: else: continue - raise HTTPConflict(reason="Could not find a free port between {} and {} on host {}, last exception: {}".format(start_port, + raise HTTPConflict(text="Could not find a free port between {} and {} on host {}, last exception: {}".format(start_port, end_port, host, last_exception)) @@ -174,7 +174,7 @@ class PortManager: """ if port in self._used_tcp_ports: - raise HTTPConflict(reason="TCP port already {} in use on host".format(port, self._console_host)) + raise HTTPConflict(text="TCP port already {} in use on host".format(port, self._console_host)) self._used_tcp_ports.add(port) return port @@ -209,7 +209,7 @@ class PortManager: """ if port in self._used_udp_ports: - raise Exception("UDP port already {} in use on host".format(port, self._host)) + raise HTTPConflict(text="UDP port already {} in use on host".format(port, self._console_host)) self._used_udp_ports.add(port) def release_udp_port(self, port): diff --git a/gns3server/modules/project.py b/gns3server/modules/project.py index 26694e21..9d98519c 100644 --- a/gns3server/modules/project.py +++ b/gns3server/modules/project.py @@ -15,13 +15,13 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import aiohttp import os import tempfile -from uuid import uuid4 +from uuid import UUID, uuid4 class Project: - """ A project contains a list of VM. In theory VM are isolated project/project. @@ -35,17 +35,23 @@ class Project: if uuid is None: self._uuid = str(uuid4()) else: - assert len(uuid) == 36 + try: + UUID(uuid, version=4) + except ValueError: + raise aiohttp.web.HTTPBadRequest(text="{} is not a valid UUID".format(uuid)) self._uuid = uuid self._location = location if location is None: self._location = tempfile.mkdtemp() - self._path = os.path.join(self._location, self._uuid) - if os.path.exists(self._path) is False: - os.mkdir(self._path) - os.mkdir(os.path.join(self._path, "vms")) + self._path = os.path.join(self._location, self._uuid, "vms") + try: + os.makedirs(self._path) + except FileExistsError: + pass + except OSError as e: + raise aiohttp.web.HTTPInternalServerError(text="Could not create project directory: {}".format(e)) @property def uuid(self): @@ -62,17 +68,21 @@ class Project: return self._path - def vm_working_directory(self, vm_identifier): + def vm_working_directory(self, vm_uuid): """ Return a working directory for a specific VM. If the directory doesn't exist, the directory is created. - :param vm_identifier: UUID of VM + :param vm_uuid: VM UUID """ - path = os.path.join(self._path, 'vms', vm_identifier) - if os.path.exists(path) is False: - os.mkdir(path) + path = os.path.join(self._path, "vms", vm_uuid) + try: + os.makedirs(self._path) + except FileExistsError: + pass + except OSError as e: + raise aiohttp.web.HTTPInternalServerError(text="Could not create VM working directory: {}".format(e)) return path def __json__(self): diff --git a/gns3server/modules/project_manager.py b/gns3server/modules/project_manager.py index cad199c4..f44ab0fd 100644 --- a/gns3server/modules/project_manager.py +++ b/gns3server/modules/project_manager.py @@ -17,6 +17,7 @@ import aiohttp from .project import Project +from uuid import UUID class ProjectManager: @@ -40,20 +41,23 @@ class ProjectManager: cls._instance = cls() return cls._instance - def get_project(self, project_id): + def get_project(self, project_uuid): """ Returns a Project instance. - :param project_id: Project identifier + :param project_uuid: Project UUID :returns: Project instance """ - assert len(project_id) == 36 + try: + UUID(project_uuid, version=4) + except ValueError: + raise aiohttp.web.HTTPBadRequest(text="{} is not a valid UUID".format(project_uuid)) - if project_id not in self._projects: - raise aiohttp.web.HTTPNotFound(text="Project UUID {} doesn't exist".format(project_id)) - return self._projects[project_id] + if project_uuid not in self._projects: + raise aiohttp.web.HTTPNotFound(text="Project UUID {} doesn't exist".format(project_uuid)) + return self._projects[project_uuid] def create_project(self, **kwargs): """ diff --git a/gns3server/modules/virtualbox/virtualbox_vm.py b/gns3server/modules/virtualbox/virtualbox_vm.py index f922e681..9a61f76b 100644 --- a/gns3server/modules/virtualbox/virtualbox_vm.py +++ b/gns3server/modules/virtualbox/virtualbox_vm.py @@ -29,6 +29,7 @@ import json import socket import time import asyncio +import shutil from .virtualbox_error import VirtualBoxError from ..adapters.ethernet_adapter import EthernetAdapter @@ -45,41 +46,44 @@ log = logging.getLogger(__name__) class VirtualBoxVM(BaseVM): - """ VirtualBox VM implementation. """ - _instances = [] - _allocated_console_ports = [] - - def __init__(self, name, uuid, project, manager): + def __init__(self, name, uuid, project, manager, vmname, linked_clone): super().__init__(name, uuid, project, manager) + # look for VBoxManage + self._vboxmanage_path = manager.config.get_section_config("VirtualBox").get("vboxmanage_path") + if not self._vboxmanage_path: + if sys.platform.startswith("win"): + if "VBOX_INSTALL_PATH" in os.environ: + self._vboxmanage_path = os.path.join(os.environ["VBOX_INSTALL_PATH"], "VBoxManage.exe") + elif "VBOX_MSI_INSTALL_PATH" in os.environ: + self._vboxmanage_path = os.path.join(os.environ["VBOX_MSI_INSTALL_PATH"], "VBoxManage.exe") + elif sys.platform.startswith("darwin"): + self._vboxmanage_path = "/Applications/VirtualBox.app/Contents/MacOS/VBoxManage" + else: + self._vboxmanage_path = shutil.which("vboxmanage") + + if not self._vboxmanage_path: + raise VirtualBoxError("Could not find VBoxManage") + if not os.access(self._vboxmanage_path, os.X_OK): + raise VirtualBoxError("VBoxManage is not executable") + + self._vmname = vmname + self._started = False + self._linked_clone = linked_clone self._system_properties = {} - - # FIXME: harcoded values - if sys.platform.startswith("win"): - self._vboxmanage_path = r"C:\Program Files\Oracle\VirtualBox\VBoxManage.exe" - else: - self._vboxmanage_path = "/usr/bin/vboxmanage" - self._queue = asyncio.Queue() self._created = asyncio.Future() self._worker = asyncio.async(self._run()) return - self._linked_clone = linked_clone - self._working_dir = None self._command = [] - self._vboxmanage_path = vboxmanage_path self._vbox_user = vbox_user - self._started = False - self._console_host = console_host - self._console_start_port_range = console_start_port_range - self._console_end_port_range = console_end_port_range self._telnet_server_thread = None self._serial_pipe = None @@ -101,28 +105,6 @@ class VirtualBoxVM(BaseVM): # 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 VirtualBoxError(e) - - if self._console in self._allocated_console_ports: - raise VirtualBoxError("Console port {} is already used by another VirtualBox VM".format(console)) - self._allocated_console_ports.append(self._console) - - self._system_properties = {} - properties = self._execute("list", ["systemproperties"]) - for prop in properties: - try: - name, value = prop.split(':', 1) - except ValueError: - continue - self._system_properties[name.strip()] = value.strip() if linked_clone: if vbox_id and os.path.isdir(os.path.join(self.working_dir, self._vmname)): diff --git a/gns3server/modules/vpcs/vpcs_vm.py b/gns3server/modules/vpcs/vpcs_vm.py index dfeda3e4..00ad5a73 100644 --- a/gns3server/modules/vpcs/vpcs_vm.py +++ b/gns3server/modules/vpcs/vpcs_vm.py @@ -16,7 +16,7 @@ # along with this program. If not, see . """ -VPCS vm management (creates command line, processes, files etc.) in +VPCS VM management (creates command line, processes, files etc.) in order to run an VPCS instance. """ @@ -59,7 +59,7 @@ class VPCSVM(BaseVM): super().__init__(name, uuid, project, manager) - self._path = self._config.get_section_config("VPCS").get("path", "vpcs") + self._path = manager.config.get_section_config("VPCS").get("path", "vpcs") self._console = console diff --git a/gns3server/web/route.py b/gns3server/web/route.py index cd1ece5d..b59acb4a 100644 --- a/gns3server/web/route.py +++ b/gns3server/web/route.py @@ -111,8 +111,8 @@ class Route(object): response.json({"message": e.text, "status": e.status}) except VMError as e: response = Response(route=route) - response.set_status(400) - response.json({"message": str(e), "status": 400}) + response.set_status(500) + response.json({"message": str(e), "status": 500}) return response cls._routes.append((method, cls._path, control_schema))