mirror of
https://github.com/GNS3/gns3-server
synced 2024-11-28 11:18:11 +00:00
UUID support for VMs.
Basic VirtualBox support (create, start and stop). Some refactoring for BaseVM class. Updated CURL command in tests.
This commit is contained in:
parent
fe22576ae2
commit
7fff25a9a9
80
gns3server/handlers/virtualbox_handler.py
Normal file
80
gns3server/handlers/virtualbox_handler.py
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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({})
|
@ -22,7 +22,7 @@ from ..schemas.vpcs import VPCS_NIO_SCHEMA
|
|||||||
from ..modules.vpcs import VPCS
|
from ..modules.vpcs import VPCS
|
||||||
|
|
||||||
|
|
||||||
class VPCSHandler(object):
|
class VPCSHandler:
|
||||||
"""
|
"""
|
||||||
API entry points for VPCS.
|
API entry points for VPCS.
|
||||||
"""
|
"""
|
||||||
@ -40,53 +40,56 @@ class VPCSHandler(object):
|
|||||||
def create(request, response):
|
def create(request, response):
|
||||||
|
|
||||||
vpcs = VPCS.instance()
|
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,
|
response.json({"name": vm.name,
|
||||||
"id": vm.id,
|
"uuid": vm.uuid,
|
||||||
"console": vm.console})
|
"console": vm.console})
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@Route.post(
|
@Route.post(
|
||||||
r"/vpcs/{id:\d+}/start",
|
r"/vpcs/{uuid}/start",
|
||||||
parameters={
|
parameters={
|
||||||
"id": "VPCS instance ID"
|
"uuid": "VPCS instance UUID"
|
||||||
},
|
},
|
||||||
status_codes={
|
status_codes={
|
||||||
204: "VPCS instance started",
|
204: "VPCS instance started",
|
||||||
|
400: "Invalid VPCS instance UUID",
|
||||||
404: "VPCS instance doesn't exist"
|
404: "VPCS instance doesn't exist"
|
||||||
},
|
},
|
||||||
description="Start a VPCS instance")
|
description="Start a VPCS instance")
|
||||||
def create(request, response):
|
def create(request, response):
|
||||||
|
|
||||||
vpcs_manager = VPCS.instance()
|
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({})
|
response.json({})
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@Route.post(
|
@Route.post(
|
||||||
r"/vpcs/{id:\d+}/stop",
|
r"/vpcs/{uuid}/stop",
|
||||||
parameters={
|
parameters={
|
||||||
"id": "VPCS instance ID"
|
"uuid": "VPCS instance UUID"
|
||||||
},
|
},
|
||||||
status_codes={
|
status_codes={
|
||||||
204: "VPCS instance stopped",
|
204: "VPCS instance stopped",
|
||||||
|
400: "Invalid VPCS instance UUID",
|
||||||
404: "VPCS instance doesn't exist"
|
404: "VPCS instance doesn't exist"
|
||||||
},
|
},
|
||||||
description="Stop a VPCS instance")
|
description="Stop a VPCS instance")
|
||||||
def create(request, response):
|
def create(request, response):
|
||||||
|
|
||||||
vpcs_manager = VPCS.instance()
|
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({})
|
response.json({})
|
||||||
|
|
||||||
@Route.post(
|
@Route.post(
|
||||||
r"/vpcs/{id:\d+}/ports/{port_id}/nio",
|
r"/vpcs/{uuid}/ports/{port_id}/nio",
|
||||||
parameters={
|
parameters={
|
||||||
"id": "VPCS instance ID",
|
"uuid": "VPCS instance UUID",
|
||||||
"port_id": "Id of the port where the nio should be add"
|
"port_id": "Id of the port where the nio should be add"
|
||||||
},
|
},
|
||||||
status_codes={
|
status_codes={
|
||||||
201: "NIO created",
|
201: "NIO created",
|
||||||
|
400: "Invalid VPCS instance UUID",
|
||||||
404: "VPCS instance doesn't exist"
|
404: "VPCS instance doesn't exist"
|
||||||
},
|
},
|
||||||
description="Add a NIO to a VPCS",
|
description="Add a NIO to a VPCS",
|
||||||
@ -95,26 +98,26 @@ class VPCSHandler(object):
|
|||||||
def create_nio(request, response):
|
def create_nio(request, response):
|
||||||
|
|
||||||
vpcs_manager = VPCS.instance()
|
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)
|
nio = vm.port_add_nio_binding(int(request.match_info["port_id"]), request.json)
|
||||||
|
|
||||||
response.json(nio)
|
response.json(nio)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@Route.delete(
|
@Route.delete(
|
||||||
r"/vpcs/{id:\d+}/ports/{port_id}/nio",
|
r"/vpcs/{uuid}/ports/{port_id}/nio",
|
||||||
parameters={
|
parameters={
|
||||||
"id": "VPCS instance ID",
|
"uuid": "VPCS instance UUID",
|
||||||
"port_id": "Id of the port where the nio should be remove"
|
"port_id": "ID of the port where the nio should be removed"
|
||||||
},
|
},
|
||||||
status_codes={
|
status_codes={
|
||||||
200: "NIO deleted",
|
200: "NIO deleted",
|
||||||
|
400: "Invalid VPCS instance UUID",
|
||||||
404: "VPCS instance doesn't exist"
|
404: "VPCS instance doesn't exist"
|
||||||
},
|
},
|
||||||
description="Remove a NIO from a VPCS")
|
description="Remove a NIO from a VPCS")
|
||||||
def delete_nio(request, response):
|
def delete_nio(request, response):
|
||||||
|
|
||||||
vpcs_manager = VPCS.instance()
|
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"]))
|
nio = vm.port_remove_nio_binding(int(request.match_info["port_id"]))
|
||||||
response.json({})
|
response.json({})
|
||||||
|
@ -17,8 +17,9 @@
|
|||||||
|
|
||||||
import sys
|
import sys
|
||||||
from .vpcs import VPCS
|
from .vpcs import VPCS
|
||||||
|
from .virtualbox import VirtualBox
|
||||||
|
|
||||||
MODULES = [VPCS]
|
MODULES = [VPCS, VirtualBox]
|
||||||
|
|
||||||
#if sys.platform.startswith("linux"):
|
#if sys.platform.startswith("linux"):
|
||||||
# # IOU runs only on Linux
|
# # IOU runs only on Linux
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
|
||||||
from .vm_error import VMError
|
from uuid import UUID, uuid4
|
||||||
|
|
||||||
|
|
||||||
class BaseManager:
|
class BaseManager:
|
||||||
@ -63,44 +63,52 @@ class BaseManager:
|
|||||||
@classmethod
|
@classmethod
|
||||||
@asyncio.coroutine # FIXME: why coroutine?
|
@asyncio.coroutine # FIXME: why coroutine?
|
||||||
def destroy(cls):
|
def destroy(cls):
|
||||||
|
|
||||||
cls._instance = None
|
cls._instance = None
|
||||||
|
|
||||||
def get_vm(self, vm_id):
|
def get_vm(self, uuid):
|
||||||
"""
|
"""
|
||||||
Returns a VM instance.
|
Returns a VM instance.
|
||||||
|
|
||||||
:param vm_id: VM identifier
|
:param uuid: VM UUID
|
||||||
|
|
||||||
:returns: VM instance
|
:returns: VM instance
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if vm_id not in self._vms:
|
try:
|
||||||
raise aiohttp.web.HTTPNotFound(text="ID {} doesn't exist".format(vm_id))
|
UUID(uuid, version=4)
|
||||||
return self._vms[vm_id]
|
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
|
@asyncio.coroutine
|
||||||
def create_vm(self, vmname, identifier=None):
|
def create_vm(self, name, uuid=None):
|
||||||
if not identifier:
|
|
||||||
for i in range(1, 1024):
|
#TODO: support for old projects with normal IDs.
|
||||||
if i not in self._vms:
|
|
||||||
identifier = i
|
#TODO: supports specific args: pass kwargs to VM_CLASS?
|
||||||
break
|
|
||||||
if identifier == 0:
|
if not uuid:
|
||||||
raise VMError("Maximum number of VM instances reached")
|
uuid = str(uuid4())
|
||||||
else:
|
|
||||||
if identifier in self._vms:
|
vm = self._VM_CLASS(name, uuid, self)
|
||||||
raise VMError("VM identifier {} is already used by another VM instance".format(identifier))
|
future = vm.create()
|
||||||
vm = self._VM_CLASS(vmname, identifier, self)
|
if isinstance(future, asyncio.Future):
|
||||||
yield from vm.wait_for_creation()
|
yield from future
|
||||||
self._vms[vm.id] = vm
|
self._vms[vm.uuid] = vm
|
||||||
return vm
|
return vm
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def start_vm(self, vm_id):
|
def start_vm(self, uuid):
|
||||||
vm = self.get_vm(vm_id)
|
|
||||||
|
vm = self.get_vm(uuid)
|
||||||
yield from vm.start()
|
yield from vm.start()
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def stop_vm(self, vm_id):
|
def stop_vm(self, uuid):
|
||||||
vm = self.get_vm(vm_id)
|
|
||||||
|
vm = self.get_vm(uuid)
|
||||||
yield from vm.stop()
|
yield from vm.stop()
|
||||||
|
@ -15,9 +15,6 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
import asyncio
|
|
||||||
from .vm_error import VMError
|
|
||||||
from ..config import Config
|
from ..config import Config
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
@ -26,64 +23,62 @@ log = logging.getLogger(__name__)
|
|||||||
|
|
||||||
class BaseVM:
|
class BaseVM:
|
||||||
|
|
||||||
def __init__(self, name, identifier, manager):
|
def __init__(self, name, uuid, manager):
|
||||||
|
|
||||||
self._name = name
|
self._name = name
|
||||||
self._id = identifier
|
self._uuid = uuid
|
||||||
self._created = asyncio.Future()
|
|
||||||
self._manager = manager
|
self._manager = manager
|
||||||
self._config = Config.instance()
|
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
|
#TODO: When delete release console ports
|
||||||
|
|
||||||
|
|
||||||
@property
|
|
||||||
def id(self):
|
|
||||||
"""
|
|
||||||
Returns the unique ID for this VM.
|
|
||||||
|
|
||||||
:returns: id (integer)
|
|
||||||
"""
|
|
||||||
|
|
||||||
return self._id
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""
|
"""
|
||||||
Returns the name for this VM.
|
Returns the name for this VM.
|
||||||
|
|
||||||
:returns: name (string)
|
:returns: name
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return self._name
|
return self._name
|
||||||
|
|
||||||
@asyncio.coroutine
|
@name.setter
|
||||||
def _execute(self, command):
|
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
|
@property
|
||||||
def _create(self):
|
def uuid(self):
|
||||||
"""
|
"""
|
||||||
Called when the run module is created and ready to receive
|
Returns the UUID for this VM.
|
||||||
commands. It's asynchronous.
|
|
||||||
|
: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._uuid
|
||||||
return self._created
|
|
||||||
|
@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):
|
def start(self):
|
||||||
"""
|
"""
|
||||||
Starts the VM process.
|
Starts the VM process.
|
||||||
@ -91,8 +86,6 @@ class BaseVM:
|
|||||||
|
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
@asyncio.coroutine
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
"""
|
"""
|
||||||
Starts the VM process.
|
Starts the VM process.
|
||||||
|
@ -43,13 +43,23 @@ class Project:
|
|||||||
self._path = os.path.join(self._location, self._uuid)
|
self._path = os.path.join(self._location, self._uuid)
|
||||||
if os.path.exists(self._path) is False:
|
if os.path.exists(self._path) is False:
|
||||||
os.mkdir(self._path)
|
os.mkdir(self._path)
|
||||||
os.mkdir(os.path.join(self._path, 'files'))
|
os.mkdir(os.path.join(self._path, "files"))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def uuid(self):
|
def uuid(self):
|
||||||
|
|
||||||
return self._uuid
|
return self._uuid
|
||||||
|
|
||||||
|
@property
|
||||||
|
def location(self):
|
||||||
|
|
||||||
|
return self._location
|
||||||
|
|
||||||
|
@property
|
||||||
|
def path(self):
|
||||||
|
|
||||||
|
return self._path
|
||||||
|
|
||||||
def __json__(self):
|
def __json__(self):
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -28,11 +28,13 @@ import tempfile
|
|||||||
import json
|
import json
|
||||||
import socket
|
import socket
|
||||||
import time
|
import time
|
||||||
|
import asyncio
|
||||||
|
|
||||||
from .virtualbox_error import VirtualBoxError
|
from .virtualbox_error import VirtualBoxError
|
||||||
from ..adapters.ethernet_adapter import EthernetAdapter
|
from ..adapters.ethernet_adapter import EthernetAdapter
|
||||||
from ..attic import find_unused_port
|
from ..attic import find_unused_port
|
||||||
from .telnet_server import TelnetServer
|
from .telnet_server import TelnetServer
|
||||||
|
from ..base_vm import BaseVM
|
||||||
|
|
||||||
if sys.platform.startswith('win'):
|
if sys.platform.startswith('win'):
|
||||||
import msvcrt
|
import msvcrt
|
||||||
@ -42,55 +44,32 @@ import logging
|
|||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class VirtualBoxVM(object):
|
class VirtualBoxVM(BaseVM):
|
||||||
"""
|
"""
|
||||||
VirtualBox VM implementation.
|
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 = []
|
_instances = []
|
||||||
_allocated_console_ports = []
|
_allocated_console_ports = []
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self, name, uuid, manager):
|
||||||
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):
|
|
||||||
|
|
||||||
if not vbox_id:
|
super().__init__(name, uuid, manager)
|
||||||
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:
|
self._system_properties = {}
|
||||||
raise VirtualBoxError("Maximum number of VirtualBox VM instances reached")
|
|
||||||
|
#FIXME: harcoded values
|
||||||
|
if sys.platform.startswith("win"):
|
||||||
|
self._vboxmanage_path = r"C:\Program Files\Oracle\VirtualBox\VBoxManage.exe"
|
||||||
else:
|
else:
|
||||||
if vbox_id in self._instances:
|
self._vboxmanage_path = "/usr/bin/vboxmanage"
|
||||||
raise VirtualBoxError("VirtualBox identifier {} is already used by another VirtualBox VM instance".format(vbox_id))
|
|
||||||
self._id = vbox_id
|
self._queue = asyncio.Queue()
|
||||||
self._instances.append(self._id)
|
self._created = asyncio.Future()
|
||||||
|
self._worker = asyncio.async(self._run())
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
self._name = name
|
|
||||||
self._linked_clone = linked_clone
|
self._linked_clone = linked_clone
|
||||||
self._working_dir = None
|
self._working_dir = None
|
||||||
self._command = []
|
self._command = []
|
||||||
@ -158,6 +137,82 @@ class VirtualBoxVM(object):
|
|||||||
log.info("VirtualBox VM {name} [id={id}] has been created".format(name=self._name,
|
log.info("VirtualBox VM {name} [id={id}] has been created".format(name=self._name,
|
||||||
id=self._id))
|
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):
|
def defaults(self):
|
||||||
"""
|
"""
|
||||||
Returns all the default attribute values for this VirtualBox VM.
|
Returns all the default attribute values for this VirtualBox VM.
|
||||||
@ -176,49 +231,6 @@ class VirtualBoxVM(object):
|
|||||||
|
|
||||||
return vbox_defaults
|
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
|
@property
|
||||||
def working_dir(self):
|
def working_dir(self):
|
||||||
"""
|
"""
|
||||||
@ -540,7 +552,7 @@ class VirtualBoxVM(object):
|
|||||||
id=self._id,
|
id=self._id,
|
||||||
adapter_type=adapter_type))
|
adapter_type=adapter_type))
|
||||||
|
|
||||||
def _execute(self, subcommand, args, timeout=60):
|
def _old_execute(self, subcommand, args, timeout=60):
|
||||||
"""
|
"""
|
||||||
Executes a command with VBoxManage.
|
Executes a command with VBoxManage.
|
||||||
|
|
||||||
@ -831,7 +843,7 @@ class VirtualBoxVM(object):
|
|||||||
self._serial_pipe.close()
|
self._serial_pipe.close()
|
||||||
self._serial_pipe = None
|
self._serial_pipe = None
|
||||||
|
|
||||||
def start(self):
|
def old_start(self):
|
||||||
"""
|
"""
|
||||||
Starts this VirtualBox VM.
|
Starts this VirtualBox VM.
|
||||||
"""
|
"""
|
||||||
@ -864,7 +876,7 @@ class VirtualBoxVM(object):
|
|||||||
if self._enable_remote_console:
|
if self._enable_remote_console:
|
||||||
self._start_remote_console()
|
self._start_remote_console()
|
||||||
|
|
||||||
def stop(self):
|
def old_stop(self):
|
||||||
"""
|
"""
|
||||||
Stops this VirtualBox VM.
|
Stops this VirtualBox VM.
|
||||||
"""
|
"""
|
||||||
|
@ -47,14 +47,14 @@ class VPCSDevice(BaseVM):
|
|||||||
VPCS device implementation.
|
VPCS device implementation.
|
||||||
|
|
||||||
:param name: name of this VPCS device
|
:param name: name of this VPCS device
|
||||||
:param vpcs_id: VPCS instance ID
|
:param uuid: VPCS instance UUID
|
||||||
:param manager: parent VM Manager
|
:param manager: parent VM Manager
|
||||||
:param working_dir: path to a working directory
|
:param working_dir: path to a working directory
|
||||||
:param console: TCP console port
|
: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
|
# TODO: Hardcodded for testing
|
||||||
#self._working_dir = working_dir
|
#self._working_dir = working_dir
|
||||||
@ -120,17 +120,8 @@ class VPCSDevice(BaseVM):
|
|||||||
|
|
||||||
return self._console
|
return self._console
|
||||||
|
|
||||||
@property
|
#FIXME: correct way to subclass a property?
|
||||||
def name(self):
|
@BaseVM.name.setter
|
||||||
"""
|
|
||||||
Returns the name of this VPCS device.
|
|
||||||
|
|
||||||
:returns: name
|
|
||||||
"""
|
|
||||||
|
|
||||||
return self._name
|
|
||||||
|
|
||||||
@name.setter
|
|
||||||
def name(self, new_name):
|
def name(self, new_name):
|
||||||
"""
|
"""
|
||||||
Sets the name of this VPCS device.
|
Sets the name of this VPCS device.
|
||||||
@ -151,10 +142,10 @@ class VPCSDevice(BaseVM):
|
|||||||
except OSError as e:
|
except OSError as e:
|
||||||
raise VPCSError("Could not amend the configuration {}: {}".format(config_path, 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,
|
log.info("VPCS {name} [{uuid}]: renamed to {new_name}".format(name=self._name,
|
||||||
id=self._id,
|
uuid=self.uuid,
|
||||||
new_name=new_name))
|
new_name=new_name))
|
||||||
self._name = new_name
|
BaseVM.name = new_name
|
||||||
|
|
||||||
def _check_vpcs_version(self):
|
def _check_vpcs_version(self):
|
||||||
"""
|
"""
|
||||||
@ -197,7 +188,7 @@ class VPCSDevice(BaseVM):
|
|||||||
stderr=subprocess.STDOUT,
|
stderr=subprocess.STDOUT,
|
||||||
cwd=self._working_dir,
|
cwd=self._working_dir,
|
||||||
creationflags=flags)
|
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
|
self._started = True
|
||||||
except (OSError, subprocess.SubprocessError) as e:
|
except (OSError, subprocess.SubprocessError) as e:
|
||||||
vpcs_stdout = self.read_vpcs_stdout()
|
vpcs_stdout = self.read_vpcs_stdout()
|
||||||
@ -212,7 +203,7 @@ class VPCSDevice(BaseVM):
|
|||||||
|
|
||||||
# stop the VPCS process
|
# stop the VPCS process
|
||||||
if self.is_running():
|
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"):
|
if sys.platform.startswith("win32"):
|
||||||
self._process.send_signal(signal.CTRL_BREAK_EVENT)
|
self._process.send_signal(signal.CTRL_BREAK_EVENT)
|
||||||
else:
|
else:
|
||||||
@ -283,8 +274,8 @@ class VPCSDevice(BaseVM):
|
|||||||
|
|
||||||
|
|
||||||
self._ethernet_adapter.add_nio(port_id, nio)
|
self._ethernet_adapter.add_nio(port_id, nio)
|
||||||
log.info("VPCS {name} [id={id}]: {nio} added to port {port_id}".format(name=self._name,
|
log.info("VPCS {name} {uuid}]: {nio} added to port {port_id}".format(name=self._name,
|
||||||
id=self._id,
|
uuid=self.uuid,
|
||||||
nio=nio,
|
nio=nio,
|
||||||
port_id=port_id))
|
port_id=port_id))
|
||||||
return nio
|
return nio
|
||||||
@ -304,8 +295,8 @@ class VPCSDevice(BaseVM):
|
|||||||
|
|
||||||
nio = self._ethernet_adapter.get_nio(port_id)
|
nio = self._ethernet_adapter.get_nio(port_id)
|
||||||
self._ethernet_adapter.remove_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,
|
log.info("VPCS {name} [{uuid}]: {nio} removed from port {port_id}".format(name=self._name,
|
||||||
id=self._id,
|
uuid=self.uuid,
|
||||||
nio=nio,
|
nio=nio,
|
||||||
port_id=port_id))
|
port_id=port_id))
|
||||||
return nio
|
return nio
|
||||||
@ -364,7 +355,8 @@ class VPCSDevice(BaseVM):
|
|||||||
command.extend(["-e"])
|
command.extend(["-e"])
|
||||||
command.extend(["-d", nio.tap_device])
|
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(["-i", "1"]) # option to start only one VPC instance
|
||||||
command.extend(["-F"]) # option to avoid the daemonization of VPCS
|
command.extend(["-F"]) # option to avoid the daemonization of VPCS
|
||||||
if self._script_file:
|
if self._script_file:
|
||||||
@ -390,6 +382,6 @@ class VPCSDevice(BaseVM):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
self._script_file = script_file
|
self._script_file = script_file
|
||||||
log.info("VPCS {name} [id={id}]: script_file set to {config}".format(name=self._name,
|
log.info("VPCS {name} [{uuid}]: script_file set to {config}".format(name=self._name,
|
||||||
id=self._id,
|
uuid=self.uuid,
|
||||||
config=self._script_file))
|
config=self._script_file))
|
||||||
|
@ -36,69 +36,37 @@ VBOX_CREATE_SCHEMA = {
|
|||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"vbox_id": {
|
"vbox_id": {
|
||||||
"description": "VirtualBox VM instance ID",
|
"description": "VirtualBox VM instance ID (for project created before GNS3 1.3)",
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"console": {
|
"uuid": {
|
||||||
"description": "console TCP port",
|
"description": "VirtualBox VM instance UUID",
|
||||||
"minimum": 1,
|
"type": "string",
|
||||||
"maximum": 65535,
|
"minLength": 36,
|
||||||
"type": "integer"
|
"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,
|
"additionalProperties": False,
|
||||||
"required": ["name", "vmname"],
|
"required": ["name", "vmname"],
|
||||||
}
|
}
|
||||||
|
|
||||||
VBOX_DELETE_SCHEMA = {
|
VBOX_OBJECT_SCHEMA = {
|
||||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
"description": "Request validation to delete a VirtualBox VM instance",
|
"description": "VirtualBox VM instance",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"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": {
|
"name": {
|
||||||
"description": "VirtualBox VM instance name",
|
"description": "VirtualBox VM instance name",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
},
|
},
|
||||||
"vmname": {
|
"uuid": {
|
||||||
"description": "VirtualBox VM name (in VirtualBox itself)",
|
"description": "VirtualBox VM instance UUID",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"minLength": 1,
|
"minLength": 36,
|
||||||
},
|
"maxLength": 36,
|
||||||
"adapters": {
|
"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}$"
|
||||||
"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,
|
|
||||||
},
|
},
|
||||||
"console": {
|
"console": {
|
||||||
"description": "console TCP port",
|
"description": "console TCP port",
|
||||||
@ -106,327 +74,8 @@ VBOX_UPDATE_SCHEMA = {
|
|||||||
"maximum": 65535,
|
"maximum": 65535,
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"enable_remote_console": {
|
|
||||||
"description": "enable the remote console",
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"headless": {
|
|
||||||
"description": "headless mode",
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"additionalProperties": False,
|
"additionalProperties": False,
|
||||||
"required": ["id"]
|
"required": ["name", "uuid"]
|
||||||
}
|
|
||||||
|
|
||||||
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"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,8 +26,8 @@ VPCS_CREATE_SCHEMA = {
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
},
|
},
|
||||||
"id": {
|
"vpcs_id": {
|
||||||
"description": "VPCS device instance ID",
|
"description": "VPCS device instance ID (for project created before GNS3 1.3)",
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"uuid": {
|
"uuid": {
|
||||||
@ -117,9 +117,12 @@ VPCS_OBJECT_SCHEMA = {
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
},
|
},
|
||||||
"id": {
|
"uuid": {
|
||||||
"description": "VPCS device instance ID",
|
"description": "VPCS device UUID",
|
||||||
"type": "integer"
|
"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": {
|
"console": {
|
||||||
"description": "console TCP port",
|
"description": "console TCP port",
|
||||||
@ -129,6 +132,6 @@ VPCS_OBJECT_SCHEMA = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
"additionalProperties": False,
|
"additionalProperties": False,
|
||||||
"required": ["name", "id", "console"]
|
"required": ["name", "uuid", "console"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
#TODO: get rid of * have something generic to automatically import handlers so the routes can be found
|
||||||
from gns3server.handlers import *
|
from gns3server.handlers import *
|
||||||
|
from gns3server.handlers.virtualbox_handler import VirtualBoxHandler
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
@ -25,6 +25,7 @@ import pytest
|
|||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
|
||||||
|
|
||||||
from gns3server.web.route import Route
|
from gns3server.web.route import Route
|
||||||
#TODO: get rid of *
|
#TODO: get rid of *
|
||||||
from gns3server.handlers import *
|
from gns3server.handlers import *
|
||||||
@ -95,7 +96,7 @@ class Query:
|
|||||||
if path is None:
|
if path is None:
|
||||||
return
|
return
|
||||||
with open(self._example_file_path(method, path), 'w+') as f:
|
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:
|
if body:
|
||||||
f.write(" -d '{}'".format(re.sub(r"\n", "", json.dumps(json.loads(body), sort_keys=True))))
|
f.write(" -d '{}'".format(re.sub(r"\n", "", json.dumps(json.loads(body), sort_keys=True))))
|
||||||
f.write("\n\n")
|
f.write("\n\n")
|
||||||
@ -116,7 +117,7 @@ class Query:
|
|||||||
|
|
||||||
def _example_file_path(self, method, path):
|
def _example_file_path(self, method, path):
|
||||||
path = re.sub('[^a-z0-9]', '', 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():
|
def _get_unused_port():
|
||||||
|
41
tests/api/test_virtualbox.py
Normal file
41
tests/api/test_virtualbox.py
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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"
|
@ -15,55 +15,49 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from tests.api.base import server, loop
|
|
||||||
from tests.utils import asyncio_patch
|
from tests.utils import asyncio_patch
|
||||||
from gns3server import modules
|
|
||||||
from unittest.mock import patch
|
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):
|
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.status == 200
|
||||||
assert response.route == '/vpcs'
|
assert response.route == "/vpcs"
|
||||||
assert response.json['name'] == 'PC TEST 1'
|
assert response.json["name"] == "PC TEST 1"
|
||||||
assert response.json['id'] == 84
|
assert response.json["uuid"] == "61d61bdd-aa7d-4912-817f-65a9eb54d3ab"
|
||||||
|
|
||||||
|
|
||||||
|
#FIXME
|
||||||
def test_vpcs_nio_create_udp(server):
|
def test_vpcs_nio_create_udp(server):
|
||||||
vm = server.post('/vpcs', {'name': 'PC TEST 1'})
|
vm = server.post("/vpcs", {"name": "PC TEST 1"})
|
||||||
response = server.post('/vpcs/{}/ports/0/nio'.format(vm.json["id"]), {
|
response = server.post("/vpcs/{}/ports/0/nio".format(vm.json["uuid"]), {"type": "nio_udp",
|
||||||
'type': 'nio_udp',
|
"lport": 4242,
|
||||||
'lport': 4242,
|
"rport": 4343,
|
||||||
'rport': 4343,
|
"rhost": "127.0.0.1"},
|
||||||
'rhost': '127.0.0.1'
|
|
||||||
},
|
|
||||||
example=True)
|
example=True)
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
assert response.route == '/vpcs/{id:\d+}/ports/{port_id}/nio'
|
assert response.route == "/vpcs/{uuid}/ports/{port_id}/nio"
|
||||||
assert response.json['type'] == 'nio_udp'
|
assert response.json["type"] == "nio_udp"
|
||||||
|
|
||||||
|
|
||||||
@patch("gns3server.modules.vpcs.vpcs_device.has_privileged_access", return_value=True)
|
@patch("gns3server.modules.vpcs.vpcs_device.has_privileged_access", return_value=True)
|
||||||
def test_vpcs_nio_create_tap(mock, server):
|
def test_vpcs_nio_create_tap(mock, server):
|
||||||
vm = server.post('/vpcs', {'name': 'PC TEST 1'})
|
vm = server.post("/vpcs", {"name": "PC TEST 1"})
|
||||||
response = server.post('/vpcs/{}/ports/0/nio'.format(vm.json["id"]), {
|
response = server.post("/vpcs/{}/ports/0/nio".format(vm.json["uuid"]), {"type": "nio_tap",
|
||||||
'type': 'nio_tap',
|
"tap_device": "test"})
|
||||||
'tap_device': 'test',
|
|
||||||
})
|
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
assert response.route == '/vpcs/{id:\d+}/ports/{port_id}/nio'
|
assert response.route == "/vpcs/{uuid}/ports/{port_id}/nio"
|
||||||
assert response.json['type'] == 'nio_tap'
|
assert response.json["type"] == "nio_tap"
|
||||||
|
|
||||||
|
|
||||||
|
#FIXME
|
||||||
def test_vpcs_delete_nio(server):
|
def test_vpcs_delete_nio(server):
|
||||||
vm = server.post('/vpcs', {'name': 'PC TEST 1'})
|
vm = server.post("/vpcs", {"name": "PC TEST 1"})
|
||||||
response = server.post('/vpcs/{}/ports/0/nio'.format(vm.json["id"]), {
|
response = server.post("/vpcs/{}/ports/0/nio".format(vm.json["uuid"]), {"type": "nio_udp",
|
||||||
'type': 'nio_udp',
|
"lport": 4242,
|
||||||
'lport': 4242,
|
"rport": 4343,
|
||||||
'rport': 4343,
|
"rhost": "127.0.0.1"})
|
||||||
'rhost': '127.0.0.1'
|
response = server.delete("/vpcs/{}/ports/0/nio".format(vm.json["uuid"]), example=True)
|
||||||
},
|
|
||||||
)
|
|
||||||
response = server.delete('/vpcs/{}/ports/0/nio'.format(vm.json["id"]), example=True)
|
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
assert response.route == '/vpcs/{id:\d+}/ports/{port_id}/nio'
|
assert response.route == "/vpcs/{uuid}/ports/{port_id}/nio"
|
||||||
|
|
||||||
|
|
||||||
|
@ -14,6 +14,6 @@ def server(request):
|
|||||||
cwd = os.path.dirname(os.path.abspath(__file__))
|
cwd = os.path.dirname(os.path.abspath(__file__))
|
||||||
server_script = os.path.join(cwd, "../gns3server/main.py")
|
server_script = os.path.join(cwd, "../gns3server/main.py")
|
||||||
process = subprocess.Popen([sys.executable, server_script, "--port=8000"])
|
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)
|
request.addfinalizer(process.terminate)
|
||||||
return process
|
return process
|
||||||
|
Loading…
Reference in New Issue
Block a user