VirtualBox

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

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

testmanager: Testboxes can now be members of more than one scheduling group.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 44.1 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: testboxcontroller.py 61502 2016-06-06 17:53:01Z 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: 61502 $"
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-abandoned-testcase
276
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 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 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);
371 self._checkForUnknownParameters();
372
373 # Null conversions for new parameters.
374 if len(sReport) == 0:
375 sReport = None;
376 if len(sCpuName) == 0:
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=R0916
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 oStatusData.enmState == TestBoxStatusData.ksTestBoxState_Idle: # (paranoia)
573 dResponse = SchedulerBase.scheduleNewTask(oDb, oTestBoxData, oStatusData.iWorkItem, self._oSrvGlue.getBaseUrl());
574 if dResponse is not None:
575 return self._writeResponse(dResponse);
576
577 #
578 # Touch the status row every couple of mins so we can tell that the box is alive.
579 #
580 oStatusLogic = TestBoxStatusLogic(oDb);
581 if oStatusData.enmState != TestBoxStatusData.ksTestBoxState_GangGathering \
582 and oStatusLogic.timeSinceLastChangeInSecs(oStatusData) >= TestBoxStatusLogic.kcSecIdleTouchStatus:
583 oStatusLogic.touchStatus(oTestBoxData.idTestBox, fCommit = True);
584
585 return self._idleResponse();
586
587 def _actionRequestCommandBusy(self):
588 """ Implement request for command. """
589 self._checkForUnknownParameters();
590 return self._doRequestCommand(False);
591
592 def _actionRequestCommandIdle(self):
593 """ Implement request for command. """
594 self._checkForUnknownParameters();
595 return self._doRequestCommand(True);
596
597 def _doCommandAckNck(self, sCmd):
598 """ Implements ACK, NACK and NACK(ENOTSUP). """
599
600 (oDb, _, _) = self._connectToDbAndValidateTb();
601 if oDb is None:
602 return False;
603
604 #
605 # If the command maps to a TestBoxCmd_T value, it means we have to
606 # check and update TestBoxes. If it's an ACK, the testbox status will
607 # need updating as well.
608 #
609 sPendingCmd = TestBoxController.kdTbRespCmdToCmd[sCmd];
610 if sPendingCmd is not None:
611 oTestBoxLogic = TestBoxLogic(oDb)
612 oTestBoxLogic.setCommand(self._idTestBox, sOldCommand = sPendingCmd,
613 sNewCommand = TestBoxData.ksTestBoxCmd_None, fCommit = False);
614
615 if self._sAction == constants.tbreq.COMMAND_ACK \
616 and TestBoxController.kdCmdToState[sPendingCmd] is not None:
617 oStatusLogic = TestBoxStatusLogic(oDb);
618 oStatusLogic.updateState(self._idTestBox, TestBoxController.kdCmdToState[sPendingCmd], fCommit = False);
619
620 # Commit the two updates.
621 oDb.commit();
622
623 #
624 # Log NACKs.
625 #
626 if self._sAction != constants.tbreq.COMMAND_ACK:
627 oSysLogLogic = SystemLogLogic(oDb);
628 oSysLogLogic.addEntry(SystemLogData.ksEvent_CmdNacked,
629 'idTestBox=%s sCmd=%s' % (self._idTestBox, sPendingCmd),
630 24, fCommit = True);
631
632 return self._resultResponse(constants.tbresp.STATUS_ACK);
633
634 def _actionCommandAck(self):
635 """ Implement command ACK'ing """
636 sCmd = self._getStringParam(constants.tbreq.COMMAND_ACK_PARAM_CMD_NAME, TestBoxController.kasAckableCmds);
637 self._checkForUnknownParameters();
638 return self._doCommandAckNck(sCmd);
639
640 def _actionCommandNack(self):
641 """ Implement command NACK'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 _actionCommandNotSup(self):
647 """ Implement command NACK(ENOTSUP)'ing """
648 sCmd = self._getStringParam(constants.tbreq.COMMAND_ACK_PARAM_CMD_NAME, TestBoxController.kasNackableCmds);
649 self._checkForUnknownParameters();
650 return self._doCommandAckNck(sCmd);
651
652 def _actionLogMain(self):
653 """ Implement submitting log entries to the main log file. """
654 #
655 # Parameter validation.
656 #
657 sBody = self._getStringParam(constants.tbreq.LOG_PARAM_BODY, fStrip = False);
658 if len(sBody) == 0:
659 return self._resultResponse(constants.tbresp.STATUS_NACK);
660 self._checkForUnknownParameters();
661
662 (oDb, oStatusData, _) = self._connectToDbAndValidateTb([TestBoxStatusData.ksTestBoxState_Testing,
663 TestBoxStatusData.ksTestBoxState_GangTesting]);
664 if oStatusData is None:
665 return False;
666
667 #
668 # Write the text to the log file.
669 #
670 oTestSet = TestSetData().initFromDbWithId(oDb, oStatusData.idTestSet);
671 self.writeToMainLog(oTestSet, sBody);
672 ## @todo Overflow is a hanging offence, need to note it and fail whatever is going on...
673
674 # Done.
675 return self._resultResponse(constants.tbresp.STATUS_ACK);
676
677 def _actionUpload(self):
678 """ Implement uploading of files. """
679 #
680 # Parameter validation.
681 #
682 sName = self._getStringParam(constants.tbreq.UPLOAD_PARAM_NAME);
683 sMime = self._getStringParam(constants.tbreq.UPLOAD_PARAM_MIME);
684 sKind = self._getStringParam(constants.tbreq.UPLOAD_PARAM_KIND);
685 sDesc = self._getStringParam(constants.tbreq.UPLOAD_PARAM_DESC);
686 self._checkForUnknownParameters();
687
688 (oDb, oStatusData, _) = self._connectToDbAndValidateTb([TestBoxStatusData.ksTestBoxState_Testing,
689 TestBoxStatusData.ksTestBoxState_GangTesting]);
690 if oStatusData is None:
691 return False;
692
693 if len(sName) > 128 or len(sName) < 3:
694 raise TestBoxControllerException('Invalid file name "%s"' % (sName,));
695 if re.match(r'^[a-zA-Z0-9_\-(){}#@+,.=]*$', sName) is None:
696 raise TestBoxControllerException('Invalid file name "%s"' % (sName,));
697
698 if sMime not in [ 'text/plain', #'text/html', 'text/xml',
699 'application/octet-stream',
700 'image/png', #'image/gif', 'image/jpeg',
701 #'video/webm', 'video/mpeg', 'video/mpeg4-generic',
702 ]:
703 raise TestBoxControllerException('Invalid MIME type "%s"' % (sMime,));
704
705 if sKind not in [ 'log/release/vm',
706 'log/debug/vm',
707 'log/release/svc',
708 'log/debug/svc',
709 'log/release/client',
710 'log/debug/client',
711 'log/installer',
712 'log/uninstaller',
713 'log/guest/kernel',
714 'crash/report/vm',
715 'crash/dump/vm',
716 'crash/report/svc',
717 'crash/dump/svc',
718 'crash/report/client',
719 'crash/dump/client',
720 'misc/other',
721 'screenshot/failure',
722 'screenshot/success',
723 #'screencapture/failure',
724 ]:
725 raise TestBoxControllerException('Invalid kind "%s"' % (sKind,));
726
727 if len(sDesc) > 256:
728 raise TestBoxControllerException('Invalid description "%s"' % (sDesc,));
729 if not set(sDesc).issubset(set(string.printable)):
730 raise TestBoxControllerException('Invalid description "%s"' % (sDesc,));
731
732 if ('application/octet-stream', {}) != self._oSrvGlue.getContentType():
733 raise TestBoxControllerException('Unexpected content type: %s; %s' % self._oSrvGlue.getContentType());
734
735 cbFile = self._oSrvGlue.getContentLength();
736 if cbFile <= 0:
737 raise TestBoxControllerException('File "%s" is empty or negative in size (%s)' % (sName, cbFile));
738 if (cbFile + 1048575) / 1048576 > config.g_kcMbMaxUploadSingle:
739 raise TestBoxControllerException('File "%s" is too big %u bytes (max %u MiB)'
740 % (sName, cbFile, config.g_kcMbMaxUploadSingle,));
741
742 #
743 # Write the text to the log file.
744 #
745 oTestSet = TestSetData().initFromDbWithId(oDb, oStatusData.idTestSet);
746 oDstFile = TestSetLogic(oDb).createFile(oTestSet, sName = sName, sMime = sMime, sKind = sKind, sDesc = sDesc,
747 cbFile = cbFile, fCommit = True);
748
749 offFile = 0;
750 oSrcFile = self._oSrvGlue.getBodyIoStream();
751 while offFile < cbFile:
752 cbToRead = cbFile - offFile;
753 if cbToRead > 256*1024:
754 cbToRead = 256*1024;
755 offFile += cbToRead;
756
757 abBuf = oSrcFile.read(cbToRead);
758 oDstFile.write(abBuf); # pylint: disable=E1103
759 del abBuf;
760
761 oDstFile.close(); # pylint: disable=E1103
762
763 # Done.
764 return self._resultResponse(constants.tbresp.STATUS_ACK);
765
766 def _actionXmlResults(self):
767 """ Implement submitting "XML" like test result stream. """
768 #
769 # Parameter validation.
770 #
771 sXml = self._getStringParam(constants.tbreq.XML_RESULT_PARAM_BODY, fStrip = False);
772 self._checkForUnknownParameters();
773 if len(sXml) == 0: # Used for link check by vboxinstaller.py on Windows.
774 return self._resultResponse(constants.tbresp.STATUS_ACK);
775
776 (oDb, oStatusData, _) = self._connectToDbAndValidateTb([TestBoxStatusData.ksTestBoxState_Testing,
777 TestBoxStatusData.ksTestBoxState_GangTesting]);
778 if oStatusData is None:
779 return False;
780
781 #
782 # Process the XML.
783 #
784 (sError, fUnforgivable) = TestResultLogic(oDb).processXmlStream(sXml, self._idTestSet);
785 if sError is not None:
786 oTestSet = TestSetData().initFromDbWithId(oDb, oStatusData.idTestSet);
787 self.writeToMainLog(oTestSet, '\n!!XML error: %s\n%s\n\n' % (sError, sXml,));
788 if fUnforgivable:
789 return self._resultResponse(constants.tbresp.STATUS_NACK);
790 return self._resultResponse(constants.tbresp.STATUS_ACK);
791
792
793 def _actionExecCompleted(self):
794 """
795 Implement EXEC completion.
796
797 Because the action is request by the worker thread of the testbox
798 script we cannot pass pending commands back to it like originally
799 planned. So, we just complete the test set and update the status.
800 """
801 #
802 # Parameter validation.
803 #
804 sStatus = self._getStringParam(constants.tbreq.EXEC_COMPLETED_PARAM_RESULT, TestBoxController.kasValidResults);
805 self._checkForUnknownParameters();
806
807 (oDb, oStatusData, _) = self._connectToDbAndValidateTb([TestBoxStatusData.ksTestBoxState_Testing,
808 TestBoxStatusData.ksTestBoxState_GangTesting]);
809 if oStatusData is None:
810 return False;
811
812 #
813 # Complete the status.
814 #
815 oDb.rollback();
816 oDb.begin();
817 oTestSetLogic = TestSetLogic(oDb);
818 idTestSetGangLeader = oTestSetLogic.complete(oStatusData.idTestSet, self.kadTbResultToStatus[sStatus], fCommit = False);
819
820 oStatusLogic = TestBoxStatusLogic(oDb);
821 if oStatusData.enmState == TestBoxStatusData.ksTestBoxState_Testing:
822 assert idTestSetGangLeader is None;
823 GlobalResourceLogic(oDb).freeGlobalResourcesByTestBox(self._idTestBox);
824 oStatusLogic.updateState(self._idTestBox, TestBoxStatusData.ksTestBoxState_Idle, fCommit = False);
825 else:
826 assert idTestSetGangLeader is not None;
827 oStatusLogic.updateState(self._idTestBox, TestBoxStatusData.ksTestBoxState_GangCleanup, oStatusData.idTestSet,
828 fCommit = False);
829 if oStatusLogic.isWholeGangDoneTesting(idTestSetGangLeader):
830 GlobalResourceLogic(oDb).freeGlobalResourcesByTestBox(self._idTestBox);
831 oStatusLogic.updateState(self._idTestBox, TestBoxStatusData.ksTestBoxState_Idle, fCommit = False);
832
833 oDb.commit();
834 return self._resultResponse(constants.tbresp.STATUS_ACK);
835
836
837
838 def _getStandardParams(self, dParams):
839 """
840 Gets the standard parameters and validates them.
841
842 The parameters are returned as a tuple: sAction, idTestBox, sTestBoxUuid.
843 Note! the sTextBoxId can be None if it's a SIGNON request.
844
845 Raises TestBoxControllerException on invalid input.
846 """
847 #
848 # Get the action parameter and validate it.
849 #
850 if constants.tbreq.ALL_PARAM_ACTION not in dParams:
851 raise TestBoxControllerException('No "%s" parameter in request (params: %s)' \
852 % (constants.tbreq.ALL_PARAM_ACTION, dParams,));
853 sAction = dParams[constants.tbreq.ALL_PARAM_ACTION];
854
855 if sAction not in self._dActions:
856 raise TestBoxControllerException('Unknown action "%s" in request (params: %s; action: %s)' \
857 % (sAction, dParams, self._dActions));
858
859 #
860 # TestBox UUID.
861 #
862 if constants.tbreq.ALL_PARAM_TESTBOX_UUID not in dParams:
863 raise TestBoxControllerException('No "%s" parameter in request (params: %s)' \
864 % (constants.tbreq.ALL_PARAM_TESTBOX_UUID, dParams,));
865 sTestBoxUuid = dParams[constants.tbreq.ALL_PARAM_TESTBOX_UUID];
866 try:
867 sTestBoxUuid = str(uuid.UUID(sTestBoxUuid));
868 except Exception as oXcpt:
869 raise TestBoxControllerException('Invalid %s parameter value "%s": %s ' \
870 % (constants.tbreq.ALL_PARAM_TESTBOX_UUID, sTestBoxUuid, oXcpt));
871 if sTestBoxUuid == '00000000-0000-0000-0000-000000000000':
872 raise TestBoxControllerException('Invalid %s parameter value "%s": NULL UUID not allowed.' \
873 % (constants.tbreq.ALL_PARAM_TESTBOX_UUID, sTestBoxUuid));
874
875 #
876 # TestBox ID.
877 #
878 if constants.tbreq.ALL_PARAM_TESTBOX_ID in dParams:
879 sTestBoxId = dParams[constants.tbreq.ALL_PARAM_TESTBOX_ID];
880 try:
881 idTestBox = int(sTestBoxId);
882 if idTestBox <= 0 or idTestBox >= 0x7fffffff:
883 raise Exception;
884 except:
885 raise TestBoxControllerException('Bad value for "%s": "%s"' \
886 % (constants.tbreq.ALL_PARAM_TESTBOX_ID, sTestBoxId));
887 elif sAction == constants.tbreq.SIGNON:
888 idTestBox = None;
889 else:
890 raise TestBoxControllerException('No "%s" parameter in request (params: %s)' \
891 % (constants.tbreq.ALL_PARAM_TESTBOX_ID, dParams,));
892
893 #
894 # Test Set ID.
895 #
896 if constants.tbreq.RESULT_PARAM_TEST_SET_ID in dParams:
897 sTestSetId = dParams[constants.tbreq.RESULT_PARAM_TEST_SET_ID];
898 try:
899 idTestSet = int(sTestSetId);
900 if idTestSet <= 0 or idTestSet >= 0x7fffffff:
901 raise Exception;
902 except:
903 raise TestBoxControllerException('Bad value for "%s": "%s"' \
904 % (constants.tbreq.RESULT_PARAM_TEST_SET_ID, sTestSetId));
905 elif sAction not in [ constants.tbreq.XML_RESULTS, ]: ## More later.
906 idTestSet = None;
907 else:
908 raise TestBoxControllerException('No "%s" parameter in request (params: %s)' \
909 % (constants.tbreq.RESULT_PARAM_TEST_SET_ID, dParams,));
910
911 #
912 # The testbox address.
913 #
914 sTestBoxAddr = self._oSrvGlue.getClientAddr();
915 if sTestBoxAddr is None or sTestBoxAddr.strip() == '':
916 raise TestBoxControllerException('Invalid client address "%s"' % (sTestBoxAddr,));
917
918 #
919 # Update the list of checked parameters.
920 #
921 self._asCheckedParams.extend([constants.tbreq.ALL_PARAM_TESTBOX_UUID, constants.tbreq.ALL_PARAM_ACTION]);
922 if idTestBox is not None:
923 self._asCheckedParams.append(constants.tbreq.ALL_PARAM_TESTBOX_ID);
924 if idTestSet is not None:
925 self._asCheckedParams.append(constants.tbreq.RESULT_PARAM_TEST_SET_ID);
926
927 return (sAction, idTestBox, sTestBoxUuid, sTestBoxAddr, idTestSet);
928
929 def dispatchRequest(self):
930 """
931 Dispatches the incoming request.
932
933 Will raise TestBoxControllerException on failure.
934 """
935
936 #
937 # Must be a POST request.
938 #
939 try:
940 sMethod = self._oSrvGlue.getMethod();
941 except Exception as oXcpt:
942 raise TestBoxControllerException('Error retriving request method: %s' % (oXcpt,));
943 if sMethod != 'POST':
944 raise TestBoxControllerException('Error expected POST request not "%s"' % (sMethod,));
945
946 #
947 # Get the parameters and checks for duplicates.
948 #
949 try:
950 dParams = self._oSrvGlue.getParameters();
951 except Exception as oXcpt:
952 raise TestBoxControllerException('Error retriving parameters: %s' % (oXcpt,));
953 for sKey in dParams.keys():
954 if len(dParams[sKey]) > 1:
955 raise TestBoxControllerException('Parameter "%s" is given multiple times: %s' % (sKey, dParams[sKey]));
956 dParams[sKey] = dParams[sKey][0];
957 self._dParams = dParams;
958
959 #
960 # Get+validate the standard action parameters and dispatch the request.
961 #
962 (self._sAction, self._idTestBox, self._sTestBoxUuid, self._sTestBoxAddr, self._idTestSet) = \
963 self._getStandardParams(dParams);
964 return self._dActions[self._sAction]();
965
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