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:
parent
0ef727ce4b
commit
1fb4ab7e33
@ -152,6 +152,7 @@ class JSONRPCWebSocket(tornado.websocket.WebSocketHandler):
|
||||
|
||||
if method not in self.destinations:
|
||||
if request_id:
|
||||
log.warn("JSON-RPC method not found: {}".format(method))
|
||||
return self.write_message(JSONRPCMethodNotFound(request_id)())
|
||||
else:
|
||||
# This is a notification, silently ignore this error...
|
||||
@ -176,11 +177,7 @@ class JSONRPCWebSocket(tornado.websocket.WebSocketHandler):
|
||||
Invoked when the WebSocket is closed.
|
||||
"""
|
||||
|
||||
try:
|
||||
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)
|
||||
|
||||
# Reset the modules if there are no clients anymore
|
||||
|
@ -19,8 +19,8 @@
|
||||
VirtualBox server module.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import base64
|
||||
import socket
|
||||
import shutil
|
||||
|
||||
@ -28,18 +28,28 @@ from gns3server.modules import IModule
|
||||
from gns3server.config import Config
|
||||
from .virtualbox_vm import VirtualBoxVM
|
||||
from .virtualbox_error import VirtualBoxError
|
||||
from .vboxwrapper_client import VboxWrapperClient
|
||||
from .nios.nio_udp import NIO_UDP
|
||||
from ..attic import find_unused_port
|
||||
|
||||
#from .schemas import VBOX_CREATE_SCHEMA
|
||||
#from .schemas import VBOX_DELETE_SCHEMA
|
||||
#from .schemas import VBOX_UPDATE_SCHEMA
|
||||
#from .schemas import VBOX_START_SCHEMA
|
||||
#from .schemas import VBOX_STOP_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
|
||||
if sys.platform.startswith("win"):
|
||||
# automatically generate the Typelib wrapper
|
||||
import win32com
|
||||
win32com.client.gencache.is_readonly = False
|
||||
win32com.client.gencache.GetGeneratePath()
|
||||
|
||||
from .schemas import VBOX_CREATE_SCHEMA
|
||||
from .schemas import VBOX_DELETE_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
|
||||
log = logging.getLogger(__name__)
|
||||
@ -56,6 +66,26 @@ class VirtualBox(IModule):
|
||||
|
||||
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
|
||||
IModule.__init__(self, name, *args, **kwargs)
|
||||
self._vbox_instances = {}
|
||||
@ -71,6 +101,43 @@ class VirtualBox(IModule):
|
||||
self._projects_dir = kwargs["projects_dir"]
|
||||
self._tempdir = kwargs["temp_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):
|
||||
"""
|
||||
@ -84,6 +151,9 @@ class VirtualBox(IModule):
|
||||
vbox_instance = self._vbox_instances[vbox_id]
|
||||
vbox_instance.delete()
|
||||
|
||||
if self._vboxwrapper and self._vboxwrapper.started:
|
||||
self._vboxwrapper.stop()
|
||||
|
||||
IModule.stop(self, signum) # this will stop the I/O loop
|
||||
|
||||
def get_vbox_instance(self, vbox_id):
|
||||
@ -120,6 +190,9 @@ class VirtualBox(IModule):
|
||||
self._vbox_instances.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")
|
||||
|
||||
@IModule.route("virtualbox.settings")
|
||||
@ -129,6 +202,7 @@ class VirtualBox(IModule):
|
||||
|
||||
Optional request parameters:
|
||||
- working_dir (path to a working directory)
|
||||
- vboxwrapper_path (path to vboxwrapper)
|
||||
- project_name
|
||||
- console_start_port_range
|
||||
- console_end_port_range
|
||||
@ -165,6 +239,9 @@ class VirtualBox(IModule):
|
||||
vbox_instance = self._vbox_instances[vbox_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:
|
||||
self._console_start_port_range = request["console_start_port_range"]
|
||||
self._console_end_port_range = request["console_end_port_range"]
|
||||
@ -195,16 +272,23 @@ class VirtualBox(IModule):
|
||||
"""
|
||||
|
||||
# validate the request
|
||||
#if not self.validate_request(request, VBOX_CREATE_SCHEMA):
|
||||
# return
|
||||
if not self.validate_request(request, VBOX_CREATE_SCHEMA):
|
||||
return
|
||||
|
||||
name = request["name"]
|
||||
vmname = request["vmname"]
|
||||
console = request.get("console")
|
||||
vbox_id = request.get("vbox_id")
|
||||
|
||||
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._host,
|
||||
vbox_id,
|
||||
@ -239,8 +323,8 @@ class VirtualBox(IModule):
|
||||
"""
|
||||
|
||||
# validate the request
|
||||
#if not self.validate_request(request, VBOX_DELETE_SCHEMA):
|
||||
# return
|
||||
if not self.validate_request(request, VBOX_DELETE_SCHEMA):
|
||||
return
|
||||
|
||||
# get the instance
|
||||
vbox_instance = self.get_vbox_instance(request["id"])
|
||||
@ -274,8 +358,8 @@ class VirtualBox(IModule):
|
||||
"""
|
||||
|
||||
# validate the request
|
||||
#if not self.validate_request(request, VBOX_UPDATE_SCHEMA):
|
||||
# return
|
||||
if not self.validate_request(request, VBOX_UPDATE_SCHEMA):
|
||||
return
|
||||
|
||||
# get the instance
|
||||
vbox_instance = self.get_vbox_instance(request["id"])
|
||||
@ -310,8 +394,8 @@ class VirtualBox(IModule):
|
||||
"""
|
||||
|
||||
# validate the request
|
||||
#if not self.validate_request(request, VBOX_START_SCHEMA):
|
||||
# return
|
||||
if not self.validate_request(request, VBOX_START_SCHEMA):
|
||||
return
|
||||
|
||||
# get the instance
|
||||
vbox_instance = self.get_vbox_instance(request["id"])
|
||||
@ -340,8 +424,8 @@ class VirtualBox(IModule):
|
||||
"""
|
||||
|
||||
# validate the request
|
||||
#if not self.validate_request(request, VBOX_STOP_SCHEMA):
|
||||
# return
|
||||
if not self.validate_request(request, VBOX_STOP_SCHEMA):
|
||||
return
|
||||
|
||||
# get the instance
|
||||
vbox_instance = self.get_vbox_instance(request["id"])
|
||||
@ -370,18 +454,76 @@ class VirtualBox(IModule):
|
||||
"""
|
||||
|
||||
# validate the request
|
||||
#if not self.validate_request(request, VBOX_RELOAD_SCHEMA):
|
||||
# return
|
||||
if not self.validate_request(request, VBOX_RELOAD_SCHEMA):
|
||||
return
|
||||
|
||||
# get the instance
|
||||
vbox_instance = self.get_vpcs_instance(request["id"])
|
||||
vbox_instance = self.get_vbox_instance(request["id"])
|
||||
if not vbox_instance:
|
||||
return
|
||||
|
||||
try:
|
||||
vbox_instance.reload()
|
||||
except VirtualBoxError as e:
|
||||
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:
|
||||
if vbox_instance.is_running():
|
||||
vbox_instance.stop()
|
||||
vbox_instance.start()
|
||||
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:
|
||||
self.send_custom_error(str(e))
|
||||
return
|
||||
@ -404,8 +546,8 @@ class VirtualBox(IModule):
|
||||
"""
|
||||
|
||||
# validate the request
|
||||
#if not self.validate_request(request, VBOX_ALLOCATE_UDP_PORT_SCHEMA):
|
||||
# return
|
||||
if not self.validate_request(request, VBOX_ALLOCATE_UDP_PORT_SCHEMA):
|
||||
return
|
||||
|
||||
# get the instance
|
||||
vbox_instance = self.get_vbox_instance(request["id"])
|
||||
@ -454,8 +596,8 @@ class VirtualBox(IModule):
|
||||
"""
|
||||
|
||||
# validate the request
|
||||
#if not self.validate_request(request, VBOX_ADD_NIO_SCHEMA):
|
||||
# return
|
||||
if not self.validate_request(request, VBOX_ADD_NIO_SCHEMA):
|
||||
return
|
||||
|
||||
# get the instance
|
||||
vbox_instance = self.get_vbox_instance(request["id"])
|
||||
@ -506,8 +648,8 @@ class VirtualBox(IModule):
|
||||
"""
|
||||
|
||||
# validate the request
|
||||
#if not self.validate_request(request, VBOX_DELETE_NIO_SCHEMA):
|
||||
# return
|
||||
if not self.validate_request(request, VBOX_DELETE_NIO_SCHEMA):
|
||||
return
|
||||
|
||||
# get the instance
|
||||
vbox_instance = self.get_vbox_instance(request["id"])
|
||||
@ -525,6 +667,110 @@ class VirtualBox(IModule):
|
||||
|
||||
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")
|
||||
def echo(self, request):
|
||||
"""
|
||||
|
65
gns3server/modules/virtualbox/nios/nio.py
Normal file
65
gns3server/modules/virtualbox/nios/nio.py
Normal 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
|
@ -19,8 +19,10 @@
|
||||
Interface for UDP NIOs.
|
||||
"""
|
||||
|
||||
from .nio import NIO
|
||||
|
||||
class NIO_UDP(object):
|
||||
|
||||
class NIO_UDP(NIO):
|
||||
"""
|
||||
IOU UDP NIO.
|
||||
|
||||
@ -33,6 +35,7 @@ class NIO_UDP(object):
|
||||
|
||||
def __init__(self, lport, rhost, rport):
|
||||
|
||||
NIO.__init__(self)
|
||||
self._lport = lport
|
||||
self._rhost = rhost
|
||||
self._rport = rport
|
||||
|
418
gns3server/modules/virtualbox/schemas.py
Normal file
418
gns3server/modules/virtualbox/schemas.py
Normal 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"]
|
||||
}
|
||||
|
1
gns3server/modules/virtualbox/vboxapi_py3
Submodule
1
gns3server/modules/virtualbox/vboxapi_py3
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit ed33ed344d5687302979972f518a4cee17517d28
|
@ -26,6 +26,7 @@ import tempfile
|
||||
import socket
|
||||
import re
|
||||
|
||||
from ..attic import wait_socket_is_ready
|
||||
from .virtualbox_error import VirtualBoxError
|
||||
|
||||
import logging
|
||||
@ -51,6 +52,7 @@ class VboxWrapperClient(object):
|
||||
self._path = path
|
||||
self._command = []
|
||||
self._process = None
|
||||
self._working_dir = working_dir
|
||||
self._stdout_file = ""
|
||||
self._started = False
|
||||
self._host = host
|
||||
@ -144,21 +146,47 @@ class VboxWrapperClient(object):
|
||||
stderr=subprocess.STDOUT,
|
||||
cwd=self._working_dir)
|
||||
log.info("VirtualBox wrapper started PID={}".format(self._process.pid))
|
||||
self.wait_for_vboxwrapper(self._host, self._port)
|
||||
self.connect()
|
||||
self._started = True
|
||||
except OSError as e:
|
||||
log.error("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):
|
||||
"""
|
||||
Stops the VirtualBox wrapper process.
|
||||
"""
|
||||
|
||||
if self.is_running():
|
||||
self.send("hypervisor stop")
|
||||
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():
|
||||
log.info("stopping VirtualBox wrapper PID={}".format(self._process.pid))
|
||||
try:
|
||||
# give some time for the VirtualBox wrapper to properly stop.
|
||||
@ -210,10 +238,10 @@ class VboxWrapperClient(object):
|
||||
"""
|
||||
|
||||
command = [self._path]
|
||||
#if self._host != "0.0.0.0" and self._host != "::":
|
||||
# command.extend(["-H", "{}:{}".format(self._host, self._port)])
|
||||
#else:
|
||||
# command.extend(["-H", str(self._port)])
|
||||
if self._host != "0.0.0.0" and self._host != "::":
|
||||
command.extend(["-l", self._host, "-p", str(self._port)])
|
||||
else:
|
||||
command.extend(["-p", str(self._port)])
|
||||
return command
|
||||
|
||||
def connect(self):
|
||||
@ -235,6 +263,17 @@ class VboxWrapperClient(object):
|
||||
except OSError as 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):
|
||||
"""
|
||||
Resets the VirtualBox wrapper (used to get an empty configuration).
|
||||
@ -276,6 +315,15 @@ class VboxWrapperClient(object):
|
||||
assert 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):
|
||||
"""
|
||||
Sends commands to the VirtualBox wrapper.
|
||||
@ -306,6 +354,7 @@ class VboxWrapperClient(object):
|
||||
log.debug("sending {}".format(command))
|
||||
self.socket.sendall(command.encode('utf-8'))
|
||||
except OSError as e:
|
||||
self._socket = None
|
||||
raise VirtualBoxError("Lost communication with {host}:{port} :{error}"
|
||||
.format(host=self._host, port=self._port, error=e))
|
||||
|
||||
@ -317,14 +366,17 @@ class VboxWrapperClient(object):
|
||||
chunk = self.socket.recv(1024)
|
||||
buf += chunk.decode("utf-8")
|
||||
except OSError as e:
|
||||
self._socket = None
|
||||
raise VirtualBoxError("Communication timed out with {host}:{port} :{error}"
|
||||
.format(host=self._host, port=self._port, error=e))
|
||||
|
||||
|
||||
# If the buffer doesn't end in '\n' then we can't be done
|
||||
try:
|
||||
if buf[-1] != '\n':
|
||||
continue
|
||||
except IndexError:
|
||||
self._socket = None
|
||||
raise VirtualBoxError("Could not communicate with {host}:{port}"
|
||||
.format(host=self._host, port=self._port))
|
||||
|
||||
|
@ -19,24 +19,28 @@
|
||||
VirtualBox VM instance.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
import re
|
||||
import time
|
||||
|
||||
from pkg_resources import parse_version
|
||||
from .virtualbox_error import VirtualBoxError
|
||||
from .adapters.ethernet_adapter import EthernetAdapter
|
||||
from .nios.nio_udp import NIO_UDP
|
||||
from ..attic import find_unused_port
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class VirtualBoxVM(object):
|
||||
"""
|
||||
VirtualBox VM implementation.
|
||||
|
||||
:param vboxwrapper client: VboxWrapperClient instance
|
||||
:param vboxmanager: VirtualBox manager from the VirtualBox API
|
||||
: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 host: host/address to bind for console and UDP connections
|
||||
:param vbox_id: VirtalBox VM instance ID
|
||||
@ -49,8 +53,10 @@ class VirtualBoxVM(object):
|
||||
_allocated_console_ports = []
|
||||
|
||||
def __init__(self,
|
||||
vboxwrapper,
|
||||
vboxmanager,
|
||||
name,
|
||||
path,
|
||||
vmname,
|
||||
working_dir,
|
||||
host="127.0.0.1",
|
||||
vbox_id=None,
|
||||
@ -75,19 +81,28 @@ class VirtualBoxVM(object):
|
||||
self._instances.append(self._id)
|
||||
|
||||
self._name = name
|
||||
self._console = console
|
||||
self._working_dir = None
|
||||
self._host = host
|
||||
self._command = []
|
||||
self._vboxwrapper_process = None
|
||||
self._vboxwrapper_stdout_file = ""
|
||||
self._vboxwrapper = vboxwrapper
|
||||
|
||||
self._host = "127.0.0.1"
|
||||
self._started = False
|
||||
self._console_start_port_range = console_start_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
|
||||
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))
|
||||
|
||||
@ -111,6 +126,23 @@ class VirtualBoxVM(object):
|
||||
raise VirtualBoxError("Console port {} is already used by another VirtualBox VM".format(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,
|
||||
id=self._id))
|
||||
|
||||
@ -122,7 +154,11 @@ class VirtualBoxVM(object):
|
||||
"""
|
||||
|
||||
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
|
||||
|
||||
@ -166,6 +202,9 @@ class VirtualBoxVM(object):
|
||||
log.info("VirtualBox VM {name} [id={id}]: renamed to {new_name}".format(name=self._name,
|
||||
id=self._id,
|
||||
new_name=new_name))
|
||||
|
||||
if self._vboxwrapper:
|
||||
self._vboxwrapper.send('vbox rename "{}" "{}"'.format(self._name, new_name))
|
||||
self._name = new_name
|
||||
|
||||
@property
|
||||
@ -222,6 +261,10 @@ class VirtualBoxVM(object):
|
||||
self._allocated_console_ports.remove(self._console)
|
||||
self._console = 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,
|
||||
id=self._id,
|
||||
port=console))
|
||||
@ -238,6 +281,9 @@ class VirtualBoxVM(object):
|
||||
if self.console and self.console in self._allocated_console_ports:
|
||||
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,
|
||||
id=self._id))
|
||||
|
||||
@ -253,6 +299,9 @@ class VirtualBoxVM(object):
|
||||
if self.console:
|
||||
self._allocated_console_ports.remove(self.console)
|
||||
|
||||
if self._vboxwrapper:
|
||||
self._vboxwrapper.send('vbox delete "{}"'.format(self._name))
|
||||
|
||||
try:
|
||||
shutil.rmtree(self._working_dir)
|
||||
except OSError as e:
|
||||
@ -264,11 +313,155 @@ class VirtualBoxVM(object):
|
||||
log.info("VirtualBox VM {name} [id={id}] has been deleted (including associated files)".format(name=self._name,
|
||||
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):
|
||||
"""
|
||||
Starts this VirtualBox VM.
|
||||
"""
|
||||
|
||||
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):
|
||||
@ -276,43 +469,462 @@ class VirtualBoxVM(object):
|
||||
Stops this VirtualBox VM.
|
||||
"""
|
||||
|
||||
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))
|
||||
|
||||
self._lock_machine()
|
||||
for adapter_id in range(0, len(self._ethernet_adapters)):
|
||||
self._disable_adapter(adapter_id, disable=True)
|
||||
self._session.machine.saveSettings()
|
||||
self._unlock_machine()
|
||||
except Exception as e:
|
||||
# Do not crash "vboxwrapper", if stopping VM fails.
|
||||
# But return True anyway, so VM state in GNS3 can become "stopped"
|
||||
# This can happen, if user manually kills VBox VM.
|
||||
log.warn("could not stop VM for {}: {}".format(self._vmname, e))
|
||||
return
|
||||
|
||||
def suspend(self):
|
||||
"""
|
||||
Suspends this VirtualBox VM.
|
||||
"""
|
||||
|
||||
if self._vboxwrapper:
|
||||
self._vboxwrapper.send('vbox suspend "{}"'.format(self._name))
|
||||
else:
|
||||
try:
|
||||
self._session.console.pause()
|
||||
except Exception as e:
|
||||
raise VirtualBoxError("VirtualBox error: {}".format(e))
|
||||
|
||||
def reload(self):
|
||||
"""
|
||||
Reloads this VirtualBox VM.
|
||||
"""
|
||||
|
||||
if self._vboxwrapper:
|
||||
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))
|
||||
|
||||
# def port_add_nio_binding(self, port_id, nio):
|
||||
# """
|
||||
# Adds a port NIO binding.
|
||||
#
|
||||
# :param port_id: port ID
|
||||
# :param nio: NIO instance to add to the slot/port
|
||||
# """
|
||||
#
|
||||
# if not self._ethernet_adapter.port_exists(port_id):
|
||||
# raise VPCSError("Port {port_id} doesn't exist in adapter {adapter}".format(adapter=self._ethernet_adapter,
|
||||
# port_id=port_id))
|
||||
#
|
||||
# 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))
|
||||
nio.startPacketCapture(output_file)
|
||||
|
||||
# def port_remove_nio_binding(self, port_id):
|
||||
# """
|
||||
# Removes a port NIO binding.
|
||||
#
|
||||
# :param port_id: port ID
|
||||
#
|
||||
# :returns: NIO instance
|
||||
# """
|
||||
#
|
||||
# if not self._ethernet_adapter.port_exists(port_id):
|
||||
# raise VPCSError("Port {port_id} doesn't exist in adapter {adapter}".format(adapter=self._ethernet_adapter,
|
||||
# port_id=port_id))
|
||||
#
|
||||
# nio = self._ethernet_adapter.get_nio(port_id)
|
||||
# self._ethernet_adapter.remove_nio(port_id)
|
||||
# log.info("VPCS {name} [id={id}]: {nio} removed from port {port_id}".format(name=self._name,
|
||||
# id=self._id,
|
||||
# nio=nio,
|
||||
# port_id=port_id))
|
||||
# return nio
|
||||
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
|
||||
|
@ -173,8 +173,12 @@ class Server(object):
|
||||
tornado.autoreload.add_reload_hook(self._reload_callback)
|
||||
|
||||
def signal_handler(signum=None, frame=None):
|
||||
try:
|
||||
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]
|
||||
if not sys.platform.startswith("win"):
|
||||
|
Loading…
Reference in New Issue
Block a user