1
0
mirror of https://github.com/GNS3/gns3-server synced 2024-11-28 11:18:11 +00:00

Minimal VirtualBox integration.

This commit is contained in:
grossmj 2014-07-17 15:28:02 -06:00
parent 0ef727ce4b
commit 1fb4ab7e33
9 changed files with 1495 additions and 97 deletions

View File

@ -152,6 +152,7 @@ class JSONRPCWebSocket(tornado.websocket.WebSocketHandler):
if method not in self.destinations: if method not in self.destinations:
if request_id: if request_id:
log.warn("JSON-RPC method not found: {}".format(method))
return self.write_message(JSONRPCMethodNotFound(request_id)()) return self.write_message(JSONRPCMethodNotFound(request_id)())
else: else:
# This is a notification, silently ignore this error... # This is a notification, silently ignore this error...
@ -176,11 +177,7 @@ class JSONRPCWebSocket(tornado.websocket.WebSocketHandler):
Invoked when the WebSocket is closed. Invoked when the WebSocket is closed.
""" """
try: log.info("Websocket client {} disconnected".format(self.session_id))
log.info("Websocket client {} disconnected".format(self.session_id))
except RuntimeError:
# to ignore logging exception: RuntimeError: reentrant call inside <_io.BufferedWriter name='<stderr>'>
pass
self.clients.remove(self) self.clients.remove(self)
# Reset the modules if there are no clients anymore # Reset the modules if there are no clients anymore

View File

@ -19,8 +19,8 @@
VirtualBox server module. VirtualBox server module.
""" """
import sys
import os import os
import base64
import socket import socket
import shutil import shutil
@ -28,18 +28,28 @@ from gns3server.modules import IModule
from gns3server.config import Config from gns3server.config import Config
from .virtualbox_vm import VirtualBoxVM from .virtualbox_vm import VirtualBoxVM
from .virtualbox_error import VirtualBoxError from .virtualbox_error import VirtualBoxError
from .vboxwrapper_client import VboxWrapperClient
from .nios.nio_udp import NIO_UDP from .nios.nio_udp import NIO_UDP
from ..attic import find_unused_port from ..attic import find_unused_port
#from .schemas import VBOX_CREATE_SCHEMA if sys.platform.startswith("win"):
#from .schemas import VBOX_DELETE_SCHEMA # automatically generate the Typelib wrapper
#from .schemas import VBOX_UPDATE_SCHEMA import win32com
#from .schemas import VBOX_START_SCHEMA win32com.client.gencache.is_readonly = False
#from .schemas import VBOX_STOP_SCHEMA win32com.client.gencache.GetGeneratePath()
#from .schemas import VBOX_RELOAD_SCHEMA
#from .schemas import VBOX_ALLOCATE_UDP_PORT_SCHEMA from .schemas import VBOX_CREATE_SCHEMA
#from .schemas import VBOX_ADD_NIO_SCHEMA from .schemas import VBOX_DELETE_SCHEMA
#from .schemas import VBOX_DELETE_NIO_SCHEMA from .schemas import VBOX_UPDATE_SCHEMA
from .schemas import VBOX_START_SCHEMA
from .schemas import VBOX_STOP_SCHEMA
from .schemas import VBOX_SUSPEND_SCHEMA
from .schemas import VBOX_RELOAD_SCHEMA
from .schemas import VBOX_ALLOCATE_UDP_PORT_SCHEMA
from .schemas import VBOX_ADD_NIO_SCHEMA
from .schemas import VBOX_DELETE_NIO_SCHEMA
from .schemas import VBOX_START_CAPTURE_SCHEMA
from .schemas import VBOX_STOP_CAPTURE_SCHEMA
import logging import logging
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -56,6 +66,26 @@ class VirtualBox(IModule):
def __init__(self, name, *args, **kwargs): def __init__(self, name, *args, **kwargs):
# get the vboxwrapper location
config = Config.instance()
vbox_config = config.get_section_config(name.upper())
self._vboxwrapper_path = vbox_config.get("vboxwrapper_path")
if not self._vboxwrapper_path or not os.path.isfile(self._vboxwrapper_path):
paths = [os.getcwd()] + os.environ["PATH"].split(":")
# look for iouyap in the current working directory and $PATH
for path in paths:
try:
if "vboxwrapper" in os.listdir(path) and os.access(os.path.join(path, "vboxwrapper"), os.X_OK):
self._vboxwrapper_path = os.path.join(path, "vboxwrapper")
break
except OSError:
continue
if not self._vboxwrapper_path:
log.warning("vboxwrapper couldn't be found!")
elif not os.access(self._vboxwrapper_path, os.X_OK):
log.warning("vboxwrapper is not executable")
# a new process start when calling IModule # a new process start when calling IModule
IModule.__init__(self, name, *args, **kwargs) IModule.__init__(self, name, *args, **kwargs)
self._vbox_instances = {} self._vbox_instances = {}
@ -71,6 +101,43 @@ class VirtualBox(IModule):
self._projects_dir = kwargs["projects_dir"] self._projects_dir = kwargs["projects_dir"]
self._tempdir = kwargs["temp_dir"] self._tempdir = kwargs["temp_dir"]
self._working_dir = self._projects_dir self._working_dir = self._projects_dir
self._vboxmanager = None
self._vboxwrapper = None
def _start_vbox_service(self):
"""
Starts the VirtualBox backend.
vboxapi on Windows or vboxwrapper on other platforms.
"""
if sys.platform.startswith("win"):
import win32com.client
if win32com.client.gencache.is_readonly is True:
# dynamically generate the cache
# http://www.py2exe.org/index.cgi/IncludingTypelibs
# http://www.py2exe.org/index.cgi/UsingEnsureDispatch
win32com.client.gencache.is_readonly = False
#win32com.client.gencache.Rebuild()
win32com.client.gencache.GetGeneratePath()
try:
from .vboxapi_py3 import VirtualBoxManager
self._vboxmanager = VirtualBoxManager(None, None)
except Exception as e:
raise VirtualBoxError("Could not initialize the VirtualBox Manager: {}".format(e))
log.info("VirtualBox Manager has successful started: version is {} r{}".format(self._vboxmanager.vbox.version,
self._vboxmanager.vbox.revision))
else:
if not self._vboxwrapper_path:
raise VirtualBoxError("No vboxwrapper path has been configured")
if not os.path.isfile(self._vboxwrapper_path):
raise VirtualBoxError("vboxwrapper path doesn't exist {}".format(self._vboxwrapper_path))
self._vboxwrapper = VboxWrapperClient(self._vboxwrapper_path, self._tempdir, "127.0.0.1")
#self._vboxwrapper.connect()
self._vboxwrapper.start()
def stop(self, signum=None): def stop(self, signum=None):
""" """
@ -84,6 +151,9 @@ class VirtualBox(IModule):
vbox_instance = self._vbox_instances[vbox_id] vbox_instance = self._vbox_instances[vbox_id]
vbox_instance.delete() vbox_instance.delete()
if self._vboxwrapper and self._vboxwrapper.started:
self._vboxwrapper.stop()
IModule.stop(self, signum) # this will stop the I/O loop IModule.stop(self, signum) # this will stop the I/O loop
def get_vbox_instance(self, vbox_id): def get_vbox_instance(self, vbox_id):
@ -120,6 +190,9 @@ class VirtualBox(IModule):
self._vbox_instances.clear() self._vbox_instances.clear()
self._allocated_udp_ports.clear() self._allocated_udp_ports.clear()
if self._vboxwrapper and self._vboxwrapper.connected():
self._vboxwrapper.send("vboxwrapper reset")
log.info("VirtualBox module has been reset") log.info("VirtualBox module has been reset")
@IModule.route("virtualbox.settings") @IModule.route("virtualbox.settings")
@ -129,6 +202,7 @@ class VirtualBox(IModule):
Optional request parameters: Optional request parameters:
- working_dir (path to a working directory) - working_dir (path to a working directory)
- vboxwrapper_path (path to vboxwrapper)
- project_name - project_name
- console_start_port_range - console_start_port_range
- console_end_port_range - console_end_port_range
@ -165,6 +239,9 @@ class VirtualBox(IModule):
vbox_instance = self._vbox_instances[vbox_id] vbox_instance = self._vbox_instances[vbox_id]
vbox_instance.working_dir = os.path.join(self._working_dir, "vbox", "vm-{}".format(vbox_instance.id)) vbox_instance.working_dir = os.path.join(self._working_dir, "vbox", "vm-{}".format(vbox_instance.id))
if "vboxwrapper_path" in request:
self._vboxwrapper_path = request["vboxwrapper_path"]
if "console_start_port_range" in request and "console_end_port_range" in request: if "console_start_port_range" in request and "console_end_port_range" in request:
self._console_start_port_range = request["console_start_port_range"] self._console_start_port_range = request["console_start_port_range"]
self._console_end_port_range = request["console_end_port_range"] self._console_end_port_range = request["console_end_port_range"]
@ -195,16 +272,23 @@ class VirtualBox(IModule):
""" """
# validate the request # validate the request
#if not self.validate_request(request, VBOX_CREATE_SCHEMA): if not self.validate_request(request, VBOX_CREATE_SCHEMA):
# return return
name = request["name"] name = request["name"]
vmname = request["vmname"]
console = request.get("console") console = request.get("console")
vbox_id = request.get("vbox_id") vbox_id = request.get("vbox_id")
try: try:
vbox_instance = VirtualBoxVM(name, if not self._vboxwrapper and not self._vboxmanager:
self._start_vbox_service()
vbox_instance = VirtualBoxVM(self._vboxwrapper,
self._vboxmanager,
name,
vmname,
self._working_dir, self._working_dir,
self._host, self._host,
vbox_id, vbox_id,
@ -239,8 +323,8 @@ class VirtualBox(IModule):
""" """
# validate the request # validate the request
#if not self.validate_request(request, VBOX_DELETE_SCHEMA): if not self.validate_request(request, VBOX_DELETE_SCHEMA):
# return return
# get the instance # get the instance
vbox_instance = self.get_vbox_instance(request["id"]) vbox_instance = self.get_vbox_instance(request["id"])
@ -274,8 +358,8 @@ class VirtualBox(IModule):
""" """
# validate the request # validate the request
#if not self.validate_request(request, VBOX_UPDATE_SCHEMA): if not self.validate_request(request, VBOX_UPDATE_SCHEMA):
# return return
# get the instance # get the instance
vbox_instance = self.get_vbox_instance(request["id"]) vbox_instance = self.get_vbox_instance(request["id"])
@ -310,8 +394,8 @@ class VirtualBox(IModule):
""" """
# validate the request # validate the request
#if not self.validate_request(request, VBOX_START_SCHEMA): if not self.validate_request(request, VBOX_START_SCHEMA):
# return return
# get the instance # get the instance
vbox_instance = self.get_vbox_instance(request["id"]) vbox_instance = self.get_vbox_instance(request["id"])
@ -340,8 +424,8 @@ class VirtualBox(IModule):
""" """
# validate the request # validate the request
#if not self.validate_request(request, VBOX_STOP_SCHEMA): if not self.validate_request(request, VBOX_STOP_SCHEMA):
# return return
# get the instance # get the instance
vbox_instance = self.get_vbox_instance(request["id"]) vbox_instance = self.get_vbox_instance(request["id"])
@ -370,18 +454,76 @@ class VirtualBox(IModule):
""" """
# validate the request # validate the request
#if not self.validate_request(request, VBOX_RELOAD_SCHEMA): if not self.validate_request(request, VBOX_RELOAD_SCHEMA):
# return return
# get the instance # get the instance
vbox_instance = self.get_vpcs_instance(request["id"]) vbox_instance = self.get_vbox_instance(request["id"])
if not vbox_instance: if not vbox_instance:
return return
try: try:
if vbox_instance.is_running(): vbox_instance.reload()
vbox_instance.stop() except VirtualBoxError as e:
vbox_instance.start() self.send_custom_error(str(e))
return
self.send_response(True)
@IModule.route("virtualbox.stop")
def vbox_stop(self, request):
"""
Stops a VirtualBox VM instance.
Mandatory request parameters:
- id (VirtualBox VM instance identifier)
Response parameters:
- True on success
:param request: JSON request
"""
# validate the request
if not self.validate_request(request, VBOX_STOP_SCHEMA):
return
# get the instance
vbox_instance = self.get_vbox_instance(request["id"])
if not vbox_instance:
return
try:
vbox_instance.stop()
except VirtualBoxError as e:
self.send_custom_error(str(e))
return
self.send_response(True)
@IModule.route("virtualbox.suspend")
def vbox_suspend(self, request):
"""
Suspends a VirtualBox VM instance.
Mandatory request parameters:
- id (VirtualBox VM instance identifier)
Response parameters:
- True on success
:param request: JSON request
"""
# validate the request
if not self.validate_request(request, VBOX_SUSPEND_SCHEMA):
return
# get the instance
vbox_instance = self.get_vbox_instance(request["id"])
if not vbox_instance:
return
try:
vbox_instance.suspend()
except VirtualBoxError as e: except VirtualBoxError as e:
self.send_custom_error(str(e)) self.send_custom_error(str(e))
return return
@ -404,8 +546,8 @@ class VirtualBox(IModule):
""" """
# validate the request # validate the request
#if not self.validate_request(request, VBOX_ALLOCATE_UDP_PORT_SCHEMA): if not self.validate_request(request, VBOX_ALLOCATE_UDP_PORT_SCHEMA):
# return return
# get the instance # get the instance
vbox_instance = self.get_vbox_instance(request["id"]) vbox_instance = self.get_vbox_instance(request["id"])
@ -454,8 +596,8 @@ class VirtualBox(IModule):
""" """
# validate the request # validate the request
#if not self.validate_request(request, VBOX_ADD_NIO_SCHEMA): if not self.validate_request(request, VBOX_ADD_NIO_SCHEMA):
# return return
# get the instance # get the instance
vbox_instance = self.get_vbox_instance(request["id"]) vbox_instance = self.get_vbox_instance(request["id"])
@ -506,8 +648,8 @@ class VirtualBox(IModule):
""" """
# validate the request # validate the request
#if not self.validate_request(request, VBOX_DELETE_NIO_SCHEMA): if not self.validate_request(request, VBOX_DELETE_NIO_SCHEMA):
# return return
# get the instance # get the instance
vbox_instance = self.get_vbox_instance(request["id"]) vbox_instance = self.get_vbox_instance(request["id"])
@ -525,6 +667,110 @@ class VirtualBox(IModule):
self.send_response(True) self.send_response(True)
@IModule.route("virtualbox.start_capture")
def vbox_start_capture(self, request):
"""
Starts a packet capture.
Mandatory request parameters:
- id (vm identifier)
- port (port number)
- port_id (port identifier)
- capture_file_name
Response parameters:
- port_id (port identifier)
- capture_file_path (path to the capture file)
:param request: JSON request
"""
# validate the request
if not self.validate_request(request, VBOX_START_CAPTURE_SCHEMA):
return
# get the instance
vbox_instance = self.get_vbox_instance(request["id"])
if not vbox_instance:
return
port = request["port"]
capture_file_name = request["capture_file_name"]
try:
capture_file_path = os.path.join(self._working_dir, "captures", capture_file_name)
vbox_instance.start_capture(port, capture_file_path)
except VirtualBoxError as e:
self.send_custom_error(str(e))
return
response = {"port_id": request["port_id"],
"capture_file_path": capture_file_path}
self.send_response(response)
@IModule.route("virtualbox.stop_capture")
def vbox_stop_capture(self, request):
"""
Stops a packet capture.
Mandatory request parameters:
- id (vm identifier)
- port (port number)
- port_id (port identifier)
Response parameters:
- port_id (port identifier)
:param request: JSON request
"""
# validate the request
if not self.validate_request(request, VBOX_STOP_CAPTURE_SCHEMA):
return
# get the instance
vbox_instance = self.get_vbox_instance(request["id"])
if not vbox_instance:
return
port = request["port"]
try:
vbox_instance.stop_capture(port)
except VirtualBoxError as e:
self.send_custom_error(str(e))
return
response = {"port_id": request["port_id"]}
self.send_response(response)
@IModule.route("virtualbox.vm_list")
def vm_list(self, request):
"""
Gets VirtualBox VM list.
Response parameters:
- Server address/host
- List of VM names
"""
if not self._vboxwrapper and not self._vboxmanager:
self._start_vbox_service()
if self._vboxwrapper:
vms = self._vboxwrapper.get_vm_list()
elif self._vboxmanager:
vms = []
machines = self._vboxmanager.getArray(self._vboxmanager.vbox, "machines")
for machine in range(len(machines)):
vms.append(machines[machine].name)
else:
self.send_custom_error("Vboxmanager hasn't been initialized!")
return
response = {"server": self._host,
"vms": vms}
self.send_response(response)
@IModule.route("virtualbox.echo") @IModule.route("virtualbox.echo")
def echo(self, request): def echo(self, request):
""" """

View File

@ -0,0 +1,65 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2013 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/>.
"""
Base interface for NIOs.
"""
class NIO(object):
"""
IOU NIO.
"""
def __init__(self):
self._capturing = False
self._pcap_output_file = ""
def startPacketCapture(self, pcap_output_file):
"""
:param pcap_output_file: PCAP destination file for the capture
"""
self._capturing = True
self._pcap_output_file = pcap_output_file
def stopPacketCapture(self):
self._capturing = False
self._pcap_output_file = ""
@property
def capturing(self):
"""
Returns either a capture is configured on this NIO.
:returns: boolean
"""
return self._capturing
@property
def pcap_output_file(self):
"""
Returns the path to the PCAP output file.
:returns: path to the PCAP output file
"""
return self._pcap_output_file

View File

@ -19,8 +19,10 @@
Interface for UDP NIOs. Interface for UDP NIOs.
""" """
from .nio import NIO
class NIO_UDP(object):
class NIO_UDP(NIO):
""" """
IOU UDP NIO. IOU UDP NIO.
@ -33,6 +35,7 @@ class NIO_UDP(object):
def __init__(self, lport, rhost, rport): def __init__(self, lport, rhost, rport):
NIO.__init__(self)
self._lport = lport self._lport = lport
self._rhost = rhost self._rhost = rhost
self._rport = rport self._rport = rport

View File

@ -0,0 +1,418 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2014 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/>.
VBOX_CREATE_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Request validation to create a new 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,
},
"vbox_id": {
"description": "VirtualBox VM instance ID",
"type": "integer"
},
"console": {
"description": "console TCP port",
"minimum": 1,
"maximum": 65535,
"type": "integer"
},
},
"additionalProperties": False,
"required": ["name", "vmname"],
}
VBOX_DELETE_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Request validation to delete a VirtualBox VM instance",
"type": "object",
"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": {
"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_type": {
"description": "VirtualBox adapter type",
"type": "string",
"minLength": 1,
},
"console": {
"description": "console TCP port",
"minimum": 1,
"maximum": 65535,
"type": "integer"
},
"headless": {
"description": "headless mode",
"type": "boolean"
},
},
"additionalProperties": False,
"required": ["id"]
}
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"]
}

@ -0,0 +1 @@
Subproject commit ed33ed344d5687302979972f518a4cee17517d28

View File

@ -26,6 +26,7 @@ import tempfile
import socket import socket
import re import re
from ..attic import wait_socket_is_ready
from .virtualbox_error import VirtualBoxError from .virtualbox_error import VirtualBoxError
import logging import logging
@ -51,6 +52,7 @@ class VboxWrapperClient(object):
self._path = path self._path = path
self._command = [] self._command = []
self._process = None self._process = None
self._working_dir = working_dir
self._stdout_file = "" self._stdout_file = ""
self._started = False self._started = False
self._host = host self._host = host
@ -144,21 +146,47 @@ class VboxWrapperClient(object):
stderr=subprocess.STDOUT, stderr=subprocess.STDOUT,
cwd=self._working_dir) cwd=self._working_dir)
log.info("VirtualBox wrapper started PID={}".format(self._process.pid)) log.info("VirtualBox wrapper started PID={}".format(self._process.pid))
self.wait_for_vboxwrapper(self._host, self._port)
self.connect()
self._started = True self._started = True
except OSError as e: except OSError as e:
log.error("could not start VirtualBox wrapper: {}".format(e)) log.error("could not start VirtualBox wrapper: {}".format(e))
raise VirtualBoxError("could not start VirtualBox wrapper: {}".format(e)) raise VirtualBoxError("could not start VirtualBox wrapper: {}".format(e))
def wait_for_vboxwrapper(self, host, port):
"""
Waits for vboxwrapper to be started (accepting a socket connection)
:param host: host/address to connect to the vboxwrapper
:param port: port to connect to the vboxwrapper
"""
begin = time.time()
# wait for the socket for a maximum of 10 seconds.
connection_success, last_exception = wait_socket_is_ready(host, port, wait=10.0)
if not connection_success:
raise VirtualBoxError("Couldn't connect to vboxwrapper on {}:{} :{}".format(host, port,
last_exception))
else:
log.info("vboxwrapper server ready after {:.4f} seconds".format(time.time() - begin))
def stop(self): def stop(self):
""" """
Stops the VirtualBox wrapper process. Stops the VirtualBox wrapper process.
""" """
if self.connected():
try:
self.send("vboxwrapper stop")
except VirtualBoxError:
pass
if self._socket:
self._socket.shutdown(socket.SHUT_RDWR)
self._socket.close()
self._socket = None
if self.is_running(): if self.is_running():
self.send("hypervisor stop")
self._socket.shutdown(socket.SHUT_RDWR)
self._socket.close()
self._socket = None
log.info("stopping VirtualBox wrapper PID={}".format(self._process.pid)) log.info("stopping VirtualBox wrapper PID={}".format(self._process.pid))
try: try:
# give some time for the VirtualBox wrapper to properly stop. # give some time for the VirtualBox wrapper to properly stop.
@ -210,10 +238,10 @@ class VboxWrapperClient(object):
""" """
command = [self._path] command = [self._path]
#if self._host != "0.0.0.0" and self._host != "::": if self._host != "0.0.0.0" and self._host != "::":
# command.extend(["-H", "{}:{}".format(self._host, self._port)]) command.extend(["-l", self._host, "-p", str(self._port)])
#else: else:
# command.extend(["-H", str(self._port)]) command.extend(["-p", str(self._port)])
return command return command
def connect(self): def connect(self):
@ -235,6 +263,17 @@ class VboxWrapperClient(object):
except OSError as e: except OSError as e:
raise VirtualBoxError("Could not connect to the VirtualBox wrapper: {}".format(e)) raise VirtualBoxError("Could not connect to the VirtualBox wrapper: {}".format(e))
def connected(self):
"""
Returns either the client is connected to vboxwrapper or not.
:return: boolean
"""
if self._socket:
return True
return False
def reset(self): def reset(self):
""" """
Resets the VirtualBox wrapper (used to get an empty configuration). Resets the VirtualBox wrapper (used to get an empty configuration).
@ -276,6 +315,15 @@ class VboxWrapperClient(object):
assert self._socket assert self._socket
return self._socket return self._socket
def get_vm_list(self):
"""
Returns the list of all VirtualBox VMs.
:returns: list of VM names
"""
return self.send('vbox vm_list')
def send(self, command): def send(self, command):
""" """
Sends commands to the VirtualBox wrapper. Sends commands to the VirtualBox wrapper.
@ -306,6 +354,7 @@ class VboxWrapperClient(object):
log.debug("sending {}".format(command)) log.debug("sending {}".format(command))
self.socket.sendall(command.encode('utf-8')) self.socket.sendall(command.encode('utf-8'))
except OSError as e: except OSError as e:
self._socket = None
raise VirtualBoxError("Lost communication with {host}:{port} :{error}" raise VirtualBoxError("Lost communication with {host}:{port} :{error}"
.format(host=self._host, port=self._port, error=e)) .format(host=self._host, port=self._port, error=e))
@ -317,14 +366,17 @@ class VboxWrapperClient(object):
chunk = self.socket.recv(1024) chunk = self.socket.recv(1024)
buf += chunk.decode("utf-8") buf += chunk.decode("utf-8")
except OSError as e: except OSError as e:
self._socket = None
raise VirtualBoxError("Communication timed out with {host}:{port} :{error}" raise VirtualBoxError("Communication timed out with {host}:{port} :{error}"
.format(host=self._host, port=self._port, error=e)) .format(host=self._host, port=self._port, error=e))
# If the buffer doesn't end in '\n' then we can't be done # If the buffer doesn't end in '\n' then we can't be done
try: try:
if buf[-1] != '\n': if buf[-1] != '\n':
continue continue
except IndexError: except IndexError:
self._socket = None
raise VirtualBoxError("Could not communicate with {host}:{port}" raise VirtualBoxError("Could not communicate with {host}:{port}"
.format(host=self._host, port=self._port)) .format(host=self._host, port=self._port))

View File

@ -19,24 +19,28 @@
VirtualBox VM instance. VirtualBox VM instance.
""" """
import sys
import os import os
import shutil import shutil
import tempfile
import re
import time
from pkg_resources import parse_version
from .virtualbox_error import VirtualBoxError from .virtualbox_error import VirtualBoxError
from .adapters.ethernet_adapter import EthernetAdapter from .adapters.ethernet_adapter import EthernetAdapter
from .nios.nio_udp import NIO_UDP
from ..attic import find_unused_port from ..attic import find_unused_port
import logging import logging
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class VirtualBoxVM(object): class VirtualBoxVM(object):
""" """
VirtualBox VM implementation. VirtualBox VM implementation.
:param vboxwrapper client: VboxWrapperClient instance
:param vboxmanager: VirtualBox manager from the VirtualBox API
:param name: name of this VirtualBox VM :param name: name of this VirtualBox VM
:param vmname: name of this VirtualBox VM in VirtualBox itself
:param working_dir: path to a working directory :param working_dir: path to a working directory
:param host: host/address to bind for console and UDP connections :param host: host/address to bind for console and UDP connections
:param vbox_id: VirtalBox VM instance ID :param vbox_id: VirtalBox VM instance ID
@ -49,8 +53,10 @@ class VirtualBoxVM(object):
_allocated_console_ports = [] _allocated_console_ports = []
def __init__(self, def __init__(self,
vboxwrapper,
vboxmanager,
name, name,
path, vmname,
working_dir, working_dir,
host="127.0.0.1", host="127.0.0.1",
vbox_id=None, vbox_id=None,
@ -75,19 +81,28 @@ class VirtualBoxVM(object):
self._instances.append(self._id) self._instances.append(self._id)
self._name = name self._name = name
self._console = console
self._working_dir = None self._working_dir = None
self._host = host self._host = host
self._command = [] self._command = []
self._vboxwrapper_process = None self._vboxwrapper = vboxwrapper
self._vboxwrapper_stdout_file = ""
self._host = "127.0.0.1" self._host = "127.0.0.1"
self._started = False self._started = False
self._console_start_port_range = console_start_port_range self._console_start_port_range = console_start_port_range
self._console_end_port_range = console_end_port_range self._console_end_port_range = console_end_port_range
# VirtualBox API variables
self._machine = None
self._session = None
self._vboxmanager = vboxmanager
self._maximum_adapters = 0
# VirtualBox settings # VirtualBox settings
self._ethernet_adapter = EthernetAdapter() # one adapter with 1 Ethernet interface self._console = console
self._ethernet_adapters = []
self._headless = False
self._vmname = vmname
self._adapter_type = "Automatic"
working_dir_path = os.path.join(working_dir, "vbox", "vm-{}".format(self._id)) working_dir_path = os.path.join(working_dir, "vbox", "vm-{}".format(self._id))
@ -111,6 +126,23 @@ class VirtualBoxVM(object):
raise VirtualBoxError("Console port {} is already used by another VirtualBox VM".format(console)) raise VirtualBoxError("Console port {} is already used by another VirtualBox VM".format(console))
self._allocated_console_ports.append(self._console) self._allocated_console_ports.append(self._console)
if self._vboxwrapper:
self._vboxwrapper.send('vbox create vbox "{}"'.format(self._name))
self._vboxwrapper.send('vbox setattr "{}" image "{}"'.format(self._name, vmname))
self._vboxwrapper.send('vbox setattr "{}" console {}'.format(self._name, self._console))
self._vboxwrapper.send('vbox setattr "{}" console_support True'.format(self._name))
self._vboxwrapper.send('vbox setattr "{}" console_telnet_server True'.format(self._name))
else:
try:
self._machine = self._vboxmanager.vbox.findMachine(self._vmname)
except Exception as e:
raise VirtualBoxError("VirtualBox error: {}".format(e))
# The maximum support network cards depends on the Chipset (PIIX3 or ICH9)
self._maximum_adapters = self._vboxmanager.vbox.systemProperties.getMaxNetworkAdapters(self._machine.chipsetType)
self.adapters = 2
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))
@ -122,7 +154,11 @@ class VirtualBoxVM(object):
""" """
vbox_defaults = {"name": self._name, vbox_defaults = {"name": self._name,
"console": self._console} "vmname": self._vmname,
"adapters": len(self._ethernet_adapters),
"adapter_type": "Automatic",
"console": self._console,
"headless": self._headless}
return vbox_defaults return vbox_defaults
@ -166,6 +202,9 @@ class VirtualBoxVM(object):
log.info("VirtualBox VM {name} [id={id}]: renamed to {new_name}".format(name=self._name, log.info("VirtualBox VM {name} [id={id}]: renamed to {new_name}".format(name=self._name,
id=self._id, id=self._id,
new_name=new_name)) new_name=new_name))
if self._vboxwrapper:
self._vboxwrapper.send('vbox rename "{}" "{}"'.format(self._name, new_name))
self._name = new_name self._name = new_name
@property @property
@ -222,6 +261,10 @@ class VirtualBoxVM(object):
self._allocated_console_ports.remove(self._console) self._allocated_console_ports.remove(self._console)
self._console = console self._console = console
self._allocated_console_ports.append(self._console) self._allocated_console_ports.append(self._console)
if self._vboxwrapper:
self._vboxwrapper.send('vbox setattr "{}" console {}'.format(self._name, self._console))
log.info("VirtualBox VM {name} [id={id}]: console port set to {port}".format(name=self._name, log.info("VirtualBox VM {name} [id={id}]: console port set to {port}".format(name=self._name,
id=self._id, id=self._id,
port=console)) port=console))
@ -238,6 +281,9 @@ class VirtualBoxVM(object):
if self.console and self.console in self._allocated_console_ports: if self.console and self.console in self._allocated_console_ports:
self._allocated_console_ports.remove(self.console) self._allocated_console_ports.remove(self.console)
if self._vboxwrapper:
self._vboxwrapper.send('vbox delete "{}"'.format(self._name))
log.info("VirtualBox VM {name} [id={id}] has been deleted".format(name=self._name, log.info("VirtualBox VM {name} [id={id}] has been deleted".format(name=self._name,
id=self._id)) id=self._id))
@ -253,6 +299,9 @@ class VirtualBoxVM(object):
if self.console: if self.console:
self._allocated_console_ports.remove(self.console) self._allocated_console_ports.remove(self.console)
if self._vboxwrapper:
self._vboxwrapper.send('vbox delete "{}"'.format(self._name))
try: try:
shutil.rmtree(self._working_dir) shutil.rmtree(self._working_dir)
except OSError as e: except OSError as e:
@ -264,55 +313,618 @@ class VirtualBoxVM(object):
log.info("VirtualBox VM {name} [id={id}] has been deleted (including associated files)".format(name=self._name, log.info("VirtualBox VM {name} [id={id}] has been deleted (including associated files)".format(name=self._name,
id=self._id)) id=self._id))
@property
def headless(self):
"""
Returns either the VM will start in headless mode
:returns: boolean
"""
return self._headless
@headless.setter
def headless(self, headless):
"""
Sets either the VM will start in headless mode
:param headless: boolean
"""
if headless:
if self._vboxwrapper:
self._vboxwrapper.send('vbox setattr "{}" headless_mode True'.format(self._name))
log.info("VirtualBox VM {name} [id={id}] has enabled the headless mode".format(name=self._name, id=self._id))
else:
if self._vboxwrapper:
self._vboxwrapper.send('vbox setattr "{}" headless_mode False'.format(self._name))
log.info("VirtualBox VM {name} [id={id}] has disabled the headless mode".format(name=self._name, id=self._id))
self._headless = headless
@property
def vmname(self):
"""
Returns the VM name associated with this VirtualBox VM.
:returns: VirtualBox VM name
"""
return self._vmname
@vmname.setter
def vmname(self, vmname):
"""
Sets the VM name associated with this VirtualBox VM.
:param vmname: VirtualBox VM name
"""
if self._vboxwrapper:
self._vboxwrapper.send('vbox setattr "{}" image "{}"'.format(self._name, vmname))
else:
try:
self._machine = self._vboxmanager.vbox.findMachine(vmname)
except Exception as e:
raise VirtualBoxError("VirtualBox error: {}".format(e))
# The maximum support network cards depends on the Chipset (PIIX3 or ICH9)
self._maximum_adapters = self._vboxmanager.vbox.systemProperties.getMaxNetworkAdapters(self._machine.chipsetType)
log.info("VirtualBox VM {name} [id={id}] has set the VM name to {vmname}".format(name=self._name, id=self._id, vmname=vmname))
self._vmname = vmname
@property
def adapters(self):
"""
Returns the number of Ethernet adapters for this VirtualBox VM instance.
:returns: number of adapters
"""
return len(self._ethernet_adapters)
@adapters.setter
def adapters(self, adapters):
"""
Sets the number of Ethernet adapters for this VirtualBox VM instance.
:param adapters: number of adapters
"""
self._ethernet_adapters.clear()
for _ in range(0, adapters):
self._ethernet_adapters.append(EthernetAdapter())
if self._vboxwrapper:
self._vboxwrapper.send('vbox setattr "{}" nics {}'.format(self._name, len(self._ethernet_adapters)))
log.info("VirtualBox VM {name} [id={id}]: number of Ethernet adapters changed to {adapters}".format(name=self._name,
id=self._id,
adapters=len(self._ethernet_adapters)))
@property
def adapter_type(self):
"""
Returns the adapter type for this VirtualBox VM instance.
:returns: adapter type (string)
"""
return self._adapter_type
@adapter_type.setter
def adapter_type(self, adapter_type):
"""
Sets the adapter type for this VirtualBox VM instance.
:param adapter_type: adapter type (string)
"""
self._adapter_type = adapter_type
if self._vboxwrapper:
self._vboxwrapper.send('vbox setattr "{}" netcard "{}"'.format(self._name, adapter_type))
log.info("VirtualBox VM {name} [id={id}]: adapter type changed to {adapter_type}".format(name=self._name,
id=self._id,
adapter_type=adapter_type))
def start(self): def start(self):
""" """
Starts this VirtualBox VM. Starts this VirtualBox VM.
""" """
pass if self._vboxwrapper:
status = int(self._vboxwrapper.send('vbox status "{}"'.format(self._name))[0])
if status == 6: # paused
self.resume()
return
self._vboxwrapper.send('vbox start "{}"'.format(self._name))
else:
if self._machine.state == self._vboxmanager.constants.MachineState_Paused:
self.resume()
return
self._get_session()
self._set_network_options()
self._set_console_options()
progress = self._launch_vm_process()
log.info("VM is starting with {}% completed".format(progress.percent))
if progress.percent != 100:
# This will happen if you attempt to start VirtualBox with unloaded "vboxdrv" module.
# or have too little RAM or damaged vHDD, or connected to non-existent network.
# We must unlock machine, otherwise it locks the VirtualBox Manager GUI. (on Linux hosts)
self._unlock_machine()
raise VirtualBoxError("Unable to start the VM (failed at {}%)".format(progress.percent))
try:
self._machine.setGuestPropertyValue("NameInGNS3", self._name)
except Exception:
pass
def stop(self): def stop(self):
""" """
Stops this VirtualBox VM. Stops this VirtualBox VM.
""" """
pass if self._vboxwrapper:
self._vboxwrapper.send('vbox stop "{}"'.format(self._name))
else:
try:
progress = self._session.console.powerDown()
# wait for VM to actually go down
progress.waitForCompletion(-1)
log.info("VM is stopping with {}% completed".format(self.vmname, progress.percent))
# def port_add_nio_binding(self, port_id, nio): self._lock_machine()
# """ for adapter_id in range(0, len(self._ethernet_adapters)):
# Adds a port NIO binding. self._disable_adapter(adapter_id, disable=True)
# self._session.machine.saveSettings()
# :param port_id: port ID self._unlock_machine()
# :param nio: NIO instance to add to the slot/port except Exception as e:
# """ # Do not crash "vboxwrapper", if stopping VM fails.
# # But return True anyway, so VM state in GNS3 can become "stopped"
# if not self._ethernet_adapter.port_exists(port_id): # This can happen, if user manually kills VBox VM.
# raise VPCSError("Port {port_id} doesn't exist in adapter {adapter}".format(adapter=self._ethernet_adapter, log.warn("could not stop VM for {}: {}".format(self._vmname, e))
# port_id=port_id)) return
#
# self._ethernet_adapter.add_nio(port_id, nio)
# log.info("VPCS {name} [id={id}]: {nio} added to port {port_id}".format(name=self._name,
# id=self._id,
# nio=nio,
# port_id=port_id))
# def port_remove_nio_binding(self, port_id): def suspend(self):
# """ """
# Removes a port NIO binding. Suspends this VirtualBox VM.
# """
# :param port_id: port ID
# if self._vboxwrapper:
# :returns: NIO instance self._vboxwrapper.send('vbox suspend "{}"'.format(self._name))
# """ else:
# try:
# if not self._ethernet_adapter.port_exists(port_id): self._session.console.pause()
# raise VPCSError("Port {port_id} doesn't exist in adapter {adapter}".format(adapter=self._ethernet_adapter, except Exception as e:
# port_id=port_id)) raise VirtualBoxError("VirtualBox error: {}".format(e))
#
# nio = self._ethernet_adapter.get_nio(port_id) def reload(self):
# self._ethernet_adapter.remove_nio(port_id) """
# log.info("VPCS {name} [id={id}]: {nio} removed from port {port_id}".format(name=self._name, Reloads this VirtualBox VM.
# id=self._id, """
# nio=nio,
# port_id=port_id)) if self._vboxwrapper:
# return nio self._vboxwrapper.send('vbox reset "{}"'.format(self._name))
else:
try:
progress = self._session.console.reset()
progress.waitForCompletion(-1)
except Exception as e:
raise VirtualBoxError("VirtualBox error: {}".format(e))
def resume(self):
"""
Resumes this VirtualBox VM.
"""
if self._vboxwrapper:
self._vboxwrapper.send('vbox resume "{}"'.format(self._name))
else:
try:
self._session.console.resume()
except Exception as e:
raise VirtualBoxError("VirtualBox error: {}".format(e))
def port_add_nio_binding(self, adapter_id, nio):
"""
Adds a port NIO binding.
:param adapter_id: adapter ID
:param nio: NIO instance to add to the slot/port
"""
try:
adapter = self._ethernet_adapters[adapter_id]
except IndexError:
raise VirtualBoxError("Adapter {adapter_id} doesn't exist on VirtualBox VM {name}".format(name=self._name,
adapter_id=adapter_id))
if self._vboxwrapper:
self._vboxwrapper.send('vbox create_udp "{}" {} {} {} {}'.format(self._name,
adapter_id,
nio.lport,
nio.rhost,
nio.rport))
else:
self._create_udp(adapter_id, nio.lport, nio.rhost, nio.rport)
adapter.add_nio(0, nio)
log.info("VirtualBox VM {name} [id={id}]: {nio} added to adapter {adapter_id}".format(name=self._name,
id=self._id,
nio=nio,
adapter_id=adapter_id))
def port_remove_nio_binding(self, adapter_id):
"""
Removes a port NIO binding.
:param adapter_id: adapter ID
:returns: NIO instance
"""
try:
adapter = self._ethernet_adapters[adapter_id]
except IndexError:
raise VirtualBoxError("Adapter {adapter_id} doesn't exist on VirtualBox VM {name}".format(name=self._name,
adapter_id=adapter_id))
if self._vboxwrapper:
self._vboxwrapper.send('vbox delete_udp "{}" {}'.format(self._name,
adapter_id))
else:
self._delete_udp(adapter_id)
nio = adapter.get_nio(0)
adapter.remove_nio(0)
log.info("VirtualBox VM {name} [id={id}]: {nio} removed from adapter {adapter_id}".format(name=self._name,
id=self._id,
nio=nio,
adapter_id=adapter_id))
return nio
def start_capture(self, adapter_id, output_file):
"""
Starts a packet capture.
:param adapter_id: adapter ID
:param output_file: PCAP destination file for the capture
"""
try:
adapter = self._ethernet_adapters[adapter_id]
except IndexError:
raise VirtualBoxError("Adapter {adapter_id} doesn't exist on VirtualBox VM {name}".format(name=self._name,
adapter_id=adapter_id))
nio = adapter.get_nio(0)
if nio.capturing:
raise VirtualBoxError("Packet capture is already activated on adapter {adapter_id}".format(adapter_id=adapter_id))
try:
os.makedirs(os.path.dirname(output_file))
except FileExistsError:
pass
except OSError as e:
raise VirtualBoxError("Could not create captures directory {}".format(e))
nio.startPacketCapture(output_file)
if self._vboxwrapper:
self._vboxwrapper.send('vbox create_capture "{}" {} "{}"'.format(self._name,
adapter_id,
output_file))
log.info("VirtualBox VM {name} [id={id}]: starting packet capture on adapter {adapter_id}".format(name=self._name,
id=self._id,
adapter_id=adapter_id))
def stop_capture(self, adapter_id):
"""
Stops a packet capture.
:param adapter_id: adapter ID
"""
try:
adapter = self._ethernet_adapters[adapter_id]
except IndexError:
raise VirtualBoxError("Adapter {adapter_id} doesn't exist on VirtualBox VM {name}".format(name=self._name,
adapter_id=adapter_id))
nio = adapter.get_nio(0)
nio.stopPacketCapture()
if self._vboxwrapper:
self._vboxwrapper.send('vbox delete_capture "{}" {}'.format(self._name,
adapter_id))
log.info("VirtualBox VM {name} [id={id}]: stopping packet capture on adapter {adapter_id}".format(name=self._name,
id=self._id,
adapter_id=adapter_id))
def _get_session(self):
log.debug("getting session for {}".format(self._vmname))
try:
self._session = self._vboxmanager.mgr.getSessionObject(self._vboxmanager.vbox)
except Exception as e:
# fails on heavily loaded hosts...
raise VirtualBoxError("VirtualBox error: {}".format(e))
def _set_network_options(self):
log.debug("setting network options for {}".format(self._vmname))
self._lock_machine()
first_adapter_type = self._vboxmanager.constants.NetworkAdapterType_I82540EM
try:
first_adapter = self._session.machine.getNetworkAdapter(0)
first_adapter_type = first_adapter.adapterType
except Exception as e:
pass
#raise VirtualBoxError("VirtualBox error: {}".format(e))
for adapter_id in range(0, len(self._ethernet_adapters)):
try:
# VirtualBox starts counting from 0
adapter = self._session.machine.getNetworkAdapter(adapter_id)
adapter_type = adapter.adapterType
if self._adapter_type == "PCnet-PCI II (Am79C970A)":
adapter_type = self._vboxmanager.constants.NetworkAdapterType_Am79C970A
if self._adapter_type == "PCNet-FAST III (Am79C973)":
adapter_type = self._vboxmanager.constants.NetworkAdapterType_Am79C973
if self._adapter_type == "Intel PRO/1000 MT Desktop (82540EM)":
adapter_type = self._vboxmanager.constants.NetworkAdapterType_I82540EM
if self._adapter_type == "Intel PRO/1000 T Server (82543GC)":
adapter_type = self._vboxmanager.constants.NetworkAdapterType_I82543GC
if self._adapter_type == "Intel PRO/1000 MT Server (82545EM)":
adapter_type = self._vboxmanager.constants.NetworkAdapterType_I82545EM
if self._adapter_type == "Paravirtualized Network (virtio-net)":
adapter_type = self._vboxmanager.constants.NetworkAdapterType_Virtio
if self._adapter_type == "Automatic": # "Auto-guess, based on first NIC"
adapter_type = first_adapter_type
adapter.adapterType = adapter_type
except Exception as e:
raise VirtualBoxError("VirtualBox error: {}".format(e))
nio = self._ethernet_adapters[adapter_id].get_nio(0)
if nio:
log.debug("setting UDP params on adapter {}".format(adapter_id))
try:
adapter.enabled = True
adapter.cableConnected = True
adapter.traceEnabled = False
# Temporary hack around VBox-UDP patch limitation: inability to use DNS
if nio.rhost == 'localhost':
rhost = '127.0.0.1'
else:
rhost = nio.rhost
adapter.attachmentType = self._vboxmanager.constants.NetworkAttachmentType_Generic
adapter.genericDriver = "UDPTunnel"
adapter.setProperty("sport", str(nio.lport))
adapter.setProperty("dest", rhost)
adapter.setProperty("dport", str(nio.rport))
except Exception as e:
# usually due to COM Error: "The object is not ready"
raise VirtualBoxError("VirtualBox error: {}".format(e))
if nio.capturing:
self._enable_capture(adapter, nio.pcap_output_file)
else:
# shutting down unused adapters...
try:
adapter.enabled = True
adapter.attachmentType = self._vboxmanager.constants.NetworkAttachmentType_Null
adapter.cableConnected = False
except Exception as e:
raise VirtualBoxError("VirtualBox error: {}".format(e))
#for adapter_id in range(len(self._ethernet_adapters), self._maximum_adapters):
# log.debug("disabling remaining adapter {}".format(adapter_id))
# self._disable_adapter(adapter_id)
try:
self._session.machine.saveSettings()
except Exception as e:
raise VirtualBoxError("VirtualBox error: {}".format(e))
self._unlock_machine()
def _disable_adapter(self, adapter_id, disable=True):
log.debug("disabling network adapter for {}".format(self._vmname))
# this command is retried several times, because it fails more often...
retries = 6
last_exception = None
for retry in range(retries):
if retry == (retries - 1):
raise VirtualBoxError("Could not disable network adapter after 4 retries: {}".format(last_exception))
try:
adapter = self._session.machine.getNetworkAdapter(adapter_id)
adapter.traceEnabled = False
adapter.attachmentType = self._vboxmanager.constants.NetworkAttachmentType_Null
if disable:
adapter.enabled = False
break
except Exception as e:
# usually due to COM Error: "The object is not ready"
log.warn("cannot disable network adapter for {}, retrying {}: {}".format(self._vmname, retry + 1, e))
last_exception = e
time.sleep(1)
continue
def _enable_capture(self, adapter, output_file):
log.debug("enabling capture for {}".format(self._vmname))
# this command is retried several times, because it fails more often...
retries = 4
last_exception = None
for retry in range(retries):
if retry == (retries - 1):
raise VirtualBoxError("Could not enable packet capture after 4 retries: {}".format(last_exception))
try:
adapter.traceEnabled = True
adapter.traceFile = output_file
break
except Exception as e:
log.warn("cannot enable packet capture for {}, retrying {}: {}".format(self._vmname, retry + 1, e))
last_exception = e
time.sleep(0.75)
continue
def _create_udp(self, adapter_id, sport, daddr, dport):
if self._machine.state >= self._vboxmanager.constants.MachineState_FirstOnline and \
self._machine.state <= self._vboxmanager.constants.MachineState_LastOnline:
# the machine is being executed
retries = 4
last_exception = None
for retry in range(retries):
if retry == (retries - 1):
raise VirtualBoxError("Could not create an UDP tunnel after 4 retries :{}".format(last_exception))
try:
adapter = self._session.machine.getNetworkAdapter(adapter_id)
adapter.cableConnected = True
adapter.attachmentType = self._vboxmanager.constants.NetworkAttachmentType_Null
self._session.machine.saveSettings()
adapter.attachmentType = self._vboxmanager.constants.NetworkAttachmentType_Generic
adapter.genericDriver = "UDPTunnel"
adapter.setProperty("sport", str(sport))
adapter.setProperty("dest", daddr)
adapter.setProperty("dport", str(dport))
self._session.machine.saveSettings()
break
except Exception as e:
# usually due to COM Error: "The object is not ready"
log.warn("cannot create UDP tunnel for {}: {}".format(self._vmname, e))
last_exception = e
time.sleep(0.75)
continue
def _delete_udp(self, adapter_id):
if self._machine.state >= self._vboxmanager.constants.MachineState_FirstOnline and \
self._machine.state <= self._vboxmanager.constants.MachineState_LastOnline:
# the machine is being executed
retries = 4
last_exception = None
for retry in range(retries):
if retry == (retries - 1):
raise VirtualBoxError("Could not delete an UDP tunnel after 4 retries :{}".format(last_exception))
try:
adapter = self._session.machine.getNetworkAdapter(adapter_id)
adapter.attachmentType = self._vboxmanager.constants.NetworkAttachmentType_Null
adapter.cableConnected = False
self._session.machine.saveSettings()
break
except Exception as e:
# usually due to COM Error: "The object is not ready"
log.debug("cannot delete UDP tunnel for {}: {}".format(self._vmname, e))
last_exception = e
time.sleep(0.75)
continue
def _set_console_options(self):
log.info("setting console options for {}".format(self.vmname))
self._lock_machine()
# pick a pipe name
p = re.compile('\s+', re.UNICODE)
pipe_name = p.sub("_", self._vmname)
if sys.platform.startswith('win'):
pipe_name = r"\\.\pipe\VBOX\{}".format(pipe_name)
else:
pipe_name = os.path.join(tempfile.gettempdir(), "pipe_{}".format(pipe_name))
try:
serial_port = self._session.machine.getSerialPort(0)
serial_port.enabled = True
serial_port.path = pipe_name
serial_port.hostMode = 1
serial_port.server = True
self._session.machine.saveSettings()
except Exception as e:
raise VirtualBoxError("VirtualBox error: {}".format(e))
self._unlock_machine()
def _launch_vm_process(self):
log.debug("launching VM {}".format(self._vmname))
# this command is retried several times, because it fails more often...
retries = 4
last_exception = None
for retry in range(retries):
if retry == (retries - 1):
raise VirtualBoxError("Could not launch the VM after 4 retries: {}".format(last_exception))
try:
if self._headless:
mode = "headless"
else:
mode = "gui"
log.info("starting {} in {} mode".format(self._vmname, mode))
progress = self._machine.launchVMProcess(self._session, mode, "")
break
except Exception as e:
# This will usually happen if you try to start the same VM twice,
# but may happen on loaded hosts too...
log.warn("cannot launch VM {}, retrying {}: {}".format(self._vmname, retry + 1, e))
last_exception = e
time.sleep(0.6)
continue
try:
progress.waitForCompletion(-1)
except Exception as e:
raise VirtualBoxError("VirtualBox error: {}".format(e))
return progress
def _lock_machine(self):
log.debug("locking machine for {}".format(self._vmname))
# this command is retried several times, because it fails more often...
retries = 4
last_exception = None
for retry in range(retries):
if retry == (retries - 1):
raise VirtualBoxError("Could not lock the machine after 4 retries: {}".format(last_exception))
try:
self._machine.lockMachine(self._session, 1)
break
except Exception as e:
log.warn("cannot lock the machine for {}, retrying {}: {}".format(self._vmname, retry + 1, e))
last_exception = e
time.sleep(1)
continue
def _unlock_machine(self):
log.debug("unlocking machine for {}".format(self._vmname))
# this command is retried several times, because it fails more often...
retries = 4
last_exception = None
for retry in range(retries):
if retry == (retries - 1):
raise VirtualBoxError("Could not unlock the machine after 4 retries: {}".format(last_exception))
try:
self._session.unlockMachine()
break
except Exception as e:
log.warn("cannot unlock the machine for {}, retrying {}: {}".format(self._vmname, retry + 1, e))
time.sleep(1)
last_exception = e
continue

View File

@ -173,8 +173,12 @@ class Server(object):
tornado.autoreload.add_reload_hook(self._reload_callback) tornado.autoreload.add_reload_hook(self._reload_callback)
def signal_handler(signum=None, frame=None): def signal_handler(signum=None, frame=None):
log.warning("Server got signal {}, exiting...".format(signum)) try:
self._cleanup(signum) log.warning("Server got signal {}, exiting...".format(signum))
self._cleanup(signum)
except RuntimeError:
# to ignore logging exception: RuntimeError: reentrant call inside <_io.BufferedWriter name='<stderr>'>
pass
signals = [signal.SIGTERM, signal.SIGINT] signals = [signal.SIGTERM, signal.SIGINT]
if not sys.platform.startswith("win"): if not sys.platform.startswith("win"):