mirror of
https://github.com/GNS3/gns3-server
synced 2024-11-28 03:08:14 +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:
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)
|
||||
|
||||
# look for VBoxManage
|
||||
self._vboxmanage_path = manager.config.get_section_config("VirtualBox").get("vboxmanage_path")
|
||||
if not self._vboxmanage_path:
|
||||
if sys.platform.startswith("win"):
|
||||
if "VBOX_INSTALL_PATH" in os.environ:
|
||||
self._vboxmanage_path = os.path.join(os.environ["VBOX_INSTALL_PATH"], "VBoxManage.exe")
|
||||
elif "VBOX_MSI_INSTALL_PATH" in os.environ:
|
||||
self._vboxmanage_path = os.path.join(os.environ["VBOX_MSI_INSTALL_PATH"], "VBoxManage.exe")
|
||||
elif sys.platform.startswith("darwin"):
|
||||
self._vboxmanage_path = "/Applications/VirtualBox.app/Contents/MacOS/VBoxManage"
|
||||
else:
|
||||
self._vboxmanage_path = shutil.which("vboxmanage")
|
||||
|
||||
if not self._vboxmanage_path:
|
||||
raise VirtualBoxError("Could not find VBoxManage")
|
||||
if not os.access(self._vboxmanage_path, os.X_OK):
|
||||
raise VirtualBoxError("VBoxManage is not executable")
|
||||
|
||||
self._vmname = vmname
|
||||
self._started = False
|
||||
self._linked_clone = linked_clone
|
||||
self._system_properties = {}
|
||||
|
||||
# FIXME: harcoded values
|
||||
if sys.platform.startswith("win"):
|
||||
self._vboxmanage_path = r"C:\Program Files\Oracle\VirtualBox\VBoxManage.exe"
|
||||
else:
|
||||
self._vboxmanage_path = "/usr/bin/vboxmanage"
|
||||
|
||||
self._queue = asyncio.Queue()
|
||||
self._created = asyncio.Future()
|
||||
self._worker = asyncio.async(self._run())
|
||||
|
||||
return
|
||||
|
||||
self._linked_clone = linked_clone
|
||||
self._working_dir = None
|
||||
self._command = []
|
||||
self._vboxmanage_path = vboxmanage_path
|
||||
self._vbox_user = vbox_user
|
||||
self._started = False
|
||||
self._console_host = console_host
|
||||
self._console_start_port_range = console_start_port_range
|
||||
self._console_end_port_range = console_end_port_range
|
||||
|
||||
self._telnet_server_thread = None
|
||||
self._serial_pipe = None
|
||||
@ -101,28 +105,6 @@ class VirtualBoxVM(BaseVM):
|
||||
# create the device own working directory
|
||||
self.working_dir = working_dir_path
|
||||
|
||||
if not self._console:
|
||||
# allocate a console port
|
||||
try:
|
||||
self._console = find_unused_port(self._console_start_port_range,
|
||||
self._console_end_port_range,
|
||||
self._console_host,
|
||||
ignore_ports=self._allocated_console_ports)
|
||||
except Exception as e:
|
||||
raise VirtualBoxError(e)
|
||||
|
||||
if self._console in self._allocated_console_ports:
|
||||
raise VirtualBoxError("Console port {} is already used by another VirtualBox VM".format(console))
|
||||
self._allocated_console_ports.append(self._console)
|
||||
|
||||
self._system_properties = {}
|
||||
properties = self._execute("list", ["systemproperties"])
|
||||
for prop in properties:
|
||||
try:
|
||||
name, value = prop.split(':', 1)
|
||||
except ValueError:
|
||||
continue
|
||||
self._system_properties[name.strip()] = value.strip()
|
||||
|
||||
if linked_clone:
|
||||
if vbox_id and os.path.isdir(os.path.join(self.working_dir, self._vmname)):
|
||||
|
@ -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…
Reference in New Issue
Block a user