1
0
mirror of https://github.com/GNS3/gns3-server synced 2024-12-26 00:38:10 +00:00

Merge branch 'master' into dev

This commit is contained in:
Jerry Seutter 2014-10-31 10:02:58 -06:00
commit ff02bb977a
7 changed files with 383 additions and 3076 deletions

View File

@ -50,6 +50,7 @@ def find_unused_port(start_port, end_port, host='127.0.0.1', socket_type="TCP",
else: else:
socket_type = socket.SOCK_STREAM socket_type = socket.SOCK_STREAM
last_exception = None
for port in range(start_port, end_port + 1): for port in range(start_port, end_port + 1):
if port in ignore_ports: if port in ignore_ports:
continue continue
@ -63,15 +64,16 @@ def find_unused_port(start_port, end_port, host='127.0.0.1', socket_type="TCP",
s.bind((host, port)) # the port is available if bind is a success s.bind((host, port)) # the port is available if bind is a success
return port return port
except OSError as e: except OSError as e:
if e.errno == errno.EADDRINUSE: # socket already in use last_exception = e
if e.errno == errno.EADDRINUSE or e.errno == errno.EACCES: # socket already in use or permission denied
if port + 1 == end_port: if port + 1 == end_port:
break break
else: else:
continue continue
else: else:
raise Exception("Could not find an unused port: {}".format(e)) raise Exception("Could not find an unused port between {} and {} on host {}: {}".format(start_port, end_port, host, e))
raise Exception("Could not find a free port between {0} and {1}".format(start_port, end_port)) raise Exception("Could not find a free port between {} and {} on host {}, last exception: {}".format(start_port, end_port, host, last_exception))
def wait_socket_is_ready(host, port, wait=2.0, socket_timeout=10): def wait_socket_is_ready(host, port, wait=2.0, socket_timeout=10):

View File

@ -23,13 +23,12 @@ import sys
import os import os
import socket import socket
import shutil import shutil
import subprocess
from pkg_resources import parse_version
from gns3server.modules import IModule 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
@ -61,26 +60,29 @@ class VirtualBox(IModule):
def __init__(self, name, *args, **kwargs): def __init__(self, name, *args, **kwargs):
# get the vboxwrapper location (only non-Windows platforms) # get the vboxmanage location
if not sys.platform.startswith("win"): self._vboxmanage_path = None
if sys.platform.startswith("win") and "VBOX_INSTALL_PATH" in os.environ:
self._vboxmanage_path = os.path.join(os.environ["VBOX_INSTALL_PATH"], "VBoxManage.exe")
else:
config = Config.instance() config = Config.instance()
vbox_config = config.get_section_config(name.upper()) vbox_config = config.get_section_config(name.upper())
self._vboxwrapper_path = vbox_config.get("vboxwrapper_path") self._vboxmanage_path = vbox_config.get("vboxmanage_path")
if not self._vboxwrapper_path or not os.path.isfile(self._vboxwrapper_path): if not self._vboxmanage_path or not os.path.isfile(self._vboxmanage_path):
paths = [os.getcwd()] + os.environ["PATH"].split(os.pathsep) paths = [os.getcwd()] + os.environ["PATH"].split(os.pathsep)
# look for vboxwrapper in the current working directory and $PATH # look for vboxmanage in the current working directory and $PATH
for path in paths: for path in paths:
try: try:
if "vboxwrapper" in os.listdir(path) and os.access(os.path.join(path, "vboxwrapper"), os.X_OK): if "vboxmanage" in os.listdir(path) and os.access(os.path.join(path, "vboxmanage"), os.X_OK):
self._vboxwrapper_path = os.path.join(path, "vboxwrapper") self._vboxmanage_path = os.path.join(path, "vboxmanage")
break break
except OSError: except OSError:
continue continue
if not self._vboxwrapper_path: if not self._vboxmanage_path:
log.warning("vboxwrapper couldn't be found!") log.warning("vboxmanage couldn't be found!")
elif not os.access(self._vboxwrapper_path, os.X_OK): elif not os.access(self._vboxmanage_path, os.X_OK):
log.warning("vboxwrapper is not executable") log.warning("vboxmanage 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)
@ -97,59 +99,6 @@ 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 pywintypes
import win32com.client
try:
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()
win32com.client.gencache.EnsureDispatch("VirtualBox.VirtualBox")
except pywintypes.com_error:
raise VirtualBoxError("VirtualBox is not installed.")
try:
from .vboxapi_py3 import VirtualBoxManager
self._vboxmanager = VirtualBoxManager(None, None)
vbox_major_version, vbox_minor_version, _ = self._vboxmanager.vbox.version.split('.')
if parse_version("{}.{}".format(vbox_major_version, vbox_minor_version)) <= parse_version("4.1"):
raise VirtualBoxError("VirtualBox version must be >= 4.2")
except Exception as e:
self._vboxmanager = None
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()
try:
self._vboxwrapper.start()
except VirtualBoxError:
self._vboxwrapper = None
raise
def stop(self, signum=None): def stop(self, signum=None):
""" """
@ -163,9 +112,6 @@ 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):
@ -202,9 +148,6 @@ 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")
@ -214,7 +157,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) - vboxmanage_path (path to vboxmanage)
- project_name - project_name
- console_start_port_range - console_start_port_range
- console_end_port_range - console_end_port_range
@ -251,8 +194,8 @@ 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: if "vboxmanage_path" in request:
self._vboxwrapper_path = request["vboxwrapper_path"] self._vboxmanage_path = request["vboxmanage_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"]
@ -295,11 +238,10 @@ class VirtualBox(IModule):
try: try:
if not self._vboxwrapper and not self._vboxmanager: if not self._vboxmanage_path or not os.path.exists(self._vboxmanage_path):
self._start_vbox_service() raise VirtualBoxError("Could not find VBoxManage, is VirtualBox correctly installed?")
vbox_instance = VirtualBoxVM(self._vboxwrapper, vbox_instance = VirtualBoxVM(self._vboxmanage_path,
self._vboxmanager,
name, name,
vmname, vmname,
self._working_dir, self._working_dir,
@ -418,10 +360,7 @@ class VirtualBox(IModule):
try: try:
vbox_instance.start() vbox_instance.start()
except VirtualBoxError as e: except VirtualBoxError as e:
if self._vboxwrapper: self.send_custom_error(str(e))
self.send_custom_error("{}: {}".format(e, self._vboxwrapper.read_stderr()))
else:
self.send_custom_error(str(e))
return return
self.send_response(True) self.send_response(True)
@ -769,26 +708,36 @@ class VirtualBox(IModule):
- List of VM names - List of VM names
""" """
if not self._vboxwrapper and not self._vboxmanager: try:
try:
self._start_vbox_service()
except VirtualBoxError as e:
self.send_custom_error(str(e))
return
if self._vboxwrapper: if request and "vboxmanage_path" in request:
vms = self._vboxwrapper.get_vm_list() vboxmanage_path = request["vboxmanage_path"]
elif self._vboxmanager: else:
vms = [] vboxmanage_path = self._vboxmanage_path
machines = self._vboxmanager.getArray(self._vboxmanager.vbox, "machines")
for machine in range(len(machines)): if not vboxmanage_path or not os.path.exists(vboxmanage_path):
vms.append(machines[machine].name) raise VirtualBoxError("Could not find VBoxManage, is VirtualBox correctly installed?")
else:
self.send_custom_error("Vboxmanager hasn't been initialized!") command = [self._vboxmanage_path, "--nologo", "list", "vms"]
try:
result = subprocess.check_output(command, stderr=subprocess.STDOUT, universal_newlines=True, timeout=30)
except subprocess.CalledProcessError as e:
raise VirtualBoxError("Could not execute VBoxManage {}".format(e))
except subprocess.TimeoutExpired:
raise VirtualBoxError("VBoxManage has timed out")
except VirtualBoxError as e:
self.send_custom_error(str(e))
return return
vms = []
lines = result.splitlines()
for line in lines:
vmname, uuid = line.rsplit(' ', 1)
vms.append(vmname.strip('"'))
response = {"server": self._host, response = {"server": self._host,
"vms": vms} "vms": vms}
self.send_response(response) self.send_response(response)
@IModule.route("virtualbox.echo") @IModule.route("virtualbox.echo")

View File

@ -1,612 +0,0 @@
"""
Copyright (C) 2009-2012 Oracle Corporation
This file is part of VirtualBox Open Source Edition (OSE), as
available from http://www.virtualbox.org. This file is free software;
you can redistribute it and/or modify it under the terms of the GNU
General Public License (GPL) as published by the Free Software
Foundation, in version 2 as it comes in the "COPYING" file of the
VirtualBox OSE distribution. VirtualBox OSE is distributed in the
hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
"""
import sys,os
import traceback
# To set Python bitness on OSX use 'export VERSIONER_PYTHON_PREFER_32_BIT=yes'
VboxBinDir = os.environ.get("VBOX_PROGRAM_PATH", None)
VboxSdkDir = os.environ.get("VBOX_SDK_PATH", None)
if VboxBinDir is None:
# Will be set by the installer
VboxBinDir = "C:\\Program Files\\Oracle\\VirtualBox\\"
if VboxSdkDir is None:
# Will be set by the installer
VboxSdkDir = "C:\\Program Files\\Oracle\\VirtualBox\\sdk\\"
os.environ["VBOX_PROGRAM_PATH"] = VboxBinDir
os.environ["VBOX_SDK_PATH"] = VboxSdkDir
sys.path.append(VboxBinDir)
from .VirtualBox_constants import VirtualBoxReflectionInfo
class PerfCollector:
""" This class provides a wrapper over IPerformanceCollector in order to
get more 'pythonic' interface.
To begin collection of metrics use setup() method.
To get collected data use query() method.
It is possible to disable metric collection without changing collection
parameters with disable() method. The enable() method resumes metric
collection.
"""
def __init__(self, mgr, vbox):
""" Initializes the instance.
"""
self.mgr = mgr
self.isMscom = (mgr.type == 'MSCOM')
self.collector = vbox.performanceCollector
def setup(self, names, objects, period, nsamples):
""" Discards all previously collected values for the specified
metrics, sets the period of collection and the number of retained
samples, enables collection.
"""
self.collector.setupMetrics(names, objects, period, nsamples)
def enable(self, names, objects):
""" Resumes metric collection for the specified metrics.
"""
self.collector.enableMetrics(names, objects)
def disable(self, names, objects):
""" Suspends metric collection for the specified metrics.
"""
self.collector.disableMetrics(names, objects)
def query(self, names, objects):
""" Retrieves collected metric values as well as some auxiliary
information. Returns an array of dictionaries, one dictionary per
metric. Each dictionary contains the following entries:
'name': metric name
'object': managed object this metric associated with
'unit': unit of measurement
'scale': divide 'values' by this number to get float numbers
'values': collected data
'values_as_string': pre-processed values ready for 'print' statement
"""
# Get around the problem with input arrays returned in output
# parameters (see #3953) for MSCOM.
if self.isMscom:
(values, names, objects, names_out, objects_out, units, scales, sequence_numbers,
indices, lengths) = self.collector.queryMetricsData(names, objects)
else:
(values, names_out, objects_out, units, scales, sequence_numbers,
indices, lengths) = self.collector.queryMetricsData(names, objects)
out = []
for i in xrange(0, len(names_out)):
scale = int(scales[i])
if scale != 1:
fmt = '%.2f%s'
else:
fmt = '%d %s'
out.append({
'name':str(names_out[i]),
'object':str(objects_out[i]),
'unit':str(units[i]),
'scale':scale,
'values':[int(values[j]) for j in xrange(int(indices[i]), int(indices[i])+int(lengths[i]))],
'values_as_string':'['+', '.join([fmt % (int(values[j])/scale, units[i]) for j in xrange(int(indices[i]), int(indices[i])+int(lengths[i]))])+']'
})
return out
def ComifyName(name):
return name[0].capitalize()+name[1:]
_COMForward = { 'getattr' : None,
'setattr' : None}
def CustomGetAttr(self, attr):
# fastpath
if self.__class__.__dict__.get(attr) != None:
return self.__class__.__dict__.get(attr)
# try case-insensitivity workaround for class attributes (COM methods)
for k in self.__class__.__dict__.keys():
if k.lower() == attr.lower():
setattr(self.__class__, attr, self.__class__.__dict__[k])
return getattr(self, k)
try:
return _COMForward['getattr'](self,ComifyName(attr))
except AttributeError:
return _COMForward['getattr'](self,attr)
def CustomSetAttr(self, attr, value):
try:
return _COMForward['setattr'](self, ComifyName(attr), value)
except AttributeError:
return _COMForward['setattr'](self, attr, value)
class PlatformMSCOM:
# Class to fake access to constants in style of foo.bar.boo
class ConstantFake:
def __init__(self, parent, name):
self.__dict__['_parent'] = parent
self.__dict__['_name'] = name
self.__dict__['_consts'] = {}
try:
self.__dict__['_depth']=parent.__dict__['_depth']+1
except:
self.__dict__['_depth']=0
if self.__dict__['_depth'] > 4:
raise AttributeError
def __getattr__(self, attr):
import win32com
from win32com.client import constants
if attr.startswith("__"):
raise AttributeError
consts = self.__dict__['_consts']
fake = consts.get(attr, None)
if fake != None:
return fake
try:
name = self.__dict__['_name']
parent = self.__dict__['_parent']
while parent != None:
if parent._name is not None:
name = parent._name+'_'+name
parent = parent._parent
if name is not None:
name += "_" + attr
else:
name = attr
return win32com.client.constants.__getattr__(name)
except AttributeError as e:
fake = PlatformMSCOM.ConstantFake(self, attr)
consts[attr] = fake
return fake
class InterfacesWrapper:
def __init__(self):
self.__dict__['_rootFake'] = PlatformMSCOM.ConstantFake(None, None)
def __getattr__(self, a):
import win32com
from win32com.client import constants
if a.startswith("__"):
raise AttributeError
try:
return win32com.client.constants.__getattr__(a)
except AttributeError as e:
return self.__dict__['_rootFake'].__getattr__(a)
VBOX_TLB_GUID = '{46137EEC-703B-4FE5-AFD4-7C9BBBBA0259}'
VBOX_TLB_LCID = 0
VBOX_TLB_MAJOR = 1
VBOX_TLB_MINOR = 0
def __init__(self, params):
from win32com import universal
from win32com.client import gencache, DispatchBaseClass
from win32com.client import constants, getevents
import win32com
import pythoncom
import win32api
from win32con import DUPLICATE_SAME_ACCESS
from win32api import GetCurrentThread,GetCurrentThreadId,DuplicateHandle,GetCurrentProcess
import threading
pid = GetCurrentProcess()
self.tid = GetCurrentThreadId()
handle = DuplicateHandle(pid, GetCurrentThread(), pid, 0, 0, DUPLICATE_SAME_ACCESS)
self.handles = []
self.handles.append(handle)
_COMForward['getattr'] = DispatchBaseClass.__dict__['__getattr__']
DispatchBaseClass.__getattr__ = CustomGetAttr
_COMForward['setattr'] = DispatchBaseClass.__dict__['__setattr__']
DispatchBaseClass.__setattr__ = CustomSetAttr
win32com.client.gencache.EnsureDispatch('VirtualBox.Session')
win32com.client.gencache.EnsureDispatch('VirtualBox.VirtualBox')
self.oIntCv = threading.Condition()
self.fInterrupted = False;
def getSessionObject(self, vbox):
import win32com
from win32com.client import Dispatch
return win32com.client.Dispatch("VirtualBox.Session")
def getVirtualBox(self):
import win32com
from win32com.client import Dispatch
return win32com.client.Dispatch("VirtualBox.VirtualBox")
def getType(self):
return 'MSCOM'
def getRemote(self):
return False
def getArray(self, obj, field):
return obj.__getattr__(field)
def initPerThread(self):
import pythoncom
pythoncom.CoInitializeEx(0)
def deinitPerThread(self):
import pythoncom
pythoncom.CoUninitialize()
def createListener(self, impl, arg):
d = {}
d['BaseClass'] = impl
d['arg'] = arg
d['tlb_guid'] = PlatformMSCOM.VBOX_TLB_GUID
str = ""
str += "import win32com.server.util\n"
str += "import pythoncom\n"
str += "class ListenerImpl(BaseClass):\n"
str += " _com_interfaces_ = ['IEventListener']\n"
str += " _typelib_guid_ = tlb_guid\n"
str += " _typelib_version_ = 1, 0\n"
str += " _reg_clsctx_ = pythoncom.CLSCTX_INPROC_SERVER\n"
# Maybe we'd better implement Dynamic invoke policy, to be more flexible here
str += " _reg_policy_spec_ = 'win32com.server.policy.EventHandlerPolicy'\n"
# capitalized version of listener method
str += " HandleEvent=BaseClass.handleEvent\n"
str += " def __init__(self): BaseClass.__init__(self, arg)\n"
str += "result = win32com.server.util.wrap(ListenerImpl())\n"
exec(str,d,d)
return d['result']
def waitForEvents(self, timeout):
from win32api import GetCurrentThreadId
from win32event import INFINITE
from win32event import MsgWaitForMultipleObjects, \
QS_ALLINPUT, WAIT_TIMEOUT, WAIT_OBJECT_0
from pythoncom import PumpWaitingMessages
import types
if not isinstance(timeout, types.IntType):
raise TypeError("The timeout argument is not an integer")
if (self.tid != GetCurrentThreadId()):
raise Exception("wait for events from the same thread you inited!")
if timeout < 0: cMsTimeout = INFINITE
else: cMsTimeout = timeout
rc = MsgWaitForMultipleObjects(self.handles, 0, cMsTimeout, QS_ALLINPUT)
if rc >= WAIT_OBJECT_0 and rc < WAIT_OBJECT_0+len(self.handles):
# is it possible?
rc = 2;
elif rc==WAIT_OBJECT_0 + len(self.handles):
# Waiting messages
PumpWaitingMessages()
rc = 0;
else:
# Timeout
rc = 1;
# check for interruption
self.oIntCv.acquire()
if self.fInterrupted:
self.fInterrupted = False
rc = 1;
self.oIntCv.release()
return rc;
def interruptWaitEvents(self):
"""
Basically a python implementation of EventQueue::postEvent().
The magic value must be in sync with the C++ implementation or this
won't work.
Note that because of this method we cannot easily make use of a
non-visible Window to handle the message like we would like to do.
"""
from win32api import PostThreadMessage
from win32con import WM_USER
self.oIntCv.acquire()
self.fInterrupted = True
self.oIntCv.release()
try:
PostThreadMessage(self.tid, WM_USER, None, 0xf241b819)
except:
return False;
return True;
def deinit(self):
import pythoncom
from win32file import CloseHandle
for h in self.handles:
if h is not None:
CloseHandle(h)
self.handles = None
pythoncom.CoUninitialize()
pass
def queryInterface(self, obj, klazzName):
from win32com.client import CastTo
return CastTo(obj, klazzName)
class PlatformXPCOM:
def __init__(self, params):
sys.path.append(VboxSdkDir+'/bindings/xpcom/python/')
import xpcom.vboxxpcom
import xpcom
import xpcom.components
def getSessionObject(self, vbox):
import xpcom.components
return xpcom.components.classes["@virtualbox.org/Session;1"].createInstance()
def getVirtualBox(self):
import xpcom.components
return xpcom.components.classes["@virtualbox.org/VirtualBox;1"].createInstance()
def getType(self):
return 'XPCOM'
def getRemote(self):
return False
def getArray(self, obj, field):
return obj.__getattr__('get'+ComifyName(field))()
def initPerThread(self):
import xpcom
xpcom._xpcom.AttachThread()
def deinitPerThread(self):
import xpcom
xpcom._xpcom.DetachThread()
def createListener(self, impl, arg):
d = {}
d['BaseClass'] = impl
d['arg'] = arg
str = ""
str += "import xpcom.components\n"
str += "class ListenerImpl(BaseClass):\n"
str += " _com_interfaces_ = xpcom.components.interfaces.IEventListener\n"
str += " def __init__(self): BaseClass.__init__(self, arg)\n"
str += "result = ListenerImpl()\n"
exec(str,d,d)
return d['result']
def waitForEvents(self, timeout):
import xpcom
return xpcom._xpcom.WaitForEvents(timeout)
def interruptWaitEvents(self):
import xpcom
return xpcom._xpcom.InterruptWait()
def deinit(self):
import xpcom
xpcom._xpcom.DeinitCOM()
def queryInterface(self, obj, klazzName):
import xpcom.components
return obj.queryInterface(getattr(xpcom.components.interfaces, klazzName))
class PlatformWEBSERVICE:
def __init__(self, params):
sys.path.append(os.path.join(VboxSdkDir,'bindings', 'webservice', 'python', 'lib'))
#import VirtualBox_services
import VirtualBox_wrappers
from VirtualBox_wrappers import IWebsessionManager2
if params is not None:
self.user = params.get("user", "")
self.password = params.get("password", "")
self.url = params.get("url", "")
else:
self.user = ""
self.password = ""
self.url = None
self.vbox = None
def getSessionObject(self, vbox):
return self.wsmgr.getSessionObject(vbox)
def getVirtualBox(self):
return self.connect(self.url, self.user, self.password)
def connect(self, url, user, passwd):
if self.vbox is not None:
self.disconnect()
from VirtualBox_wrappers import IWebsessionManager2
if url is None:
url = ""
self.url = url
if user is None:
user = ""
self.user = user
if passwd is None:
passwd = ""
self.password = passwd
self.wsmgr = IWebsessionManager2(self.url)
self.vbox = self.wsmgr.logon(self.user, self.password)
if not self.vbox.handle:
raise Exception("cannot connect to '"+self.url+"' as '"+self.user+"'")
return self.vbox
def disconnect(self):
if self.vbox is not None and self.wsmgr is not None:
self.wsmgr.logoff(self.vbox)
self.vbox = None
self.wsmgr = None
def getType(self):
return 'WEBSERVICE'
def getRemote(self):
return True
def getArray(self, obj, field):
return obj.__getattr__(field)
def initPerThread(self):
pass
def deinitPerThread(self):
pass
def createListener(self, impl, arg):
raise Exception("no active listeners for webservices")
def waitForEvents(self, timeout):
# Webservices cannot do that yet
return 2;
def interruptWaitEvents(self, timeout):
# Webservices cannot do that yet
return False;
def deinit(self):
try:
disconnect()
except:
pass
def queryInterface(self, obj, klazzName):
d = {}
d['obj'] = obj
str = ""
str += "from VirtualBox_wrappers import "+klazzName+"\n"
str += "result = "+klazzName+"(obj.mgr,obj.handle)\n"
# wrong, need to test if class indeed implements this interface
exec(str,d,d)
return d['result']
class SessionManager:
def __init__(self, mgr):
self.mgr = mgr
def getSessionObject(self, vbox):
return self.mgr.platform.getSessionObject(vbox)
class VirtualBoxManager:
def __init__(self, style, platparams):
if style is None:
if sys.platform == 'win32':
style = "MSCOM"
else:
style = "XPCOM"
exec("self.platform = Platform"+style+"(platparams)")
# for webservices, enums are symbolic
self.constants = VirtualBoxReflectionInfo(style == "WEBSERVICE")
self.type = self.platform.getType()
self.remote = self.platform.getRemote()
self.style = style
self.mgr = SessionManager(self)
try:
self.vbox = self.platform.getVirtualBox()
except NameError as ne:
print("Installation problem: check that appropriate libs in place")
traceback.print_exc()
raise ne
except Exception as e:
print("init exception: ",e)
traceback.print_exc()
if self.remote:
self.vbox = None
else:
raise e
def getArray(self, obj, field):
return self.platform.getArray(obj, field)
def getVirtualBox(self):
return self.platform.getVirtualBox()
def __del__(self):
self.deinit()
def deinit(self):
if hasattr(self, "vbox"):
del self.vbox
self.vbox = None
if hasattr(self, "platform"):
self.platform.deinit()
self.platform = None
def initPerThread(self):
self.platform.initPerThread()
def openMachineSession(self, mach, permitSharing = True):
session = self.mgr.getSessionObject(self.vbox)
if permitSharing:
type = self.constants.LockType_Shared
else:
type = self.constants.LockType_Write
mach.lockMachine(session, type)
return session
def closeMachineSession(self, session):
if session is not None:
session.unlockMachine()
def deinitPerThread(self):
self.platform.deinitPerThread()
def createListener(self, impl, arg = None):
return self.platform.createListener(impl, arg)
def waitForEvents(self, timeout):
"""
Wait for events to arrive and process them.
The timeout is in milliseconds. A negative value means waiting for
ever, while 0 does not wait at all.
Returns 0 if events was processed.
Returns 1 if timed out or interrupted in some way.
Returns 2 on error (like not supported for web services).
Raises an exception if the calling thread is not the main thread (the one
that initialized VirtualBoxManager) or if the time isn't an integer.
"""
return self.platform.waitForEvents(timeout)
def interruptWaitEvents(self):
"""
Interrupt a waitForEvents call.
This is normally called from a worker thread.
Returns True on success, False on failure.
"""
return self.platform.interruptWaitEvents()
def getPerfCollector(self, vbox):
return PerfCollector(self, vbox)
def getBinDir(self):
global VboxBinDir
return VboxBinDir
def getSdkDir(self):
global VboxSdkDir
return VboxSdkDir
def queryInterface(self, obj, klazzName):
return self.platform.queryInterface(obj, klazzName)

View File

@ -1,422 +0,0 @@
# -*- 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/>.
"""
Client to VirtualBox wrapper.
"""
import os
import time
import subprocess
import tempfile
import socket
import re
from pkg_resources import parse_version
from ..attic import wait_socket_is_ready
from .virtualbox_error import VirtualBoxError
import logging
log = logging.getLogger(__name__)
class VboxWrapperClient(object):
"""
VirtualBox Wrapper client.
:param path: path to VirtualBox wrapper executable
:param working_dir: working directory
:param port: port
:param host: host/address
"""
# Used to parse the VirtualBox wrapper response codes
error_re = re.compile(r"""^2[0-9]{2}-""")
success_re = re.compile(r"""^1[0-9]{2}\s{1}""")
def __init__(self, path, working_dir, host, port=11525, timeout=30.0):
self._path = path
self._command = []
self._process = None
self._working_dir = working_dir
self._stderr_file = ""
self._started = False
self._host = host
self._port = port
self._timeout = timeout
self._socket = None
@property
def started(self):
"""
Returns either VirtualBox wrapper has been started or not.
:returns: boolean
"""
return self._started
@property
def path(self):
"""
Returns the path to the VirtualBox wrapper executable.
:returns: path to VirtualBox wrapper
"""
return self._path
@path.setter
def path(self, path):
"""
Sets the path to the VirtualBox wrapper executable.
:param path: path to VirtualBox wrapper
"""
self._path = path
@property
def port(self):
"""
Returns the port used to start the VirtualBox wrapper.
:returns: port number (integer)
"""
return self._port
@port.setter
def port(self, port):
"""
Sets the port used to start the VirtualBox wrapper.
:param port: port number (integer)
"""
self._port = port
@property
def host(self):
"""
Returns the host (binding) used to start the VirtualBox wrapper.
:returns: host/address (string)
"""
return self._host
@host.setter
def host(self, host):
"""
Sets the host (binding) used to start the VirtualBox wrapper.
:param host: host/address (string)
"""
self._host = host
def start(self):
"""
Starts the VirtualBox wrapper process.
"""
self._command = self._build_command()
try:
log.info("starting VirtualBox wrapper: {}".format(self._command))
with tempfile.NamedTemporaryFile(delete=False) as fd:
with open(os.devnull, "w") as null:
self._stderr_file = fd.name
log.info("VirtualBox wrapper process logging to {}".format(fd.name))
self._process = subprocess.Popen(self._command,
stdout=null,
stderr=fd,
cwd=self._working_dir)
log.info("VirtualBox wrapper started PID={}".format(self._process.pid))
time.sleep(0.1) # give some time for vboxwrapper to start
if self._process.poll() is not None:
raise VirtualBoxError("Could not start VirtualBox wrapper: {}".format(self.read_stderr()))
self.wait_for_vboxwrapper(self._host, self._port)
self.connect()
self._started = True
version = self.send('vboxwrapper version')[0]
if parse_version(version) < parse_version("0.9.1"):
self.stop()
raise VirtualBoxError("VirtualBox wrapper version must be >= 0.9.1")
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.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.
time.sleep(0.01)
self._process.terminate()
self._process.wait(1)
except subprocess.TimeoutExpired:
self._process.kill()
if self._process.poll() is None:
log.warn("VirtualBox wrapper process {} is still running".format(self._process.pid))
if self._stderr_file and os.access(self._stderr_file, os.W_OK):
try:
os.remove(self._stderr_file)
except OSError as e:
log.warning("could not delete temporary VirtualBox wrapper log file: {}".format(e))
self._started = False
def read_stderr(self):
"""
Reads the standard error output of the VirtualBox wrapper process.
Only use when the process has been stopped or has crashed.
"""
output = ""
if self._stderr_file and os.access(self._stderr_file, os.R_OK):
try:
with open(self._stderr_file, errors="replace") as file:
output = file.read()
except OSError as e:
log.warn("could not read {}: {}".format(self._stderr_file, e))
return output
def is_running(self):
"""
Checks if the process is running
:returns: True or False
"""
if self._process and self._process.poll() is None:
return True
return False
def _build_command(self):
"""
Command to start the VirtualBox wrapper process.
(to be passed to subprocess.Popen())
"""
command = [self._path]
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):
"""
Connects to the VirtualBox wrapper.
"""
# connect to a local address by default
# if listening to all addresses (IPv4 or IPv6)
if self._host == "0.0.0.0":
host = "127.0.0.1"
elif self._host == "::":
host = "::1"
else:
host = self._host
try:
self._socket = socket.create_connection((host, self._port), self._timeout)
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).
"""
pass
@property
def working_dir(self):
"""
Returns current working directory
:returns: path to the working directory
"""
return self._working_dir
@working_dir.setter
def working_dir(self, working_dir):
"""
Sets the working directory for the VirtualBox wrapper.
:param working_dir: path to the working directory
"""
# encase working_dir in quotes to protect spaces in the path
#self.send("hypervisor working_dir {}".format('"' + working_dir + '"'))
self._working_dir = working_dir
log.debug("working directory set to {}".format(self._working_dir))
@property
def socket(self):
"""
Returns the current socket used to communicate with the VirtualBox wrapper.
:returns: socket instance
"""
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.
:param command: a VirtualBox wrapper command
:returns: results as a list
"""
# VirtualBox wrapper responses are of the form:
# 1xx yyyyyy\r\n
# 1xx yyyyyy\r\n
# ...
# 100-yyyy\r\n
# or
# 2xx-yyyy\r\n
#
# Where 1xx is a code from 100-199 for a success or 200-299 for an error
# The result might be multiple lines and might be less than the buffer size
# but still have more data. The only thing we know for sure is the last line
# will begin with '100-' or a '2xx-' and end with '\r\n'
if not self._socket:
raise VirtualBoxError("Not connected")
try:
command = command.strip() + '\n'
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))
# Now retrieve the result
data = []
buf = ''
while True:
try:
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))
data += buf.split('\r\n')
if data[-1] == '':
data.pop()
buf = ''
if len(data) == 0:
raise VirtualBoxError("no data returned from {host}:{port}"
.format(host=self._host, port=self._port))
# Does it contain an error code?
if self.error_re.search(data[-1]):
raise VirtualBoxError(data[-1][4:])
# Or does the last line begin with '100-'? Then we are done!
if data[-1][:4] == '100-':
data[-1] = data[-1][4:]
if data[-1] == 'OK':
data.pop()
break
# Remove success responses codes
for index in range(len(data)):
if self.success_re.search(data[index]):
data[index] = data[index][4:]
log.debug("returned result {}".format(data))
return data

View File

@ -1,555 +0,0 @@
# -*- 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/>.
"""
Controls VirtualBox using the VBox API.
"""
import sys
import os
import tempfile
import re
import time
import socket
import subprocess
if sys.platform.startswith('win'):
import msvcrt
import win32file
from .virtualbox_error import VirtualBoxError
from .pipe_proxy import PipeProxy
import logging
log = logging.getLogger(__name__)
class VirtualBoxController(object):
def __init__(self, vmname, vboxmanager, host):
self._host = host
self._machine = None
self._session = None
self._vboxmanager = vboxmanager
self._maximum_adapters = 0
self._serial_pipe_thread = None
self._serial_pipe = None
self._vmname = vmname
self._console = 0
self._adapters = []
self._headless = False
self._enable_console = False
self._adapter_type = "Automatic"
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)
@property
def vmname(self):
return self._vmname
@vmname.setter
def vmname(self, new_vmname):
self._vmname = new_vmname
try:
self._machine = self._vboxmanager.vbox.findMachine(new_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)
@property
def console(self):
return self._console
@console.setter
def console(self, console):
self._console = console
@property
def headless(self):
return self._headless
@headless.setter
def headless(self, headless):
self._headless = headless
@property
def enable_console(self):
return self._enable_console
@enable_console.setter
def enable_console(self, enable_console):
self._enable_console = enable_console
@property
def adapters(self):
return self._adapters
@adapters.setter
def adapters(self, adapters):
self._adapters = adapters
@property
def adapter_type(self):
return self._adapter_type
@adapter_type.setter
def adapter_type(self, adapter_type):
self._adapter_type = adapter_type
def start(self):
if len(self._adapters) > self._maximum_adapters:
raise VirtualBoxError("Number of adapters above the maximum supported of {}".format(self._maximum_adapters))
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
if self._enable_console:
# starts the Telnet to pipe thread
pipe_name = self._get_pipe_name()
if sys.platform.startswith('win'):
try:
self._serial_pipe = open(pipe_name, "a+b")
except OSError as e:
raise VirtualBoxError("Could not open the pipe {}: {}".format(pipe_name, e))
self._serial_pipe_thread = PipeProxy(self._vmname, msvcrt.get_osfhandle(self._serial_pipe.fileno()), self._host, self._console)
#self._serial_pipe_thread.setDaemon(True)
self._serial_pipe_thread.start()
else:
try:
self._serial_pipe = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self._serial_pipe.connect(pipe_name)
except OSError as e:
raise VirtualBoxError("Could not connect to the pipe {}: {}".format(pipe_name, e))
self._serial_pipe_thread = PipeProxy(self._vmname, self._serial_pipe, self._host, self._console)
#self._serial_pipe_thread.setDaemon(True)
self._serial_pipe_thread.start()
def stop(self):
if self._serial_pipe_thread:
self._serial_pipe_thread.stop()
self._serial_pipe_thread.join(1)
if self._serial_pipe_thread.isAlive():
log.warn("Serial pire thread is still alive!")
self._serial_pipe_thread = None
if self._serial_pipe:
if sys.platform.startswith('win'):
win32file.CloseHandle(msvcrt.get_osfhandle(self._serial_pipe.fileno()))
else:
self._serial_pipe.close()
self._serial_pipe = None
if self._machine.state >= self._vboxmanager.constants.MachineState_FirstOnline and \
self._machine.state <= self._vboxmanager.constants.MachineState_LastOnline:
try:
if sys.platform.startswith('win') and "VBOX_INSTALL_PATH" in os.environ:
# work around VirtualBox bug #9239
vboxmanage_path = os.path.join(os.environ["VBOX_INSTALL_PATH"], "VBoxManage.exe")
command = '"{}" controlvm "{}" poweroff'.format(vboxmanage_path, self._vmname)
subprocess.call(command, timeout=3)
else:
progress = self._session.console.powerDown()
# wait for VM to actually go down
progress.waitForCompletion(3000)
log.info("VM is stopping with {}% completed".format(self.vmname, progress.percent))
self._lock_machine()
for adapter_id in range(0, len(self._adapters)):
if self._adapters[adapter_id] is None:
continue
self._disable_adapter(adapter_id, disable=True)
serial_port = self._session.machine.getSerialPort(0)
serial_port.enabled = False
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):
try:
self._session.console.pause()
except Exception as e:
raise VirtualBoxError("VirtualBox error: {}".format(e))
def reload(self):
try:
self._session.console.reset()
except Exception as e:
raise VirtualBoxError("VirtualBox error: {}".format(e))
def resume(self):
try:
self._session.console.resume()
except Exception as e:
raise VirtualBoxError("VirtualBox error: {}".format(e))
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._adapters)):
try:
# VirtualBox starts counting from 0
adapter = self._session.machine.getNetworkAdapter(adapter_id)
if self._adapters[adapter_id] is None:
# force enable to avoid any discrepancy in the interface numbering inside the VM
# e.g. Ethernet2 in GNS3 becoming eth0 inside the VM when using a start index of 2.
adapter.enabled = True
continue
vbox_adapter_type = adapter.adapterType
if self._adapter_type == "PCnet-PCI II (Am79C970A)":
vbox_adapter_type = self._vboxmanager.constants.NetworkAdapterType_Am79C970A
if self._adapter_type == "PCNet-FAST III (Am79C973)":
vbox_adapter_type = self._vboxmanager.constants.NetworkAdapterType_Am79C973
if self._adapter_type == "Intel PRO/1000 MT Desktop (82540EM)":
vbox_adapter_type = self._vboxmanager.constants.NetworkAdapterType_I82540EM
if self._adapter_type == "Intel PRO/1000 T Server (82543GC)":
vbox_adapter_type = self._vboxmanager.constants.NetworkAdapterType_I82543GC
if self._adapter_type == "Intel PRO/1000 MT Server (82545EM)":
vbox_adapter_type = self._vboxmanager.constants.NetworkAdapterType_I82545EM
if self._adapter_type == "Paravirtualized Network (virtio-net)":
vbox_adapter_type = self._vboxmanager.constants.NetworkAdapterType_Virtio
if self._adapter_type == "Automatic": # "Auto-guess, based on first NIC"
vbox_adapter_type = first_adapter_type
adapter.adapterType = vbox_adapter_type
except Exception as e:
raise VirtualBoxError("VirtualBox error: {}".format(e))
nio = self._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._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 _get_pipe_name(self):
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))
return pipe_name
def _set_console_options(self):
"""
# Example to manually set serial parameters using Python
from vboxapi import VirtualBoxManager
mgr = VirtualBoxManager(None, None)
mach = mgr.vbox.findMachine("My VM")
session = mgr.mgr.getSessionObject(mgr.vbox)
mach.lockMachine(session, 1)
mach2=session.machine
serial_port = mach2.getSerialPort(0)
serial_port.enabled = True
serial_port.path = "/tmp/test_pipe"
serial_port.hostMode = 1
serial_port.server = True
session.unlockMachine()
"""
log.info("setting console options for {}".format(self._vmname))
self._lock_machine()
pipe_name = self._get_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

@ -19,13 +19,24 @@
VirtualBox VM instance. VirtualBox VM instance.
""" """
import sys
import shlex
import re
import os import os
import subprocess
import tempfile
import shutil import shutil
import socket
import time
from .virtualbox_error import VirtualBoxError from .virtualbox_error import VirtualBoxError
from .virtualbox_controller import VirtualBoxController
from .adapters.ethernet_adapter import EthernetAdapter from .adapters.ethernet_adapter import EthernetAdapter
from ..attic import find_unused_port from ..attic import find_unused_port
from .pipe_proxy import PipeProxy
if sys.platform.startswith('win'):
import msvcrt
import win32file
import logging import logging
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -35,8 +46,7 @@ class VirtualBoxVM(object):
""" """
VirtualBox VM implementation. VirtualBox VM implementation.
:param vboxwrapper client: VboxWrapperClient instance :param vboxmanage_path: path to the VBoxManage tool
: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 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
@ -51,8 +61,7 @@ class VirtualBoxVM(object):
_allocated_console_ports = [] _allocated_console_ports = []
def __init__(self, def __init__(self,
vboxwrapper, vboxmanage_path,
vboxmanager,
name, name,
vmname, vmname,
working_dir, working_dir,
@ -82,11 +91,14 @@ class VirtualBoxVM(object):
self._working_dir = None self._working_dir = None
self._host = host self._host = host
self._command = [] self._command = []
self._vboxwrapper = vboxwrapper self._vboxmanage_path = vboxmanage_path
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
self._serial_pipe_thread = None
self._serial_pipe = None
# VirtualBox settings # VirtualBox settings
self._console = console self._console = console
self._ethernet_adapters = [] self._ethernet_adapters = []
@ -118,13 +130,14 @@ 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._system_properties = {}
self._vboxwrapper.send('vbox create vbox "{}"'.format(self._name)) properties = self._execute("list", ["systemproperties"])
self._vboxwrapper.send('vbox setattr "{}" image "{}"'.format(self._name, vmname)) for prop in properties:
self._vboxwrapper.send('vbox setattr "{}" console {}'.format(self._name, self._console)) try:
else: name, value = prop.split(':', 1)
self._vboxcontroller = VirtualBoxController(self._vmname, vboxmanager, self._host) except ValueError:
self._vboxcontroller.console = self._console continue
self._system_properties[name.strip()] = value.strip()
self.adapters = 2 # creates 2 adapters by default self.adapters = 2 # creates 2 adapters by default
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,
@ -189,8 +202,6 @@ class VirtualBoxVM(object):
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
@ -248,11 +259,6 @@ class VirtualBoxVM(object):
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))
else:
self._vboxcontroller.console = 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))
@ -269,9 +275,6 @@ 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))
@ -287,9 +290,6 @@ 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:
@ -320,16 +320,8 @@ class VirtualBoxVM(object):
""" """
if headless: if headless:
if self._vboxwrapper:
self._vboxwrapper.send('vbox setattr "{}" headless_mode True'.format(self._name))
else:
self._vboxcontroller.headless = True
log.info("VirtualBox VM {name} [id={id}] has enabled the headless mode".format(name=self._name, id=self._id)) log.info("VirtualBox VM {name} [id={id}] has enabled the headless mode".format(name=self._name, id=self._id))
else: else:
if self._vboxwrapper:
self._vboxwrapper.send('vbox setattr "{}" headless_mode False'.format(self._name))
else:
self._vboxcontroller.headless = False
log.info("VirtualBox VM {name} [id={id}] has disabled the headless mode".format(name=self._name, id=self._id)) log.info("VirtualBox VM {name} [id={id}] has disabled the headless mode".format(name=self._name, id=self._id))
self._headless = headless self._headless = headless
@ -352,16 +344,8 @@ class VirtualBoxVM(object):
""" """
if enable_console: if enable_console:
if self._vboxwrapper:
self._vboxwrapper.send('vbox setattr "{}" enable_console True'.format(self._name))
else:
self._vboxcontroller.enable_console = True
log.info("VirtualBox VM {name} [id={id}] has enabled the console".format(name=self._name, id=self._id)) log.info("VirtualBox VM {name} [id={id}] has enabled the console".format(name=self._name, id=self._id))
else: else:
if self._vboxwrapper:
self._vboxwrapper.send('vbox setattr "{}" enable_console False'.format(self._name))
else:
self._vboxcontroller.enable_console = False
log.info("VirtualBox VM {name} [id={id}] has disabled the console".format(name=self._name, id=self._id)) log.info("VirtualBox VM {name} [id={id}] has disabled the console".format(name=self._name, id=self._id))
self._enable_console = enable_console self._enable_console = enable_console
@ -383,11 +367,6 @@ class VirtualBoxVM(object):
:param vmname: VirtualBox VM name :param vmname: VirtualBox VM name
""" """
if self._vboxwrapper:
self._vboxwrapper.send('vbox setattr "{}" image "{}"'.format(self._name, vmname))
else:
self._vboxcontroller.vmname = vmname
log.info("VirtualBox VM {name} [id={id}] has set the VM name to {vmname}".format(name=self._name, id=self._id, vmname=vmname)) 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 self._vmname = vmname
@ -416,11 +395,6 @@ class VirtualBoxVM(object):
continue continue
self._ethernet_adapters.append(EthernetAdapter()) self._ethernet_adapters.append(EthernetAdapter())
if self._vboxwrapper:
self._vboxwrapper.send('vbox setattr "{}" nics {}'.format(self._name, adapters))
else:
self._vboxcontroller.adapters = self._ethernet_adapters
log.info("VirtualBox VM {name} [id={id}]: number of Ethernet adapters changed to {adapters}".format(name=self._name, log.info("VirtualBox VM {name} [id={id}]: number of Ethernet adapters changed to {adapters}".format(name=self._name,
id=self._id, id=self._id,
adapters=adapters)) adapters=adapters))
@ -443,9 +417,6 @@ class VirtualBoxVM(object):
:param adapter_start_index: index :param adapter_start_index: index
""" """
if self._vboxwrapper:
self._vboxwrapper.send('vbox setattr "{}" nic_start_index {}'.format(self._name, adapter_start_index))
self._adapter_start_index = adapter_start_index self._adapter_start_index = adapter_start_index
self.adapters = self.adapters # this forces to recreate the adapter list with the correct index self.adapters = self.adapters # this forces to recreate the adapter list with the correct index
log.info("VirtualBox VM {name} [id={id}]: adapter start index changed to {index}".format(name=self._name, log.info("VirtualBox VM {name} [id={id}]: adapter start index changed to {index}".format(name=self._name,
@ -472,68 +443,327 @@ class VirtualBoxVM(object):
self._adapter_type = adapter_type self._adapter_type = adapter_type
if self._vboxwrapper:
self._vboxwrapper.send('vbox setattr "{}" netcard "{}"'.format(self._name, adapter_type))
else:
self._vboxcontroller.adapter_type = adapter_type
log.info("VirtualBox VM {name} [id={id}]: adapter type changed to {adapter_type}".format(name=self._name, log.info("VirtualBox VM {name} [id={id}]: adapter type changed to {adapter_type}".format(name=self._name,
id=self._id, id=self._id,
adapter_type=adapter_type)) adapter_type=adapter_type))
def _execute(self, subcommand, args, timeout=30):
"""
Executes a command with VBoxManage.
:param subcommand: vboxmanage subcommand (e.g. modifyvm, controlvm etc.)
:param args: arguments for the subcommand.
:param timeout: how long to wait for vboxmanage
:returns: result (list)
"""
command = [self._vboxmanage_path, "--nologo", subcommand]
command.extend(args)
try:
result = subprocess.check_output(command, stderr=subprocess.STDOUT, universal_newlines=True, timeout=timeout)
except subprocess.CalledProcessError as e:
if e.output:
# only the first line of the output is useful
virtualbox_error = e.output.splitlines()[0]
raise VirtualBoxError("{}".format(e))
except subprocess.TimeoutExpired:
raise VirtualBoxError("VBoxManage has timed out")
return result.splitlines()
def _get_vm_info(self):
"""
Returns this VM info.
:returns: dict of info
"""
vm_info = {}
results = self._execute("showvminfo", [self._vmname])
for info in results:
try:
name, value = info.split(':', 1)
except ValueError:
continue
vm_info[name.strip()] = value.strip()
return vm_info
def _get_vm_state(self):
"""
Returns this VM state (e.g. running, paused etc.)
:returns: state (string)
"""
vm_info = self._get_vm_info()
state = vm_info["State"].rsplit('(', 1)[0]
return state.lower().strip()
def _get_maximum_supported_adapters(self):
"""
Returns the maximum adapters supported by this VM.
:returns: maximum number of supported adapters (int)
"""
# check the maximum number of adapters supported by the VM
vm_info = self._get_vm_info()
chipset = vm_info["Chipset"]
maximum_adapters = 8
if chipset == "ich9":
maximum_adapters = int(self._system_properties["Maximum ICH9 Network Adapter count"])
return maximum_adapters
def _get_pipe_name(self):
"""
Returns the pipe name to create a serial connection.
:returns: pipe path (string)
"""
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))
return pipe_name
def _set_serial_console(self):
"""
Configures the first serial port to allow a serial console connection.
"""
# activate the first serial port
self._modify_vm("--uart1 0x3F8 4")
# set server mode with a pipe on the first serial port
pipe_name = self._get_pipe_name()
args = [self._vmname, "--uartmode1", "server", pipe_name]
self._execute("modifyvm", args)
def _modify_vm(self, params):
"""
Change setting in this VM when not running.
:param params: params to use with sub-command modifyvm
"""
args = shlex.split(params)
self._execute("modifyvm", [self._vmname] + args)
def _control_vm(self, params):
"""
Change setting in this VM when running.
:param params: params to use with sub-command controlvm
:returns: result of the command.
"""
args = shlex.split(params)
return self._execute("controlvm", [self._vmname] + args)
def _get_nic_attachements(self, maximum_adapters):
"""
Returns NIC attachements.
:param maximum_adapters: maximum number of supported adapters
:returns: list of adapters with their Attachment setting (NAT, bridged etc.)
"""
nics = []
vm_info = self._get_vm_info()
for adapter_id in range(0, maximum_adapters):
entry = "NIC {}".format(adapter_id + 1)
if entry in vm_info:
value = vm_info[entry]
match = re.search("Attachment: (\w+)[\s,]+", value)
if match:
nics.append(match.group(1))
else:
nics.append(None)
return nics
def _set_network_options(self, maximum_adapters):
"""
Configures network options.
:param maximum_adapters: maximum number of supported adapters
"""
nic_attachements = self._get_nic_attachements(maximum_adapters)
for adapter_id in range(0, len(self._ethernet_adapters)):
if self._ethernet_adapters[adapter_id] is None:
# force enable to avoid any discrepancy in the interface numbering inside the VM
# e.g. Ethernet2 in GNS3 becoming eth0 inside the VM when using a start index of 2.
attachement = nic_attachements[adapter_id]
if attachement:
self._modify_vm("--nic{} {}".format(adapter_id + 1, attachement.lower()))
continue
vbox_adapter_type = "82540EM"
if self._adapter_type == "PCnet-PCI II (Am79C970A)":
vbox_adapter_type = "Am79C970A"
if self._adapter_type == "PCNet-FAST III (Am79C973)":
vbox_adapter_type = "Am79C973"
if self._adapter_type == "Intel PRO/1000 MT Desktop (82540EM)":
vbox_adapter_type = "82540EM"
if self._adapter_type == "Intel PRO/1000 T Server (82543GC)":
vbox_adapter_type = "82543GC"
if self._adapter_type == "Intel PRO/1000 MT Server (82545EM)":
vbox_adapter_type = "82545EM"
if self._adapter_type == "Paravirtualized Network (virtio-net)":
vbox_adapter_type = "virtio"
args = [self._vmname, "--nictype{}".format(adapter_id + 1), vbox_adapter_type]
self._execute("modifyvm", args)
nio = self._ethernet_adapters[adapter_id].get_nio(0)
if nio:
log.debug("setting UDP params on adapter {}".format(adapter_id))
try:
self._modify_vm("--nic{} generic".format(adapter_id + 1))
self._modify_vm("--nicgenericdrv{} UDPTunnel".format(adapter_id + 1))
self._modify_vm("--nicproperty{} sport={}".format(adapter_id + 1, nio.lport))
self._modify_vm("--nicproperty{} dest={}".format(adapter_id + 1, nio.rhost))
self._modify_vm("--nicproperty{} dport={}".format(adapter_id + 1, nio.rport))
self._modify_vm("--cableconnected{} on".format(adapter_id + 1))
except Exception as e:
raise VirtualBoxError("VirtualBox error: {}".format(e))
if nio.capturing:
self._modify_vm("--nictrace{} on".format(adapter_id + 1))
self._modify_vm("--nictracefile{} {}".format(adapter_id + 1, nio.pcap_output_file))
else:
self._modify_vm("--nictrace{} off".format(adapter_id + 1))
else:
# shutting down unused adapters...
try:
self._modify_vm("--nic{} null".format(adapter_id + 1))
except Exception as e:
raise VirtualBoxError("VirtualBox error: {}".format(e))
for adapter_id in range(len(self._ethernet_adapters), maximum_adapters):
log.debug("disabling remaining adapter {}".format(adapter_id))
self._modify_vm("--nic{} null".format(adapter_id + 1))
def start(self): def start(self):
""" """
Starts this VirtualBox VM. Starts this VirtualBox VM.
""" """
if self._vboxwrapper: # resume the VM if it is paused
self._vboxwrapper.send('vbox start "{}"'.format(self._name)) vm_state = self._get_vm_state()
else: if vm_state == "paused":
self._vboxcontroller.start() self.resume()
return
# VM must be powered off and in saved state to start it
if vm_state != "powered off" and vm_state != "saved":
raise VirtualBoxError("VirtualBox VM not powered off or saved")
# check for the maximum adapters supported by the VM
maximum_adapters = self._get_maximum_supported_adapters()
if len(self._ethernet_adapters) > maximum_adapters:
raise VirtualBoxError("Number of adapters above the maximum supported of {}".format(maximum_adapters))
self._set_network_options(maximum_adapters)
self._set_serial_console()
args = [self._vmname]
if self._headless:
args.extend(["--type", "headless"])
result = self._execute("startvm", args)
log.debug("started VirtualBox VM: {}".format(result))
if self._enable_console:
# starts the Telnet to pipe thread
pipe_name = self._get_pipe_name()
if sys.platform.startswith('win'):
try:
self._serial_pipe = open(pipe_name, "a+b")
except OSError as e:
raise VirtualBoxError("Could not open the pipe {}: {}".format(pipe_name, e))
self._serial_pipe_thread = PipeProxy(self._vmname, msvcrt.get_osfhandle(self._serial_pipe.fileno()), self._host, self._console)
#self._serial_pipe_thread.setDaemon(True)
self._serial_pipe_thread.start()
else:
try:
self._serial_pipe = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self._serial_pipe.connect(pipe_name)
except OSError as e:
raise VirtualBoxError("Could not connect to the pipe {}: {}".format(pipe_name, e))
self._serial_pipe_thread = PipeProxy(self._vmname, self._serial_pipe, self._host, self._console)
#self._serial_pipe_thread.setDaemon(True)
self._serial_pipe_thread.start()
def stop(self): def stop(self):
""" """
Stops this VirtualBox VM. Stops this VirtualBox VM.
""" """
if self._vboxwrapper: if self._serial_pipe_thread:
self._serial_pipe_thread.stop()
self._serial_pipe_thread.join(1)
if self._serial_pipe_thread.isAlive():
log.warn("Serial pire thread is still alive!")
self._serial_pipe_thread = None
if self._serial_pipe:
if sys.platform.startswith('win'):
win32file.CloseHandle(msvcrt.get_osfhandle(self._serial_pipe.fileno()))
else:
self._serial_pipe.close()
self._serial_pipe = None
vm_state = self._get_vm_state()
if vm_state == "running" or vm_state == "paused" or vm_state == "stuck":
# power off the VM
result = self._control_vm("poweroff")
log.debug("VirtualBox VM has been stopped: {}".format(result))
time.sleep(0.5) # give some time for VirtualBox to unlock the VM
# deactivate the first serial port
try: try:
self._vboxwrapper.send('vbox stop "{}"'.format(self._name)) self._modify_vm("--uart1 off")
except VirtualBoxError: except VirtualBoxError as e:
# probably lost the connection log.warn("Could not deactivate the first serial port: {}".format(e))
return
else: for adapter_id in range(0, len(self._ethernet_adapters)):
self._vboxcontroller.stop() if self._ethernet_adapters[adapter_id] is None:
continue
self._modify_vm("--nic{} null".format(adapter_id + 1))
def suspend(self): def suspend(self):
""" """
Suspends this VirtualBox VM. Suspends this VirtualBox VM.
""" """
if self._vboxwrapper: vm_state = self._get_vm_state()
self._vboxwrapper.send('vbox suspend "{}"'.format(self._name)) if vm_state == "running":
result = self._control_vm("pause")
log.debug("VirtualBox VM has been suspended: {}".format(result))
else: else:
self._vboxcontroller.suspend() log.info("VirtualBox VM is not running to be suspended, current state is {}".format(vm_state))
def reload(self):
"""
Reloads this VirtualBox VM.
"""
if self._vboxwrapper:
self._vboxwrapper.send('vbox reset "{}"'.format(self._name))
else:
self._vboxcontroller.reload()
def resume(self): def resume(self):
""" """
Resumes this VirtualBox VM. Resumes this VirtualBox VM.
""" """
if self._vboxwrapper: result = self._control_vm("resume")
self._vboxwrapper.send('vbox resume "{}"'.format(self._name)) log.debug("VirtualBox VM has been resumed: {}".format(result))
else:
self._vboxcontroller.resume() def reload(self):
"""
Reloads this VirtualBox VM.
"""
result = self._control_vm("reset")
log.debug("VirtualBox VM has been reset: {}".format(result))
def port_add_nio_binding(self, adapter_id, nio): def port_add_nio_binding(self, adapter_id, nio):
""" """
@ -549,14 +779,14 @@ class VirtualBoxVM(object):
raise VirtualBoxError("Adapter {adapter_id} doesn't exist on VirtualBox VM {name}".format(name=self._name, raise VirtualBoxError("Adapter {adapter_id} doesn't exist on VirtualBox VM {name}".format(name=self._name,
adapter_id=adapter_id)) adapter_id=adapter_id))
if self._vboxwrapper: vm_state = self._get_vm_state()
self._vboxwrapper.send('vbox create_udp "{}" {} {} {} {}'.format(self._name, if vm_state == "running":
adapter_id, # dynamically configure an UDP tunnel on the VirtualBox adapter
nio.lport, self._control_vm("nic{} generic UDPTunnel".format(adapter_id + 1))
nio.rhost, self._control_vm("nicproperty{} sport={}".format(adapter_id + 1, nio.lport))
nio.rport)) self._control_vm("nicproperty{} dest={}".format(adapter_id + 1, nio.rhost))
else: self._control_vm("nicproperty{} dport={}".format(adapter_id + 1, nio.rport))
self._vboxcontroller.create_udp(adapter_id, nio.lport, nio.rhost, nio.rport) self._control_vm("setlinkstate{} on".format(adapter_id + 1))
adapter.add_nio(0, nio) adapter.add_nio(0, nio)
log.info("VirtualBox VM {name} [id={id}]: {nio} added to adapter {adapter_id}".format(name=self._name, log.info("VirtualBox VM {name} [id={id}]: {nio} added to adapter {adapter_id}".format(name=self._name,
@ -579,11 +809,11 @@ class VirtualBoxVM(object):
raise VirtualBoxError("Adapter {adapter_id} doesn't exist on VirtualBox VM {name}".format(name=self._name, raise VirtualBoxError("Adapter {adapter_id} doesn't exist on VirtualBox VM {name}".format(name=self._name,
adapter_id=adapter_id)) adapter_id=adapter_id))
if self._vboxwrapper: vm_state = self._get_vm_state()
self._vboxwrapper.send('vbox delete_udp "{}" {}'.format(self._name, if vm_state == "running":
adapter_id)) # dynamically disable the VirtualBox adapter
else: self._control_vm("setlinkstate{} off".format(adapter_id + 1))
self._vboxcontroller.delete_udp(adapter_id) self._control_vm("nic{} null".format(adapter_id + 1))
nio = adapter.get_nio(0) nio = adapter.get_nio(0)
adapter.remove_nio(0) adapter.remove_nio(0)
@ -620,11 +850,6 @@ class VirtualBoxVM(object):
nio.startPacketCapture(output_file) 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, log.info("VirtualBox VM {name} [id={id}]: starting packet capture on adapter {adapter_id}".format(name=self._name,
id=self._id, id=self._id,
adapter_id=adapter_id)) adapter_id=adapter_id))
@ -645,10 +870,6 @@ class VirtualBoxVM(object):
nio = adapter.get_nio(0) nio = adapter.get_nio(0)
nio.stopPacketCapture() 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, log.info("VirtualBox VM {name} [id={id}]: stopping packet capture on adapter {adapter_id}".format(name=self._name,
id=self._id, id=self._id,
adapter_id=adapter_id)) adapter_id=adapter_id))