VirtualBox

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

Last change on this file since 63694 was 62484, checked in by vboxsync, 8 years ago

(C) 2016

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 44.3 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: testboxcontroller.py 62484 2016-07-22 18:35:33Z vboxsync $
3
4"""
5Test Manager Core - Web Server Abstraction Base Class.
6"""
7
8__copyright__ = \
9"""
10Copyright (C) 2012-2016 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: 62484 $"
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 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. """
590 self._checkForUnknownParameters();
591 return self._doRequestCommand(False);
592
593 def _actionRequestCommandIdle(self):
594 """ Implement request for command. """
595 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 len(sBody) == 0:
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 [ 'log/release/vm',
707 'log/debug/vm',
708 'log/release/svc',
709 'log/debug/svc',
710 'log/release/client',
711 'log/debug/client',
712 'log/installer',
713 'log/uninstaller',
714 'log/guest/kernel',
715 'crash/report/vm',
716 'crash/dump/vm',
717 'crash/report/svc',
718 'crash/dump/svc',
719 'crash/report/client',
720 'crash/dump/client',
721 'info/collection',
722 'info/vgatext',
723 'misc/other',
724 'screenshot/failure',
725 'screenshot/success',
726 #'screencapture/failure',
727 ]:
728 raise TestBoxControllerException('Invalid kind "%s"' % (sKind,));
729
730 if len(sDesc) > 256:
731 raise TestBoxControllerException('Invalid description "%s"' % (sDesc,));
732 if not set(sDesc).issubset(set(string.printable)):
733 raise TestBoxControllerException('Invalid description "%s"' % (sDesc,));
734
735 if ('application/octet-stream', {}) != self._oSrvGlue.getContentType():
736 raise TestBoxControllerException('Unexpected content type: %s; %s' % self._oSrvGlue.getContentType());
737
738 cbFile = self._oSrvGlue.getContentLength();
739 if cbFile <= 0:
740 raise TestBoxControllerException('File "%s" is empty or negative in size (%s)' % (sName, cbFile));
741 if (cbFile + 1048575) / 1048576 > config.g_kcMbMaxUploadSingle:
742 raise TestBoxControllerException('File "%s" is too big %u bytes (max %u MiB)'
743 % (sName, cbFile, config.g_kcMbMaxUploadSingle,));
744
745 #
746 # Write the text to the log file.
747 #
748 oTestSet = TestSetData().initFromDbWithId(oDb, oStatusData.idTestSet);
749 oDstFile = TestSetLogic(oDb).createFile(oTestSet, sName = sName, sMime = sMime, sKind = sKind, sDesc = sDesc,
750 cbFile = cbFile, fCommit = True);
751
752 offFile = 0;
753 oSrcFile = self._oSrvGlue.getBodyIoStream();
754 while offFile < cbFile:
755 cbToRead = cbFile - offFile;
756 if cbToRead > 256*1024:
757 cbToRead = 256*1024;
758 offFile += cbToRead;
759
760 abBuf = oSrcFile.read(cbToRead);
761 oDstFile.write(abBuf); # pylint: disable=E1103
762 del abBuf;
763
764 oDstFile.close(); # pylint: disable=E1103
765
766 # Done.
767 return self._resultResponse(constants.tbresp.STATUS_ACK);
768
769 def _actionXmlResults(self):
770 """ Implement submitting "XML" like test result stream. """
771 #
772 # Parameter validation.
773 #
774 sXml = self._getStringParam(constants.tbreq.XML_RESULT_PARAM_BODY, fStrip = False);
775 self._checkForUnknownParameters();
776 if len(sXml) == 0: # Used for link check by vboxinstaller.py on Windows.
777 return self._resultResponse(constants.tbresp.STATUS_ACK);
778
779 (oDb, oStatusData, _) = self._connectToDbAndValidateTb([TestBoxStatusData.ksTestBoxState_Testing,
780 TestBoxStatusData.ksTestBoxState_GangTesting]);
781 if oStatusData is None:
782 return False;
783
784 #
785 # Process the XML.
786 #
787 (sError, fUnforgivable) = TestResultLogic(oDb).processXmlStream(sXml, self._idTestSet);
788 if sError is not None:
789 oTestSet = TestSetData().initFromDbWithId(oDb, oStatusData.idTestSet);
790 self.writeToMainLog(oTestSet, '\n!!XML error: %s\n%s\n\n' % (sError, sXml,));
791 if fUnforgivable:
792 return self._resultResponse(constants.tbresp.STATUS_NACK);
793 return self._resultResponse(constants.tbresp.STATUS_ACK);
794
795
796 def _actionExecCompleted(self):
797 """
798 Implement EXEC completion.
799
800 Because the action is request by the worker thread of the testbox
801 script we cannot pass pending commands back to it like originally
802 planned. So, we just complete the test set and update the status.
803 """
804 #
805 # Parameter validation.
806 #
807 sStatus = self._getStringParam(constants.tbreq.EXEC_COMPLETED_PARAM_RESULT, TestBoxController.kasValidResults);
808 self._checkForUnknownParameters();
809
810 (oDb, oStatusData, _) = self._connectToDbAndValidateTb([TestBoxStatusData.ksTestBoxState_Testing,
811 TestBoxStatusData.ksTestBoxState_GangTesting]);
812 if oStatusData is None:
813 return False;
814
815 #
816 # Complete the status.
817 #
818 oDb.rollback();
819 oDb.begin();
820 oTestSetLogic = TestSetLogic(oDb);
821 idTestSetGangLeader = oTestSetLogic.complete(oStatusData.idTestSet, self.kadTbResultToStatus[sStatus], fCommit = False);
822
823 oStatusLogic = TestBoxStatusLogic(oDb);
824 if oStatusData.enmState == TestBoxStatusData.ksTestBoxState_Testing:
825 assert idTestSetGangLeader is None;
826 GlobalResourceLogic(oDb).freeGlobalResourcesByTestBox(self._idTestBox);
827 oStatusLogic.updateState(self._idTestBox, TestBoxStatusData.ksTestBoxState_Idle, fCommit = False);
828 else:
829 assert idTestSetGangLeader is not None;
830 oStatusLogic.updateState(self._idTestBox, TestBoxStatusData.ksTestBoxState_GangCleanup, oStatusData.idTestSet,
831 fCommit = False);
832 if oStatusLogic.isWholeGangDoneTesting(idTestSetGangLeader):
833 GlobalResourceLogic(oDb).freeGlobalResourcesByTestBox(self._idTestBox);
834 oStatusLogic.updateState(self._idTestBox, TestBoxStatusData.ksTestBoxState_Idle, fCommit = False);
835
836 oDb.commit();
837 return self._resultResponse(constants.tbresp.STATUS_ACK);
838
839
840
841 def _getStandardParams(self, dParams):
842 """
843 Gets the standard parameters and validates them.
844
845 The parameters are returned as a tuple: sAction, idTestBox, sTestBoxUuid.
846 Note! the sTextBoxId can be None if it's a SIGNON request.
847
848 Raises TestBoxControllerException on invalid input.
849 """
850 #
851 # Get the action parameter and validate it.
852 #
853 if constants.tbreq.ALL_PARAM_ACTION not in dParams:
854 raise TestBoxControllerException('No "%s" parameter in request (params: %s)' \
855 % (constants.tbreq.ALL_PARAM_ACTION, dParams,));
856 sAction = dParams[constants.tbreq.ALL_PARAM_ACTION];
857
858 if sAction not in self._dActions:
859 raise TestBoxControllerException('Unknown action "%s" in request (params: %s; action: %s)' \
860 % (sAction, dParams, self._dActions));
861
862 #
863 # TestBox UUID.
864 #
865 if constants.tbreq.ALL_PARAM_TESTBOX_UUID not in dParams:
866 raise TestBoxControllerException('No "%s" parameter in request (params: %s)' \
867 % (constants.tbreq.ALL_PARAM_TESTBOX_UUID, dParams,));
868 sTestBoxUuid = dParams[constants.tbreq.ALL_PARAM_TESTBOX_UUID];
869 try:
870 sTestBoxUuid = str(uuid.UUID(sTestBoxUuid));
871 except Exception as oXcpt:
872 raise TestBoxControllerException('Invalid %s parameter value "%s": %s ' \
873 % (constants.tbreq.ALL_PARAM_TESTBOX_UUID, sTestBoxUuid, oXcpt));
874 if sTestBoxUuid == '00000000-0000-0000-0000-000000000000':
875 raise TestBoxControllerException('Invalid %s parameter value "%s": NULL UUID not allowed.' \
876 % (constants.tbreq.ALL_PARAM_TESTBOX_UUID, sTestBoxUuid));
877
878 #
879 # TestBox ID.
880 #
881 if constants.tbreq.ALL_PARAM_TESTBOX_ID in dParams:
882 sTestBoxId = dParams[constants.tbreq.ALL_PARAM_TESTBOX_ID];
883 try:
884 idTestBox = int(sTestBoxId);
885 if idTestBox <= 0 or idTestBox >= 0x7fffffff:
886 raise Exception;
887 except:
888 raise TestBoxControllerException('Bad value for "%s": "%s"' \
889 % (constants.tbreq.ALL_PARAM_TESTBOX_ID, sTestBoxId));
890 elif sAction == constants.tbreq.SIGNON:
891 idTestBox = None;
892 else:
893 raise TestBoxControllerException('No "%s" parameter in request (params: %s)' \
894 % (constants.tbreq.ALL_PARAM_TESTBOX_ID, dParams,));
895
896 #
897 # Test Set ID.
898 #
899 if constants.tbreq.RESULT_PARAM_TEST_SET_ID in dParams:
900 sTestSetId = dParams[constants.tbreq.RESULT_PARAM_TEST_SET_ID];
901 try:
902 idTestSet = int(sTestSetId);
903 if idTestSet <= 0 or idTestSet >= 0x7fffffff:
904 raise Exception;
905 except:
906 raise TestBoxControllerException('Bad value for "%s": "%s"' \
907 % (constants.tbreq.RESULT_PARAM_TEST_SET_ID, sTestSetId));
908 elif sAction not in [ constants.tbreq.XML_RESULTS, ]: ## More later.
909 idTestSet = None;
910 else:
911 raise TestBoxControllerException('No "%s" parameter in request (params: %s)' \
912 % (constants.tbreq.RESULT_PARAM_TEST_SET_ID, dParams,));
913
914 #
915 # The testbox address.
916 #
917 sTestBoxAddr = self._oSrvGlue.getClientAddr();
918 if sTestBoxAddr is None or sTestBoxAddr.strip() == '':
919 raise TestBoxControllerException('Invalid client address "%s"' % (sTestBoxAddr,));
920
921 #
922 # Update the list of checked parameters.
923 #
924 self._asCheckedParams.extend([constants.tbreq.ALL_PARAM_TESTBOX_UUID, constants.tbreq.ALL_PARAM_ACTION]);
925 if idTestBox is not None:
926 self._asCheckedParams.append(constants.tbreq.ALL_PARAM_TESTBOX_ID);
927 if idTestSet is not None:
928 self._asCheckedParams.append(constants.tbreq.RESULT_PARAM_TEST_SET_ID);
929
930 return (sAction, idTestBox, sTestBoxUuid, sTestBoxAddr, idTestSet);
931
932 def dispatchRequest(self):
933 """
934 Dispatches the incoming request.
935
936 Will raise TestBoxControllerException on failure.
937 """
938
939 #
940 # Must be a POST request.
941 #
942 try:
943 sMethod = self._oSrvGlue.getMethod();
944 except Exception as oXcpt:
945 raise TestBoxControllerException('Error retriving request method: %s' % (oXcpt,));
946 if sMethod != 'POST':
947 raise TestBoxControllerException('Error expected POST request not "%s"' % (sMethod,));
948
949 #
950 # Get the parameters and checks for duplicates.
951 #
952 try:
953 dParams = self._oSrvGlue.getParameters();
954 except Exception as oXcpt:
955 raise TestBoxControllerException('Error retriving parameters: %s' % (oXcpt,));
956 for sKey in dParams.keys():
957 if len(dParams[sKey]) > 1:
958 raise TestBoxControllerException('Parameter "%s" is given multiple times: %s' % (sKey, dParams[sKey]));
959 dParams[sKey] = dParams[sKey][0];
960 self._dParams = dParams;
961
962 #
963 # Get+validate the standard action parameters and dispatch the request.
964 #
965 (self._sAction, self._idTestBox, self._sTestBoxUuid, self._sTestBoxAddr, self._idTestSet) = \
966 self._getStandardParams(dParams);
967 return self._dActions[self._sAction]();
968
Note: See TracBrowser for help on using the repository browser.

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