VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/testmanager/core/testboxcontroller.py@ 61267

Last change on this file since 61267 was 61261, checked in by vboxsync, 9 years ago

_actionSignOn: Agressively ignore fluctuations in cMbScratch as a stop gap measure to reduce new rows in TestBoxes. We ignore changes between 4-12pct relative to the amount of available scratch space.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 43.7 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: testboxcontroller.py 61261 2016-05-28 18:07:20Z vboxsync $
3
4"""
5Test Manager Core - Web Server Abstraction Base Class.
6"""
7
8__copyright__ = \
9"""
10Copyright (C) 2012-2015 Oracle Corporation
11
12This file is part of VirtualBox Open Source Edition (OSE), as
13available from http://www.virtualbox.org. This file is free software;
14you can redistribute it and/or modify it under the terms of the GNU
15General Public License (GPL) as published by the Free Software
16Foundation, in version 2 as it comes in the "COPYING" file of the
17VirtualBox OSE distribution. VirtualBox OSE is distributed in the
18hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
19
20The contents of this file may alternatively be used under the terms
21of the Common Development and Distribution License Version 1.0
22(CDDL) only, as it comes in the "COPYING.CDDL" file of the
23VirtualBox OSE distribution, in which case the provisions of the
24CDDL are applicable instead of those of the GPL.
25
26You may elect to license modified versions of this file under the
27terms and conditions of either the GPL or the CDDL or both.
28"""
29__version__ = "$Revision: 61261 $"
30
31
32# Standard python imports.
33import re;
34import os;
35import string; # pylint: disable=W0402
36import sys;
37import uuid;
38
39# Validation Kit imports.
40from common import constants;
41from testmanager import config;
42from testmanager.core import coreconsts;
43from testmanager.core.db import TMDatabaseConnection;
44from testmanager.core.base import TMExceptionBase;
45from testmanager.core.globalresource import GlobalResourceLogic;
46from testmanager.core.testboxstatus import TestBoxStatusData, TestBoxStatusLogic;
47from testmanager.core.testbox import TestBoxData, TestBoxLogic;
48from testmanager.core.testresults import TestResultLogic;
49from testmanager.core.testset import TestSetData, TestSetLogic;
50from testmanager.core.systemlog import SystemLogData, SystemLogLogic;
51from testmanager.core.schedulerbase import SchedulerBase;
52
53# Python 3 hacks:
54if sys.version_info[0] >= 3:
55 long = int; # pylint: disable=W0622,C0103
56
57
58class TestBoxControllerException(TMExceptionBase):
59 """
60 Exception class for TestBoxController.
61 """
62 pass;
63
64
65class TestBoxController(object): # pylint: disable=R0903
66 """
67 TestBox Controller class.
68 """
69
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 };
133
134
135 def __init__(self, oSrvGlue):
136 """
137 Won't raise exceptions.
138 """
139 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.
145 self._dParams = None; # _getStandardParams / dispatchRequest sets this later on.
146 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,
159 };
160
161 def _getStringParam(self, sName, asValidValues = None, fStrip = False, sDefValue = None):
162 """
163 Gets a string parameter (stripped).
164
165 Raises exception if not found and no default is provided, or if the
166 value isn't found in asValidValues.
167 """
168 if sName not in self._dParams:
169 if sDefValue is None:
170 raise TestBoxControllerException('%s parameter %s is missing' % (self._sAction, sName));
171 return sDefValue;
172 sValue = self._dParams[sName];
173 if fStrip:
174 sValue = sValue.strip();
175
176 if sName not in self._asCheckedParams:
177 self._asCheckedParams.append(sName);
178
179 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));
182 return sValue;
183
184 def _getBoolParam(self, sName, fDefValue = None):
185 """
186 Gets a boolean parameter.
187
188 Raises exception if not found and no default is provided, or if not a
189 valid boolean.
190 """
191 sValue = self._getStringParam(sName, [ 'True', 'true', '1', 'False', 'false', '0'], sDefValue = str(fDefValue));
192 return sValue == 'True' or sValue == 'true' or sValue == '1';
193
194 def _getIntParam(self, sName, iMin = None, iMax = None):
195 """
196 Gets a string parameter.
197 Raises exception if not found, not a valid integer, or if the value
198 isn't in the range defined by iMin and iMax.
199 """
200 sValue = self._getStringParam(sName);
201 try:
202 iValue = int(sValue, 0);
203 except:
204 raise TestBoxControllerException('%s parameter %s value "%s" cannot be convert to an integer' \
205 % (self._sAction, sName, sValue));
206
207 if (iMin is not None and iValue < iMin) \
208 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));
211 return iValue;
212
213 def _getLongParam(self, sName, lMin = None, lMax = None, lDefValue = None):
214 """
215 Gets a string parameter.
216 Raises exception if not found, not a valid long integer, or if the value
217 isn't in the range defined by lMin and lMax.
218 """
219 sValue = self._getStringParam(sName, sDefValue = (str(lDefValue) if lDefValue is not None else None));
220 try:
221 lValue = long(sValue, 0);
222 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));
225
226 if (lMin is not None and lValue < lMin) \
227 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));
230 return lValue;
231
232 def _checkForUnknownParameters(self):
233 """
234 Check if we've handled all parameters, raises exception if anything
235 unknown was found.
236 """
237
238 if len(self._asCheckedParams) != len(self._dParams):
239 sUnknownParams = '';
240 for sKey in self._dParams:
241 if sKey not in self._asCheckedParams:
242 sUnknownParams += ' ' + sKey + '=' + self._dParams[sKey];
243 raise TestBoxControllerException('Unknown parameters: ' + sUnknownParams);
244
245 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 the
274 state to 'idle'. See scenario #9:
275 file://../../docs/AutomaticTestingRevamp.html#cleaning-up-abandond-testcase
276
277 Note. oStatusData.enmState is set to idle, but tsUpdated is not changed.
278 """
279
280 # Cleanup any abandond test.
281 if oStatusData.idTestSet is not None:
282 SystemLogLogic(oDb).addEntry(SystemLogData.ksEvent_TestSetAbandond,
283 "idTestSet=%u idTestBox=%u enmState=%s %s"
284 % (oStatusData.idTestSet, oStatusData.idTestBox,
285 oStatusData.enmState, self._sAction),
286 fCommit = False);
287 TestSetLogic(oDb).completeAsAbandond(oStatusData.idTestSet, fCommit = False);
288 GlobalResourceLogic(oDb).freeGlobalResourcesByTestBox(self._idTestBox, fCommit = False);
289
290 # Change to idle status
291 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
322 def writeToMainLog(self, oTestSet, sText, fIgnoreSizeCheck = False):
323 """ Writes the text to the main log file. """
324
325 # Calc the file name and open the file.
326 sFile = os.path.join(config.g_ksFileAreaRootDir, oTestSet.sBaseFilename + '-main.log');
327 if not os.path.exists(os.path.dirname(sFile)):
328 os.makedirs(os.path.dirname(sFile), 0o755);
329 oFile = open(sFile, 'ab');
330
331 # Check the size.
332 fSizeOk = True;
333 if not fIgnoreSizeCheck:
334 oStat = os.fstat(oFile.fileno());
335 fSizeOk = oStat.st_size / (1024 * 1024) < config.g_kcMbMaxMainLog;
336
337 # Write the text.
338 if fSizeOk:
339 if sys.version_info[0] >= 3:
340 oFile.write(bytes(sText, 'utf-8'));
341 else:
342 oFile.write(sText);
343
344 # Done
345 oFile.close();
346 return fSizeOk;
347
348 def _actionSignOn(self): # pylint: disable=R0914
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 cMbMemory = self._getLongParam( constants.tbreq.SIGNON_PARAM_MEM_SIZE, 8, 1073741823); # 8MB..1PB
366 cMbScratch = self._getLongParam( constants.tbreq.SIGNON_PARAM_SCRATCH_SIZE, 0, 1073741823); # 0..1PB
367 sReport = self._getStringParam(constants.tbreq.SIGNON_PARAM_REPORT, fStrip = True, sDefValue = ''); # new
368 iTestBoxScriptRev = self._getIntParam( constants.tbreq.SIGNON_PARAM_SCRIPT_REV, 1, 100000000);
369 iPythonHexVersion = self._getIntParam( constants.tbreq.SIGNON_PARAM_PYTHON_VERSION, 0x020300f0, 0x030f00f0);
370 self._checkForUnknownParameters();
371
372 # Null conversions for new parameters.
373 if len(sReport) == 0:
374 sReport = None;
375 if len(sCpuName) == 0:
376 sCpuName = None;
377 if lCpuRevision <= 0:
378 lCpuRevision = None;
379
380 #
381 # Connect to the database and validate the testbox.
382 #
383 oDb = TMDatabaseConnection(self._oSrvGlue.dprint);
384 oTestBoxLogic = TestBoxLogic(oDb);
385 oTestBox = oTestBoxLogic.tryFetchTestBoxByUuid(self._sTestBoxUuid);
386 if oTestBox is None:
387 oSystemLogLogic = SystemLogLogic(oDb);
388 oSystemLogLogic.addEntry(SystemLogData.ksEvent_TestBoxUnknown,
389 'addr=%s uuid=%s os=%s %d cpus' \
390 % (self._sTestBoxAddr, self._sTestBoxUuid, sOs, cCpus),
391 24, fCommit = True);
392 return self._resultResponse(constants.tbresp.STATUS_NACK);
393
394 #
395 # Update the row in TestBoxes if something changed.
396 #
397 cPctScratchDiff = (cMbScratch - oTestBox.cMbScratch) * 100 / oTestBox.cMbScratch;
398
399 # pylint: disable=R0916
400 if self._sTestBoxAddr != oTestBox.ip \
401 or sOs != oTestBox.sOs \
402 or sOsVersion != oTestBox.sOsVersion \
403 or sCpuVendor != oTestBox.sCpuVendor \
404 or sCpuArch != oTestBox.sCpuArch \
405 or sCpuName != oTestBox.sCpuName \
406 or lCpuRevision != oTestBox.lCpuRevision \
407 or cCpus != oTestBox.cCpus \
408 or fCpuHwVirt != oTestBox.fCpuHwVirt \
409 or fCpuNestedPaging != oTestBox.fCpuNestedPaging \
410 or fCpu64BitGuest != oTestBox.fCpu64BitGuest \
411 or fChipsetIoMmu != oTestBox.fChipsetIoMmu \
412 or cMbMemory != oTestBox.cMbMemory \
413 or abs(cPctScratchDiff) >= min(4 + cMbScratch / 10240, 12) \
414 or sReport != oTestBox.sReport \
415 or iTestBoxScriptRev != oTestBox.iTestBoxScriptRev \
416 or iPythonHexVersion != oTestBox.iPythonHexVersion:
417 oTestBoxLogic.updateOnSignOn(oTestBox.idTestBox,
418 oTestBox.idGenTestBox,
419 sTestBoxAddr = self._sTestBoxAddr,
420 sOs = sOs,
421 sOsVersion = sOsVersion,
422 sCpuVendor = sCpuVendor,
423 sCpuArch = sCpuArch,
424 sCpuName = sCpuName,
425 lCpuRevision = lCpuRevision,
426 cCpus = cCpus,
427 fCpuHwVirt = fCpuHwVirt,
428 fCpuNestedPaging = fCpuNestedPaging,
429 fCpu64BitGuest = fCpu64BitGuest,
430 fChipsetIoMmu = fChipsetIoMmu,
431 cMbMemory = cMbMemory,
432 cMbScratch = cMbScratch,
433 sReport = sReport,
434 iTestBoxScriptRev = iTestBoxScriptRev,
435 iPythonHexVersion = iPythonHexVersion);
436
437 #
438 # Update the testbox status, making sure there is a status.
439 #
440 oStatusLogic = TestBoxStatusLogic(oDb);
441 oStatusData = oStatusLogic.tryFetchStatus(oTestBox.idTestBox);
442 if oStatusData is not None:
443 self._cleanupOldTest(oDb, oStatusData);
444 else:
445 oStatusLogic.insertIdleStatus(oTestBox.idTestBox, oTestBox.idGenTestBox, fCommit = True);
446
447 #
448 # ACK the request.
449 #
450 dResponse = \
451 {
452 constants.tbresp.ALL_PARAM_RESULT: constants.tbresp.STATUS_ACK,
453 constants.tbresp.SIGNON_PARAM_ID: oTestBox.idTestBox,
454 constants.tbresp.SIGNON_PARAM_NAME: oTestBox.sName,
455 }
456 return self._writeResponse(dResponse);
457
458 def _doGangCleanup(self, oDb, oStatusData):
459 """
460 _doRequestCommand worker for handling a box in gang-cleanup.
461 This will check if all testboxes has completed their run, pretending to
462 be busy until that happens. Once all are completed, resources will be
463 freed and the testbox returns to idle state (we update oStatusData).
464 """
465 oStatusLogic = TestBoxStatusLogic(oDb)
466 oTestSet = TestSetData().initFromDbWithId(oDb, oStatusData.idTestSet);
467 if oStatusLogic.isWholeGangDoneTesting(oTestSet.idTestSetGangLeader):
468 oDb.begin();
469
470 GlobalResourceLogic(oDb).freeGlobalResourcesByTestBox(self._idTestBox, fCommit = False);
471 TestBoxStatusLogic(oDb).updateState(self._idTestBox, TestBoxStatusData.ksTestBoxState_Idle, fCommit = False);
472
473 oStatusData.tsUpdated = oDb.getCurrentTimestamp();
474 oStatusData.enmState = TestBoxStatusData.ksTestBoxState_Idle;
475
476 oDb.commit();
477 return None;
478
479 def _doGangGatheringTimedOut(self, oDb, oStatusData):
480 """
481 _doRequestCommand worker for handling a box in gang-gathering-timed-out state.
482 This will do clean-ups similar to _cleanupOldTest and update the state likewise.
483 """
484 oDb.begin();
485
486 TestSetLogic(oDb).completeAsGangGatheringTimeout(oStatusData.idTestSet, fCommit = False);
487 GlobalResourceLogic(oDb).freeGlobalResourcesByTestBox(self._idTestBox, fCommit = False);
488 TestBoxStatusLogic(oDb).updateState(self._idTestBox, TestBoxStatusData.ksTestBoxState_Idle, fCommit = False);
489
490 oStatusData.tsUpdated = oDb.getCurrentTimestamp();
491 oStatusData.enmState = TestBoxStatusData.ksTestBoxState_Idle;
492
493 oDb.commit();
494 return None;
495
496 def _doGangGathering(self, oDb, oStatusData):
497 """
498 _doRequestCommand worker for handling a box in gang-gathering state.
499 This only checks for timeout. It will update the oStatusData if a
500 timeout is detected, so that the box will be idle upon return.
501 """
502 oStatusLogic = TestBoxStatusLogic(oDb);
503 if oStatusLogic.timeSinceLastChangeInSecs(oStatusData) > config.g_kcSecGangGathering \
504 and SchedulerBase.tryCancelGangGathering(oDb, oStatusData): # <-- Updates oStatusData.
505 self._doGangGatheringTimedOut(oDb, oStatusData);
506 return None;
507
508 def _doRequestCommand(self, fIdle):
509 """
510 Common code for handling command request.
511 """
512
513 (oDb, oStatusData, oTestBoxData) = self._connectToDbAndValidateTb();
514 if oDb is None:
515 return False;
516
517 #
518 # Status clean up.
519 #
520 # Only when BUSY will the TestBox Script request and execute commands
521 # concurrently. So, it must be idle when sending REQUEST_COMMAND_IDLE.
522 #
523 if fIdle:
524 if oStatusData.enmState == TestBoxStatusData.ksTestBoxState_GangGathering:
525 self._doGangGathering(oDb, oStatusData);
526 elif oStatusData.enmState == TestBoxStatusData.ksTestBoxState_GangGatheringTimedOut:
527 self._doGangGatheringTimedOut(oDb, oStatusData);
528 elif oStatusData.enmState == TestBoxStatusData.ksTestBoxState_GangTesting:
529 dResponse = SchedulerBase.composeExecResponse(oDb, oTestBoxData.idTestBox, self._oSrvGlue.getBaseUrl());
530 if dResponse is not None:
531 return dResponse;
532 elif oStatusData.enmState == TestBoxStatusData.ksTestBoxState_GangCleanup:
533 self._doGangCleanup(oDb, oStatusData);
534 elif oStatusData.enmState != TestBoxStatusData.ksTestBoxState_Idle: # (includes ksTestBoxState_GangGatheringTimedOut)
535 self._cleanupOldTest(oDb, oStatusData);
536
537 #
538 # Check for pending command.
539 #
540 if oTestBoxData.enmPendingCmd != TestBoxData.ksTestBoxCmd_None:
541 asValidCmds = TestBoxController.kasIdleCmds if fIdle else TestBoxController.kasBusyCmds;
542 if oTestBoxData.enmPendingCmd in asValidCmds:
543 dResponse = { constants.tbresp.ALL_PARAM_RESULT: TestBoxController.kdCmdToTbRespCmd[oTestBoxData.enmPendingCmd] };
544 if oTestBoxData.enmPendingCmd in [TestBoxData.ksTestBoxCmd_Upgrade, TestBoxData.ksTestBoxCmd_UpgradeAndReboot]:
545 dResponse[constants.tbresp.UPGRADE_PARAM_URL] = self._oSrvGlue.getBaseUrl() + TestBoxController.ksUpgradeZip;
546 return self._writeResponse(dResponse);
547
548 if oTestBoxData.enmPendingCmd == TestBoxData.ksTestBoxCmd_Abort and fIdle:
549 TestBoxLogic(oDb).setCommand(self._idTestBox, sOldCommand = oTestBoxData.enmPendingCmd,
550 sNewCommand = TestBoxData.ksTestBoxCmd_None, fCommit = True);
551
552 #
553 # If doing gang stuff, return 'CMD_WAIT'.
554 #
555 ## @todo r=bird: Why is GangTesting included here? Figure out when testing gang testing.
556 if oStatusData.enmState in [TestBoxStatusData.ksTestBoxState_GangGathering,
557 TestBoxStatusData.ksTestBoxState_GangTesting,
558 TestBoxStatusData.ksTestBoxState_GangCleanup]:
559 return self._resultResponse(constants.tbresp.CMD_WAIT);
560
561 #
562 # If idling and enabled try schedule a new task.
563 #
564 if fIdle \
565 and oTestBoxData.fEnabled \
566 and oStatusData.enmState == TestBoxStatusData.ksTestBoxState_Idle: # (paranoia)
567 dResponse = SchedulerBase.scheduleNewTask(oDb, oTestBoxData, self._oSrvGlue.getBaseUrl());
568 if dResponse is not None:
569 return self._writeResponse(dResponse);
570
571 #
572 # Touch the status row every couple of mins so we can tell that the box is alive.
573 #
574 oStatusLogic = TestBoxStatusLogic(oDb);
575 if oStatusData.enmState != TestBoxStatusData.ksTestBoxState_GangGathering \
576 and oStatusLogic.timeSinceLastChangeInSecs(oStatusData) >= TestBoxStatusLogic.kcSecIdleTouchStatus:
577 oStatusLogic.touchStatus(oTestBoxData.idTestBox, fCommit = True);
578
579 return self._idleResponse();
580
581 def _actionRequestCommandBusy(self):
582 """ Implement request for command. """
583 self._checkForUnknownParameters();
584 return self._doRequestCommand(False);
585
586 def _actionRequestCommandIdle(self):
587 """ Implement request for command. """
588 self._checkForUnknownParameters();
589 return self._doRequestCommand(True);
590
591 def _doCommandAckNck(self, sCmd):
592 """ Implements ACK, NACK and NACK(ENOTSUP). """
593
594 (oDb, _, _) = self._connectToDbAndValidateTb();
595 if oDb is None:
596 return False;
597
598 #
599 # If the command maps to a TestBoxCmd_T value, it means we have to
600 # check and update TestBoxes. If it's an ACK, the testbox status will
601 # need updating as well.
602 #
603 sPendingCmd = TestBoxController.kdTbRespCmdToCmd[sCmd];
604 if sPendingCmd is not None:
605 oTestBoxLogic = TestBoxLogic(oDb)
606 oTestBoxLogic.setCommand(self._idTestBox, sOldCommand = sPendingCmd,
607 sNewCommand = TestBoxData.ksTestBoxCmd_None, fCommit = False);
608
609 if self._sAction == constants.tbreq.COMMAND_ACK \
610 and TestBoxController.kdCmdToState[sPendingCmd] is not None:
611 oStatusLogic = TestBoxStatusLogic(oDb);
612 oStatusLogic.updateState(self._idTestBox, TestBoxController.kdCmdToState[sPendingCmd], fCommit = False);
613
614 # Commit the two updates.
615 oDb.commit();
616
617 #
618 # Log NACKs.
619 #
620 if self._sAction != constants.tbreq.COMMAND_ACK:
621 oSysLogLogic = SystemLogLogic(oDb);
622 oSysLogLogic.addEntry(SystemLogData.ksEvent_CmdNacked,
623 'idTestBox=%s sCmd=%s' % (self._idTestBox, sPendingCmd),
624 24, fCommit = True);
625
626 return self._resultResponse(constants.tbresp.STATUS_ACK);
627
628 def _actionCommandAck(self):
629 """ Implement command ACK'ing """
630 sCmd = self._getStringParam(constants.tbreq.COMMAND_ACK_PARAM_CMD_NAME, TestBoxController.kasAckableCmds);
631 self._checkForUnknownParameters();
632 return self._doCommandAckNck(sCmd);
633
634 def _actionCommandNack(self):
635 """ Implement command NACK'ing """
636 sCmd = self._getStringParam(constants.tbreq.COMMAND_ACK_PARAM_CMD_NAME, TestBoxController.kasNackableCmds);
637 self._checkForUnknownParameters();
638 return self._doCommandAckNck(sCmd);
639
640 def _actionCommandNotSup(self):
641 """ Implement command NACK(ENOTSUP)'ing """
642 sCmd = self._getStringParam(constants.tbreq.COMMAND_ACK_PARAM_CMD_NAME, TestBoxController.kasNackableCmds);
643 self._checkForUnknownParameters();
644 return self._doCommandAckNck(sCmd);
645
646 def _actionLogMain(self):
647 """ Implement submitting log entries to the main log file. """
648 #
649 # Parameter validation.
650 #
651 sBody = self._getStringParam(constants.tbreq.LOG_PARAM_BODY, fStrip = False);
652 if len(sBody) == 0:
653 return self._resultResponse(constants.tbresp.STATUS_NACK);
654 self._checkForUnknownParameters();
655
656 (oDb, oStatusData, _) = self._connectToDbAndValidateTb([TestBoxStatusData.ksTestBoxState_Testing,
657 TestBoxStatusData.ksTestBoxState_GangTesting]);
658 if oStatusData is None:
659 return False;
660
661 #
662 # Write the text to the log file.
663 #
664 oTestSet = TestSetData().initFromDbWithId(oDb, oStatusData.idTestSet);
665 self.writeToMainLog(oTestSet, sBody);
666 ## @todo Overflow is a hanging offence, need to note it and fail whatever is going on...
667
668 # Done.
669 return self._resultResponse(constants.tbresp.STATUS_ACK);
670
671 def _actionUpload(self):
672 """ Implement uploading of files. """
673 #
674 # Parameter validation.
675 #
676 sName = self._getStringParam(constants.tbreq.UPLOAD_PARAM_NAME);
677 sMime = self._getStringParam(constants.tbreq.UPLOAD_PARAM_MIME);
678 sKind = self._getStringParam(constants.tbreq.UPLOAD_PARAM_KIND);
679 sDesc = self._getStringParam(constants.tbreq.UPLOAD_PARAM_DESC);
680 self._checkForUnknownParameters();
681
682 (oDb, oStatusData, _) = self._connectToDbAndValidateTb([TestBoxStatusData.ksTestBoxState_Testing,
683 TestBoxStatusData.ksTestBoxState_GangTesting]);
684 if oStatusData is None:
685 return False;
686
687 if len(sName) > 128 or len(sName) < 3:
688 raise TestBoxControllerException('Invalid file name "%s"' % (sName,));
689 if re.match(r'^[a-zA-Z0-9_\-(){}#@+,.=]*$', sName) is None:
690 raise TestBoxControllerException('Invalid file name "%s"' % (sName,));
691
692 if sMime not in [ 'text/plain', #'text/html', 'text/xml',
693 'application/octet-stream',
694 'image/png', #'image/gif', 'image/jpeg',
695 #'video/webm', 'video/mpeg', 'video/mpeg4-generic',
696 ]:
697 raise TestBoxControllerException('Invalid MIME type "%s"' % (sMime,));
698
699 if sKind not in [ 'log/release/vm',
700 'log/debug/vm',
701 'log/release/svc',
702 'log/debug/svc',
703 'log/release/client',
704 'log/debug/client',
705 'log/installer',
706 'log/uninstaller',
707 'crash/report/vm',
708 'crash/dump/vm',
709 'crash/report/svc',
710 'crash/dump/svc',
711 'crash/report/client',
712 'crash/dump/client',
713 'misc/other',
714 'screenshot/failure',
715 'screenshot/success',
716 #'screencapture/failure',
717 ]:
718 raise TestBoxControllerException('Invalid kind "%s"' % (sKind,));
719
720 if len(sDesc) > 256:
721 raise TestBoxControllerException('Invalid description "%s"' % (sDesc,));
722 if not set(sDesc).issubset(set(string.printable)):
723 raise TestBoxControllerException('Invalid description "%s"' % (sDesc,));
724
725 if ('application/octet-stream', {}) != self._oSrvGlue.getContentType():
726 raise TestBoxControllerException('Unexpected content type: %s; %s' % self._oSrvGlue.getContentType());
727
728 cbFile = self._oSrvGlue.getContentLength();
729 if cbFile <= 0:
730 raise TestBoxControllerException('File "%s" is empty or negative in size (%s)' % (sName, cbFile));
731 if (cbFile + 1048575) / 1048576 > config.g_kcMbMaxUploadSingle:
732 raise TestBoxControllerException('File "%s" is too big %u bytes (max %u MiB)'
733 % (sName, cbFile, config.g_kcMbMaxUploadSingle,));
734
735 #
736 # Write the text to the log file.
737 #
738 oTestSet = TestSetData().initFromDbWithId(oDb, oStatusData.idTestSet);
739 oDstFile = TestSetLogic(oDb).createFile(oTestSet, sName = sName, sMime = sMime, sKind = sKind, sDesc = sDesc,
740 cbFile = cbFile, fCommit = True);
741
742 offFile = 0;
743 oSrcFile = self._oSrvGlue.getBodyIoStream();
744 while offFile < cbFile:
745 cbToRead = cbFile - offFile;
746 if cbToRead > 256*1024:
747 cbToRead = 256*1024;
748 offFile += cbToRead;
749
750 abBuf = oSrcFile.read(cbToRead);
751 oDstFile.write(abBuf); # pylint: disable=E1103
752 del abBuf;
753
754 oDstFile.close(); # pylint: disable=E1103
755
756 # Done.
757 return self._resultResponse(constants.tbresp.STATUS_ACK);
758
759 def _actionXmlResults(self):
760 """ Implement submitting "XML" like test result stream. """
761 #
762 # Parameter validation.
763 #
764 sXml = self._getStringParam(constants.tbreq.XML_RESULT_PARAM_BODY, fStrip = False);
765 self._checkForUnknownParameters();
766 if len(sXml) == 0: # Used for link check by vboxinstaller.py on Windows.
767 return self._resultResponse(constants.tbresp.STATUS_ACK);
768
769 (oDb, oStatusData, _) = self._connectToDbAndValidateTb([TestBoxStatusData.ksTestBoxState_Testing,
770 TestBoxStatusData.ksTestBoxState_GangTesting]);
771 if oStatusData is None:
772 return False;
773
774 #
775 # Process the XML.
776 #
777 (sError, fUnforgivable) = TestResultLogic(oDb).processXmlStream(sXml, self._idTestSet);
778 if sError is not None:
779 oTestSet = TestSetData().initFromDbWithId(oDb, oStatusData.idTestSet);
780 self.writeToMainLog(oTestSet, '\n!!XML error: %s\n%s\n\n' % (sError, sXml,));
781 if fUnforgivable:
782 return self._resultResponse(constants.tbresp.STATUS_NACK);
783 return self._resultResponse(constants.tbresp.STATUS_ACK);
784
785
786 def _actionExecCompleted(self):
787 """
788 Implement EXEC completion.
789
790 Because the action is request by the worker thread of the testbox
791 script we cannot pass pending commands back to it like originally
792 planned. So, we just complete the test set and update the status.
793 """
794 #
795 # Parameter validation.
796 #
797 sStatus = self._getStringParam(constants.tbreq.EXEC_COMPLETED_PARAM_RESULT, TestBoxController.kasValidResults);
798 self._checkForUnknownParameters();
799
800 (oDb, oStatusData, _) = self._connectToDbAndValidateTb([TestBoxStatusData.ksTestBoxState_Testing,
801 TestBoxStatusData.ksTestBoxState_GangTesting]);
802 if oStatusData is None:
803 return False;
804
805 #
806 # Complete the status.
807 #
808 oDb.rollback();
809 oDb.begin();
810 oTestSetLogic = TestSetLogic(oDb);
811 idTestSetGangLeader = oTestSetLogic.complete(oStatusData.idTestSet, self.kadTbResultToStatus[sStatus], fCommit = False);
812
813 oStatusLogic = TestBoxStatusLogic(oDb);
814 if oStatusData.enmState == TestBoxStatusData.ksTestBoxState_Testing:
815 assert idTestSetGangLeader is None;
816 GlobalResourceLogic(oDb).freeGlobalResourcesByTestBox(self._idTestBox);
817 oStatusLogic.updateState(self._idTestBox, TestBoxStatusData.ksTestBoxState_Idle, fCommit = False);
818 else:
819 assert idTestSetGangLeader is not None;
820 oStatusLogic.updateState(self._idTestBox, TestBoxStatusData.ksTestBoxState_GangCleanup, oStatusData.idTestSet,
821 fCommit = False);
822 if oStatusLogic.isWholeGangDoneTesting(idTestSetGangLeader):
823 GlobalResourceLogic(oDb).freeGlobalResourcesByTestBox(self._idTestBox);
824 oStatusLogic.updateState(self._idTestBox, TestBoxStatusData.ksTestBoxState_Idle, fCommit = False);
825
826 oDb.commit();
827 return self._resultResponse(constants.tbresp.STATUS_ACK);
828
829
830
831 def _getStandardParams(self, dParams):
832 """
833 Gets the standard parameters and validates them.
834
835 The parameters are returned as a tuple: sAction, idTestBox, sTestBoxUuid.
836 Note! the sTextBoxId can be None if it's a SIGNON request.
837
838 Raises TestBoxControllerException on invalid input.
839 """
840 #
841 # Get the action parameter and validate it.
842 #
843 if constants.tbreq.ALL_PARAM_ACTION not in dParams:
844 raise TestBoxControllerException('No "%s" parameter in request (params: %s)' \
845 % (constants.tbreq.ALL_PARAM_ACTION, dParams,));
846 sAction = dParams[constants.tbreq.ALL_PARAM_ACTION];
847
848 if sAction not in self._dActions:
849 raise TestBoxControllerException('Unknown action "%s" in request (params: %s; action: %s)' \
850 % (sAction, dParams, self._dActions));
851
852 #
853 # TestBox UUID.
854 #
855 if constants.tbreq.ALL_PARAM_TESTBOX_UUID not in dParams:
856 raise TestBoxControllerException('No "%s" parameter in request (params: %s)' \
857 % (constants.tbreq.ALL_PARAM_TESTBOX_UUID, dParams,));
858 sTestBoxUuid = dParams[constants.tbreq.ALL_PARAM_TESTBOX_UUID];
859 try:
860 sTestBoxUuid = str(uuid.UUID(sTestBoxUuid));
861 except Exception as oXcpt:
862 raise TestBoxControllerException('Invalid %s parameter value "%s": %s ' \
863 % (constants.tbreq.ALL_PARAM_TESTBOX_UUID, sTestBoxUuid, oXcpt));
864 if sTestBoxUuid == '00000000-0000-0000-0000-000000000000':
865 raise TestBoxControllerException('Invalid %s parameter value "%s": NULL UUID not allowed.' \
866 % (constants.tbreq.ALL_PARAM_TESTBOX_UUID, sTestBoxUuid));
867
868 #
869 # TestBox ID.
870 #
871 if constants.tbreq.ALL_PARAM_TESTBOX_ID in dParams:
872 sTestBoxId = dParams[constants.tbreq.ALL_PARAM_TESTBOX_ID];
873 try:
874 idTestBox = int(sTestBoxId);
875 if idTestBox <= 0 or idTestBox >= 0x7fffffff:
876 raise Exception;
877 except:
878 raise TestBoxControllerException('Bad value for "%s": "%s"' \
879 % (constants.tbreq.ALL_PARAM_TESTBOX_ID, sTestBoxId));
880 elif sAction == constants.tbreq.SIGNON:
881 idTestBox = None;
882 else:
883 raise TestBoxControllerException('No "%s" parameter in request (params: %s)' \
884 % (constants.tbreq.ALL_PARAM_TESTBOX_ID, dParams,));
885
886 #
887 # Test Set ID.
888 #
889 if constants.tbreq.RESULT_PARAM_TEST_SET_ID in dParams:
890 sTestSetId = dParams[constants.tbreq.RESULT_PARAM_TEST_SET_ID];
891 try:
892 idTestSet = int(sTestSetId);
893 if idTestSet <= 0 or idTestSet >= 0x7fffffff:
894 raise Exception;
895 except:
896 raise TestBoxControllerException('Bad value for "%s": "%s"' \
897 % (constants.tbreq.RESULT_PARAM_TEST_SET_ID, sTestSetId));
898 elif sAction not in [ constants.tbreq.XML_RESULTS, ]: ## More later.
899 idTestSet = None;
900 else:
901 raise TestBoxControllerException('No "%s" parameter in request (params: %s)' \
902 % (constants.tbreq.RESULT_PARAM_TEST_SET_ID, dParams,));
903
904 #
905 # The testbox address.
906 #
907 sTestBoxAddr = self._oSrvGlue.getClientAddr();
908 if sTestBoxAddr is None or sTestBoxAddr.strip() == '':
909 raise TestBoxControllerException('Invalid client address "%s"' % (sTestBoxAddr,));
910
911 #
912 # Update the list of checked parameters.
913 #
914 self._asCheckedParams.extend([constants.tbreq.ALL_PARAM_TESTBOX_UUID, constants.tbreq.ALL_PARAM_ACTION]);
915 if idTestBox is not None:
916 self._asCheckedParams.append(constants.tbreq.ALL_PARAM_TESTBOX_ID);
917 if idTestSet is not None:
918 self._asCheckedParams.append(constants.tbreq.RESULT_PARAM_TEST_SET_ID);
919
920 return (sAction, idTestBox, sTestBoxUuid, sTestBoxAddr, idTestSet);
921
922 def dispatchRequest(self):
923 """
924 Dispatches the incoming request.
925
926 Will raise TestBoxControllerException on failure.
927 """
928
929 #
930 # Must be a POST request.
931 #
932 try:
933 sMethod = self._oSrvGlue.getMethod();
934 except Exception as oXcpt:
935 raise TestBoxControllerException('Error retriving request method: %s' % (oXcpt,));
936 if sMethod != 'POST':
937 raise TestBoxControllerException('Error expected POST request not "%s"' % (sMethod,));
938
939 #
940 # Get the parameters and checks for duplicates.
941 #
942 try:
943 dParams = self._oSrvGlue.getParameters();
944 except Exception as oXcpt:
945 raise TestBoxControllerException('Error retriving parameters: %s' % (oXcpt,));
946 for sKey in dParams.keys():
947 if len(dParams[sKey]) > 1:
948 raise TestBoxControllerException('Parameter "%s" is given multiple times: %s' % (sKey, dParams[sKey]));
949 dParams[sKey] = dParams[sKey][0];
950 self._dParams = dParams;
951
952 #
953 # Get+validate the standard action parameters and dispatch the request.
954 #
955 (self._sAction, self._idTestBox, self._sTestBoxUuid, self._sTestBoxAddr, self._idTestSet) = \
956 self._getStandardParams(dParams);
957 return self._dActions[self._sAction]();
958
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette