# -*- coding: utf-8 -*-
# $Id$
"""
Test Manager Core - Web Server Abstraction Base Class.
"""
__copyright__ = \
"""
Copyright (C) 2012-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.
"""
# Standard python imports.
import re;
import os;
import sys;
import uuid;
# Validation Kit imports.
# Python 3 hacks:
"""
Exception class for TestBoxController.
"""
pass;
"""
TestBox Controller class.
"""
## Applicable testbox commands to an idle TestBox.
## Applicable testbox commands to a busy TestBox.
## Commands that can be ACK'ed.
kasAckableCmds = [constants.tbresp.CMD_EXEC, constants.tbresp.CMD_ABORT, constants.tbresp.CMD_REBOOT,
constants.tbresp.CMD_UPGRADE, constants.tbresp.CMD_UPGRADE_AND_REBOOT, constants.tbresp.CMD_SPECIAL];
## Commands that can be NACK'ed or NOTSUP'ed.
kasNackableCmds = kasAckableCmds + [kasAckableCmds, constants.tbresp.CMD_IDLE, constants.tbresp.CMD_WAIT];
## Mapping from TestBoxCmd_T to TestBoxState_T
kdCmdToState = \
{ \
TestBoxData.ksTestBoxCmd_Abort: None,
};
## Mapping from TestBoxCmd_T to TestBox responses commands.
kdCmdToTbRespCmd = \
{
};
## Mapping from TestBox responses to TestBoxCmd_T commands.
kdTbRespCmdToCmd = \
{
};
## The path to the upgrade zip, relative WebServerGlueBase.getBaseUrl().
## Valid TestBox result values.
## Mapping TestBox result values to TestStatus_T values.
{
};
"""
Won't raise exceptions.
"""
self._asCheckedParams = [];
{ \
};
"""
Gets a string parameter (stripped).
Raises exception if not found and no default is provided, or if the
value isn't found in asValidValues.
"""
if sDefValue is None:
return sDefValue;
if fStrip:
raise TestBoxControllerException('%s parameter %s value "%s" not in %s ' \
return sValue;
"""
Gets a boolean parameter.
Raises exception if not found and no default is provided, or if not a
valid boolean.
"""
sValue = self._getStringParam(sName, [ 'True', 'true', '1', 'False', 'false', '0'], sDefValue = str(fDefValue));
"""
Gets a string parameter.
Raises exception if not found, not a valid integer, or if the value
isn't in the range defined by iMin and iMax.
"""
try:
except:
raise TestBoxControllerException('%s parameter %s value "%s" cannot be convert to an integer' \
raise TestBoxControllerException('%s parameter %s value %d is out of range [%s..%s]' \
return iValue;
"""
Gets a string parameter.
Raises exception if not found, not a valid long integer, or if the value
isn't in the range defined by lMin and lMax.
"""
sValue = self._getStringParam(sName, sDefValue = (str(lDefValue) if lDefValue is not None else None));
try:
raise TestBoxControllerException('%s parameter %s value "%s" cannot be convert to an integer (%s)' \
raise TestBoxControllerException('%s parameter %s value %d is out of range [%s..%s]' \
return lValue;
"""
Check if we've handled all parameters, raises exception if anything
unknown was found.
"""
sUnknownParams = '';
return True;
"""
Makes a reply to the testbox script.
Will raise exception on failure.
"""
return True;
"""
Makes a simple reply to the testbox script.
Will raise exception on failure.
"""
"""
Makes an IDLE reply to the testbox script.
Will raise exception on failure.
"""
"""
Cleans up any old test set that may be left behind and changes the
state to 'idle'. See scenario #9:
file://../../docs/AutomaticTestingRevamp.html#cleaning-up-abandond-testcase
Note. oStatusData.enmState is set to idle, but tsUpdated is not changed.
"""
# Cleanup any abandond test.
if oStatusData.idTestSet is not None:
"idTestSet=%u idTestBox=%u enmState=%s %s"
# Change to idle status
TestBoxStatusLogic(oDb).updateState(self._idTestBox, TestBoxStatusData.ksTestBoxState_Idle, fCommit = False);
# Commit.
return True;
"""
Connects to the database and validates the testbox.
Returns (TMDatabaseConnection, TestBoxStatusData, TestBoxData) on success.
Returns (None, None, None) on failure after sending the box an appropriate response.
May raise exception on DB error.
"""
(oStatusData, oTestBoxData) = oLogic.tryFetchStatusAndConfig(self._idTestBox, self._sTestBoxUuid, self._sTestBoxAddr);
if oStatusData is None:
else:
return (None, None, None);
""" Writes the text to the main log file. """
# Calc the file name and open the file.
# Check the size.
if not fIgnoreSizeCheck:
# Write the text.
if fSizeOk:
else:
# Done
return fSizeOk;
""" Implement sign-on """
#
# Validate parameters (raises exception on failure).
#
sCpuName = self._getStringParam(constants.tbreq.SIGNON_PARAM_CPU_NAME, fStrip = True, sDefValue = ''); # new
lCpuRevision = self._getLongParam( constants.tbreq.SIGNON_PARAM_CPU_REVISION, lMin = 0, lDefValue = 0); # new
fCpu64BitGuest = self._getBoolParam( constants.tbreq.SIGNON_PARAM_HAS_64_BIT_GUEST, fDefValue = True);
cMbScratch = self._getLongParam( constants.tbreq.SIGNON_PARAM_SCRATCH_SIZE, 0, 1073741823); # 0..1PB
sReport = self._getStringParam(constants.tbreq.SIGNON_PARAM_REPORT, fStrip = True, sDefValue = ''); # new
iPythonHexVersion = self._getIntParam( constants.tbreq.SIGNON_PARAM_PYTHON_VERSION, 0x020300f0, 0x030f00f0);
# Null conversions for new parameters.
sReport = None;
sCpuName = None;
if lCpuRevision <= 0:
lCpuRevision = None;
#
# Connect to the database and validate the testbox.
#
if oTestBox is None:
'addr=%s uuid=%s os=%s %d cpus' \
#
# Update the row in TestBoxes if something changed.
#
#
# Update the testbox status, making sure there is a status.
#
if oStatusData is not None:
else:
#
# ACK the request.
#
dResponse = \
{
}
"""
_doRequestCommand worker for handling a box in gang-cleanup.
This will check if all testboxes has completed their run, pretending to
be busy until that happens. Once all are completed, resources will be
freed and the testbox returns to idle state (we update oStatusData).
"""
TestBoxStatusLogic(oDb).updateState(self._idTestBox, TestBoxStatusData.ksTestBoxState_Idle, fCommit = False);
return None;
"""
_doRequestCommand worker for handling a box in gang-gathering-timed-out state.
This will do clean-ups similar to _cleanupOldTest and update the state likewise.
"""
TestBoxStatusLogic(oDb).updateState(self._idTestBox, TestBoxStatusData.ksTestBoxState_Idle, fCommit = False);
return None;
"""
_doRequestCommand worker for handling a box in gang-gathering state.
This only checks for timeout. It will update the oStatusData if a
timeout is detected, so that the box will be idle upon return.
"""
return None;
"""
Common code for handling command request.
"""
if oDb is None:
return False;
#
# Status clean up.
#
# Only when BUSY will the TestBox Script request and execute commands
# concurrently. So, it must be idle when sending REQUEST_COMMAND_IDLE.
#
if fIdle:
dResponse = SchedulerBase.composeExecResponse(oDb, oTestBoxData.idTestBox, self._oSrvGlue.getBaseUrl());
if dResponse is not None:
return dResponse;
elif oStatusData.enmState != TestBoxStatusData.ksTestBoxState_Idle: # (includes ksTestBoxState_GangGatheringTimedOut)
#
# Check for pending command.
#
dResponse = { constants.tbresp.ALL_PARAM_RESULT: TestBoxController.kdCmdToTbRespCmd[oTestBoxData.enmPendingCmd] };
if oTestBoxData.enmPendingCmd in [TestBoxData.ksTestBoxCmd_Upgrade, TestBoxData.ksTestBoxCmd_UpgradeAndReboot]:
dResponse[constants.tbresp.UPGRADE_PARAM_URL] = self._oSrvGlue.getBaseUrl() + TestBoxController.ksUpgradeZip;
#
# If doing gang stuff, return 'CMD_WAIT'.
#
## @todo r=bird: Why is GangTesting included here? Figure out when testing gang testing.
#
# If idling and enabled try schedule a new task.
#
if fIdle \
and oTestBoxData.fEnabled \
if dResponse is not None:
#
# Touch the status row every couple of mins so we can tell that the box is alive.
#
return self._idleResponse();
""" Implement request for command. """
""" Implement request for command. """
""" Implements ACK, NACK and NACK(ENOTSUP). """
if oDb is None:
return False;
#
# If the command maps to a TestBoxCmd_T value, it means we have to
# check and update TestBoxes. If it's an ACK, the testbox status will
# need updating as well.
#
if sPendingCmd is not None:
oStatusLogic.updateState(self._idTestBox, TestBoxController.kdCmdToState[sPendingCmd], fCommit = False);
# Commit the two updates.
#
# Log NACKs.
#
""" Implement command ACK'ing """
sCmd = self._getStringParam(constants.tbreq.COMMAND_ACK_PARAM_CMD_NAME, TestBoxController.kasAckableCmds);
""" Implement command NACK'ing """
sCmd = self._getStringParam(constants.tbreq.COMMAND_ACK_PARAM_CMD_NAME, TestBoxController.kasNackableCmds);
""" Implement command NACK(ENOTSUP)'ing """
sCmd = self._getStringParam(constants.tbreq.COMMAND_ACK_PARAM_CMD_NAME, TestBoxController.kasNackableCmds);
""" Implement submitting log entries to the main log file. """
#
# Parameter validation.
#
if oStatusData is None:
return False;
#
# Write the text to the log file.
#
## @todo Overflow is a hanging offence, need to note it and fail whatever is going on...
# Done.
""" Implement uploading of files. """
#
# Parameter validation.
#
if oStatusData is None:
return False;
]:
'log/uninstaller',
#'screencapture/failure',
]:
raise TestBoxControllerException('Unexpected content type: %s; %s' % self._oSrvGlue.getContentType());
if cbFile <= 0:
raise TestBoxControllerException('File "%s" is too big %u bytes (max %u MiB)'
#
# Write the text to the log file.
#
oDstFile = TestSetLogic(oDb).createFile(oTestSet, sName = sName, sMime = sMime, sKind = sKind, sDesc = sDesc,
offFile = 0;
del abBuf;
# Done.
""" Implement submitting "XML" like test result stream. """
#
# Parameter validation.
#
if oStatusData is None:
return False;
#
# Process the XML.
#
if sError is not None:
if fUnforgivable:
"""
Implement EXEC completion.
Because the action is request by the worker thread of the testbox
script we cannot pass pending commands back to it like originally
planned. So, we just complete the test set and update the status.
"""
#
# Parameter validation.
#
sStatus = self._getStringParam(constants.tbreq.EXEC_COMPLETED_PARAM_RESULT, TestBoxController.kasValidResults);
if oStatusData is None:
return False;
#
# Complete the status.
#
idTestSetGangLeader = oTestSetLogic.complete(oStatusData.idTestSet, self.kadTbResultToStatus[sStatus], fCommit = False);
assert idTestSetGangLeader is None;
else:
assert idTestSetGangLeader is not None;
oStatusLogic.updateState(self._idTestBox, TestBoxStatusData.ksTestBoxState_GangCleanup, oStatusData.idTestSet,
"""
Gets the standard parameters and validates them.
The parameters are returned as a tuple: sAction, idTestBox, sTestBoxUuid.
Note! the sTextBoxId can be None if it's a SIGNON request.
Raises TestBoxControllerException on invalid input.
"""
#
# Get the action parameter and validate it.
#
raise TestBoxControllerException('No "%s" parameter in request (params: %s)' \
raise TestBoxControllerException('Unknown action "%s" in request (params: %s; action: %s)' \
#
# TestBox UUID.
#
raise TestBoxControllerException('No "%s" parameter in request (params: %s)' \
try:
raise TestBoxControllerException('Invalid %s parameter value "%s": %s ' \
if sTestBoxUuid == '00000000-0000-0000-0000-000000000000':
raise TestBoxControllerException('Invalid %s parameter value "%s": NULL UUID not allowed.' \
#
# TestBox ID.
#
try:
raise Exception;
except:
raise TestBoxControllerException('Bad value for "%s": "%s"' \
idTestBox = None;
else:
raise TestBoxControllerException('No "%s" parameter in request (params: %s)' \
#
# Test Set ID.
#
try:
raise Exception;
except:
raise TestBoxControllerException('Bad value for "%s": "%s"' \
idTestSet = None;
else:
raise TestBoxControllerException('No "%s" parameter in request (params: %s)' \
#
# The testbox address.
#
#
# Update the list of checked parameters.
#
self._asCheckedParams.extend([constants.tbreq.ALL_PARAM_TESTBOX_UUID, constants.tbreq.ALL_PARAM_ACTION]);
if idTestBox is not None:
if idTestSet is not None:
"""
Dispatches the incoming request.
Will raise TestBoxControllerException on failure.
"""
#
# Must be a POST request.
#
try:
if sMethod != 'POST':
#
# Get the parameters and checks for duplicates.
#
try:
raise TestBoxControllerException('Parameter "%s" is given multiple times: %s' % (sKey, dParams[sKey]));
#
# Get+validate the standard action parameters and dispatch the request.
#