diff --git a/gns3server/handlers/virtualbox_handler.py b/gns3server/handlers/virtualbox_handler.py
new file mode 100644
index 00000000..2b8714dd
--- /dev/null
+++ b/gns3server/handlers/virtualbox_handler.py
@@ -0,0 +1,80 @@
+# -*- 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.virtualbox import VBOX_CREATE_SCHEMA
+from ..schemas.virtualbox import VBOX_OBJECT_SCHEMA
+from ..modules.virtualbox import VirtualBox
+
+
+class VirtualBoxHandler:
+ """
+ API entry points for VirtualBox.
+ """
+
+ @classmethod
+ @Route.post(
+ r"/virtualbox",
+ status_codes={
+ 201: "VirtualBox VM instance created",
+ 409: "Conflict"
+ },
+ description="Create a new VirtualBox VM instance",
+ input=VBOX_CREATE_SCHEMA,
+ output=VBOX_OBJECT_SCHEMA)
+ def create(request, response):
+
+ vbox_manager = VirtualBox.instance()
+ vm = yield from vbox_manager.create_vm(request.json["name"], request.json.get("uuid"))
+ response.json({"name": vm.name,
+ "uuid": vm.uuid})
+
+ @classmethod
+ @Route.post(
+ r"/virtualbox/{uuid}/start",
+ parameters={
+ "uuid": "VirtualBox VM instance UUID"
+ },
+ status_codes={
+ 204: "VirtualBox VM instance started",
+ 400: "Invalid VirtualBox VM instance UUID",
+ 404: "VirtualBox VM instance doesn't exist"
+ },
+ description="Start a VirtualBox VM instance")
+ def create(request, response):
+
+ vbox_manager = VirtualBox.instance()
+ yield from vbox_manager.start_vm(request.match_info["uuid"])
+ response.json({})
+
+ @classmethod
+ @Route.post(
+ r"/virtualbox/{uuid}/stop",
+ parameters={
+ "uuid": "VirtualBox VM instance UUID"
+ },
+ status_codes={
+ 204: "VirtualBox VM instance stopped",
+ 400: "Invalid VirtualBox VM instance UUID",
+ 404: "VirtualBox VM instance doesn't exist"
+ },
+ description="Stop a VirtualBox VM instance")
+ def create(request, response):
+
+ vbox_manager = VirtualBox.instance()
+ yield from vbox_manager.stop_vm(request.match_info["uuid"])
+ response.json({})
diff --git a/gns3server/handlers/vpcs_handler.py b/gns3server/handlers/vpcs_handler.py
index 4719f0b0..28e72331 100644
--- a/gns3server/handlers/vpcs_handler.py
+++ b/gns3server/handlers/vpcs_handler.py
@@ -22,7 +22,7 @@ from ..schemas.vpcs import VPCS_NIO_SCHEMA
from ..modules.vpcs import VPCS
-class VPCSHandler(object):
+class VPCSHandler:
"""
API entry points for VPCS.
"""
@@ -40,53 +40,56 @@ class VPCSHandler(object):
def create(request, response):
vpcs = VPCS.instance()
- vm = yield from vpcs.create_vm(request.json["name"])
+ vm = yield from vpcs.create_vm(request.json["name"], request.json.get("uuid"))
response.json({"name": vm.name,
- "id": vm.id,
+ "uuid": vm.uuid,
"console": vm.console})
@classmethod
@Route.post(
- r"/vpcs/{id:\d+}/start",
+ r"/vpcs/{uuid}/start",
parameters={
- "id": "VPCS instance ID"
+ "uuid": "VPCS instance UUID"
},
status_codes={
204: "VPCS instance started",
+ 400: "Invalid VPCS instance UUID",
404: "VPCS instance doesn't exist"
},
description="Start a VPCS instance")
def create(request, response):
vpcs_manager = VPCS.instance()
- yield from vpcs_manager.start_vm(int(request.match_info["id"]))
+ yield from vpcs_manager.start_vm(request.match_info["uuid"])
response.json({})
@classmethod
@Route.post(
- r"/vpcs/{id:\d+}/stop",
+ r"/vpcs/{uuid}/stop",
parameters={
- "id": "VPCS instance ID"
+ "uuid": "VPCS instance UUID"
},
status_codes={
204: "VPCS instance stopped",
+ 400: "Invalid VPCS instance UUID",
404: "VPCS instance doesn't exist"
},
description="Stop a VPCS instance")
def create(request, response):
vpcs_manager = VPCS.instance()
- yield from vpcs_manager.stop_vm(int(request.match_info["id"]))
+ yield from vpcs_manager.stop_vm(request.match_info["uuid"])
response.json({})
@Route.post(
- r"/vpcs/{id:\d+}/ports/{port_id}/nio",
+ r"/vpcs/{uuid}/ports/{port_id}/nio",
parameters={
- "id": "VPCS instance ID",
+ "uuid": "VPCS instance UUID",
"port_id": "Id of the port where the nio should be add"
},
status_codes={
201: "NIO created",
+ 400: "Invalid VPCS instance UUID",
404: "VPCS instance doesn't exist"
},
description="Add a NIO to a VPCS",
@@ -95,26 +98,26 @@ class VPCSHandler(object):
def create_nio(request, response):
vpcs_manager = VPCS.instance()
- vm = vpcs_manager.get_vm(int(request.match_info["id"]))
+ vm = vpcs_manager.get_vm(request.match_info["uuid"])
nio = vm.port_add_nio_binding(int(request.match_info["port_id"]), request.json)
-
response.json(nio)
@classmethod
@Route.delete(
- r"/vpcs/{id:\d+}/ports/{port_id}/nio",
+ r"/vpcs/{uuid}/ports/{port_id}/nio",
parameters={
- "id": "VPCS instance ID",
- "port_id": "Id of the port where the nio should be remove"
+ "uuid": "VPCS instance UUID",
+ "port_id": "ID of the port where the nio should be removed"
},
status_codes={
200: "NIO deleted",
+ 400: "Invalid VPCS instance UUID",
404: "VPCS instance doesn't exist"
},
description="Remove a NIO from a VPCS")
def delete_nio(request, response):
vpcs_manager = VPCS.instance()
- vm = vpcs_manager.get_vm(int(request.match_info["id"]))
+ vm = vpcs_manager.get_vm(request.match_info["uuid"])
nio = vm.port_remove_nio_binding(int(request.match_info["port_id"]))
response.json({})
diff --git a/gns3server/modules/__init__.py b/gns3server/modules/__init__.py
index 5fc0b897..43b95792 100644
--- a/gns3server/modules/__init__.py
+++ b/gns3server/modules/__init__.py
@@ -17,8 +17,9 @@
import sys
from .vpcs import VPCS
+from .virtualbox import VirtualBox
-MODULES = [VPCS]
+MODULES = [VPCS, VirtualBox]
#if sys.platform.startswith("linux"):
# # IOU runs only on Linux
diff --git a/gns3server/modules/base_manager.py b/gns3server/modules/base_manager.py
index 42fb1576..71f78d2b 100644
--- a/gns3server/modules/base_manager.py
+++ b/gns3server/modules/base_manager.py
@@ -19,7 +19,7 @@
import asyncio
import aiohttp
-from .vm_error import VMError
+from uuid import UUID, uuid4
class BaseManager:
@@ -63,44 +63,52 @@ class BaseManager:
@classmethod
@asyncio.coroutine # FIXME: why coroutine?
def destroy(cls):
+
cls._instance = None
- def get_vm(self, vm_id):
+ def get_vm(self, uuid):
"""
Returns a VM instance.
- :param vm_id: VM identifier
+ :param uuid: VM UUID
:returns: VM instance
"""
- if vm_id not in self._vms:
- raise aiohttp.web.HTTPNotFound(text="ID {} doesn't exist".format(vm_id))
- return self._vms[vm_id]
+ try:
+ UUID(uuid, version=4)
+ except ValueError:
+ raise aiohttp.web.HTTPBadRequest(text="{} is not a valid UUID".format(uuid))
+
+ if uuid not in self._vms:
+ raise aiohttp.web.HTTPNotFound(text="UUID {} doesn't exist".format(uuid))
+ return self._vms[uuid]
@asyncio.coroutine
- def create_vm(self, vmname, identifier=None):
- if not identifier:
- for i in range(1, 1024):
- if i not in self._vms:
- identifier = i
- break
- if identifier == 0:
- raise VMError("Maximum number of VM instances reached")
- else:
- if identifier in self._vms:
- raise VMError("VM identifier {} is already used by another VM instance".format(identifier))
- vm = self._VM_CLASS(vmname, identifier, self)
- yield from vm.wait_for_creation()
- self._vms[vm.id] = vm
+ def create_vm(self, name, uuid=None):
+
+ #TODO: support for old projects with normal IDs.
+
+ #TODO: supports specific args: pass kwargs to VM_CLASS?
+
+ if not uuid:
+ uuid = str(uuid4())
+
+ vm = self._VM_CLASS(name, uuid, self)
+ future = vm.create()
+ if isinstance(future, asyncio.Future):
+ yield from future
+ self._vms[vm.uuid] = vm
return vm
@asyncio.coroutine
- def start_vm(self, vm_id):
- vm = self.get_vm(vm_id)
+ def start_vm(self, uuid):
+
+ vm = self.get_vm(uuid)
yield from vm.start()
@asyncio.coroutine
- def stop_vm(self, vm_id):
- vm = self.get_vm(vm_id)
+ def stop_vm(self, uuid):
+
+ vm = self.get_vm(uuid)
yield from vm.stop()
diff --git a/gns3server/modules/base_vm.py b/gns3server/modules/base_vm.py
index 17aa103b..181faa2d 100644
--- a/gns3server/modules/base_vm.py
+++ b/gns3server/modules/base_vm.py
@@ -15,9 +15,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-
-import asyncio
-from .vm_error import VMError
from ..config import Config
import logging
@@ -26,64 +23,62 @@ log = logging.getLogger(__name__)
class BaseVM:
- def __init__(self, name, identifier, manager):
+ def __init__(self, name, uuid, manager):
self._name = name
- self._id = identifier
- self._created = asyncio.Future()
+ self._uuid = uuid
self._manager = manager
self._config = Config.instance()
- asyncio.async(self._create())
- log.info("{type} device {name} [id={id}] has been created".format(type=self.__class__.__name__,
- name=self._name,
- id=self._id))
#TODO: When delete release console ports
-
- @property
- def id(self):
- """
- Returns the unique ID for this VM.
-
- :returns: id (integer)
- """
-
- return self._id
-
@property
def name(self):
"""
Returns the name for this VM.
- :returns: name (string)
+ :returns: name
"""
return self._name
- @asyncio.coroutine
- def _execute(self, command):
+ @name.setter
+ def name(self, new_name):
"""
- Called when we receive an event.
+ Sets the name of this VM.
+
+ :param new_name: name
"""
- raise NotImplementedError
+ self._name = new_name
- @asyncio.coroutine
- def _create(self):
+ @property
+ def uuid(self):
"""
- Called when the run module is created and ready to receive
- commands. It's asynchronous.
+ Returns the UUID for this VM.
+
+ :returns: uuid (string)
"""
- self._created.set_result(True)
- log.info("{type} device {name} [id={id}] has been created".format(type=self.__class__.__name__,
- name=self._name,
- id=self._id))
- def wait_for_creation(self):
- return self._created
+ return self._uuid
+
+ @property
+ def manager(self):
+ """
+ Returns the manager for this VM.
+
+ :returns: instance of manager
+ """
+
+ return self._manager
+
+ def create(self):
+ """
+ Creates the VM.
+ """
+
+ return
- @asyncio.coroutine
def start(self):
"""
Starts the VM process.
@@ -91,8 +86,6 @@ class BaseVM:
raise NotImplementedError
-
- @asyncio.coroutine
def stop(self):
"""
Starts the VM process.
diff --git a/gns3server/modules/project.py b/gns3server/modules/project.py
index 289b18a2..9be2f740 100644
--- a/gns3server/modules/project.py
+++ b/gns3server/modules/project.py
@@ -43,13 +43,23 @@ class Project:
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, 'files'))
+ os.mkdir(os.path.join(self._path, "files"))
@property
def uuid(self):
return self._uuid
+ @property
+ def location(self):
+
+ return self._location
+
+ @property
+ def path(self):
+
+ return self._path
+
def __json__(self):
return {
diff --git a/gns3server/modules/virtualbox/virtualbox_vm.py b/gns3server/modules/virtualbox/virtualbox_vm.py
index efebb34a..12dfb934 100644
--- a/gns3server/modules/virtualbox/virtualbox_vm.py
+++ b/gns3server/modules/virtualbox/virtualbox_vm.py
@@ -28,11 +28,13 @@ import tempfile
import json
import socket
import time
+import asyncio
from .virtualbox_error import VirtualBoxError
from ..adapters.ethernet_adapter import EthernetAdapter
from ..attic import find_unused_port
from .telnet_server import TelnetServer
+from ..base_vm import BaseVM
if sys.platform.startswith('win'):
import msvcrt
@@ -42,55 +44,32 @@ import logging
log = logging.getLogger(__name__)
-class VirtualBoxVM(object):
+class VirtualBoxVM(BaseVM):
"""
VirtualBox VM implementation.
-
- :param vboxmanage_path: path to the VBoxManage tool
- :param name: name of this VirtualBox VM
- :param vmname: name of this VirtualBox VM in VirtualBox itself
- :param linked_clone: flag if a linked clone must be created
- :param working_dir: path to a working directory
- :param vbox_id: VirtalBox 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
"""
_instances = []
_allocated_console_ports = []
- def __init__(self,
- vboxmanage_path,
- vbox_user,
- name,
- vmname,
- linked_clone,
- working_dir,
- vbox_id=None,
- console=None,
- console_host="0.0.0.0",
- console_start_port_range=4512,
- console_end_port_range=5000):
+ def __init__(self, name, uuid, manager):
- if not vbox_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
+ super().__init__(name, uuid, manager)
- if self._id == 0:
- raise VirtualBoxError("Maximum number of VirtualBox VM instances reached")
+ self._system_properties = {}
+
+ #FIXME: harcoded values
+ if sys.platform.startswith("win"):
+ self._vboxmanage_path = r"C:\Program Files\Oracle\VirtualBox\VBoxManage.exe"
else:
- if vbox_id in self._instances:
- raise VirtualBoxError("VirtualBox identifier {} is already used by another VirtualBox VM instance".format(vbox_id))
- self._id = vbox_id
- self._instances.append(self._id)
+ self._vboxmanage_path = "/usr/bin/vboxmanage"
+
+ self._queue = asyncio.Queue()
+ self._created = asyncio.Future()
+ self._worker = asyncio.async(self._run())
+
+ return
- self._name = name
self._linked_clone = linked_clone
self._working_dir = None
self._command = []
@@ -158,6 +137,82 @@ class VirtualBoxVM(object):
log.info("VirtualBox VM {name} [id={id}] has been created".format(name=self._name,
id=self._id))
+ @asyncio.coroutine
+ def _execute(self, subcommand, args, timeout=60):
+
+ command = [self._vboxmanage_path, "--nologo", subcommand]
+ command.extend(args)
+ try:
+ process = yield from asyncio.create_subprocess_exec(*command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
+ except (OSError, subprocess.SubprocessError) as e:
+ raise VirtualBoxError("Could not execute VBoxManage: {}".format(e))
+
+ try:
+ stdout_data, stderr_data = yield from asyncio.wait_for(process.communicate(), timeout=timeout)
+ except asyncio.TimeoutError:
+ raise VirtualBoxError("VBoxManage has timed out after {} seconds!".format(timeout))
+
+ if process.returncode:
+ # only the first line of the output is useful
+ vboxmanage_error = stderr_data.decode("utf-8", errors="ignore").splitlines()[0]
+ raise VirtualBoxError(vboxmanage_error)
+
+ return stdout_data.decode("utf-8", errors="ignore").splitlines()
+
+ @asyncio.coroutine
+ def _get_system_properties(self):
+
+ properties = yield from self._execute("list", ["systemproperties"])
+ for prop in properties:
+ try:
+ name, value = prop.split(':', 1)
+ except ValueError:
+ continue
+ self._system_properties[name.strip()] = value.strip()
+
+ @asyncio.coroutine
+ def _run(self):
+
+ try:
+ yield from self._get_system_properties()
+ self._created.set_result(True)
+ except VirtualBoxError as e:
+ self._created.set_exception(e)
+ return
+
+ while True:
+ future, subcommand, args = yield from self._queue.get()
+ try:
+ yield from self._execute(subcommand, args)
+ future.set_result(True)
+ except VirtualBoxError as e:
+ future.set_exception(e)
+
+ def create(self):
+
+ return self._created
+
+ def _put(self, item):
+
+ try:
+ self._queue.put_nowait(item)
+ except asyncio.qeues.QueueFull:
+ raise VirtualBoxError("Queue is full")
+
+ def start(self):
+
+ args = [self._name]
+ future = asyncio.Future()
+ self._put((future, "startvm", args))
+ return future
+
+ def stop(self):
+
+ args = [self._name, "poweroff"]
+ future = asyncio.Future()
+ self._put((future, "controlvm", args))
+ return future
+
def defaults(self):
"""
Returns all the default attribute values for this VirtualBox VM.
@@ -176,49 +231,6 @@ class VirtualBoxVM(object):
return vbox_defaults
- @property
- def id(self):
- """
- Returns the unique ID for this VirtualBox VM.
-
- :returns: id (integer)
- """
-
- return self._id
-
- @classmethod
- def reset(cls):
- """
- Resets allocated instance list.
- """
-
- cls._instances.clear()
- cls._allocated_console_ports.clear()
-
- @property
- def name(self):
- """
- Returns the name of this VirtualBox VM.
-
- :returns: name
- """
-
- return self._name
-
- @name.setter
- def name(self, new_name):
- """
- Sets the name of this VirtualBox VM.
-
- :param new_name: name
- """
-
- log.info("VirtualBox 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):
"""
@@ -540,7 +552,7 @@ class VirtualBoxVM(object):
id=self._id,
adapter_type=adapter_type))
- def _execute(self, subcommand, args, timeout=60):
+ def _old_execute(self, subcommand, args, timeout=60):
"""
Executes a command with VBoxManage.
@@ -831,7 +843,7 @@ class VirtualBoxVM(object):
self._serial_pipe.close()
self._serial_pipe = None
- def start(self):
+ def old_start(self):
"""
Starts this VirtualBox VM.
"""
@@ -864,7 +876,7 @@ class VirtualBoxVM(object):
if self._enable_remote_console:
self._start_remote_console()
- def stop(self):
+ def old_stop(self):
"""
Stops this VirtualBox VM.
"""
diff --git a/gns3server/modules/vpcs/vpcs_device.py b/gns3server/modules/vpcs/vpcs_device.py
index 9d36c305..eed301c3 100644
--- a/gns3server/modules/vpcs/vpcs_device.py
+++ b/gns3server/modules/vpcs/vpcs_device.py
@@ -47,14 +47,14 @@ class VPCSDevice(BaseVM):
VPCS device implementation.
:param name: name of this VPCS device
- :param vpcs_id: VPCS instance ID
+ :param uuid: VPCS instance UUID
:param manager: parent VM Manager
:param working_dir: path to a working directory
:param console: TCP console port
"""
- def __init__(self, name, vpcs_id, manager, working_dir=None, console=None):
+ def __init__(self, name, uuid, manager, working_dir=None, console=None):
- super().__init__(name, vpcs_id, manager)
+ super().__init__(name, uuid, manager)
# TODO: Hardcodded for testing
#self._working_dir = working_dir
@@ -120,17 +120,8 @@ class VPCSDevice(BaseVM):
return self._console
- @property
- def name(self):
- """
- Returns the name of this VPCS device.
-
- :returns: name
- """
-
- return self._name
-
- @name.setter
+ #FIXME: correct way to subclass a property?
+ @BaseVM.name.setter
def name(self, new_name):
"""
Sets the name of this VPCS device.
@@ -151,10 +142,10 @@ class VPCSDevice(BaseVM):
except OSError as e:
raise VPCSError("Could not amend the configuration {}: {}".format(config_path, e))
- log.info("VPCS {name} [id={id}]: renamed to {new_name}".format(name=self._name,
- id=self._id,
- new_name=new_name))
- self._name = new_name
+ log.info("VPCS {name} [{uuid}]: renamed to {new_name}".format(name=self._name,
+ uuid=self.uuid,
+ new_name=new_name))
+ BaseVM.name = new_name
def _check_vpcs_version(self):
"""
@@ -197,7 +188,7 @@ class VPCSDevice(BaseVM):
stderr=subprocess.STDOUT,
cwd=self._working_dir,
creationflags=flags)
- log.info("VPCS instance {} started PID={}".format(self._id, self._process.pid))
+ log.info("VPCS instance {} started PID={}".format(self.name, self._process.pid))
self._started = True
except (OSError, subprocess.SubprocessError) as e:
vpcs_stdout = self.read_vpcs_stdout()
@@ -212,7 +203,7 @@ class VPCSDevice(BaseVM):
# stop the VPCS process
if self.is_running():
- log.info("stopping VPCS instance {} PID={}".format(self._id, self._process.pid))
+ log.info("stopping VPCS instance {} PID={}".format(self.name, self._process.pid))
if sys.platform.startswith("win32"):
self._process.send_signal(signal.CTRL_BREAK_EVENT)
else:
@@ -283,10 +274,10 @@ class VPCSDevice(BaseVM):
self._ethernet_adapter.add_nio(port_id, nio)
- log.info("VPCS {name} [id={id}]: {nio} added to port {port_id}".format(name=self._name,
- id=self._id,
- nio=nio,
- port_id=port_id))
+ log.info("VPCS {name} {uuid}]: {nio} added to port {port_id}".format(name=self._name,
+ uuid=self.uuid,
+ nio=nio,
+ port_id=port_id))
return nio
def port_remove_nio_binding(self, port_id):
@@ -304,10 +295,10 @@ class VPCSDevice(BaseVM):
nio = self._ethernet_adapter.get_nio(port_id)
self._ethernet_adapter.remove_nio(port_id)
- log.info("VPCS {name} [id={id}]: {nio} removed from port {port_id}".format(name=self._name,
- id=self._id,
- nio=nio,
- port_id=port_id))
+ log.info("VPCS {name} [{uuid}]: {nio} removed from port {port_id}".format(name=self._name,
+ uuid=self.uuid,
+ nio=nio,
+ port_id=port_id))
return nio
def _build_command(self):
@@ -364,7 +355,8 @@ class VPCSDevice(BaseVM):
command.extend(["-e"])
command.extend(["-d", nio.tap_device])
- command.extend(["-m", str(self._id)]) # the unique ID is used to set the MAC address offset
+ #FIXME: find workaround
+ #command.extend(["-m", str(self._id)]) # the unique ID is used to set the MAC address offset
command.extend(["-i", "1"]) # option to start only one VPC instance
command.extend(["-F"]) # option to avoid the daemonization of VPCS
if self._script_file:
@@ -390,6 +382,6 @@ class VPCSDevice(BaseVM):
"""
self._script_file = script_file
- log.info("VPCS {name} [id={id}]: script_file set to {config}".format(name=self._name,
- id=self._id,
- config=self._script_file))
+ log.info("VPCS {name} [{uuid}]: script_file set to {config}".format(name=self._name,
+ uuid=self.uuid,
+ config=self._script_file))
diff --git a/gns3server/schemas/virtualbox.py b/gns3server/schemas/virtualbox.py
index 67c0568c..1dea7163 100644
--- a/gns3server/schemas/virtualbox.py
+++ b/gns3server/schemas/virtualbox.py
@@ -36,69 +36,37 @@ VBOX_CREATE_SCHEMA = {
"type": "boolean"
},
"vbox_id": {
- "description": "VirtualBox VM instance ID",
+ "description": "VirtualBox VM instance ID (for project created before GNS3 1.3)",
"type": "integer"
},
- "console": {
- "description": "console TCP port",
- "minimum": 1,
- "maximum": 65535,
- "type": "integer"
+ "uuid": {
+ "description": "VirtualBox VM instance 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}$"
},
},
"additionalProperties": False,
"required": ["name", "vmname"],
}
-VBOX_DELETE_SCHEMA = {
+VBOX_OBJECT_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
- "description": "Request validation to delete a VirtualBox VM instance",
+ "description": "VirtualBox VM instance",
"type": "object",
"properties": {
- "id": {
- "description": "VirtualBox VM instance ID",
- "type": "integer"
- },
- },
- "additionalProperties": False,
- "required": ["id"]
-}
-
-VBOX_UPDATE_SCHEMA = {
- "$schema": "http://json-schema.org/draft-04/schema#",
- "description": "Request validation to update a VirtualBox VM instance",
- "type": "object",
- "properties": {
- "id": {
- "description": "VirtualBox VM instance ID",
- "type": "integer"
- },
"name": {
"description": "VirtualBox VM instance name",
"type": "string",
"minLength": 1,
},
- "vmname": {
- "description": "VirtualBox VM name (in VirtualBox itself)",
+ "uuid": {
+ "description": "VirtualBox VM instance UUID",
"type": "string",
- "minLength": 1,
- },
- "adapters": {
- "description": "number of adapters",
- "type": "integer",
- "minimum": 1,
- "maximum": 36, # maximum given by the ICH9 chipset in VirtualBox
- },
- "adapter_start_index": {
- "description": "adapter index from which to start using adapters",
- "type": "integer",
- "minimum": 0,
- "maximum": 35, # maximum given by the ICH9 chipset in VirtualBox
- },
- "adapter_type": {
- "description": "VirtualBox adapter type",
- "type": "string",
- "minLength": 1,
+ "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",
@@ -106,327 +74,8 @@ VBOX_UPDATE_SCHEMA = {
"maximum": 65535,
"type": "integer"
},
- "enable_remote_console": {
- "description": "enable the remote console",
- "type": "boolean"
- },
- "headless": {
- "description": "headless mode",
- "type": "boolean"
- },
},
"additionalProperties": False,
- "required": ["id"]
-}
-
-VBOX_START_SCHEMA = {
- "$schema": "http://json-schema.org/draft-04/schema#",
- "description": "Request validation to start a VirtualBox VM instance",
- "type": "object",
- "properties": {
- "id": {
- "description": "VirtualBox VM instance ID",
- "type": "integer"
- },
- },
- "additionalProperties": False,
- "required": ["id"]
-}
-
-VBOX_STOP_SCHEMA = {
- "$schema": "http://json-schema.org/draft-04/schema#",
- "description": "Request validation to stop a VirtualBox VM instance",
- "type": "object",
- "properties": {
- "id": {
- "description": "VirtualBox VM instance ID",
- "type": "integer"
- },
- },
- "additionalProperties": False,
- "required": ["id"]
-}
-
-VBOX_SUSPEND_SCHEMA = {
- "$schema": "http://json-schema.org/draft-04/schema#",
- "description": "Request validation to suspend a VirtualBox VM instance",
- "type": "object",
- "properties": {
- "id": {
- "description": "VirtualBox VM instance ID",
- "type": "integer"
- },
- },
- "additionalProperties": False,
- "required": ["id"]
-}
-
-VBOX_RELOAD_SCHEMA = {
- "$schema": "http://json-schema.org/draft-04/schema#",
- "description": "Request validation to reload a VirtualBox VM instance",
- "type": "object",
- "properties": {
- "id": {
- "description": "VirtualBox VM instance ID",
- "type": "integer"
- },
- },
- "additionalProperties": False,
- "required": ["id"]
-}
-
-VBOX_ALLOCATE_UDP_PORT_SCHEMA = {
- "$schema": "http://json-schema.org/draft-04/schema#",
- "description": "Request validation to allocate an UDP port for a VirtualBox VM instance",
- "type": "object",
- "properties": {
- "id": {
- "description": "VirtualBox VM instance ID",
- "type": "integer"
- },
- "port_id": {
- "description": "Unique port identifier for the VirtualBox VM instance",
- "type": "integer"
- },
- },
- "additionalProperties": False,
- "required": ["id", "port_id"]
-}
-
-VBOX_ADD_NIO_SCHEMA = {
- "$schema": "http://json-schema.org/draft-04/schema#",
- "description": "Request validation to add a NIO for a VirtualBox 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": "VirtualBox VM instance ID",
- "type": "integer"
- },
- "port_id": {
- "description": "Unique port identifier for the VirtualBox VM instance",
- "type": "integer"
- },
- "port": {
- "description": "Port number",
- "type": "integer",
- "minimum": 0,
- "maximum": 36 # maximum given by the ICH9 chipset in VirtualBox
- },
- "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"]
-}
-
-
-VBOX_DELETE_NIO_SCHEMA = {
- "$schema": "http://json-schema.org/draft-04/schema#",
- "description": "Request validation to delete a NIO for a VirtualBox VM instance",
- "type": "object",
- "properties": {
- "id": {
- "description": "VirtualBox VM instance ID",
- "type": "integer"
- },
- "port": {
- "description": "Port number",
- "type": "integer",
- "minimum": 0,
- "maximum": 36 # maximum given by the ICH9 chipset in VirtualBox
- },
- },
- "additionalProperties": False,
- "required": ["id", "port"]
-}
-
-VBOX_START_CAPTURE_SCHEMA = {
- "$schema": "http://json-schema.org/draft-04/schema#",
- "description": "Request validation to start a packet capture on a VirtualBox VM instance port",
- "type": "object",
- "properties": {
- "id": {
- "description": "VirtualBox VM instance ID",
- "type": "integer"
- },
- "port": {
- "description": "Port number",
- "type": "integer",
- "minimum": 0,
- "maximum": 36 # maximum given by the ICH9 chipset in VirtualBox
- },
- "port_id": {
- "description": "Unique port identifier for the VirtualBox VM instance",
- "type": "integer"
- },
- "capture_file_name": {
- "description": "Capture file name",
- "type": "string",
- "minLength": 1,
- },
- },
- "additionalProperties": False,
- "required": ["id", "port", "port_id", "capture_file_name"]
-}
-
-VBOX_STOP_CAPTURE_SCHEMA = {
- "$schema": "http://json-schema.org/draft-04/schema#",
- "description": "Request validation to stop a packet capture on a VirtualBox VM instance port",
- "type": "object",
- "properties": {
- "id": {
- "description": "VirtualBox VM instance ID",
- "type": "integer"
- },
- "port": {
- "description": "Port number",
- "type": "integer",
- "minimum": 0,
- "maximum": 36 # maximum given by the ICH9 chipset in VirtualBox
- },
- "port_id": {
- "description": "Unique port identifier for the VirtualBox VM instance",
- "type": "integer"
- },
- },
- "additionalProperties": False,
- "required": ["id", "port", "port_id"]
+ "required": ["name", "uuid"]
}
diff --git a/gns3server/schemas/vpcs.py b/gns3server/schemas/vpcs.py
index c4b7c71c..275320de 100644
--- a/gns3server/schemas/vpcs.py
+++ b/gns3server/schemas/vpcs.py
@@ -26,8 +26,8 @@ VPCS_CREATE_SCHEMA = {
"type": "string",
"minLength": 1,
},
- "id": {
- "description": "VPCS device instance ID",
+ "vpcs_id": {
+ "description": "VPCS device instance ID (for project created before GNS3 1.3)",
"type": "integer"
},
"uuid": {
@@ -117,9 +117,12 @@ VPCS_OBJECT_SCHEMA = {
"type": "string",
"minLength": 1,
},
- "id": {
- "description": "VPCS device instance ID",
- "type": "integer"
+ "uuid": {
+ "description": "VPCS device 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",
@@ -129,6 +132,6 @@ VPCS_OBJECT_SCHEMA = {
},
},
"additionalProperties": False,
- "required": ["name", "id", "console"]
+ "required": ["name", "uuid", "console"]
}
diff --git a/gns3server/server.py b/gns3server/server.py
index 0db04449..d476a003 100644
--- a/gns3server/server.py
+++ b/gns3server/server.py
@@ -35,6 +35,7 @@ from .modules.port_manager import PortManager
#TODO: get rid of * have something generic to automatically import handlers so the routes can be found
from gns3server.handlers import *
+from gns3server.handlers.virtualbox_handler import VirtualBoxHandler
import logging
log = logging.getLogger(__name__)
diff --git a/tests/api/base.py b/tests/api/base.py
index 10a432a4..b63b7674 100644
--- a/tests/api/base.py
+++ b/tests/api/base.py
@@ -25,6 +25,7 @@ import pytest
from aiohttp import web
import aiohttp
+
from gns3server.web.route import Route
#TODO: get rid of *
from gns3server.handlers import *
@@ -95,7 +96,7 @@ class Query:
if path is None:
return
with open(self._example_file_path(method, path), 'w+') as f:
- f.write("curl -i -x{} 'http://localhost:8000{}'".format(method, path))
+ f.write("curl -i -X {} 'http://localhost:8000{}'".format(method, path))
if body:
f.write(" -d '{}'".format(re.sub(r"\n", "", json.dumps(json.loads(body), sort_keys=True))))
f.write("\n\n")
@@ -116,7 +117,7 @@ class Query:
def _example_file_path(self, method, path):
path = re.sub('[^a-z0-9]', '', path)
- return "docs/api/examples/{}_{}.txt".format(method.lower(), path)
+ return "docs/api/examples/{}_{}.txt".format(method.lower(), path) # FIXME: cannot find path when running tests
def _get_unused_port():
diff --git a/tests/api/test_virtualbox.py b/tests/api/test_virtualbox.py
new file mode 100644
index 00000000..a73700f9
--- /dev/null
+++ b/tests/api/test_virtualbox.py
@@ -0,0 +1,41 @@
+# -*- 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 tests.utils import asyncio_patch
+
+
+@asyncio_patch("gns3server.modules.VirtualBox.create_vm", return_value="61d61bdd-aa7d-4912-817f-65a9eb54d3ab")
+def test_vbox_create(server):
+ response = server.post("/virtualbox", {"name": "VM1"}, example=False)
+ assert response.status == 200
+ assert response.route == "/virtualbox"
+ assert response.json["name"] == "VM1"
+ assert response.json["uuid"] == "61d61bdd-aa7d-4912-817f-65a9eb54d3ab"
+
+
+@asyncio_patch("gns3server.modules.VirtualBox.start_vm", return_value=True)
+def test_vbox_start(server):
+ response = server.post("/virtualbox/61d61bdd-aa7d-4912-817f-65a9eb54d3ab/start", {}, example=False)
+ assert response.status == 204
+ assert response.route == "/virtualbox/61d61bdd-aa7d-4912-817f-65a9eb54d3ab/start"
+
+
+@asyncio_patch("gns3server.modules.VirtualBox.stop_vm", return_value=True)
+def test_vbox_stop(server):
+ response = server.post("/virtualbox/61d61bdd-aa7d-4912-817f-65a9eb54d3ab/stop", {}, example=False)
+ assert response.status == 204
+ assert response.route == "/virtualbox/61d61bdd-aa7d-4912-817f-65a9eb54d3ab/stop"
diff --git a/tests/api/test_vpcs.py b/tests/api/test_vpcs.py
index 9dbbf94c..12244d3f 100644
--- a/tests/api/test_vpcs.py
+++ b/tests/api/test_vpcs.py
@@ -15,55 +15,49 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-from tests.api.base import server, loop
from tests.utils import asyncio_patch
-from gns3server import modules
from unittest.mock import patch
-@asyncio_patch('gns3server.modules.VPCS.create_vm', return_value=84)
+
+@asyncio_patch("gns3server.modules.VPCS.create_vm", return_value="61d61bdd-aa7d-4912-817f-65a9eb54d3ab")
def test_vpcs_create(server):
- response = server.post('/vpcs', {'name': 'PC TEST 1'}, example=False)
+ response = server.post("/vpcs", {"name": "PC TEST 1"}, example=False)
assert response.status == 200
- assert response.route == '/vpcs'
- assert response.json['name'] == 'PC TEST 1'
- assert response.json['id'] == 84
+ assert response.route == "/vpcs"
+ assert response.json["name"] == "PC TEST 1"
+ assert response.json["uuid"] == "61d61bdd-aa7d-4912-817f-65a9eb54d3ab"
+#FIXME
def test_vpcs_nio_create_udp(server):
- vm = server.post('/vpcs', {'name': 'PC TEST 1'})
- response = server.post('/vpcs/{}/ports/0/nio'.format(vm.json["id"]), {
- 'type': 'nio_udp',
- 'lport': 4242,
- 'rport': 4343,
- 'rhost': '127.0.0.1'
- },
- example=True)
+ vm = server.post("/vpcs", {"name": "PC TEST 1"})
+ response = server.post("/vpcs/{}/ports/0/nio".format(vm.json["uuid"]), {"type": "nio_udp",
+ "lport": 4242,
+ "rport": 4343,
+ "rhost": "127.0.0.1"},
+ example=True)
assert response.status == 200
- assert response.route == '/vpcs/{id:\d+}/ports/{port_id}/nio'
- assert response.json['type'] == 'nio_udp'
+ assert response.route == "/vpcs/{uuid}/ports/{port_id}/nio"
+ assert response.json["type"] == "nio_udp"
+
@patch("gns3server.modules.vpcs.vpcs_device.has_privileged_access", return_value=True)
def test_vpcs_nio_create_tap(mock, server):
- vm = server.post('/vpcs', {'name': 'PC TEST 1'})
- response = server.post('/vpcs/{}/ports/0/nio'.format(vm.json["id"]), {
- 'type': 'nio_tap',
- 'tap_device': 'test',
- })
+ vm = server.post("/vpcs", {"name": "PC TEST 1"})
+ response = server.post("/vpcs/{}/ports/0/nio".format(vm.json["uuid"]), {"type": "nio_tap",
+ "tap_device": "test"})
assert response.status == 200
- assert response.route == '/vpcs/{id:\d+}/ports/{port_id}/nio'
- assert response.json['type'] == 'nio_tap'
+ assert response.route == "/vpcs/{uuid}/ports/{port_id}/nio"
+ assert response.json["type"] == "nio_tap"
+
+#FIXME
def test_vpcs_delete_nio(server):
- vm = server.post('/vpcs', {'name': 'PC TEST 1'})
- response = server.post('/vpcs/{}/ports/0/nio'.format(vm.json["id"]), {
- 'type': 'nio_udp',
- 'lport': 4242,
- 'rport': 4343,
- 'rhost': '127.0.0.1'
- },
- )
- response = server.delete('/vpcs/{}/ports/0/nio'.format(vm.json["id"]), example=True)
+ vm = server.post("/vpcs", {"name": "PC TEST 1"})
+ response = server.post("/vpcs/{}/ports/0/nio".format(vm.json["uuid"]), {"type": "nio_udp",
+ "lport": 4242,
+ "rport": 4343,
+ "rhost": "127.0.0.1"})
+ response = server.delete("/vpcs/{}/ports/0/nio".format(vm.json["uuid"]), example=True)
assert response.status == 200
- assert response.route == '/vpcs/{id:\d+}/ports/{port_id}/nio'
-
-
+ assert response.route == "/vpcs/{uuid}/ports/{port_id}/nio"
diff --git a/tests/conftest.py b/tests/conftest.py
index a7ca89ef..3c62baca 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -14,6 +14,6 @@ def server(request):
cwd = os.path.dirname(os.path.abspath(__file__))
server_script = os.path.join(cwd, "../gns3server/main.py")
process = subprocess.Popen([sys.executable, server_script, "--port=8000"])
- time.sleep(1) # give some time for the process to start
+ #time.sleep(1) # give some time for the process to start
request.addfinalizer(process.terminate)
return process