1
0
mirror of https://github.com/GNS3/gns3-server synced 2024-12-03 13:48:11 +00:00

Pass *args to VM_CLASS.

Move Config the the base manager.
More checks for projects (UUID,  makedirs).
Return error 500 when a VMError exception is raised.
Some more progress to VirtualBox.
This commit is contained in:
Jeremy 2015-01-20 19:02:22 -07:00
parent 3530b85b56
commit 7a19c9062e
11 changed files with 95 additions and 82 deletions

View File

@ -19,5 +19,5 @@ X-ROUTE: /virtualbox
{ {
"name": "VM1", "name": "VM1",
"project_uuid": "a1e920ca-338a-4e9f-b363-aa607b09dd80", "project_uuid": "a1e920ca-338a-4e9f-b363-aa607b09dd80",
"uuid": "a028124a-9a69-4b06-b673-21f7eb3d034f" "uuid": "3142e932-d316-40d7-bed3-7ef8e2d313b3"
} }

View File

@ -32,6 +32,7 @@ class VirtualBoxHandler:
r"/virtualbox", r"/virtualbox",
status_codes={ status_codes={
201: "VirtualBox VM instance created", 201: "VirtualBox VM instance created",
400: "Invalid project UUID",
409: "Conflict" 409: "Conflict"
}, },
description="Create a new VirtualBox VM instance", description="Create a new VirtualBox VM instance",
@ -40,8 +41,10 @@ class VirtualBoxHandler:
def create(request, response): def create(request, response):
vbox_manager = VirtualBox.instance() vbox_manager = VirtualBox.instance()
vm = yield from vbox_manager.create_vm(request.json["name"], request.json["project_uuid"], request.json.get("uuid")) vm = yield from vbox_manager.create_vm(request.json["name"],
print(vm) request.json["project_uuid"],
request.json.get("uuid"),
vmname=request.json["vmname"])
response.json({"name": vm.name, response.json({"name": vm.name,
"uuid": vm.uuid, "uuid": vm.uuid,
"project_uuid": vm.project.uuid}) "project_uuid": vm.project.uuid})

View File

@ -33,6 +33,7 @@ class VPCSHandler:
r"/vpcs", r"/vpcs",
status_codes={ status_codes={
201: "VPCS instance created", 201: "VPCS instance created",
400: "Invalid project UUID",
409: "Conflict" 409: "Conflict"
}, },
description="Create a new VPCS instance", description="Create a new VPCS instance",
@ -43,7 +44,7 @@ class VPCSHandler:
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["project_uuid"], request.json["project_uuid"],
uuid=request.json.get("uuid"), request.json.get("uuid"),
console=request.json.get("console"), console=request.json.get("console"),
script_file=request.json.get("script_file")) script_file=request.json.get("script_file"))
response.json(vm) response.json(vm)

View File

@ -20,6 +20,7 @@ import asyncio
import aiohttp import aiohttp
from uuid import UUID, uuid4 from uuid import UUID, uuid4
from ..config import Config
from .project_manager import ProjectManager from .project_manager import ProjectManager
@ -34,6 +35,7 @@ class BaseManager:
self._vms = {} self._vms = {}
self._port_manager = None self._port_manager = None
self._config = Config.instance()
@classmethod @classmethod
def instance(cls): def instance(cls):
@ -50,7 +52,7 @@ class BaseManager:
@property @property
def port_manager(self): def port_manager(self):
""" """
Returns the port_manager for this VMs Returns the port manager.
:returns: Port manager :returns: Port manager
""" """
@ -62,6 +64,16 @@ class BaseManager:
self._port_manager = new_port_manager self._port_manager = new_port_manager
@property
def config(self):
"""
Returns the server config.
:returns: Config
"""
return self._config
@classmethod @classmethod
@asyncio.coroutine # FIXME: why coroutine? @asyncio.coroutine # FIXME: why coroutine?
def destroy(cls): def destroy(cls):
@ -87,25 +99,23 @@ class BaseManager:
return self._vms[uuid] return self._vms[uuid]
@asyncio.coroutine @asyncio.coroutine
def create_vm(self, name, project_identifier, uuid=None, **kwargs): def create_vm(self, name, project_uuid, uuid, *args, **kwargs):
""" """
Create a new VM Create a new VM
:param name VM name :param name: VM name
:param project_identifier UUID of Project :param project_uuid: UUID of Project
:param uuid Force UUID force VM :param uuid: restore a VM UUID
""" """
project = ProjectManager.instance().get_project(project_identifier) project = ProjectManager.instance().get_project(project_uuid)
# TODO: support for old projects VM with normal IDs. # TODO: support for old projects VM with normal IDs.
# TODO: supports specific args: pass kwargs to VM_CLASS?
if not uuid: if not uuid:
uuid = str(uuid4()) uuid = str(uuid4())
vm = self._VM_CLASS(name, uuid, project, self, **kwargs) vm = self._VM_CLASS(name, uuid, project, self, *args, **kwargs)
future = vm.create() future = vm.create()
if isinstance(future, asyncio.Future): if isinstance(future, asyncio.Future):
yield from future yield from future

View File

@ -15,7 +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/>.
from ..config import Config
import logging import logging
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -29,13 +28,17 @@ class BaseVM:
self._uuid = uuid self._uuid = uuid
self._project = project self._project = project
self._manager = manager self._manager = manager
self._config = Config.instance()
# TODO: When delete release console ports # TODO: When delete release console ports
@property @property
def project(self): def project(self):
"""Return VM current project""" """
Returns the VM current project.
:returns: Project instance.
"""
return self._project return self._project
@property @property

View File

@ -147,7 +147,7 @@ class PortManager:
else: else:
continue continue
raise HTTPConflict(reason="Could not find a free port between {} and {} on host {}, last exception: {}".format(start_port, raise HTTPConflict(text="Could not find a free port between {} and {} on host {}, last exception: {}".format(start_port,
end_port, end_port,
host, host,
last_exception)) last_exception))
@ -174,7 +174,7 @@ class PortManager:
""" """
if port in self._used_tcp_ports: if port in self._used_tcp_ports:
raise HTTPConflict(reason="TCP port already {} in use on host".format(port, self._console_host)) raise HTTPConflict(text="TCP port already {} in use on host".format(port, self._console_host))
self._used_tcp_ports.add(port) self._used_tcp_ports.add(port)
return port return port
@ -209,7 +209,7 @@ class PortManager:
""" """
if port in self._used_udp_ports: if port in self._used_udp_ports:
raise Exception("UDP port already {} in use on host".format(port, self._host)) raise HTTPConflict(text="UDP port already {} in use on host".format(port, self._console_host))
self._used_udp_ports.add(port) self._used_udp_ports.add(port)
def release_udp_port(self, port): def release_udp_port(self, port):

View File

@ -15,13 +15,13 @@
# 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 aiohttp
import os import os
import tempfile import tempfile
from uuid import uuid4 from uuid import UUID, uuid4
class Project: class Project:
""" """
A project contains a list of VM. A project contains a list of VM.
In theory VM are isolated project/project. In theory VM are isolated project/project.
@ -35,17 +35,23 @@ class Project:
if uuid is None: if uuid is None:
self._uuid = str(uuid4()) self._uuid = str(uuid4())
else: else:
assert len(uuid) == 36 try:
UUID(uuid, version=4)
except ValueError:
raise aiohttp.web.HTTPBadRequest(text="{} is not a valid UUID".format(uuid))
self._uuid = uuid self._uuid = uuid
self._location = location self._location = location
if location is None: if location is None:
self._location = tempfile.mkdtemp() self._location = tempfile.mkdtemp()
self._path = os.path.join(self._location, self._uuid) self._path = os.path.join(self._location, self._uuid, "vms")
if os.path.exists(self._path) is False: try:
os.mkdir(self._path) os.makedirs(self._path)
os.mkdir(os.path.join(self._path, "vms")) except FileExistsError:
pass
except OSError as e:
raise aiohttp.web.HTTPInternalServerError(text="Could not create project directory: {}".format(e))
@property @property
def uuid(self): def uuid(self):
@ -62,17 +68,21 @@ class Project:
return self._path return self._path
def vm_working_directory(self, vm_identifier): def vm_working_directory(self, vm_uuid):
""" """
Return a working directory for a specific VM. Return a working directory for a specific VM.
If the directory doesn't exist, the directory is created. If the directory doesn't exist, the directory is created.
:param vm_identifier: UUID of VM :param vm_uuid: VM UUID
""" """
path = os.path.join(self._path, 'vms', vm_identifier) path = os.path.join(self._path, "vms", vm_uuid)
if os.path.exists(path) is False: try:
os.mkdir(path) os.makedirs(self._path)
except FileExistsError:
pass
except OSError as e:
raise aiohttp.web.HTTPInternalServerError(text="Could not create VM working directory: {}".format(e))
return path return path
def __json__(self): def __json__(self):

View File

@ -17,6 +17,7 @@
import aiohttp import aiohttp
from .project import Project from .project import Project
from uuid import UUID
class ProjectManager: class ProjectManager:
@ -40,20 +41,23 @@ class ProjectManager:
cls._instance = cls() cls._instance = cls()
return cls._instance return cls._instance
def get_project(self, project_id): def get_project(self, project_uuid):
""" """
Returns a Project instance. Returns a Project instance.
:param project_id: Project identifier :param project_uuid: Project UUID
:returns: Project instance :returns: Project instance
""" """
assert len(project_id) == 36 try:
UUID(project_uuid, version=4)
except ValueError:
raise aiohttp.web.HTTPBadRequest(text="{} is not a valid UUID".format(project_uuid))
if project_id not in self._projects: if project_uuid not in self._projects:
raise aiohttp.web.HTTPNotFound(text="Project UUID {} doesn't exist".format(project_id)) raise aiohttp.web.HTTPNotFound(text="Project UUID {} doesn't exist".format(project_uuid))
return self._projects[project_id] return self._projects[project_uuid]
def create_project(self, **kwargs): def create_project(self, **kwargs):
""" """

View File

@ -29,6 +29,7 @@ import json
import socket import socket
import time import time
import asyncio import asyncio
import shutil
from .virtualbox_error import VirtualBoxError from .virtualbox_error import VirtualBoxError
from ..adapters.ethernet_adapter import EthernetAdapter from ..adapters.ethernet_adapter import EthernetAdapter
@ -45,41 +46,44 @@ log = logging.getLogger(__name__)
class VirtualBoxVM(BaseVM): class VirtualBoxVM(BaseVM):
""" """
VirtualBox VM implementation. VirtualBox VM implementation.
""" """
_instances = [] def __init__(self, name, uuid, project, manager, vmname, linked_clone):
_allocated_console_ports = []
def __init__(self, name, uuid, project, manager):
super().__init__(name, uuid, project, manager) super().__init__(name, uuid, project, manager)
# look for VBoxManage
self._vboxmanage_path = manager.config.get_section_config("VirtualBox").get("vboxmanage_path")
if not self._vboxmanage_path:
if sys.platform.startswith("win"):
if "VBOX_INSTALL_PATH" in os.environ:
self._vboxmanage_path = os.path.join(os.environ["VBOX_INSTALL_PATH"], "VBoxManage.exe")
elif "VBOX_MSI_INSTALL_PATH" in os.environ:
self._vboxmanage_path = os.path.join(os.environ["VBOX_MSI_INSTALL_PATH"], "VBoxManage.exe")
elif sys.platform.startswith("darwin"):
self._vboxmanage_path = "/Applications/VirtualBox.app/Contents/MacOS/VBoxManage"
else:
self._vboxmanage_path = shutil.which("vboxmanage")
if not self._vboxmanage_path:
raise VirtualBoxError("Could not find VBoxManage")
if not os.access(self._vboxmanage_path, os.X_OK):
raise VirtualBoxError("VBoxManage is not executable")
self._vmname = vmname
self._started = False
self._linked_clone = linked_clone
self._system_properties = {} self._system_properties = {}
# FIXME: harcoded values
if sys.platform.startswith("win"):
self._vboxmanage_path = r"C:\Program Files\Oracle\VirtualBox\VBoxManage.exe"
else:
self._vboxmanage_path = "/usr/bin/vboxmanage"
self._queue = asyncio.Queue() self._queue = asyncio.Queue()
self._created = asyncio.Future() self._created = asyncio.Future()
self._worker = asyncio.async(self._run()) self._worker = asyncio.async(self._run())
return return
self._linked_clone = linked_clone
self._working_dir = None
self._command = [] self._command = []
self._vboxmanage_path = vboxmanage_path
self._vbox_user = vbox_user self._vbox_user = vbox_user
self._started = False
self._console_host = console_host
self._console_start_port_range = console_start_port_range
self._console_end_port_range = console_end_port_range
self._telnet_server_thread = None self._telnet_server_thread = None
self._serial_pipe = None self._serial_pipe = None
@ -101,28 +105,6 @@ class VirtualBoxVM(BaseVM):
# create the device own working directory # create the device own working directory
self.working_dir = working_dir_path self.working_dir = working_dir_path
if not self._console:
# allocate a console port
try:
self._console = find_unused_port(self._console_start_port_range,
self._console_end_port_range,
self._console_host,
ignore_ports=self._allocated_console_ports)
except Exception as e:
raise VirtualBoxError(e)
if self._console in self._allocated_console_ports:
raise VirtualBoxError("Console port {} is already used by another VirtualBox VM".format(console))
self._allocated_console_ports.append(self._console)
self._system_properties = {}
properties = self._execute("list", ["systemproperties"])
for prop in properties:
try:
name, value = prop.split(':', 1)
except ValueError:
continue
self._system_properties[name.strip()] = value.strip()
if linked_clone: if linked_clone:
if vbox_id and os.path.isdir(os.path.join(self.working_dir, self._vmname)): if vbox_id and os.path.isdir(os.path.join(self.working_dir, self._vmname)):

View File

@ -16,7 +16,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
""" """
VPCS vm management (creates command line, processes, files etc.) in VPCS VM management (creates command line, processes, files etc.) in
order to run an VPCS instance. order to run an VPCS instance.
""" """
@ -59,7 +59,7 @@ class VPCSVM(BaseVM):
super().__init__(name, uuid, project, manager) super().__init__(name, uuid, project, manager)
self._path = self._config.get_section_config("VPCS").get("path", "vpcs") self._path = manager.config.get_section_config("VPCS").get("path", "vpcs")
self._console = console self._console = console

View File

@ -111,8 +111,8 @@ class Route(object):
response.json({"message": e.text, "status": e.status}) response.json({"message": e.text, "status": e.status})
except VMError as e: except VMError as e:
response = Response(route=route) response = Response(route=route)
response.set_status(400) response.set_status(500)
response.json({"message": str(e), "status": 400}) response.json({"message": str(e), "status": 500})
return response return response
cls._routes.append((method, cls._path, control_schema)) cls._routes.append((method, cls._path, control_schema))