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.
pull/100/head
Jeremy 9 years ago
parent 3530b85b56
commit 7a19c9062e

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

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

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

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

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

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

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

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

@ -29,6 +29,7 @@ import json
import socket
import time
import asyncio
import shutil
from .virtualbox_error import VirtualBoxError
from ..adapters.ethernet_adapter import EthernetAdapter
@ -45,41 +46,44 @@ log = logging.getLogger(__name__)
class VirtualBoxVM(BaseVM):
"""
VirtualBox VM implementation.
"""
_instances = []
_allocated_console_ports = []
def __init__(self, name, uuid, project, manager):
def __init__(self, name, uuid, project, manager, vmname, linked_clone):
super().__init__(name, uuid, project, manager)
self._system_properties = {}
# 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")
# 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"
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._queue = asyncio.Queue()
self._created = asyncio.Future()
self._worker = asyncio.async(self._run())
return
self._linked_clone = linked_clone
self._working_dir = None
self._command = []
self._vboxmanage_path = vboxmanage_path
self._vbox_user = vbox_user
self._started = False
self._console_host = console_host
self._console_start_port_range = console_start_port_range
self._console_end_port_range = console_end_port_range
self._telnet_server_thread = None
self._serial_pipe = None
@ -101,28 +105,6 @@ class VirtualBoxVM(BaseVM):
# create the device own working directory
self.working_dir = working_dir_path
if not self._console:
# allocate a console port
try:
self._console = find_unused_port(self._console_start_port_range,
self._console_end_port_range,
self._console_host,
ignore_ports=self._allocated_console_ports)
except Exception as e:
raise VirtualBoxError(e)
if self._console in self._allocated_console_ports:
raise VirtualBoxError("Console port {} is already used by another VirtualBox VM".format(console))
self._allocated_console_ports.append(self._console)
self._system_properties = {}
properties = self._execute("list", ["systemproperties"])
for prop in properties:
try:
name, value = prop.split(':', 1)
except ValueError:
continue
self._system_properties[name.strip()] = value.strip()
if linked_clone:
if vbox_id and os.path.isdir(os.path.join(self.working_dir, self._vmname)):

@ -16,7 +16,7 @@
# 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.
"""
@ -59,7 +59,7 @@ class VPCSVM(BaseVM):
super().__init__(name, uuid, project, manager)
self._path = self._config.get_section_config("VPCS").get("path", "vpcs")
self._path = manager.config.get_section_config("VPCS").get("path", "vpcs")
self._console = console

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

Loading…
Cancel
Save