vbox.py revision dd1ed7dae880ecef88e32d742f9192c519c18f44
# -*- coding: utf-8 -*-
# $Id$
# pylint: disable=C0302
"""
VirtualBox Specific base testdriver.
"""
__copyright__ = \
"""
Copyright (C) 2010-2014 Oracle Corporation
This file is part of VirtualBox Open Source Edition (OSE), as
available from http://www.virtualbox.org. This file is free software;
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.
The contents of this file may alternatively be used under the terms
of the Common Development and Distribution License Version 1.0
(CDDL) only, as it comes in the "COPYING.CDDL" file of the
VirtualBox OSE distribution, in which case the provisions of the
CDDL are applicable instead of those of the GPL.
You may elect to license modified versions of this file under the
terms and conditions of either the GPL or the CDDL or both.
"""
__version__ = "$Revision$"
# Standard Python imports.
import os
import platform
import sys
import threading
import time
import traceback
import datetime
# Figure out where the validation kit lives and make sure it's in the path.
try: __file__
# Validation Kit imports.
from testdriver import base;
from testdriver import reporter;
from testdriver import vboxcon;
from testdriver import vboxtestvms;
#
# Exception and Error Unification Hacks.
# Note! This is pretty gross stuff. Be warned!
# TODO: Find better ways of doing these things, preferrably in vboxapi.
#
ComException = None; # pylint: disable=C0103
__fnComExceptionGetAttr__ = None; # pylint: disable=C0103
""" __getattribute__/__getattr__ default fake."""
try:
except:
return oAttr;
""" ComException.__getattr__ wrapper - both XPCOM and COM. """
try:
except AttributeError:
if sName == 'errno':
elif sName == 'msg':
else:
raise;
else:
if sName == 'hresult':
elif sName == 'strerror':
elif sName == 'excepinfo':
oAttr = None;
elif sName == 'argerror':
oAttr = None;
else:
raise;
#print '__MyComExceptionGetAttr(,%s) -> "%s"' % (sName, oAttr);
return oAttr;
"""
Deploys the exception and error hacks that helps unifying COM and XPCOM
exceptions and errors.
"""
global ComException # pylint: disable=C0103
global __fnComExceptionGetAttr__ # pylint: disable=C0103
# Hook up our attribute getter for the exception class (ASSUMES new-style).
if __fnComExceptionGetAttr__ is None:
try:
except:
try:
except:
# Make the modified classes accessible (are there better ways to do this?)
return None;
#
# Utility functions.
#
def isIpAddrValid(sIpAddr):
"""
Checks if a IPv4 address looks valid. This will return false for
localhost and similar.
Returns True / False.
"""
return True;
def stringifyErrorInfo(oErrInfo):
"""
Stringifies the error information in a IVirtualBoxErrorInfo object.
Returns string with error info.
"""
try:
except:
else:
return sRet;
"""
Report a VirtualBox error on oErr. oErr can be IVirtualBoxErrorInfo
or IProgress. Anything else is ignored.
Returns the same a reporter.error().
"""
try:
except:
#
# Classes
#
"""
Unified COM and XPCOM status code repository.
This works more like a module than a class since it's replacing a module.
"""
# The VBOX_E_XXX bits:
__VBOX_E_BASE = -2135228416;
# Reverse lookup table.
dDecimalToConst = {}; # pylint: disable=C0103
"""
Copy all error codes from oNativeComErrorClass to this class and
install compatability mappings.
"""
# First, add the VBOX_E_XXX constants to dDecimalToConst.
# Copy all error codes from oNativeComErrorClass to this class.
# Install mappings to the other platform.
else:
return True;
"""
Checks if the ComException e is not equal to the COM status code hr.
This takes DISP_E_EXCEPTION & excepinfo into account.
This method can be used with any Exception derivate, however it will
only return True for classes similar to the two ComException variants.
"""
# The DISP_E_EXCEPTION + excptinfo fun needs checking up, only
# empirical info on it so far.
try:
except AttributeError:
return False;
else:
try:
except AttributeError:
return False;
"""
Checks if the ComException e is not equal to the COM status code hr.
See equal() for more details.
"""
"""
Converts the specified COM status code to a string.
"""
try:
except KeyError:
return sStr;
"""
A VirtualBox build.
Note! After dropping the installation of VBox from this code and instead
realizing that with the vboxinstall.py wrapper driver, this class is
of much less importance and contains unnecessary bits and pieces.
"""
"""
"""
# Initialize all members first.
self.sDesignation = None;
self.sGuestAdditionsIso = None;
# Figure out the values as best we can.
if strInstallPath is None:
#
# Both parameters are None, which means we're falling back on a
# build in the development tree.
#
self.sArch = os.environ.get("KBUILD_TARGET_ARCH", os.environ.get("BUILD_TARGET_ARCH", oDriver.sHostArch));
sSearch = os.environ.get('VBOX_TD_DEV_TREE', os.path.dirname(__file__)); # Env.var. for older trees or testboxscript.
sCandidat = None;
break;
break;
if self.sDesignation is None:
try:
except:
pass;
else:
import re;
if oMatch is not None:
if self.sDesignation is None:
else:
#
# We've been pointed to an existing installation, this could be
# in the out dir of a svn checkout, untarred VBoxAll or a real
# installation directory.
#
## @todo Much more work is required here.
# Do some checks.
sVMMR0 = os.path.join(self.sInstallPath, 'amd64' if utils.getHostArch() == 'amd64' else 'i386', 'VMMR0.r0');
# Guest additions location is different on windows for some _stupid_ reason.
else:
# __init__ end;
""" Status dumper for debugging. """
def isDevBuild(self):
""" Returns True if it's development build (kind), otherwise False. """
class EventHandlerBase(object):
"""
Base class for both Console and VirtualBox event handlers.
"""
def threadForPassiveMode(self):
"""
The thread procedure for the event processing thread.
"""
try:
except:
break;
if oEvt:
try:
except:
break;
return None;
"""
Called when working in passive mode.
"""
return None;
"""
Unregister the event handler.
"""
try:
except:
else:
try:
except:
else:
_ = fWaitForThread;
return fRc;
"""
Compatibility wrapper that child classes implement.
"""
_ = oEvt;
return None;
"""
Registers the callback / event listener.
"""
dArgsCopy['oListener'] = None;
if fpApiVer < 3.3:
try:
except:
else:
try:
return oRet;
else:
try:
if not fPassive:
else:
except:
reporter.errorXcpt('%s::eventSource.createListener(%s) failed%s' % (sSrcParentNm, oListener, sLogSuffix));
else:
try:
else:
if not fPassive:
else:
return oRet;
return None;
"""
Base class for handling IConsole events.
The class has IConsoleCallback (<=3.2) compatible callback methods which
the user can override as needed.
Note! This class must not inherit from object or we'll get type errors in VBoxPython.
"""
if sName is None:
# pylint: disable=C0111,R0913,W0613
def onAdditionsStateChange(self):
def onVRDPServerChange(self):
def onUSBControllerChange(self):
def onCanShowWindow(self):
return True
def onShowWindow(self):
return None;
# pylint: enable=C0111,R0913,W0613
"""
Compatibility wrapper.
"""
try:
except:
return None;
try:
except:
## @todo implement the other events.
return None;
"""
Base class for handling IVirtualBox events.
The class has IConsoleCallback (<=3.2) compatible callback methods which
the user can override as needed.
Note! This class must not inherit from object or we'll get type errors in VBoxPython.
"""
# pylint: disable=C0111,W0613
pass;
pass;
# The COM bridge does tuples differently. Not very funny if you ask me... ;-)
return True, ''
pass;
pass;
pass;
pass;
pass;
pass;
pass;
pass;
# pylint: enable=C0111,W0613
"""
Compatibility wrapper.
"""
try:
except:
return None;
try:
except:
try:
except:
## @todo implement the other events.
return None;
"""
For catching machine state changes and waking up the task machinery at that point.
"""
""" Just interrupt the wait loop here so it can check again. """
"""
This is the VirtualBox test driver.
"""
self.aoRemoteSessions = [];
self.sDefBridgedNic = None;
self.sSelfLogFile = None;
self.sVBoxSvcLogFile = None;
self.oVBoxSvcProcess = None;
self.sVBoxSvcPidFile = None;
self.sVBoxValidationKit = None;
self.sVBoxValidationKitIso = None;
self.sVBoxBootSectors = None;
# Quietly detect build and validation kit.
# Make sure all debug logs goes to the scratch area unless
# specified otherwise (more of this later on).
"""
Dump object state, for debugging.
"""
"""
This is used internally to try figure a locally installed build when
running tests manually.
"""
return True;
# Try dev build first since that's where I'll be using it first..
if True:
try:
return True;
pass;
# Try default installation locations.
asLocs = [
];
asLocs = [ '/opt/VirtualBox-3.2', '/opt/VirtualBox-3.1', '/opt/VirtualBox-3.0', '/opt/VirtualBox' ];
asLocs = [ '/Applications/VirtualBox.app/Contents/MacOS' ];
asLocs = [ '/opt/VirtualBox-3.2', '/opt/VirtualBox-3.1', '/opt/VirtualBox-3.0', '/opt/VirtualBox' ];
else:
asLocs = [ '/opt/VirtualBox' ];
try:
return True;
pass;
if not fQuiet:
return False;
"""
This is used internally by the constructor to try locate an unzipped
VBox Validation Kit somewhere in the immediate proximity.
"""
if self.sVBoxValidationKit is not None:
return True;
#
# Normally it's found where we're running from, which is the same as
# the script directly on the testboxes.
#
if g_ksValidationKitDir not in asCandidates:
#
# When working out of the tree, we'll search the current directory
# as well as parent dirs.
#
for i in range(10):
if sDir not in asCandidates:
#
# Do the searching.
#
sCandidate = None;
sCandidate = asCandidates[i];
break;
break;
sCandidate = None;
fRc = sCandidate is not None;
if not fQuiet:
reporter.error('failed to find VBox Validation Kit installation (candidates: %s)' % (asCandidates,));
#
# Set the member values.
#
return fRc;
def importVBoxApi(self):
"""
Import the 'vboxapi' module from the VirtualBox build we're using and
instantiate the two basic objects.
This will try detect an development or installed build if no build has
been associated with the driver yet.
"""
if self.fImportedVBoxApi:
return True;
## @todo split up this messy function.
# Make sure we've got our own VirtualBox config and VBoxSVC (on XPCOM at least).
if not self.fUseDefaultSvc:
# Do the detecting.
self._detectBuild();
return False;
# Avoid crashing when loading the 32-bit module (or whatever it is that goes bang).
print "WARNING: 64-bit python on darwin, 32-bit VBox development build => crash"
print "WARNING: or"
print "WARNING: bash-3.2$ VERSIONER_PYTHON_PREFER_32_BIT=yes ./testdriver"
return False;
# Start VBoxSVC and load the vboxapi bits.
assert(self.oVBoxSvcProcess is not None);
# Adjust the default machine folder.
try:
except:
# Kill VBoxSVC on failure.
self._stopVBoxSVC();
else:
assert(self.oVBoxSvcProcess is None);
return self.fImportedVBoxApi;
""" Starts VBoxSVC. """
assert(self.oVBoxSvcProcess is None);
# Setup vbox logging for VBoxSVC now and start it manually. This way
# we can control both logging and shutdown.
except: pass;
os.environ['VBOX_LOG_FLAGS'] = '%s append' % (self.sLogSvcFlags,); # Append becuse of VBoxXPCOMIPCD.
if self.sLogSvcDest:
else:
# Always leave a pid file behind so we can kill it during cleanup-before.
cMsFudge = 1;
# Start VBoxSVC in gdb in a new terminal.
#sTerm = '/usr/bin/gnome-terminal'; - doesn't work, some fork+exec stuff confusing us.
if self.oVBoxSvcProcess is not None:
sWinDbg = 'c:\\Program Files\\Debugging Tools for Windows\\windbg.exe';
if not os.path.isfile(sWinDbg): sWinDbg = 'c:\\Program Files\\Debugging Tools for Windows (x64)\\windbg.exe';
if not os.path.isfile(sWinDbg): sWinDbg = 'c:\\Programme\\Debugging Tools for Windows\\windbg.exe'; # Localization rulez! pylint: disable=C0301
if not os.path.isfile(sWinDbg): sWinDbg = 'c:\\Programme\\Debugging Tools for Windows (x64)\\windbg.exe';
if not os.path.isfile(sWinDbg): sWinDbg = 'windbg'; # WinDbg must be in the path; better than nothing.
# Assume that everything WinDbg needs is defined using the environment variables.
# See WinDbg help for more information.
if self.oVBoxSvcProcess is not None:
## @todo add a pipe interface similar to xpcom if feasible, i.e. if
# we can get actual handle values for pipes in python.
else:
else: # Run without a debugger attached.
#
# XPCOM - We can use a pipe to let VBoxSVC notify us when it's ready.
#
self.oVBoxSvcProcess = base.Process.spawn(sVBoxSVC, sVBoxSVC, '--auto-shutdown'); # SIGUSR1 requirement.
try: # Try make sure we get the SIGINT and not VBoxSVC.
except:
try:
except:
sResponse = None;
self.oVBoxSvcProcess = None;
#
# Windows - Just fudge it for now.
#
cMsFudge = 2000;
else:
#
# Enable automatic crash reporting if we succeeded.
#
if self.oVBoxSvcProcess is not None:
#
# Fudge and pid file.
#
if fWritePidFile:
try:
except:
#
# Finally add the task so we'll notice when it dies in a relatively timely manner.
#
else:
self.oVBoxSvcProcess = None;
except: pass;
return self.oVBoxSvcProcess != None;
""" Kill a VBoxSVC given the pid from it's pid file. """
# Read the pid file.
return False;
try:
except:
return False;
# Convert the pid to an integer and validate the range a little bit.
try:
except:
return False;
if iPid <= 0:
return False;
# Take care checking that it's VBoxSVC we're about to inhume.
return False;
# Loop thru our different ways of getting VBoxSVC to terminate.
break;
if fRc:
else:
return fRc;
def _stopVBoxSVC(self):
"""
Stops VBoxSVC. Try the polite way first.
"""
if self.oVBoxSvcProcess:
if self.oVBoxSvcProcess is not None \
and not self.fVBoxSvcInDebugger:
# by process object.
else:
self.oVBoxSvcProcess = None;
else:
# by pid file.
return fRc;
def _setupVBoxApi(self):
"""
Import and set up the vboxapi.
The caller saves and restores sys.path.
"""
# Setup vbox logging for self (the test driver).
except: pass;
if self.sLogSelfDest:
else:
# Hack the sys.path + environment so the vboxapi can be found.
try:
# pylint: disable=F0401
from vboxapi import VirtualBoxManager
import winerror as NativeComErrorClass
else:
# pylint: enable=F0401
except:
return False;
# Create the manager.
try:
except:
return False;
# Figure the API version.
try:
try:
except:
sVer = "4.0.0";
else:
except:
return False;
return True;
def _patchVBoxMgr(self):
"""
Glosses over missing self.oVBoxMgr methods on older VBox versions.
"""
""" See vboxapi. """
_ = oSelf;
import winerror; # pylint: disable=F0401
else:
return hrXcpt;
""" See vboxapi. """
0x80004004, -2147467260, # NS_ERROR_ABORT
0x800706be, -2147023170, # NS_ERROR_CALL_FAILED (RPC_S_CALL_FAILED)
0x800706ba, -2147023174, # RPC_S_SERVER_UNAVAILABLE.
0x800706be, -2147023170, # RPC_S_CALL_FAILED.
0x800706bf, -2147023169, # RPC_S_CALL_FAILED_DNE.
0x80010108, -2147417848, # RPC_E_DISCONNECTED.
0x800706b5, -2147023179, # RPC_S_UNKNOWN_IF
];
""" See vboxapi. """
_ = oSelf;
else:
""" See vboxapi. """
""" See vboxapi. """
_ = oSelf;
# Add utilities found in newer vboxapi revision.
import types;
def _teardownVBoxApi(self):
"""
"""
if not self.fImportedVBoxApi:
return True;
self.aoRemoteSessions = [];
try:
import gc
except:
else:
try:
else:
reporter.log('actionCleanupAfter: NS_ShutdownXPCOM -> %s, leaving %s objects and %s interfaces behind...' \
try:
except:
except:
try:
except:
return True;
def _powerOffAllVms(self):
"""
Tries to power off all running VMs.
"""
if uPid is not None:
else:
return None;
#
# Build type, OS and arch getters.
#
def getBuildType(self):
"""
Get the build type.
"""
if not self._detectBuild():
return 'release';
def getBuildOs(self):
"""
Get the build OS.
"""
if not self._detectBuild():
def getBuildArch(self):
"""
Get the build arch.
"""
if not self._detectBuild():
def getGuestAdditionsIso(self):
"""
Get the path to the guest addition iso.
"""
if not self._detectBuild():
return None;
#
# Override everything from the base class so the testdrivers don't have to
# check whether we have overridden a method or not.
#
if self.oTestVmSet is not None:
return rc;
iArg += 1;
iArg += 1;
except: raise base.InvalidOption('The "--vrdp-base-port" value "%s" is not a valid integer', asArgs[iArg]);
raise base.InvalidOption('The "--vrdp-base-port" value "%s" is not in the valid range (1..65530)', asArgs[iArg]);
iArg += 1;
iArg += 1;
iArg += 1;
iArg += 1;
iArg += 1;
iArg += 1;
iArg += 1;
iArg += 1;
iArg += 1;
iArg += 1;
iArg += 1;
iArg += 1;
iArg += 1;
else:
# Relevant for selecting VMs to test?
if self.oTestVmSet is not None:
return iRc;
# Hand it to the base class.
return iArg + 1;
def completeOptions(self):
def getResourceSet(self):
if self.oTestVmSet is not None:
def actionExtract(self):
def actionVerify(self):
def actionConfig(self):
def actionExecute(self):
def actionCleanupBefore(self):
"""
Kill any VBoxSVC left behind by a previous test run.
"""
def actionCleanupAfter(self):
"""
Clean up the VBox bits and then call the base driver.
If your test driver overrides this, it should normally call us at the
end of the job.
"""
# Kill any left over VM processes.
# Drop all VBox object references and shutdown xpcom then
# terminating VBoxSVC, with extreme prejudice if need be.
self._stopVBoxSVC();
# Add the VBoxSVC and testdriver debug+release log files.
self.sVBoxSvcLogFile = None;
self.sSelfLogFile = None;
# Testbox debugging - START - TEMPORARY, REMOVE ASAP.
try:
except: pass;
# Testbox debugging - END - TEMPORARY, REMOVE ASAP.
# Finally, call the base driver to wipe the scratch space.
def actionAbort(self):
"""
Terminate VBoxSVC if we've got a pid file.
"""
"""
Stop VBoxSVC if we've started it.
"""
if self.oVBoxSvcProcess is not None:
self._stopVBoxSVC();
#
# Task wait method override.
#
"""
Overriding base.TestDriver.notifyAboutReadyTask.
"""
try:
except:
"""
Overriding base.TestDriver.waitForTasksSleepWorker.
"""
try:
_ = rc; #reporter.log2('vbox.waitForTasksSleepWorker(%u): true (waitForEvents -> %s)' % (cMsTimeout, rc));
return True;
except KeyboardInterrupt:
raise;
except:
return False;
#
# Utility methods.
#
"""
Processes events, returning after the first batch has been processed
or the time limit has been reached.
Only Ctrl-C exception, no return.
"""
try:
except KeyboardInterrupt:
raise;
except:
pass;
return None;
def processPendingEvents(self):
""" processEvents(0) - no waiting. """
"""
Sleep for a specified amount of time, processing XPCOM events all the while.
"""
while True:
if cMsElapsed > cMsTimeout:
break;
#reporter.log2('cMsTimeout=%s - cMsElapsed=%d => %s' % (cMsTimeout, cMsElapsed, cMsTimeout - cMsElapsed));
return None;
"""
This is copy, paste, search, replace and edit of infoCmd from vboxshell.py.
"""
else:
reporter.log(" Nested paging: %s" % (oVM.getHWVirtExProperty(vboxcon.HWVirtExPropertyType_NestedPaging)));
else:
else:
except: sPorts = "";
else:
if aoControllers:
for oCtrl in aoControllers:
reporter.log(" %s %s bus: %s type: %s" % (oCtrl.name, oCtrl.controllerType, oCtrl.bus, oCtrl.controllerType));
if aoAttachments:
for oAtt in aoAttachments:
sCtrl = "Controller: %s port: %s device: %s type: %s" % (oAtt.controller, oAtt.port, oAtt.device, oAtt.type);
if oMedium:
if oAtt.passthrough:
else:
else:
if oMedium:
else:
else:
except: break;
continue;
else:
else:
else:
else:
else:
if oNic.traceEnabled:
return True;
"""
Logs VM configuration details.
This is copy, past, search, replace and edit of infoCmd from vboxshell.py.
"""
try:
except:
return fRc;
"""
logVmInfo + getVmByName.
"""
"""
Takes a guest OS ID or Description and returns the ID.
If nothing matching it is found, the input is returned unmodified.
"""
if sIdOrDesc == 'Solaris (64 bit)':
sIdOrDesc = 'Oracle Solaris 10 5/09 and earlier (64 bit)';
try:
except:
else:
for oGuestOS in aoGuestTypes:
try:
except:
else:
break;
return sIdOrDesc
"""
Search the test resources for the most recent VM HD.
Returns path relative to the test resource root.
"""
## @todo implement a proper search algo here.
#
# VM Api wrappers that logs errors, hides exceptions and other details.
#
# pylint: disable=R0913,R0914
def createTestVM(self, sName, iGroup, sHd = None, cMbRam = None, cCpus = 1, fVirtEx = None, fNestedPaging = None, \
"""
Creates a test VM with a immutable HD from the test resources.
"""
if not self.importVBoxApi():
return None;
# create + register the VM
try:
else:
try:
oVM.saveSettings();
try:
except:
raise;
except:
try:
else:
except:
else:
try: oVM.deleteSettings();
raise;
except:
return None;
# Configure the VM.
if oSession is not None:
if fRc and fNestedPaging is not None:
if fRc and (eNic0AttachType is not None or (sNic0NetName is not None and sNic0NetName != 'default')):
if fRc and sNic0MacAddr is not None:
if sNic0MacAddr == 'grouped':
if fRc and fFastBootLogo is not None:
if fRc and fVmmDevTestingPart is not None:
if not fRc:
except: pass;
try:
else:
except:
else:
try: oVM.deleteSettings();
return None;
# success.
return oVM;
# pylint: enable=R0913,R0914
"""
Adds an already existing (that is, configured) test VM to the
test VM list.
"""
# find + add the VM to the list.
try:
else:
except:
return None;
if not fQuiet:
return oVM;
"""
Opens a session for the VM. Returns the a Session wrapper object that
will automatically close the session when the wrapper goes out of scope.
On failure None is returned and an error is logged.
"""
try:
except:
return None;
# This loop is a kludge to deal with us racing the closing of the
# direct session of a previous VM run. See waitOnDirectSessionClose.
for i in range(10):
try:
else:
break;
except:
if i == 9:
return None;
if i > 0:
reporter.logXcpt('warning: failed to open session for "%s" ("%s") - retrying in %u secs' % (sUuid, oVM, i));
"""
Get a test VM by name. Returns None if not found, logged.
"""
# Look it up in our 'cache'.
try:
#reporter.log2('cur: %s / %s (oVM=%s)' % (oVM.name, oVM.id, oVM));
return oVM;
except:
# Look it up the standard way.
"""
Get a test VM by uuid. Returns None if not found, logged.
"""
# Look it up in our 'cache'.
try:
return oVM;
except:
# Look it up the standard way.
def waitOnProgress(self, oProgress, cMsTimeout = 1000000, fErrorOnTimeout = True, cMsInterval = 1000):
"""
Waits for a progress object to complete. Returns the status code.
"""
# Wait for progress no longer than cMsTimeout time period.
while True:
try:
break;
except:
return -1;
if fErrorOnTimeout:
return -1
except: return -2;
except: rc = -2;
return rc;
"""
Waits for the VM process to close it's current direct session.
Returns None.
"""
# Get the original values so we're not subject to
try:
else:
return None;
and sCurType != '' \
try:
break;
return None;
"""
Uploads the VBoxStartup.log when present.
"""
try:
except:
else:
return fRc;
def startVmEx(self, oVM, fWait = True, sType = None, sName = None, asEnv = None): # pylint: disable=R0914,R0915
"""
Start the VM, returning the VM session and progress object on success.
The session is also added to the task list and to the aoRemoteSessions set.
asEnv is a list of string on the putenv() form.
On failure (None, None) is returned and an error is logged.
"""
# Massage and check the input.
if sType is None:
if sName is None:
except: sName = 'bad-vm-handle';
if oVM is None:
return (None, None);
## @todo Do this elsewhere.
# Hack alert. Disables all annoying GUI popups.
try:
else:
'remindAboutMouseIntegrationOff,remindAboutPausedVMInput,confirmInputCapture,'
'confirmGoingFullscreen,remindAboutInaccessibleMedia,remindAboutWrongColorDepth,'
'confirmRemoveMedium,allPopupPanes,allMessageBoxes,all');
except:
# The UUID for the name.
try:
except:
return (None, None);
# Construct the environment.
except: pass;
if self.sLogSessionDest:
else:
sEnv = 'VBOX_LOG=%s\nVBOX_LOG_FLAGS=%s\nVBOX_LOG_DEST=%s\nVBOX_RELEASE_LOG_FLAGS=append time' \
if sType == 'gui':
sEnv += '\nVBOX_GUI_DBG_ENABLED=1'
# Shortcuts for local testing.
try:
if oTestVM is not None \
oProgress = None;
else:
if oSessionWrapperRestore is not None:
if oSnapshotCur is not None:
else:
except:
return (None, None);
# Open a remote session, wait for this operation to complete.
# (The loop is a kludge to deal with us racing the closing of the
# direct session of a previous VM run. See waitOnDirectSessionClose.)
if oWrapped is None:
for i in range(10):
try:
else:
else:
break;
except:
if i == 9:
return (None, None);
oSession = None;
if i >= 0:
reporter.logXcpt('warning: failed to start VM "%s" ("%s") - retrying in %u secs.' % (sUuid, oVM, i)); # pylint: disable=C0301
if rc < 0:
try:
if oSession is not None:
except: pass;
return (None, None);
# Wrap up the session object and push on to the list before returning it.
if oWrapped is None:
reporter.log2('startVmEx: oSession=%s, oSessionWrapper=%s, oProgress=%s' % (oSession, oWrapped, oProgress));
""" Simplified version of startVmEx. """
return oSession;
"""
Start the VM, returning the VM session and progress object on success.
The session is also added to the task list and to the aoRemoteSessions set.
On failure (None, None) is returned and an error is logged.
"""
if oVM is None:
return (None, None);
"""
Start the VM, returning the VM session on success. The session is
also added to the task list and to the aoRemoteSessions set.
On failure None is returned and an error is logged.
"""
return oSession;
"""
Terminates the VM specified by oSession and adds the release logs to
the test report.
This will try archive this by using powerOff, but will resort to
tougher methods if that fails.
The session will always be removed from the task list.
The session will be closed unless we fail to kill the process.
The session will be removed from the remote session list if closed.
The progress object (a wrapper!) is for teleportation and similar VM
operations, it will be attempted canceled before powering off the VM.
Failures are logged but ignored.
The progress object will always be removed from the task list.
Returns True if powerOff and session close both succeed.
Returns False if on failure (logged), including when we successfully
kill the VM process.
"""
reporter.log2('terminateVmBySession: oSession=%s (pid=%s) oProgress=%s' % (oSession.sName, oSession.getPid(), oProgress));
# Call getPid first to make sure the PID is cached in the wrapper.
#
# Take Screenshot and upload it (see below) to Test Manager if appropriate/requested.
#
sLastScreenshotPath = None
sLastScreenshotPath = None
#
# Terminate the VM
#
# Cancel the progress object if specified.
if oProgress is not None:
try:
except:
else:
# Check if the VM has terminated by it self before powering it off.
# power off failed, try terminate it in a nice manner.
if uPid is not None:
# Being nice failed...
# The final steps.
try:
except:
else:
#
# Add the release log, debug log and a screenshot of the VM to the test report.
#
# Add a screenshot if it has been requested and taken successfully.
if sLastScreenshotPath is not None:
else:
return fRc;
#
# Some information query functions (mix).
#
# Methods require the VBox API. If the information is provided by both
# the testboxscript as well as VBox API, we'll check if it matches.
#
"""
Common Worker for hasHostNestedPaging() and hasHostHwVirt().
Returns True / False.
Raises exception on environment / host mismatch.
"""
if fEnv is not None:
fVBox = None;
try:
except:
if not fQuiet:
if fVBox is not None:
if fEnv is not None:
return fEnv;
return fVBox;
if fEnv is not None:
return fEnv;
return False;
"""
Checks if hardware assisted virtualization is supported by the host.
Returns True / False.
Raises exception on environment / host mismatch.
"""
"""
Checks if nested paging is supported by the host.
Returns True / False.
Raises exception on environment / host mismatch.
"""
return self._hasHostCpuFeature('TESTBOX_HAS_NESTED_PAGING', 'ProcessorFeature_NestedPaging', 4.2, fQuiet) \
"""
Checks if the host supports 64-bit guests.
Returns True / False.
Raises exception on environment / host mismatch.
"""
# Note that the testboxscript doesn't export this variable atm.
"""
Returns the number of CPUs on the host.
Returns True / False.
Raises exception on environment / host mismatch.
"""
if cEnv is not None:
try:
except:
if not fQuiet:
cVBox = None;
if cVBox is not None:
if cEnv is not None:
assert cVBox == cEnv, 'Misconfigured TestBox: VBox: %u CPUs, testboxscript: %u CPUs' % (cVBox, cEnv);
return cVBox;
if cEnv is not None:
return cEnv;
return 1;
"""
Internal method used for getting the host CPU description from VBoxSVC.
Returns description string, on failure an empty string is returned.
"""
try:
except:
if not fQuiet:
return '';
"""
Checks if the host CPU vendor is AMD.
Returns True / False.
"""
"""
Checks if the host CPU vendor is Intel.
Returns True / False.
"""
"""
Checks if the host CPU vendor is VIA (or Centaur).
Returns True / False.
"""
#
# Testdriver execution methods.
#
"""
Callback method for handling unknown tasks in the various run loops.
The testdriver should override this if it already tasks running when
calling startVmAndConnectToTxsViaTcp, txsRunTest or similar methods.
Call super to handle unknown tasks.
Returns True if handled, False if not.
"""
return False;
"""
Generic TXS task wrapper which waits both on the TXS and the session tasks.
Returns False on error, logged.
Returns task result on success.
"""
# All async methods ends with the following to args.
if oTask is oTxsSession:
if oTxsSession.isSuccess():
elif fIgnoreErrors is True:
else:
else:
if oTask is None:
if fIgnoreErrors is True:
else:
else:
if fIgnoreErrors is True:
else:
else:
if fRemoveTxs:
if fRemoveVm:
return rc;
# pylint: disable=C0111
def txsMkDir(self, oSession, oTxsSession, sRemoteDir, fMode = 0700, cMsTimeout = 30000, fIgnoreErrors = False):
def txsMkDirPath(self, oSession, oTxsSession, sRemoteDir, fMode = 0700, cMsTimeout = 30000, fIgnoreErrors = False):
def txsMkSymlink(self, oSession, oTxsSession, sLinkTarget, sLink, cMsTimeout = 30000, fIgnoreErrors = False):
def txsRmSymlink(self, oSession, oTxsSession, sRemoteSymlink, cMsTimeout = 30000, fIgnoreErrors = False):
def txsIsSymlink(self, oSession, oTxsSession, sRemoteSymlink, cMsTimeout = 30000, fIgnoreErrors = False):
def txsUploadFile(self, oSession, oTxsSession, sLocalFile, sRemoteFile, cMsTimeout = 30000, fIgnoreErrors = False):
def txsUploadString(self, oSession, oTxsSession, sContent, sRemoteFile, cMsTimeout = 30000, fIgnoreErrors = False):
def txsDownloadFile(self, oSession, oTxsSession, sRemoteFile, sLocalFile, cMsTimeout = 30000, fIgnoreErrors = False):
"""
Convenience function to get files from the guest and stores it
into the scratch directory for later (manual) review.
Returns True on success.
Returns False on failure, logged.
"""
## @todo Check for already existing files on the host and create a new
# name for the current file to download.
except: pass;
if fRc:
else:
if fIgnoreErrors is not True:
return fRc;
return True;
def txsDownloadString(self, oSession, oTxsSession, sRemoteFile, cMsTimeout = 30000, fIgnoreErrors = False):
def txsUnpackFile(self, oSession, oTxsSession, sRemoteFile, sRemoteDir, cMsTimeout = 30000, fIgnoreErrors = False):
# pylint: enable=C0111
"""
Mostly an internal helper for txsRebootAndReconnectViaTcp and
startVmAndConnectToTxsViaTcp that waits for the CDROM drive to become
ready. It does this by polling for a file it knows to exist on the CD.
Returns True on success.
Returns False on failure, logged.
"""
while True:
# wait for it to complete.
if oTask is not oTxsSession:
if oTask is None:
else:
break;
if oTxsSession.isSuccess():
break;
# Check for timeout.
if cMsElapsed >= cMsTimeout:
break;
# delay.
# resubmitt the task.
if cMsTimeout2 < 500:
cMsTimeout2 = 500;
break;
else:
if fRemoveTxs:
if fRemoveVm:
return fRc;
"""
Mostly an internal worker for connecting to TXS via TCP used by the
*ViaTcp methods.
Returns a tuplet with True/False and TxsSession/None depending on the
result. Errors are logged.
"""
if oTxsConnect is not None:
if oTask is oTxsConnect:
if oTxsSession is not None:
return (True, oTxsSession);
else:
if oTask is None:
else:
if fRemoveVm:
else:
return (False, None);
"""
Starts the specified VM and tries to connect to its TXS via TCP.
The VM will be powered off if TXS doesn't respond before the specified
time has elapsed.
Returns a the VM and TXS sessions (a two tuple) on success. The VM
session is in the task list, the TXS session is not.
Returns (None, None) on failure, fully logged.
"""
# Zap the guest IP to make sure we're not getting a stale entry
# (unless we're restoring the VM of course).
if oTestVM is None \
try:
del oSession1;
except:
# Start the VM.
reporter.log('startVmAndConnectToTxsViaTcp: Starting(/preparing) "%s" (timeout %s s)...' % (sVmName, cMsTimeout / 1000));
if oSession is not None:
# Connect to TXS.
reporter.log2('startVmAndConnectToTxsViaTcp: Started(/prepared) "%s", connecting to TXS ...' % (sVmName,));
if fCdWait:
# Wait for CD?
# Success!
return (oSession, oTxsSession);
else:
# If something went wrong while waiting for TXS to be started - take VM screenshot before terminate it
return (None, None);
def txsRebootAndReconnectViaTcp(self, oSession, oTxsSession, fCdWait = False, cMsTimeout = 15*60000, \
"""
Executes the TXS reboot command
Returns A tuple of True and the new TXS session on success.
Returns A tuple of False and either the old TXS session or None on failure.
"""
#
# This stuff is a bit complicated because of rebooting being kind of
# disruptive to the TXS and such... The protocol is that TXS will:
# - ACK the reboot command.
# - Shutdown the transport layer, implicitly disconnecting us.
# - Execute the reboot operation.
# - On failure, it will be re-init the transport layer and be
# available pretty much immediately. UUID unchanged.
# - On success, it will be respawed after the reboot (hopefully),
# with a different UUID.
#
# Get UUID.
if sUuidBefore is not False:
# Reboot.
# Reconnect.
if fNatForwardingForTxs is True:
self.sleep(22); # NAT fudge - Two fixes are wanted: 1. TXS connect retries. 2. Main API reboot/reset hint.
(fRc, oTxsSession) = self.txsDoConnectViaTcp(oSession, cMsTimeout - cMsElapsed, fNatForwardingForTxs);
# Check the UUID.
if sUuidBefore is not False:
if sUuidAfter != sUuidBefore:
# Do CD wait if specified.
if fCdWait:
else:
else:
else:
else:
else:
return (fRc, oTxsSession);
# pylint: disable=R0914,R0913
def txsRunTest(self, oTxsSession, sTestName, cMsTimeout, sExecName, asArgs = (), asAddEnv = (), sAsUser = ""):
"""
Executes the specified test task, waiting till it completes or times out.
The VM session (if any) must be in the task list.
Returns True if we executed the task and nothing abnormal happend.
Query the process status from the TXS session.
Returns False if some unexpected task was signalled or we failed to
submit the job.
"""
reporter.log2('txsRunTest: cMsTimeout=%u sExecName=%s asArgs=%s' % (cMsTimeout, sExecName, asArgs));
# Submit the job.
if oTxsSession.asyncExec(sExecName, asArgs, asAddEnv, sAsUser, cMsTimeout = self.adjustTimeoutMs(cMsTimeout)):
# Wait for the job to complete.
while True:
if oTask is None:
break;
if oTask is oTxsSession:
reporter.log('txsRunTest: isSuccess=%s getResult=%s' % (oTxsSession.isSuccess(), oTxsSession.getResult()));
break;
break;
if not oTxsSession.pollTask():
else:
return fRc;
def txsRunTestRedirectStd(self, oTxsSession, sTestName, cMsTimeout, sExecName, asArgs = (), asAddEnv = (), sAsUser = "",
"""
Executes the specified test task, waiting till it completes or times out,
redirecting stdin, stdout and stderr to the given objects.
The VM session (if any) must be in the task list.
Returns True if we executed the task and nothing abnormal happend.
Query the process status from the TXS session.
Returns False if some unexpected task was signalled or we failed to
submit the job.
"""
reporter.log2('txsRunTestRedirectStd: cMsTimeout=%u sExecName=%s asArgs=%s' % (cMsTimeout, sExecName, asArgs));
# Submit the job.
# Wait for the job to complete.
while True:
if oTask is None:
break;
if oTask is oTxsSession:
break;
break;
if not oTxsSession.pollTask():
else:
return fRc;
"""
Executes the specified test tasks, waiting till they complete or
times out. The 1st task is started after the 2nd one.
The VM session (if any) must be in the task list.
Returns True if we executed the task and nothing abnormal happend.
Query the process status from the TXS sessions.
Returns False if some unexpected task was signalled or we failed to
submit the job.
"""
# Submit the jobs.
# Wait for the jobs to complete.
cPendingJobs = 2;
while True:
if oTask is None:
break;
else: iTask = 2;
cPendingJobs -= 1;
if cPendingJobs <= 0:
break;
break;
if not oTxsSession2.pollTask():
else:
if not oTxsSession1.pollTask():
else:
return fRc;
# pylint: enable=R0914,R0913