More VirtualBox implementation.

This commit is contained in:
Jeremy 2015-01-23 16:38:46 -07:00
parent bc3d63081b
commit 6460e94311
5 changed files with 256 additions and 63 deletions

View File

@ -17,6 +17,8 @@
from ..web.route import Route from ..web.route import Route
from ..schemas.virtualbox import VBOX_CREATE_SCHEMA from ..schemas.virtualbox import VBOX_CREATE_SCHEMA
from ..schemas.virtualbox import VBOX_UPDATE_SCHEMA
from ..schemas.virtualbox import VBOX_NIO_SCHEMA
from ..schemas.virtualbox import VBOX_OBJECT_SCHEMA from ..schemas.virtualbox import VBOX_OBJECT_SCHEMA
from ..modules.virtualbox import VirtualBox from ..modules.virtualbox import VirtualBox
@ -31,7 +33,7 @@ class VirtualBoxHandler:
@Route.post( @Route.post(
r"/virtualbox", r"/virtualbox",
status_codes={ status_codes={
201: "VirtualBox VM instance created", 201: "Instance created",
400: "Invalid project UUID", 400: "Invalid project UUID",
409: "Conflict" 409: "Conflict"
}, },
@ -46,20 +48,81 @@ class VirtualBoxHandler:
request.json.get("uuid"), request.json.get("uuid"),
request.json["vmname"], request.json["vmname"],
request.json["linked_clone"], request.json["linked_clone"],
console=request.json.get("console")) console=request.json.get("console"),
vbox_user=request.json.get("vbox_user"))
response.set_status(201) response.set_status(201)
response.json(vm) response.json(vm)
@classmethod
@Route.get(
r"/virtualbox/{uuid}",
parameters={
"uuid": "Instance UUID"
},
status_codes={
200: "Success",
404: "Instance doesn't exist"
},
description="Get a VirtualBox VM instance",
output=VBOX_OBJECT_SCHEMA)
def show(request, response):
vbox_manager = VirtualBox.instance()
vm = vbox_manager.get_vm(request.match_info["uuid"])
response.json(vm)
@classmethod
@Route.put(
r"/virtualbox/{uuid}",
parameters={
"uuid": "Instance UUID"
},
status_codes={
200: "Instance updated",
404: "Instance doesn't exist",
409: "Conflict"
},
description="Update a VirtualBox VM instance",
input=VBOX_UPDATE_SCHEMA,
output=VBOX_OBJECT_SCHEMA)
def update(request, response):
vbox_manager = VirtualBox.instance()
vm = vbox_manager.get_vm(request.match_info["uuid"])
for name, value in request.json.items():
if hasattr(vm, name) and getattr(vm, name) != value:
setattr(vm, name, value)
# TODO: FINISH UPDATE (adapters).
response.json(vm)
@classmethod
@Route.delete(
r"/virtualbox/{uuid}",
parameters={
"uuid": "Instance UUID"
},
status_codes={
204: "Instance deleted",
404: "Instance doesn't exist"
},
description="Delete a VirtualBox VM instance")
def delete(request, response):
yield from VirtualBox.instance().delete_vm(request.match_info["uuid"])
response.set_status(204)
@classmethod @classmethod
@Route.post( @Route.post(
r"/virtualbox/{uuid}/start", r"/virtualbox/{uuid}/start",
parameters={ parameters={
"uuid": "VirtualBox VM instance UUID" "uuid": "Instance UUID"
}, },
status_codes={ status_codes={
204: "VirtualBox VM instance started", 204: "Instance started",
400: "Invalid VirtualBox VM instance UUID", 400: "Invalid instance UUID",
404: "VirtualBox VM instance doesn't exist" 404: "Instance doesn't exist"
}, },
description="Start a VirtualBox VM instance") description="Start a VirtualBox VM instance")
def start(request, response): def start(request, response):
@ -73,12 +136,12 @@ class VirtualBoxHandler:
@Route.post( @Route.post(
r"/virtualbox/{uuid}/stop", r"/virtualbox/{uuid}/stop",
parameters={ parameters={
"uuid": "VirtualBox VM instance UUID" "uuid": "Instance UUID"
}, },
status_codes={ status_codes={
204: "VirtualBox VM instance stopped", 204: "Instance stopped",
400: "Invalid VirtualBox VM instance UUID", 400: "Invalid instance UUID",
404: "VirtualBox VM instance doesn't exist" 404: "Instance doesn't exist"
}, },
description="Stop a VirtualBox VM instance") description="Stop a VirtualBox VM instance")
def stop(request, response): def stop(request, response):
@ -92,12 +155,12 @@ class VirtualBoxHandler:
@Route.post( @Route.post(
r"/virtualbox/{uuid}/suspend", r"/virtualbox/{uuid}/suspend",
parameters={ parameters={
"uuid": "VirtualBox VM instance UUID" "uuid": "Instance UUID"
}, },
status_codes={ status_codes={
204: "VirtualBox VM instance suspended", 204: "Instance suspended",
400: "Invalid VirtualBox VM instance UUID", 400: "Invalid instance UUID",
404: "VirtualBox VM instance doesn't exist" 404: "Instance doesn't exist"
}, },
description="Suspend a VirtualBox VM instance") description="Suspend a VirtualBox VM instance")
def suspend(request, response): def suspend(request, response):
@ -111,12 +174,12 @@ class VirtualBoxHandler:
@Route.post( @Route.post(
r"/virtualbox/{uuid}/resume", r"/virtualbox/{uuid}/resume",
parameters={ parameters={
"uuid": "VirtualBox VM instance UUID" "uuid": "Instance UUID"
}, },
status_codes={ status_codes={
204: "VirtualBox VM instance resumed", 204: "Instance resumed",
400: "Invalid VirtualBox VM instance UUID", 400: "Invalid instance UUID",
404: "VirtualBox VM instance doesn't exist" 404: "Instance doesn't exist"
}, },
description="Resume a suspended VirtualBox VM instance") description="Resume a suspended VirtualBox VM instance")
def suspend(request, response): def suspend(request, response):
@ -130,12 +193,12 @@ class VirtualBoxHandler:
@Route.post( @Route.post(
r"/virtualbox/{uuid}/reload", r"/virtualbox/{uuid}/reload",
parameters={ parameters={
"uuid": "VirtualBox VM instance UUID" "uuid": "Instance UUID"
}, },
status_codes={ status_codes={
204: "VirtualBox VM instance reloaded", 204: "Instance reloaded",
400: "Invalid VirtualBox VM instance UUID", 400: "Invalid instance UUID",
404: "VirtualBox VM instance doesn't exist" 404: "Instance doesn't exist"
}, },
description="Reload a VirtualBox VM instance") description="Reload a VirtualBox VM instance")
def suspend(request, response): def suspend(request, response):
@ -144,3 +207,46 @@ class VirtualBoxHandler:
vm = vbox_manager.get_vm(request.match_info["uuid"]) vm = vbox_manager.get_vm(request.match_info["uuid"])
yield from vm.reload() yield from vm.reload()
response.set_status(204) response.set_status(204)
@Route.post(
r"/virtualbox/{uuid}/ports/{port_id:\d+}/nio",
parameters={
"uuid": "Instance UUID",
"port_id": "ID of the port where the nio should be added"
},
status_codes={
201: "NIO created",
400: "Invalid instance UUID",
404: "Instance doesn't exist"
},
description="Add a NIO to a VirtualBox VM instance",
input=VBOX_NIO_SCHEMA,
output=VBOX_NIO_SCHEMA)
def create_nio(request, response):
vbox_manager = VirtualBox.instance()
vm = vbox_manager.get_vm(request.match_info["uuid"])
nio = vbox_manager.create_nio(vm.vboxmanage_path, request.json)
vm.port_add_nio_binding(int(request.match_info["port_id"]), nio)
response.set_status(201)
response.json(nio)
@classmethod
@Route.delete(
r"/virtualbox/{uuid}/ports/{port_id:\d+}/nio",
parameters={
"uuid": "Instance UUID",
"port_id": "ID of the port from where the nio should be removed"
},
status_codes={
204: "NIO deleted",
400: "Invalid instance UUID",
404: "Instance doesn't exist"
},
description="Remove a NIO from a VirtualBox VM instance")
def delete_nio(request, response):
vbox_manager = VirtualBox.instance()
vm = vbox_manager.get_vm(request.match_info["uuid"])
vm.port_remove_nio_binding(int(request.match_info["port_id"]))
response.set_status(204)

View File

@ -173,6 +173,7 @@ class PortManager:
:param port: TCP port number :param port: TCP port number
""" """
if port in self._used_tcp_ports:
self._used_tcp_ports.remove(port) self._used_tcp_ports.remove(port)
def get_free_udp_port(self): def get_free_udp_port(self):
@ -207,4 +208,5 @@ class PortManager:
:param port: UDP port number :param port: UDP port number
""" """
if port in self._used_udp_ports:
self._used_udp_ports.remove(port) self._used_udp_ports.remove(port)

View File

@ -70,10 +70,16 @@ class VirtualBoxVM(BaseVM):
self._adapter_start_index = 0 self._adapter_start_index = 0
self._adapter_type = "Intel PRO/1000 MT Desktop (82540EM)" self._adapter_type = "Intel PRO/1000 MT Desktop (82540EM)"
if self._console is not None:
self._console = self._manager.port_manager.reserve_console_port(self._console)
else:
self._console = self._manager.port_manager.get_free_console_port()
def __json__(self): def __json__(self):
return {"name": self.name, return {"name": self.name,
"uuid": self.uuid, "uuid": self.uuid,
"console": self.console,
"project_uuid": self.project.uuid, "project_uuid": self.project.uuid,
"vmname": self.vmname, "vmname": self.vmname,
"linked_clone": self.linked_clone, "linked_clone": self.linked_clone,
@ -299,6 +305,16 @@ class VirtualBoxVM(BaseVM):
log.info("VirtualBox VM '{name}' [{uuid}] reloaded".format(name=self.name, uuid=self.uuid)) log.info("VirtualBox VM '{name}' [{uuid}] reloaded".format(name=self.name, uuid=self.uuid))
log.debug("Reload result: {}".format(result)) log.debug("Reload result: {}".format(result))
@property
def vboxmanage_path(self):
"""
Returns the path to VBoxManage.
:returns: path
"""
return self._vboxmanage_path
@property @property
def console(self): def console(self):
""" """
@ -317,13 +333,10 @@ class VirtualBoxVM(BaseVM):
:param console: console port (integer) :param console: console port (integer)
""" """
if console in self._allocated_console_ports: if self._console:
raise VirtualBoxError("Console port {} is already used by another VirtualBox VM".format(console)) self._manager.port_manager.release_console_port(self._console)
self._allocated_console_ports.remove(self._console)
self._console = console
self._allocated_console_ports.append(self._console)
self._console = self._manager.port_manager.reserve_console_port(console)
log.info("VirtualBox VM '{name}' [{uuid}]: console port set to {port}".format(name=self.name, log.info("VirtualBox VM '{name}' [{uuid}]: console port set to {port}".format(name=self.name,
uuid=self.uuid, uuid=self.uuid,
port=console)) port=console))
@ -369,12 +382,13 @@ class VirtualBoxVM(BaseVM):
self.stop() self.stop()
if self.console and self.console in self._allocated_console_ports: if self._console:
self._allocated_console_ports.remove(self.console) self._manager.port_manager.release_console_port(self._console)
self._console = None
if self._linked_clone: if self._linked_clone:
hdd_table = [] hdd_table = []
if os.path.exists(self._working_dir): if os.path.exists(self.working_dir):
hdd_files = yield from self._get_all_hdd_files() hdd_files = yield from self._get_all_hdd_files()
vm_info = self._get_vm_info() vm_info = self._get_vm_info()
for entry, value in vm_info.items(): for entry, value in vm_info.items():
@ -398,7 +412,7 @@ class VirtualBoxVM(BaseVM):
if hdd_table: if hdd_table:
try: try:
hdd_info_file = os.path.join(self._working_dir, self._vmname, "hdd_info.json") hdd_info_file = os.path.join(self.working_dir, self._vmname, "hdd_info.json")
with open(hdd_info_file, "w") as f: with open(hdd_info_file, "w") as f:
# log.info("saving project: {}".format(path)) # log.info("saving project: {}".format(path))
json.dump(hdd_table, f, indent=4) json.dump(hdd_table, f, indent=4)
@ -408,30 +422,6 @@ class VirtualBoxVM(BaseVM):
log.info("VirtualBox VM '{name}' [{uuid}] closed".format(name=self.name, log.info("VirtualBox VM '{name}' [{uuid}] closed".format(name=self.name,
uuid=self.uuid)) uuid=self.uuid))
def delete(self):
"""
Deletes this VirtualBox VM & all files.
"""
self.stop()
if self.console:
self._allocated_console_ports.remove(self.console)
if self._linked_clone:
self._execute("unregistervm", [self._vmname, "--delete"])
# try:
# shutil.rmtree(self._working_dir)
# except OSError as e:
# log.error("could not delete VirtualBox VM {name} [id={id}]: {error}".format(name=self._name,
# id=self._id,
# error=e))
# return
log.info("VirtualBox VM '{name}' [{uuid}] has been deleted (including associated files)".format(name=self.name,
uuid=self.uuid))
@property @property
def headless(self): def headless(self):
""" """
@ -501,8 +491,9 @@ class VirtualBoxVM(BaseVM):
""" """
log.info("VirtualBox VM '{name}' [{uuid}] has set the VM name to '{vmname}'".format(name=self.name, uuid=self.uuid, vmname=vmname)) log.info("VirtualBox VM '{name}' [{uuid}] has set the VM name to '{vmname}'".format(name=self.name, uuid=self.uuid, vmname=vmname))
if self._linked_clone: # TODO: test linked clone
self._modify_vm('--name "{}"'.format(vmname)) #if self._linked_clone:
# yield from self._modify_vm('--name "{}"'.format(vmname))
self._vmname = vmname self._vmname = vmname
@property @property

View File

@ -53,11 +53,107 @@ VBOX_CREATE_SCHEMA = {
"maxLength": 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}$" "pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$"
}, },
"console": {
"description": "console TCP port",
"minimum": 1,
"maximum": 65535,
"type": "integer"
},
}, },
"additionalProperties": False, "additionalProperties": False,
"required": ["name", "vmname", "linked_clone", "project_uuid"], "required": ["name", "vmname", "linked_clone", "project_uuid"],
} }
VBOX_UPDATE_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Request validation to update a VirtualBox VM instance",
"type": "object",
"properties": {
"name": {
"description": "VirtualBox VM instance name",
"type": "string",
"minLength": 1,
},
"vmname": {
"description": "VirtualBox VM name (in VirtualBox itself)",
"type": "string",
"minLength": 1,
},
"adapters": {
"description": "number of adapters",
"type": "integer",
"minimum": 0,
"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": {
"description": "console TCP port",
"minimum": 1,
"maximum": 65535,
"type": "integer"
},
"enable_remote_console": {
"description": "enable the remote console",
"type": "boolean"
},
"headless": {
"description": "headless mode",
"type": "boolean"
},
},
"additionalProperties": False,
}
VBOX_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
},
},
"oneOf": [
{"$ref": "#/definitions/UDP"},
],
"additionalProperties": True,
"required": ["type"]
}
VBOX_OBJECT_SCHEMA = { VBOX_OBJECT_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#", "$schema": "http://json-schema.org/draft-04/schema#",
"description": "VirtualBox VM instance", "description": "VirtualBox VM instance",

View File

@ -95,7 +95,6 @@ VPCS_NIO_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#", "$schema": "http://json-schema.org/draft-04/schema#",
"description": "Request validation to add a NIO for a VPCS instance", "description": "Request validation to add a NIO for a VPCS instance",
"type": "object", "type": "object",
"definitions": { "definitions": {
"UDP": { "UDP": {
"description": "UDP Network Input/Output", "description": "UDP Network Input/Output",
@ -140,13 +139,12 @@ VPCS_NIO_SCHEMA = {
"additionalProperties": False "additionalProperties": False
}, },
}, },
"oneOf": [ "oneOf": [
{"$ref": "#/definitions/UDP"}, {"$ref": "#/definitions/UDP"},
{"$ref": "#/definitions/TAP"}, {"$ref": "#/definitions/TAP"},
], ],
"additionalProperties": True, "additionalProperties": True,
"required": ['type'] "required": ["type"]
} }
VPCS_OBJECT_SCHEMA = { VPCS_OBJECT_SCHEMA = {