Changeset 84599 in vbox for trunk/src/VBox/ValidationKit/testmanager/core
- Timestamp:
- May 29, 2020 1:12:32 AM (5 years ago)
- svn:sync-xref-src-repo-rev:
- 138332
- Location:
- trunk/src/VBox/ValidationKit/testmanager/core
- Files:
-
- 3 edited
- 1 copied
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/VBox/ValidationKit/testmanager/core/base.py
r84498 r84599 33 33 # Standard python imports. 34 34 import copy; 35 import datetime; 36 import json; 35 37 import re; 36 38 import socket; … … 1062 1064 + ModelDataBase.formatSimpleNowAndPeriod(oDb, tsNow, sPeriodBack, sTablePrefix, sExpCol, sEffCol); 1063 1065 1066 1067 # 1068 # JSON 1069 # 1070 1071 @staticmethod 1072 def stringToJson(sString): 1073 """ Converts a string to a JSON value string. """ 1074 if not utils.isString(sString): 1075 sString = utils.toUnicode(sString); 1076 if not utils.isString(sString): 1077 sString = str(sString); 1078 return json.dumps(sString); 1079 1080 @staticmethod 1081 def dictToJson(dDict, dOptions = None): 1082 """ Converts a dictionary to a JSON string. """ 1083 sJson = u'{ '; 1084 for i, oKey in enumerate(dDict): 1085 if i > 0: 1086 sJson += ', '; 1087 sJson += '%s: %s' % (ModelDataBase.stringToJson(oKey), 1088 ModelDataBase.genericToJson(dDict[oKey], dOptions)); 1089 return sJson + ' }'; 1090 1091 @staticmethod 1092 def listToJson(aoList, dOptions = None): 1093 """ Converts list of something to a JSON string. """ 1094 sJson = u'[ '; 1095 for i, oValue in enumerate(aoList): 1096 if i > 0: 1097 sJson += u', '; 1098 sJson += ModelDataBase.genericToJson(oValue, dOptions); 1099 return sJson + u' ]'; 1100 1101 @staticmethod 1102 def datetimeToJson(oDateTime, dOptions = None): 1103 """ Converts a datetime instance to a JSON string. """ 1104 return '"%s"' % (oDateTime,); 1105 1106 1107 @staticmethod 1108 def genericToJson(oValue, dOptions = None): 1109 """ Converts a generic object to a JSON string. """ 1110 if isinstance(oValue, ModelDataBase): 1111 return oValue.toJson(); 1112 if isinstance(oValue, dict): 1113 return ModelDataBase.dictToJson(oValue, dOptions); 1114 if isinstance(oValue, (list, tuple, set, frozenset)): 1115 return ModelDataBase.listToJson(oValue, dOptions); 1116 if isinstance(oValue, datetime.datetime): 1117 return ModelDataBase.datetimeToJson(oValue, dOptions) 1118 return json.dumps(oValue); 1119 1120 def attribValueToJson(self, sAttr, oValue, dOptions = None): 1121 """ 1122 Converts the attribute value to JSON. 1123 Returns JSON (string). 1124 """ 1125 _ = sAttr; 1126 return self.genericToJson(oValue, dOptions); 1127 1128 def toJson(self, dOptions = None): 1129 """ 1130 Converts the object to JSON. 1131 Returns JSON (string). 1132 """ 1133 sJson = u'{ '; 1134 for iAttr, sAttr in enumerate(self.getDataAttributes()): 1135 oValue = getattr(self, sAttr); 1136 if iAttr > 0: 1137 sJson += ', '; 1138 sJson += u'"%s": ' % (sAttr,); 1139 sJson += self.attribValueToJson(sAttr, oValue, dOptions); 1140 return sJson + u' }'; 1141 1142 1064 1143 # 1065 1144 # Sub-classes. -
trunk/src/VBox/ValidationKit/testmanager/core/restdispatcher.py
r84555 r84599 3 3 4 4 """ 5 Test Manager Core - Web Server Abstraction Base Class.5 Test Manager Core - REST cgi handler. 6 6 """ 7 7 … … 31 31 32 32 # Standard python imports. 33 import re;34 33 import os; 35 import string; # pylint: disable=deprecated-module36 34 import sys; 37 import uuid;38 35 39 36 # Validation Kit imports. 40 from common import constants; 37 #from common import constants; 38 from common import utils; 41 39 from testmanager import config; 42 from testmanager.core import coreconsts;40 #from testmanager.core import coreconsts; 43 41 from testmanager.core.db import TMDatabaseConnection; 44 from testmanager.core.base import TMExceptionBase; 45 from testmanager.core.globalresource import GlobalResourceLogic; 46 from testmanager.core.testboxstatus import TestBoxStatusData, TestBoxStatusLogic; 47 from testmanager.core.testbox import TestBoxData, TestBoxLogic; 48 from testmanager.core.testresults import TestResultLogic, TestResultFileData; 49 from testmanager.core.testset import TestSetData, TestSetLogic; 50 from testmanager.core.systemlog import SystemLogData, SystemLogLogic; 51 from testmanager.core.schedulerbase import SchedulerBase; 42 from testmanager.core.base import TMExceptionBase, ModelDataBase; 52 43 53 44 # Python 3 hacks: … … 56 47 57 48 58 class TestBoxControllerException(TMExceptionBase): 49 # 50 # Exceptions 51 # 52 53 class RestDispException(TMExceptionBase): 59 54 """ 60 Exception class for TestBoxController.55 Exception class for the REST dispatcher. 61 56 """ 57 def __init__(self, sMsg, iStatus): 58 TMExceptionBase.__init__(self, sMsg); 59 self.iStatus = iStatus; 60 61 # 400 62 class RestDispException400(RestDispException): 63 """ A 400 error """ 64 def __init__(self, sMsg): 65 RestDispException.__init__(self, sMsg, 400); 66 67 class RestUnknownParameters(RestDispException400): 68 """ Unknown parameter(s). """ 62 69 pass; # pylint: disable=unnecessary-pass 63 70 64 65 class TestBoxController(object): # pylint: disable=too-few-public-methods 71 # 404 72 class RestDispException404(RestDispException): 73 """ A 404 error """ 74 def __init__(self, sMsg): 75 RestDispException.__init__(self, sMsg, 404); 76 77 class RestBadPathException(RestDispException404): 78 """ We've got a bad path. """ 79 pass; # pylint: disable=unnecessary-pass 80 81 class RestBadParameter(RestDispException404): 82 """ Bad parameter. """ 83 pass; # pylint: disable=unnecessary-pass 84 85 class RestMissingParameter(RestDispException404): 86 """ Missing parameter. """ 87 pass; # pylint: disable=unnecessary-pass 88 89 90 91 class RestMain(object): # pylint: disable=too-few-public-methods 66 92 """ 67 TestBox Controller class.93 REST main dispatcher class. 68 94 """ 69 95 70 ## Applicable testbox commands to an idle TestBox. 71 kasIdleCmds = [TestBoxData.ksTestBoxCmd_Reboot, 72 TestBoxData.ksTestBoxCmd_Upgrade, 73 TestBoxData.ksTestBoxCmd_UpgradeAndReboot, 74 TestBoxData.ksTestBoxCmd_Special]; 75 ## Applicable testbox commands to a busy TestBox. 76 kasBusyCmds = [TestBoxData.ksTestBoxCmd_Abort, TestBoxData.ksTestBoxCmd_Reboot]; 77 ## Commands that can be ACK'ed. 78 kasAckableCmds = [constants.tbresp.CMD_EXEC, constants.tbresp.CMD_ABORT, constants.tbresp.CMD_REBOOT, 79 constants.tbresp.CMD_UPGRADE, constants.tbresp.CMD_UPGRADE_AND_REBOOT, constants.tbresp.CMD_SPECIAL]; 80 ## Commands that can be NACK'ed or NOTSUP'ed. 81 kasNackableCmds = kasAckableCmds + [kasAckableCmds, constants.tbresp.CMD_IDLE, constants.tbresp.CMD_WAIT]; 82 83 ## Mapping from TestBoxCmd_T to TestBoxState_T 84 kdCmdToState = \ 85 { \ 86 TestBoxData.ksTestBoxCmd_Abort: None, 87 TestBoxData.ksTestBoxCmd_Reboot: TestBoxStatusData.ksTestBoxState_Rebooting, 88 TestBoxData.ksTestBoxCmd_Upgrade: TestBoxStatusData.ksTestBoxState_Upgrading, 89 TestBoxData.ksTestBoxCmd_UpgradeAndReboot: TestBoxStatusData.ksTestBoxState_UpgradingAndRebooting, 90 TestBoxData.ksTestBoxCmd_Special: TestBoxStatusData.ksTestBoxState_DoingSpecialCmd, 91 }; 92 93 ## Mapping from TestBoxCmd_T to TestBox responses commands. 94 kdCmdToTbRespCmd = \ 95 { 96 TestBoxData.ksTestBoxCmd_Abort: constants.tbresp.CMD_ABORT, 97 TestBoxData.ksTestBoxCmd_Reboot: constants.tbresp.CMD_REBOOT, 98 TestBoxData.ksTestBoxCmd_Upgrade: constants.tbresp.CMD_UPGRADE, 99 TestBoxData.ksTestBoxCmd_UpgradeAndReboot: constants.tbresp.CMD_UPGRADE_AND_REBOOT, 100 TestBoxData.ksTestBoxCmd_Special: constants.tbresp.CMD_SPECIAL, 101 }; 102 103 ## Mapping from TestBox responses to TestBoxCmd_T commands. 104 kdTbRespCmdToCmd = \ 105 { 106 constants.tbresp.CMD_IDLE: None, 107 constants.tbresp.CMD_WAIT: None, 108 constants.tbresp.CMD_EXEC: None, 109 constants.tbresp.CMD_ABORT: TestBoxData.ksTestBoxCmd_Abort, 110 constants.tbresp.CMD_REBOOT: TestBoxData.ksTestBoxCmd_Reboot, 111 constants.tbresp.CMD_UPGRADE: TestBoxData.ksTestBoxCmd_Upgrade, 112 constants.tbresp.CMD_UPGRADE_AND_REBOOT: TestBoxData.ksTestBoxCmd_UpgradeAndReboot, 113 constants.tbresp.CMD_SPECIAL: TestBoxData.ksTestBoxCmd_Special, 114 }; 115 116 117 ## The path to the upgrade zip, relative WebServerGlueBase.getBaseUrl(). 118 ksUpgradeZip = 'htdocs/upgrade/VBoxTestBoxScript.zip'; 119 120 ## Valid TestBox result values. 121 kasValidResults = list(constants.result.g_kasValidResults); 122 ## Mapping TestBox result values to TestStatus_T values. 123 kadTbResultToStatus = \ 124 { 125 constants.result.PASSED: TestSetData.ksTestStatus_Success, 126 constants.result.SKIPPED: TestSetData.ksTestStatus_Skipped, 127 constants.result.ABORTED: TestSetData.ksTestStatus_Aborted, 128 constants.result.BAD_TESTBOX: TestSetData.ksTestStatus_BadTestBox, 129 constants.result.FAILED: TestSetData.ksTestStatus_Failure, 130 constants.result.TIMED_OUT: TestSetData.ksTestStatus_TimedOut, 131 constants.result.REBOOTED: TestSetData.ksTestStatus_Rebooted, 132 }; 96 ksParam_sPath = 'sPath'; 133 97 134 98 135 99 def __init__(self, oSrvGlue): 136 """137 Won't raise exceptions.138 """139 100 self._oSrvGlue = oSrvGlue; 140 self._sAction = None; # _getStandardParams / dispatchRequest sets this later on. 141 self._idTestBox = None; # _getStandardParams / dispatchRequest sets this later on. 142 self._sTestBoxUuid = None; # _getStandardParams / dispatchRequest sets this later on. 143 self._sTestBoxAddr = None; # _getStandardParams / dispatchRequest sets this later on. 144 self._idTestSet = None; # _getStandardParams / dispatchRequest sets this later on. 101 self._oDb = TMDatabaseConnection(oSrvGlue.dprint); 102 self._iFirstHandlerPath = 0; 103 self._iNextHandlerPath = 0; 104 self._sPath = None; # _getStandardParams / dispatchRequest sets this later on. 105 self._asPath = None; # _getStandardParams / dispatchRequest sets this later on. 106 self._sMethod = None; # _getStandardParams / dispatchRequest sets this later on. 145 107 self._dParams = None; # _getStandardParams / dispatchRequest sets this later on. 146 108 self._asCheckedParams = []; 147 self._dActions = \ 148 { \ 149 constants.tbreq.SIGNON : self._actionSignOn, 150 constants.tbreq.REQUEST_COMMAND_BUSY: self._actionRequestCommandBusy, 151 constants.tbreq.REQUEST_COMMAND_IDLE: self._actionRequestCommandIdle, 152 constants.tbreq.COMMAND_ACK : self._actionCommandAck, 153 constants.tbreq.COMMAND_NACK : self._actionCommandNack, 154 constants.tbreq.COMMAND_NOTSUP : self._actionCommandNotSup, 155 constants.tbreq.LOG_MAIN : self._actionLogMain, 156 constants.tbreq.UPLOAD : self._actionUpload, 157 constants.tbreq.XML_RESULTS : self._actionXmlResults, 158 constants.tbreq.EXEC_COMPLETED : self._actionExecCompleted, 109 self._dGetTree = { 110 'vcs': { 111 'changelog': self._handleVcsChangelog_Get, 112 'bugreferences': self._handleVcsBugReferences_Get, 113 }, 159 114 }; 115 self._dMethodTrees = { 116 'GET': self._dGetTree, 117 } 118 119 # 120 # Helpers. 121 # 160 122 161 123 def _getStringParam(self, sName, asValidValues = None, fStrip = False, sDefValue = None): … … 168 130 if sName not in self._dParams: 169 131 if sDefValue is None: 170 raise TestBoxControllerException('%s parameter %s is missing' % (self._sAction, sName));132 raise RestMissingParameter('%s parameter %s is missing' % (self._sPath, sName)); 171 133 return sDefValue; 172 134 sValue = self._dParams[sName]; 135 if isinstance(sValue, list): 136 if len(sValue) == 1: 137 sValue = sValue[0]; 138 else: 139 raise RestBadParameter('%s parameter %s value is not a string but list: %s' 140 % (self._sPath, sName, sValue)); 173 141 if fStrip: 174 142 sValue = sValue.strip(); … … 178 146 179 147 if asValidValues is not None and sValue not in asValidValues: 180 raise TestBoxControllerException('%s parameter %s value "%s" not in %s ' \181 % (self._sAction, sName, sValue, asValidValues));148 raise RestBadParameter('%s parameter %s value "%s" not in %s ' 149 % (self._sPath, sName, sValue, asValidValues)); 182 150 return sValue; 183 151 … … 202 170 iValue = int(sValue, 0); 203 171 except: 204 raise TestBoxControllerException('%s parameter %s value "%s" cannot be convert to an integer' \205 % (self._sAction, sName, sValue));172 raise RestBadParameter('%s parameter %s value "%s" cannot be convert to an integer' 173 % (self._sPath, sName, sValue)); 206 174 207 175 if (iMin is not None and iValue < iMin) \ 208 176 or (iMax is not None and iValue > iMax): 209 raise TestBoxControllerException('%s parameter %s value %d is out of range [%s..%s]' \210 % (self._sAction, sName, iValue, iMin, iMax));177 raise RestBadParameter('%s parameter %s value %d is out of range [%s..%s]' 178 % (self._sPath, sName, iValue, iMin, iMax)); 211 179 return iValue; 212 180 … … 221 189 lValue = long(sValue, 0); 222 190 except Exception as oXcpt: 223 raise TestBoxControllerException('%s parameter %s value "%s" cannot be convert to an integer (%s)' \224 % (self._sAction, sName, sValue, oXcpt));191 raise RestBadParameter('%s parameter %s value "%s" cannot be convert to an integer (%s)' 192 % (self._sPath, sName, sValue, oXcpt)); 225 193 226 194 if (lMin is not None and lValue < lMin) \ 227 195 or (lMax is not None and lValue > lMax): 228 raise TestBoxControllerException('%s parameter %s value %d is out of range [%s..%s]' \229 % (self._sAction, sName, lValue, lMin, lMax));196 raise RestBadParameter('%s parameter %s value %d is out of range [%s..%s]' 197 % (self._sPath, sName, lValue, lMin, lMax)); 230 198 return lValue; 231 199 … … 241 209 if sKey not in self._asCheckedParams: 242 210 sUnknownParams += ' ' + sKey + '=' + self._dParams[sKey]; 243 raise TestBoxControllerException('Unknown parameters: ' + sUnknownParams);211 raise RestUnknownParameters('Unknown parameters: ' + sUnknownParams); 244 212 245 213 return True; 246 247 def _writeResponse(self, dParams):248 """249 Makes a reply to the testbox script.250 Will raise exception on failure.251 """252 self._oSrvGlue.writeParams(dParams);253 self._oSrvGlue.flush();254 return True;255 256 def _resultResponse(self, sResultValue):257 """258 Makes a simple reply to the testbox script.259 Will raise exception on failure.260 """261 return self._writeResponse({constants.tbresp.ALL_PARAM_RESULT: sResultValue});262 263 264 def _idleResponse(self):265 """266 Makes an IDLE reply to the testbox script.267 Will raise exception on failure.268 """269 return self._writeResponse({ constants.tbresp.ALL_PARAM_RESULT: constants.tbresp.CMD_IDLE });270 271 def _cleanupOldTest(self, oDb, oStatusData):272 """273 Cleans up any old test set that may be left behind and changes the274 state to 'idle'. See scenario #9:275 file://../../docs/AutomaticTestingRevamp.html#cleaning-up-abandoned-testcase276 277 Note. oStatusData.enmState is set to idle, but tsUpdated is not changed.278 """279 280 # Cleanup any abandoned test.281 if oStatusData.idTestSet is not None:282 SystemLogLogic(oDb).addEntry(SystemLogData.ksEvent_TestSetAbandoned,283 "idTestSet=%u idTestBox=%u enmState=%s %s"284 % (oStatusData.idTestSet, oStatusData.idTestBox,285 oStatusData.enmState, self._sAction),286 fCommit = False);287 TestSetLogic(oDb).completeAsAbandoned(oStatusData.idTestSet, fCommit = False);288 GlobalResourceLogic(oDb).freeGlobalResourcesByTestBox(self._idTestBox, fCommit = False);289 290 # Change to idle status291 if oStatusData.enmState != TestBoxStatusData.ksTestBoxState_Idle:292 TestBoxStatusLogic(oDb).updateState(self._idTestBox, TestBoxStatusData.ksTestBoxState_Idle, fCommit = False);293 oStatusData.tsUpdated = oDb.getCurrentTimestamp();294 oStatusData.enmState = TestBoxStatusData.ksTestBoxState_Idle;295 296 # Commit.297 oDb.commit();298 299 return True;300 301 def _connectToDbAndValidateTb(self, asValidStates = None):302 """303 Connects to the database and validates the testbox.304 305 Returns (TMDatabaseConnection, TestBoxStatusData, TestBoxData) on success.306 Returns (None, None, None) on failure after sending the box an appropriate response.307 May raise exception on DB error.308 """309 oDb = TMDatabaseConnection(self._oSrvGlue.dprint);310 oLogic = TestBoxStatusLogic(oDb);311 (oStatusData, oTestBoxData) = oLogic.tryFetchStatusAndConfig(self._idTestBox, self._sTestBoxUuid, self._sTestBoxAddr);312 if oStatusData is None:313 self._resultResponse(constants.tbresp.STATUS_DEAD);314 elif asValidStates is not None and oStatusData.enmState not in asValidStates:315 self._resultResponse(constants.tbresp.STATUS_NACK);316 elif self._idTestSet is not None and self._idTestSet != oStatusData.idTestSet:317 self._resultResponse(constants.tbresp.STATUS_NACK);318 else:319 return (oDb, oStatusData, oTestBoxData);320 return (None, None, None);321 214 322 215 def writeToMainLog(self, oTestSet, sText, fIgnoreSizeCheck = False): … … 346 239 return fSizeOk; 347 240 348 def _actionSignOn(self): # pylint: disable=too-many-locals 349 """ Implement sign-on """ 350 351 # 352 # Validate parameters (raises exception on failure). 353 # 354 sOs = self._getStringParam(constants.tbreq.SIGNON_PARAM_OS, coreconsts.g_kasOses); 355 sOsVersion = self._getStringParam(constants.tbreq.SIGNON_PARAM_OS_VERSION); 356 sCpuVendor = self._getStringParam(constants.tbreq.SIGNON_PARAM_CPU_VENDOR); 357 sCpuArch = self._getStringParam(constants.tbreq.SIGNON_PARAM_CPU_ARCH, coreconsts.g_kasCpuArches); 358 sCpuName = self._getStringParam(constants.tbreq.SIGNON_PARAM_CPU_NAME, fStrip = True, sDefValue = ''); # new 359 lCpuRevision = self._getLongParam( constants.tbreq.SIGNON_PARAM_CPU_REVISION, lMin = 0, lDefValue = 0); # new 360 cCpus = self._getIntParam( constants.tbreq.SIGNON_PARAM_CPU_COUNT, 1, 16384); 361 fCpuHwVirt = self._getBoolParam( constants.tbreq.SIGNON_PARAM_HAS_HW_VIRT); 362 fCpuNestedPaging = self._getBoolParam( constants.tbreq.SIGNON_PARAM_HAS_NESTED_PAGING); 363 fCpu64BitGuest = self._getBoolParam( constants.tbreq.SIGNON_PARAM_HAS_64_BIT_GUEST, fDefValue = True); 364 fChipsetIoMmu = self._getBoolParam( constants.tbreq.SIGNON_PARAM_HAS_IOMMU); 365 fRawMode = self._getBoolParam( constants.tbreq.SIGNON_PARAM_WITH_RAW_MODE, fDefValue = None); 366 cMbMemory = self._getLongParam( constants.tbreq.SIGNON_PARAM_MEM_SIZE, 8, 1073741823); # 8MB..1PB 367 cMbScratch = self._getLongParam( constants.tbreq.SIGNON_PARAM_SCRATCH_SIZE, 0, 1073741823); # 0..1PB 368 sReport = self._getStringParam(constants.tbreq.SIGNON_PARAM_REPORT, fStrip = True, sDefValue = ''); # new 369 iTestBoxScriptRev = self._getIntParam( constants.tbreq.SIGNON_PARAM_SCRIPT_REV, 1, 100000000); 370 iPythonHexVersion = self._getIntParam( constants.tbreq.SIGNON_PARAM_PYTHON_VERSION, 0x020300f0, 0x030f00f0); 241 def _getNextPathElementString(self, sName, oDefault = None): 242 """ 243 Gets the next handler specific path element. 244 Returns unprocessed string. 245 Throws exception 246 """ 247 i = self._iNextHandlerPath; 248 if i < len(self._asPath): 249 self._iNextHandlerPath = i + 1; 250 return self._asPath[i]; 251 if oDefault is None: 252 raise RestBadPathException('Requires a "%s" element after "%s"' % (sName, self._sPath,)); 253 return oDefault; 254 255 def _getNextPathElementInt(self, sName, iDefault = None, iMin = None, iMax = None): 256 """ 257 Gets the next handle specific path element as an integer. 258 Returns integer value. 259 Throws exception if not found or not a valid integer. 260 """ 261 sValue = self._getNextPathElementString(sName, oDefault = iDefault); 262 try: 263 iValue = int(sValue); 264 except: 265 raise RestBadPathException('Not an integer "%s" (%s)' % (sValue, sName,)); 266 if iMin is not None and iValue < iMin: 267 raise RestBadPathException('Integer "%s" value (%s) is too small, min %s' % (sValue, sName, iMin)); 268 if iMax is not None and iValue > iMax: 269 raise RestBadPathException('Integer "%s" value (%s) is too large, max %s' % (sValue, sName, iMax)); 270 return iValue; 271 272 def _getNextPathElementLong(self, sName, iDefault = None, iMin = None, iMax = None): 273 """ 274 Gets the next handle specific path element as a long integer. 275 Returns integer value. 276 Throws exception if not found or not a valid integer. 277 """ 278 sValue = self._getNextPathElementString(sName, oDefault = iDefault); 279 try: 280 iValue = long(sValue); 281 except: 282 raise RestBadPathException('Not an integer "%s" (%s)' % (sValue, sName,)); 283 if iMin is not None and iValue < iMin: 284 raise RestBadPathException('Integer "%s" value (%s) is too small, min %s' % (sValue, sName, iMin)); 285 if iMax is not None and iValue > iMax: 286 raise RestBadPathException('Integer "%s" value (%s) is too large, max %s' % (sValue, sName, iMax)); 287 return iValue; 288 289 def _checkNoMorePathElements(self): 290 """ 291 Checks that there are no more path elements. 292 Throws exception if there are. 293 """ 294 i = self._iNextHandlerPath; 295 if i < len(self._asPath): 296 raise RestBadPathException('Unknown subpath "%s" below "%s"' % 297 ('/'.join(self._asPath[i:]), '/'.join(self._asPath[:i]),)); 298 return True; 299 300 def _doneParsingArguments(self): 301 """ 302 Checks that there are no more path elements or unhandled parameters. 303 Throws exception if there are. 304 """ 305 self._checkNoMorePathElements(); 371 306 self._checkForUnknownParameters(); 372 373 # Null conversions for new parameters. 374 if not sReport: 375 sReport = None; 376 if not sCpuName: 377 sCpuName = None; 378 if lCpuRevision <= 0: 379 lCpuRevision = None; 380 381 # 382 # Connect to the database and validate the testbox. 383 # 384 oDb = TMDatabaseConnection(self._oSrvGlue.dprint); 385 oTestBoxLogic = TestBoxLogic(oDb); 386 oTestBox = oTestBoxLogic.tryFetchTestBoxByUuid(self._sTestBoxUuid); 387 if oTestBox is None: 388 oSystemLogLogic = SystemLogLogic(oDb); 389 oSystemLogLogic.addEntry(SystemLogData.ksEvent_TestBoxUnknown, 390 'addr=%s uuid=%s os=%s %d cpus' \ 391 % (self._sTestBoxAddr, self._sTestBoxUuid, sOs, cCpus), 392 24, fCommit = True); 393 return self._resultResponse(constants.tbresp.STATUS_NACK); 394 395 # 396 # Update the row in TestBoxes if something changed. 397 # 398 if oTestBox.cMbScratch is not None and oTestBox.cMbScratch != 0: 399 cPctScratchDiff = (cMbScratch - oTestBox.cMbScratch) * 100 / oTestBox.cMbScratch; 400 else: 401 cPctScratchDiff = 100; 402 403 # pylint: disable=too-many-boolean-expressions 404 if self._sTestBoxAddr != oTestBox.ip \ 405 or sOs != oTestBox.sOs \ 406 or sOsVersion != oTestBox.sOsVersion \ 407 or sCpuVendor != oTestBox.sCpuVendor \ 408 or sCpuArch != oTestBox.sCpuArch \ 409 or sCpuName != oTestBox.sCpuName \ 410 or lCpuRevision != oTestBox.lCpuRevision \ 411 or cCpus != oTestBox.cCpus \ 412 or fCpuHwVirt != oTestBox.fCpuHwVirt \ 413 or fCpuNestedPaging != oTestBox.fCpuNestedPaging \ 414 or fCpu64BitGuest != oTestBox.fCpu64BitGuest \ 415 or fChipsetIoMmu != oTestBox.fChipsetIoMmu \ 416 or fRawMode != oTestBox.fRawMode \ 417 or cMbMemory != oTestBox.cMbMemory \ 418 or abs(cPctScratchDiff) >= min(4 + cMbScratch / 10240, 12) \ 419 or sReport != oTestBox.sReport \ 420 or iTestBoxScriptRev != oTestBox.iTestBoxScriptRev \ 421 or iPythonHexVersion != oTestBox.iPythonHexVersion: 422 oTestBoxLogic.updateOnSignOn(oTestBox.idTestBox, 423 oTestBox.idGenTestBox, 424 sTestBoxAddr = self._sTestBoxAddr, 425 sOs = sOs, 426 sOsVersion = sOsVersion, 427 sCpuVendor = sCpuVendor, 428 sCpuArch = sCpuArch, 429 sCpuName = sCpuName, 430 lCpuRevision = lCpuRevision, 431 cCpus = cCpus, 432 fCpuHwVirt = fCpuHwVirt, 433 fCpuNestedPaging = fCpuNestedPaging, 434 fCpu64BitGuest = fCpu64BitGuest, 435 fChipsetIoMmu = fChipsetIoMmu, 436 fRawMode = fRawMode, 437 cMbMemory = cMbMemory, 438 cMbScratch = cMbScratch, 439 sReport = sReport, 440 iTestBoxScriptRev = iTestBoxScriptRev, 441 iPythonHexVersion = iPythonHexVersion); 442 443 # 444 # Update the testbox status, making sure there is a status. 445 # 446 oStatusLogic = TestBoxStatusLogic(oDb); 447 oStatusData = oStatusLogic.tryFetchStatus(oTestBox.idTestBox); 448 if oStatusData is not None: 449 self._cleanupOldTest(oDb, oStatusData); 450 else: 451 oStatusLogic.insertIdleStatus(oTestBox.idTestBox, oTestBox.idGenTestBox, fCommit = True); 452 453 # 454 # ACK the request. 455 # 456 dResponse = \ 457 { 458 constants.tbresp.ALL_PARAM_RESULT: constants.tbresp.STATUS_ACK, 459 constants.tbresp.SIGNON_PARAM_ID: oTestBox.idTestBox, 460 constants.tbresp.SIGNON_PARAM_NAME: oTestBox.sName, 461 } 462 return self._writeResponse(dResponse); 463 464 def _doGangCleanup(self, oDb, oStatusData): 465 """ 466 _doRequestCommand worker for handling a box in gang-cleanup. 467 This will check if all testboxes has completed their run, pretending to 468 be busy until that happens. Once all are completed, resources will be 469 freed and the testbox returns to idle state (we update oStatusData). 470 """ 471 oStatusLogic = TestBoxStatusLogic(oDb) 472 oTestSet = TestSetData().initFromDbWithId(oDb, oStatusData.idTestSet); 473 if oStatusLogic.isWholeGangDoneTesting(oTestSet.idTestSetGangLeader): 474 oDb.begin(); 475 476 GlobalResourceLogic(oDb).freeGlobalResourcesByTestBox(self._idTestBox, fCommit = False); 477 TestBoxStatusLogic(oDb).updateState(self._idTestBox, TestBoxStatusData.ksTestBoxState_Idle, fCommit = False); 478 479 oStatusData.tsUpdated = oDb.getCurrentTimestamp(); 480 oStatusData.enmState = TestBoxStatusData.ksTestBoxState_Idle; 481 482 oDb.commit(); 483 return None; 484 485 def _doGangGatheringTimedOut(self, oDb, oStatusData): 486 """ 487 _doRequestCommand worker for handling a box in gang-gathering-timed-out state. 488 This will do clean-ups similar to _cleanupOldTest and update the state likewise. 489 """ 490 oDb.begin(); 491 492 TestSetLogic(oDb).completeAsGangGatheringTimeout(oStatusData.idTestSet, fCommit = False); 493 GlobalResourceLogic(oDb).freeGlobalResourcesByTestBox(self._idTestBox, fCommit = False); 494 TestBoxStatusLogic(oDb).updateState(self._idTestBox, TestBoxStatusData.ksTestBoxState_Idle, fCommit = False); 495 496 oStatusData.tsUpdated = oDb.getCurrentTimestamp(); 497 oStatusData.enmState = TestBoxStatusData.ksTestBoxState_Idle; 498 499 oDb.commit(); 500 return None; 501 502 def _doGangGathering(self, oDb, oStatusData): 503 """ 504 _doRequestCommand worker for handling a box in gang-gathering state. 505 This only checks for timeout. It will update the oStatusData if a 506 timeout is detected, so that the box will be idle upon return. 507 """ 508 oStatusLogic = TestBoxStatusLogic(oDb); 509 if oStatusLogic.timeSinceLastChangeInSecs(oStatusData) > config.g_kcSecGangGathering \ 510 and SchedulerBase.tryCancelGangGathering(oDb, oStatusData): # <-- Updates oStatusData. 511 self._doGangGatheringTimedOut(oDb, oStatusData); 512 return None; 513 514 def _doRequestCommand(self, fIdle): 515 """ 516 Common code for handling command request. 517 """ 518 519 (oDb, oStatusData, oTestBoxData) = self._connectToDbAndValidateTb(); 520 if oDb is None: 521 return False; 522 523 # 524 # Status clean up. 525 # 526 # Only when BUSY will the TestBox Script request and execute commands 527 # concurrently. So, it must be idle when sending REQUEST_COMMAND_IDLE. 528 # 529 if fIdle: 530 if oStatusData.enmState == TestBoxStatusData.ksTestBoxState_GangGathering: 531 self._doGangGathering(oDb, oStatusData); 532 elif oStatusData.enmState == TestBoxStatusData.ksTestBoxState_GangGatheringTimedOut: 533 self._doGangGatheringTimedOut(oDb, oStatusData); 534 elif oStatusData.enmState == TestBoxStatusData.ksTestBoxState_GangTesting: 535 dResponse = SchedulerBase.composeExecResponse(oDb, oTestBoxData.idTestBox, self._oSrvGlue.getBaseUrl()); 536 if dResponse is not None: 537 return dResponse; 538 elif oStatusData.enmState == TestBoxStatusData.ksTestBoxState_GangCleanup: 539 self._doGangCleanup(oDb, oStatusData); 540 elif oStatusData.enmState != TestBoxStatusData.ksTestBoxState_Idle: # (includes ksTestBoxState_GangGatheringTimedOut) 541 self._cleanupOldTest(oDb, oStatusData); 542 543 # 544 # Check for pending command. 545 # 546 if oTestBoxData.enmPendingCmd != TestBoxData.ksTestBoxCmd_None: 547 asValidCmds = TestBoxController.kasIdleCmds if fIdle else TestBoxController.kasBusyCmds; 548 if oTestBoxData.enmPendingCmd in asValidCmds: 549 dResponse = { constants.tbresp.ALL_PARAM_RESULT: TestBoxController.kdCmdToTbRespCmd[oTestBoxData.enmPendingCmd] }; 550 if oTestBoxData.enmPendingCmd in [TestBoxData.ksTestBoxCmd_Upgrade, TestBoxData.ksTestBoxCmd_UpgradeAndReboot]: 551 dResponse[constants.tbresp.UPGRADE_PARAM_URL] = self._oSrvGlue.getBaseUrl() + TestBoxController.ksUpgradeZip; 552 return self._writeResponse(dResponse); 553 554 if oTestBoxData.enmPendingCmd == TestBoxData.ksTestBoxCmd_Abort and fIdle: 555 TestBoxLogic(oDb).setCommand(self._idTestBox, sOldCommand = oTestBoxData.enmPendingCmd, 556 sNewCommand = TestBoxData.ksTestBoxCmd_None, fCommit = True); 557 558 # 559 # If doing gang stuff, return 'CMD_WAIT'. 560 # 561 ## @todo r=bird: Why is GangTesting included here? Figure out when testing gang testing. 562 if oStatusData.enmState in [TestBoxStatusData.ksTestBoxState_GangGathering, 563 TestBoxStatusData.ksTestBoxState_GangTesting, 564 TestBoxStatusData.ksTestBoxState_GangCleanup]: 565 return self._resultResponse(constants.tbresp.CMD_WAIT); 566 567 # 568 # If idling and enabled try schedule a new task. 569 # 570 if fIdle \ 571 and oTestBoxData.fEnabled \ 572 and not TestSetLogic(oDb).isTestBoxExecutingToRapidly(oTestBoxData.idTestBox) \ 573 and oStatusData.enmState == TestBoxStatusData.ksTestBoxState_Idle: # (paranoia) 574 dResponse = SchedulerBase.scheduleNewTask(oDb, oTestBoxData, oStatusData.iWorkItem, self._oSrvGlue.getBaseUrl()); 575 if dResponse is not None: 576 return self._writeResponse(dResponse); 577 578 # 579 # Touch the status row every couple of mins so we can tell that the box is alive. 580 # 581 oStatusLogic = TestBoxStatusLogic(oDb); 582 if oStatusData.enmState != TestBoxStatusData.ksTestBoxState_GangGathering \ 583 and oStatusLogic.timeSinceLastChangeInSecs(oStatusData) >= TestBoxStatusLogic.kcSecIdleTouchStatus: 584 oStatusLogic.touchStatus(oTestBoxData.idTestBox, fCommit = True); 585 586 return self._idleResponse(); 587 588 def _actionRequestCommandBusy(self): 589 """ Implement request for command. """ 307 return True; 308 309 def _dataArrayToJsonReply(self, aoData, sName = 'aoData', dExtraFields = None, iStatus = 200): 310 """ 311 Converts aoData into an array objects 312 return True. 313 """ 314 self._oSrvGlue.setContentType('application/json'); 315 self._oSrvGlue.setStatus(iStatus); 316 self._oSrvGlue.write(u'{\n'); 317 if dExtraFields: 318 for sKey in dExtraFields: 319 self._oSrvGlue.write(u' "%s": %s,\n' % (sKey, ModelDataBase.genericToJson(dExtraFields[sKey]),)); 320 self._oSrvGlue.write(u' "c%s": %u,\n' % (sName[2:],len(aoData),)); 321 self._oSrvGlue.write(u' "%s": [\n' % (sName,)); 322 for i, oData in enumerate(aoData): 323 if i > 0: 324 self._oSrvGlue.write(u',\n'); 325 self._oSrvGlue.write(ModelDataBase.genericToJson(oData)); 326 self._oSrvGlue.write(u' ]\n'); 327 ## @todo if config.g_kfWebUiSqlDebug: 328 self._oSrvGlue.write(u'}\n'); 329 self._oSrvGlue.flush(); 330 return True; 331 332 333 # 334 # Handlers. 335 # 336 337 def _handleVcsChangelog_Get(self): 338 """ GET /vcs/changelog/{sRepository}/{iStartRev}[/{cEntriesBack}] """ 339 # Parse arguments 340 sRepository = self._getNextPathElementString('sRepository'); 341 iStartRev = self._getNextPathElementInt('iStartRev', iMin = 0); 342 cEntriesBack = self._getNextPathElementInt('cEntriesBack', iDefault = 32, iMin = 0, iMax = 8192); 343 self._checkNoMorePathElements(); 590 344 self._checkForUnknownParameters(); 591 return self._doRequestCommand(False); 592 593 def _actionRequestCommandIdle(self): 594 """ Implement request for command. """ 345 346 # Execute it. 347 from testmanager.core.vcsrevisions import VcsRevisionLogic; 348 oLogic = VcsRevisionLogic(self._oDb); 349 return self._dataArrayToJsonReply(oLogic.fetchTimeline(sRepository, iStartRev, cEntriesBack), 'aoCommits', 350 { 'sTracChangesetUrlFmt': 351 config.g_ksTracChangsetUrlFmt.replace('%(sRepository)s', sRepository), } ); 352 353 def _handleVcsBugReferences_Get(self): 354 """ GET /vcs/bugreferences/{sTrackerId}/{lBugId} """ 355 # Parse arguments 356 sTrackerId = self._getNextPathElementString('sTrackerId'); 357 lBugId = self._getNextPathElementLong('lBugId', iMin = 0); 358 self._checkNoMorePathElements(); 595 359 self._checkForUnknownParameters(); 596 return self._doRequestCommand(True); 597 598 def _doCommandAckNck(self, sCmd): 599 """ Implements ACK, NACK and NACK(ENOTSUP). """ 600 601 (oDb, _, _) = self._connectToDbAndValidateTb(); 602 if oDb is None: 603 return False; 604 605 # 606 # If the command maps to a TestBoxCmd_T value, it means we have to 607 # check and update TestBoxes. If it's an ACK, the testbox status will 608 # need updating as well. 609 # 610 sPendingCmd = TestBoxController.kdTbRespCmdToCmd[sCmd]; 611 if sPendingCmd is not None: 612 oTestBoxLogic = TestBoxLogic(oDb) 613 oTestBoxLogic.setCommand(self._idTestBox, sOldCommand = sPendingCmd, 614 sNewCommand = TestBoxData.ksTestBoxCmd_None, fCommit = False); 615 616 if self._sAction == constants.tbreq.COMMAND_ACK \ 617 and TestBoxController.kdCmdToState[sPendingCmd] is not None: 618 oStatusLogic = TestBoxStatusLogic(oDb); 619 oStatusLogic.updateState(self._idTestBox, TestBoxController.kdCmdToState[sPendingCmd], fCommit = False); 620 621 # Commit the two updates. 622 oDb.commit(); 623 624 # 625 # Log NACKs. 626 # 627 if self._sAction != constants.tbreq.COMMAND_ACK: 628 oSysLogLogic = SystemLogLogic(oDb); 629 oSysLogLogic.addEntry(SystemLogData.ksEvent_CmdNacked, 630 'idTestBox=%s sCmd=%s' % (self._idTestBox, sPendingCmd), 631 24, fCommit = True); 632 633 return self._resultResponse(constants.tbresp.STATUS_ACK); 634 635 def _actionCommandAck(self): 636 """ Implement command ACK'ing """ 637 sCmd = self._getStringParam(constants.tbreq.COMMAND_ACK_PARAM_CMD_NAME, TestBoxController.kasAckableCmds); 638 self._checkForUnknownParameters(); 639 return self._doCommandAckNck(sCmd); 640 641 def _actionCommandNack(self): 642 """ Implement command NACK'ing """ 643 sCmd = self._getStringParam(constants.tbreq.COMMAND_ACK_PARAM_CMD_NAME, TestBoxController.kasNackableCmds); 644 self._checkForUnknownParameters(); 645 return self._doCommandAckNck(sCmd); 646 647 def _actionCommandNotSup(self): 648 """ Implement command NACK(ENOTSUP)'ing """ 649 sCmd = self._getStringParam(constants.tbreq.COMMAND_ACK_PARAM_CMD_NAME, TestBoxController.kasNackableCmds); 650 self._checkForUnknownParameters(); 651 return self._doCommandAckNck(sCmd); 652 653 def _actionLogMain(self): 654 """ Implement submitting log entries to the main log file. """ 655 # 656 # Parameter validation. 657 # 658 sBody = self._getStringParam(constants.tbreq.LOG_PARAM_BODY, fStrip = False); 659 if not sBody: 660 return self._resultResponse(constants.tbresp.STATUS_NACK); 661 self._checkForUnknownParameters(); 662 663 (oDb, oStatusData, _) = self._connectToDbAndValidateTb([TestBoxStatusData.ksTestBoxState_Testing, 664 TestBoxStatusData.ksTestBoxState_GangTesting]); 665 if oStatusData is None: 666 return False; 667 668 # 669 # Write the text to the log file. 670 # 671 oTestSet = TestSetData().initFromDbWithId(oDb, oStatusData.idTestSet); 672 self.writeToMainLog(oTestSet, sBody); 673 ## @todo Overflow is a hanging offence, need to note it and fail whatever is going on... 674 675 # Done. 676 return self._resultResponse(constants.tbresp.STATUS_ACK); 677 678 def _actionUpload(self): 679 """ Implement uploading of files. """ 680 # 681 # Parameter validation. 682 # 683 sName = self._getStringParam(constants.tbreq.UPLOAD_PARAM_NAME); 684 sMime = self._getStringParam(constants.tbreq.UPLOAD_PARAM_MIME); 685 sKind = self._getStringParam(constants.tbreq.UPLOAD_PARAM_KIND); 686 sDesc = self._getStringParam(constants.tbreq.UPLOAD_PARAM_DESC); 687 self._checkForUnknownParameters(); 688 689 (oDb, oStatusData, _) = self._connectToDbAndValidateTb([TestBoxStatusData.ksTestBoxState_Testing, 690 TestBoxStatusData.ksTestBoxState_GangTesting]); 691 if oStatusData is None: 692 return False; 693 694 if len(sName) > 128 or len(sName) < 3: 695 raise TestBoxControllerException('Invalid file name "%s"' % (sName,)); 696 if re.match(r'^[a-zA-Z0-9_\-(){}#@+,.=]*$', sName) is None: 697 raise TestBoxControllerException('Invalid file name "%s"' % (sName,)); 698 699 if sMime not in [ 'text/plain', #'text/html', 'text/xml', 700 'application/octet-stream', 701 'image/png', #'image/gif', 'image/jpeg', 702 #'video/webm', 'video/mpeg', 'video/mpeg4-generic', 703 ]: 704 raise TestBoxControllerException('Invalid MIME type "%s"' % (sMime,)); 705 706 if sKind not in TestResultFileData.kasKinds: 707 raise TestBoxControllerException('Invalid kind "%s"' % (sKind,)); 708 709 if len(sDesc) > 256: 710 raise TestBoxControllerException('Invalid description "%s"' % (sDesc,)); 711 if not set(sDesc).issubset(set(string.printable)): 712 raise TestBoxControllerException('Invalid description "%s"' % (sDesc,)); 713 714 if ('application/octet-stream', {}) != self._oSrvGlue.getContentType(): 715 raise TestBoxControllerException('Unexpected content type: %s; %s' % self._oSrvGlue.getContentType()); 716 717 cbFile = self._oSrvGlue.getContentLength(); 718 if cbFile <= 0: 719 raise TestBoxControllerException('File "%s" is empty or negative in size (%s)' % (sName, cbFile)); 720 if (cbFile + 1048575) / 1048576 > config.g_kcMbMaxUploadSingle: 721 raise TestBoxControllerException('File "%s" is too big %u bytes (max %u MiB)' 722 % (sName, cbFile, config.g_kcMbMaxUploadSingle,)); 723 724 # 725 # Write the text to the log file. 726 # 727 oTestSet = TestSetData().initFromDbWithId(oDb, oStatusData.idTestSet); 728 oDstFile = TestSetLogic(oDb).createFile(oTestSet, sName = sName, sMime = sMime, sKind = sKind, sDesc = sDesc, 729 cbFile = cbFile, fCommit = True); 730 731 offFile = 0; 732 oSrcFile = self._oSrvGlue.getBodyIoStream(); 733 while offFile < cbFile: 734 cbToRead = cbFile - offFile; 735 if cbToRead > 256*1024: 736 cbToRead = 256*1024; 737 offFile += cbToRead; 738 739 abBuf = oSrcFile.read(cbToRead); 740 oDstFile.write(abBuf); # pylint: disable=maybe-no-member 741 del abBuf; 742 743 oDstFile.close(); # pylint: disable=maybe-no-member 744 745 # Done. 746 return self._resultResponse(constants.tbresp.STATUS_ACK); 747 748 def _actionXmlResults(self): 749 """ Implement submitting "XML" like test result stream. """ 750 # 751 # Parameter validation. 752 # 753 sXml = self._getStringParam(constants.tbreq.XML_RESULT_PARAM_BODY, fStrip = False); 754 self._checkForUnknownParameters(); 755 if not sXml: # Used for link check by vboxinstaller.py on Windows. 756 return self._resultResponse(constants.tbresp.STATUS_ACK); 757 758 (oDb, oStatusData, _) = self._connectToDbAndValidateTb([TestBoxStatusData.ksTestBoxState_Testing, 759 TestBoxStatusData.ksTestBoxState_GangTesting]); 760 if oStatusData is None: 761 return False; 762 763 # 764 # Process the XML. 765 # 766 (sError, fUnforgivable) = TestResultLogic(oDb).processXmlStream(sXml, self._idTestSet); 767 if sError is not None: 768 oTestSet = TestSetData().initFromDbWithId(oDb, oStatusData.idTestSet); 769 self.writeToMainLog(oTestSet, '\n!!XML error: %s\n%s\n\n' % (sError, sXml,)); 770 if fUnforgivable: 771 return self._resultResponse(constants.tbresp.STATUS_NACK); 772 return self._resultResponse(constants.tbresp.STATUS_ACK); 773 774 775 def _actionExecCompleted(self): 776 """ 777 Implement EXEC completion. 778 779 Because the action is request by the worker thread of the testbox 780 script we cannot pass pending commands back to it like originally 781 planned. So, we just complete the test set and update the status. 782 """ 783 # 784 # Parameter validation. 785 # 786 sStatus = self._getStringParam(constants.tbreq.EXEC_COMPLETED_PARAM_RESULT, TestBoxController.kasValidResults); 787 self._checkForUnknownParameters(); 788 789 (oDb, oStatusData, _) = self._connectToDbAndValidateTb([TestBoxStatusData.ksTestBoxState_Testing, 790 TestBoxStatusData.ksTestBoxState_GangTesting]); 791 if oStatusData is None: 792 return False; 793 794 # 795 # Complete the status. 796 # 797 oDb.rollback(); 798 oDb.begin(); 799 oTestSetLogic = TestSetLogic(oDb); 800 idTestSetGangLeader = oTestSetLogic.complete(oStatusData.idTestSet, self.kadTbResultToStatus[sStatus], fCommit = False); 801 802 oStatusLogic = TestBoxStatusLogic(oDb); 803 if oStatusData.enmState == TestBoxStatusData.ksTestBoxState_Testing: 804 assert idTestSetGangLeader is None; 805 GlobalResourceLogic(oDb).freeGlobalResourcesByTestBox(self._idTestBox); 806 oStatusLogic.updateState(self._idTestBox, TestBoxStatusData.ksTestBoxState_Idle, fCommit = False); 807 else: 808 assert idTestSetGangLeader is not None; 809 oStatusLogic.updateState(self._idTestBox, TestBoxStatusData.ksTestBoxState_GangCleanup, oStatusData.idTestSet, 810 fCommit = False); 811 if oStatusLogic.isWholeGangDoneTesting(idTestSetGangLeader): 812 GlobalResourceLogic(oDb).freeGlobalResourcesByTestBox(self._idTestBox); 813 oStatusLogic.updateState(self._idTestBox, TestBoxStatusData.ksTestBoxState_Idle, fCommit = False); 814 815 oDb.commit(); 816 return self._resultResponse(constants.tbresp.STATUS_ACK); 817 818 819 820 def _getStandardParams(self, dParams): 821 """ 822 Gets the standard parameters and validates them. 823 824 The parameters are returned as a tuple: sAction, idTestBox, sTestBoxUuid. 825 Note! the sTextBoxId can be None if it's a SIGNON request. 826 827 Raises TestBoxControllerException on invalid input. 828 """ 829 # 830 # Get the action parameter and validate it. 831 # 832 if constants.tbreq.ALL_PARAM_ACTION not in dParams: 833 raise TestBoxControllerException('No "%s" parameter in request (params: %s)' \ 834 % (constants.tbreq.ALL_PARAM_ACTION, dParams,)); 835 sAction = dParams[constants.tbreq.ALL_PARAM_ACTION]; 836 837 if sAction not in self._dActions: 838 raise TestBoxControllerException('Unknown action "%s" in request (params: %s; action: %s)' \ 839 % (sAction, dParams, self._dActions)); 840 841 # 842 # TestBox UUID. 843 # 844 if constants.tbreq.ALL_PARAM_TESTBOX_UUID not in dParams: 845 raise TestBoxControllerException('No "%s" parameter in request (params: %s)' \ 846 % (constants.tbreq.ALL_PARAM_TESTBOX_UUID, dParams,)); 847 sTestBoxUuid = dParams[constants.tbreq.ALL_PARAM_TESTBOX_UUID]; 848 try: 849 sTestBoxUuid = str(uuid.UUID(sTestBoxUuid)); 850 except Exception as oXcpt: 851 raise TestBoxControllerException('Invalid %s parameter value "%s": %s ' \ 852 % (constants.tbreq.ALL_PARAM_TESTBOX_UUID, sTestBoxUuid, oXcpt)); 853 if sTestBoxUuid == '00000000-0000-0000-0000-000000000000': 854 raise TestBoxControllerException('Invalid %s parameter value "%s": NULL UUID not allowed.' \ 855 % (constants.tbreq.ALL_PARAM_TESTBOX_UUID, sTestBoxUuid)); 856 857 # 858 # TestBox ID. 859 # 860 if constants.tbreq.ALL_PARAM_TESTBOX_ID in dParams: 861 sTestBoxId = dParams[constants.tbreq.ALL_PARAM_TESTBOX_ID]; 862 try: 863 idTestBox = int(sTestBoxId); 864 if idTestBox <= 0 or idTestBox >= 0x7fffffff: 865 raise Exception; 866 except: 867 raise TestBoxControllerException('Bad value for "%s": "%s"' \ 868 % (constants.tbreq.ALL_PARAM_TESTBOX_ID, sTestBoxId)); 869 elif sAction == constants.tbreq.SIGNON: 870 idTestBox = None; 871 else: 872 raise TestBoxControllerException('No "%s" parameter in request (params: %s)' \ 873 % (constants.tbreq.ALL_PARAM_TESTBOX_ID, dParams,)); 874 875 # 876 # Test Set ID. 877 # 878 if constants.tbreq.RESULT_PARAM_TEST_SET_ID in dParams: 879 sTestSetId = dParams[constants.tbreq.RESULT_PARAM_TEST_SET_ID]; 880 try: 881 idTestSet = int(sTestSetId); 882 if idTestSet <= 0 or idTestSet >= 0x7fffffff: 883 raise Exception; 884 except: 885 raise TestBoxControllerException('Bad value for "%s": "%s"' \ 886 % (constants.tbreq.RESULT_PARAM_TEST_SET_ID, sTestSetId)); 887 elif sAction not in [ constants.tbreq.XML_RESULTS, ]: ## More later. 888 idTestSet = None; 889 else: 890 raise TestBoxControllerException('No "%s" parameter in request (params: %s)' \ 891 % (constants.tbreq.RESULT_PARAM_TEST_SET_ID, dParams,)); 892 893 # 894 # The testbox address. 895 # 896 sTestBoxAddr = self._oSrvGlue.getClientAddr(); 897 if sTestBoxAddr is None or sTestBoxAddr.strip() == '': 898 raise TestBoxControllerException('Invalid client address "%s"' % (sTestBoxAddr,)); 899 900 # 901 # Update the list of checked parameters. 902 # 903 self._asCheckedParams.extend([constants.tbreq.ALL_PARAM_TESTBOX_UUID, constants.tbreq.ALL_PARAM_ACTION]); 904 if idTestBox is not None: 905 self._asCheckedParams.append(constants.tbreq.ALL_PARAM_TESTBOX_ID); 906 if idTestSet is not None: 907 self._asCheckedParams.append(constants.tbreq.RESULT_PARAM_TEST_SET_ID); 908 909 return (sAction, idTestBox, sTestBoxUuid, sTestBoxAddr, idTestSet); 910 911 def dispatchRequest(self): 912 """ 913 Dispatches the incoming request. 914 915 Will raise TestBoxControllerException on failure. 916 """ 917 918 # 919 # Must be a POST request. 360 361 # Execute it. 362 from testmanager.core.vcsbugreference import VcsBugReferenceLogic; 363 oLogic = VcsBugReferenceLogic(self._oDb); 364 oLogic.fetchForBug(sTrackerId, lBugId) 365 return self._dataArrayToJsonReply(oLogic.fetchForBug(sTrackerId, lBugId), 'aoCommits', 366 { 'sTracChangesetUrlFmt': config.g_ksTracChangsetUrlFmt, } ); 367 368 369 # 370 # Dispatching. 371 # 372 373 def _dispatchRequestCommon(self): 374 """ 375 Dispatches the incoming request after have gotten the path and parameters. 376 377 Will raise RestDispException on failure. 378 """ 379 380 # 381 # Split up the path. 382 # 383 asPath = self._sPath.split('/'); 384 self._asPath = asPath; 385 386 # 387 # Get the method and the corresponding handler tree. 920 388 # 921 389 try: 922 390 sMethod = self._oSrvGlue.getMethod(); 923 391 except Exception as oXcpt: 924 raise TestBoxControllerException('Error retriving request method: %s' % (oXcpt,)); 925 if sMethod != 'POST': 926 raise TestBoxControllerException('Error expected POST request not "%s"' % (sMethod,)); 927 928 # 929 # Get the parameters and checks for duplicates. 392 raise RestDispException('Error retriving request method: %s' % (oXcpt,), 400); 393 self._sMethod = sMethod; 394 395 try: 396 dTree = self._dMethodTrees[sMethod]; 397 except KeyError: 398 raise RestDispException('Unsupported method %s' % (sMethod,), 405); 399 400 # 401 # Walk the path till we find a handler for it. 402 # 403 iPath = 0; 404 while iPath < len(asPath): 405 try: 406 oTreeOrHandler = dTree[asPath[iPath]]; 407 except KeyError: 408 raise RestBadPathException('Path element #%u "%s" not found (path="%s")' % (iPath, asPath[iPath], self._sPath)); 409 iPath += 1; 410 if isinstance(oTreeOrHandler, dict): 411 dTree = oTreeOrHandler; 412 else: 413 # 414 # Call the handler. 415 # 416 self._iFirstHandlerPath = iPath; 417 self._iNextHandlerPath = iPath; 418 return oTreeOrHandler(); 419 420 raise RestBadPathException('Empty path (%s)' % (self._sPath,)); 421 422 def dispatchRequest(self): 423 """ 424 Dispatches the incoming request where the path is given as an argument. 425 426 Will raise RestDispException on failure. 427 """ 428 429 # 430 # Get the parameters. 930 431 # 931 432 try: 932 433 dParams = self._oSrvGlue.getParameters(); 933 434 except Exception as oXcpt: 934 raise TestBoxControllerException('Error retriving parameters: %s' % (oXcpt,)); 935 for sKey in dParams.keys(): 936 if len(dParams[sKey]) > 1: 937 raise TestBoxControllerException('Parameter "%s" is given multiple times: %s' % (sKey, dParams[sKey])); 938 dParams[sKey] = dParams[sKey][0]; 435 raise RestDispException('Error retriving parameters: %s' % (oXcpt,), 500); 939 436 self._dParams = dParams; 940 437 941 438 # 942 # Get+validate the standard action parameters and dispatch the request. 943 # 944 (self._sAction, self._idTestBox, self._sTestBoxUuid, self._sTestBoxAddr, self._idTestSet) = \ 945 self._getStandardParams(dParams); 946 return self._dActions[self._sAction](); 947 439 # Get the path parameter. 440 # 441 if self.ksParam_sPath not in dParams: 442 raise RestDispException('No "%s" parameter in request (params: %s)' % (self.ksParam_sPath, dParams,), 500); 443 self._sPath = self._getStringParam(self.ksParam_sPath); 444 assert utils.isString(self._sPath); 445 446 return self._dispatchRequestCommon(); 447 448 -
trunk/src/VBox/ValidationKit/testmanager/core/vcsbugreference.py
r84550 r84599 86 86 self.sBugTracker = sBugTracker; 87 87 self.lBugNo = lBugNo; 88 return self; 89 90 91 class VcsBugReferenceDataEx(VcsBugReferenceData): 92 """ 93 Extended version of VcsBugReferenceData that includes the commit details. 94 """ 95 def __init__(self): 96 VcsBugReferenceData.__init__(self); 97 self.tsCreated = None; 98 self.sAuthor = None; 99 self.sMessage = None; 100 101 def initFromDbRow(self, aoRow): 102 VcsBugReferenceData.initFromDbRow(self, aoRow); 103 self.tsCreated = aoRow[4]; 104 self.sAuthor = aoRow[5]; 105 self.sMessage = aoRow[6]; 88 106 return self; 89 107 … … 183 201 return self._oDb.fetchOne()[0]; 184 202 203 def fetchForBug(self, sBugTracker, lBugNo): 204 """ 205 Fetches VCS revisions for a bug. 206 207 Returns an array (list) of VcsBugReferenceDataEx items, empty list if none. 208 Raises exception on error. 209 """ 210 self._oDb.execute(''' 211 SELECT VcsBugReferences.*, 212 VcsRevisions.tsCreated, 213 VcsRevisions.sAuthor, 214 VcsRevisions.sMessage 215 FROM VcsBugReferences 216 LEFT OUTER JOIN VcsRevisions ON ( VcsRevisions.sRepository = VcsBugReferences.sRepository 217 AND VcsRevisions.iRevision = VcsBugReferences.iRevision ) 218 WHERE sBugTracker = %s 219 AND lBugNo = %s 220 ORDER BY VcsRevisions.tsCreated, VcsBugReferences.sRepository, VcsBugReferences.iRevision 221 ''', (sBugTracker, lBugNo,)); 222 223 aoRows = []; 224 for _ in range(self._oDb.getRowCount()): 225 aoRows.append(VcsBugReferenceDataEx().initFromDbRow(self._oDb.fetchOne())); 226 return aoRows; 227 185 228 186 229 # -
trunk/src/VBox/ValidationKit/testmanager/core/webservergluebase.py
r83381 r84599 62 62 ksUnknownUser = 'Unknown User'; 63 63 64 ## HTTP status codes and their messages. 65 kdStatusMsgs = { 66 100: 'Continue', 67 101: 'Switching Protocols', 68 102: 'Processing', 69 103: 'Early Hints', 70 200: 'OK', 71 201: 'Created', 72 202: 'Accepted', 73 203: 'Non-Authoritative Information', 74 204: 'No Content', 75 205: 'Reset Content', 76 206: 'Partial Content', 77 207: 'Multi-Status', 78 208: 'Already Reported', 79 226: 'IM Used', 80 300: 'Multiple Choices', 81 301: 'Moved Permantently', 82 302: 'Found', 83 303: 'See Other', 84 304: 'Not Modified', 85 305: 'Use Proxy', 86 306: 'Switch Proxy', 87 307: 'Temporary Redirect', 88 308: 'Permanent Redirect', 89 400: 'Bad Request', 90 401: 'Unauthorized', 91 402: 'Payment Required', 92 403: 'Forbidden', 93 404: 'Not Found', 94 405: 'Method Not Allowed', 95 406: 'Not Acceptable', 96 407: 'Proxy Authentication Required', 97 408: 'Request Timeout', 98 409: 'Conflict', 99 410: 'Gone', 100 411: 'Length Required', 101 412: 'Precondition Failed', 102 413: 'Payload Too Large', 103 414: 'URI Too Long', 104 415: 'Unsupported Media Type', 105 416: 'Range Not Satisfiable', 106 417: 'Expectation Failed', 107 418: 'I\'m a teapot', 108 421: 'Misdirection Request', 109 422: 'Unprocessable Entity', 110 423: 'Locked', 111 424: 'Failed Dependency', 112 425: 'Too Early', 113 426: 'Upgrade Required', 114 428: 'Precondition Required', 115 429: 'Too Many Requests', 116 431: 'Request Header Fields Too Large', 117 451: 'Unavailable For Legal Reasons', 118 500: 'Internal Server Error', 119 501: 'Not Implemented', 120 502: 'Bad Gateway', 121 503: 'Service Unavailable', 122 504: 'Gateway Timeout', 123 505: 'HTTP Version Not Supported', 124 506: 'Variant Also Negotiates', 125 507: 'Insufficient Storage', 126 508: 'Loop Detected', 127 510: 'Not Extended', 128 511: 'Network Authentication Required', 129 }; 130 64 131 65 132 def __init__(self, sValidationKitDir, fHtmlDebugOutput = True): … … 70 137 self._fHtmlDebugOutput = fHtmlDebugOutput; # For trace 71 138 self._oDbgFile = sys.stderr; 72 if config.g_ksSr cGlueDebugLogDst is not None and config.g_kfSrvGlueDebug is True:73 self._oDbgFile = open(config.g_ksSr cGlueDebugLogDst, 'a');139 if config.g_ksSrvGlueDebugLogDst is not None and config.g_kfSrvGlueDebug is True: 140 self._oDbgFile = open(config.g_ksSrvGlueDebugLogDst, 'a'); 74 141 if config.g_kfSrvGlueCgiDumpArgs: 75 142 self._oDbgFile.write('Arguments: %s\nEnvironment:\n' % (sys.argv,)); … … 221 288 Worker function which child classes can override. 222 289 """ 290 sys.stderr.write('_writeHeader: cch=%s "%s..."\n' % (len(sHeaderLine), sHeaderLine[0:10],)) 223 291 self.oOutputText.write(sHeaderLine); 224 292 return True; … … 257 325 return True; 258 326 327 def setStatus(self, iStatus, sMsg = None): 328 """ Sets the status code. """ 329 if not sMsg: 330 sMsg = self.kdStatusMsgs[iStatus]; 331 return self.setHeaderField('Status', '%u %s' % (iStatus, sMsg)); 332 333 def setContentType(self, sType): 334 """ Sets the content type header field. """ 335 return self.setHeaderField('Content-Type', sType); 336 259 337 def _writeWorker(self, sChunkOfHtml): 260 338 """ 261 339 Worker function which child classes can override. 262 340 """ 341 sys.stderr.write('_writeWorker: cch=%s "%s..."\n' % (len(sChunkOfHtml), sChunkOfHtml[0:10],)) 263 342 self.oOutputText.write(sChunkOfHtml); 264 343 return True; … … 286 365 """ 287 366 if self._sBodyType is None: 288 self._sBodyType = ' html';289 elif self._sBodyType != ' html':290 raise WebServerGlueException('Cannot use write Parameterwhen body type is "%s"' % (self._sBodyType, ));367 self._sBodyType = 'raw'; 368 elif self._sBodyType != 'raw': 369 raise WebServerGlueException('Cannot use writeRaw when body type is "%s"' % (self._sBodyType, )); 291 370 292 371 self.flushHeader(); … … 294 373 self.flush(); 295 374 375 sys.stderr.write('writeRaw: cb=%s\n' % (len(writeRaw),)) 296 376 self.oOutputRaw.write(abChunk); 297 377 return True;
Note:
See TracChangeset
for help on using the changeset viewer.