VirtualBox

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

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

testboxcontroller.py: Log the sError value and not just the raw XML in _actionXmlResults.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 43.6 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: testboxcontroller.py 57679 2015-09-09 19:24:12Z 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: 57679 $"
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 if self._sTestBoxAddr != oTestBox.ip \
398 or sOs != oTestBox.sOs \
399 or sOsVersion != oTestBox.sOsVersion \
400 or sCpuVendor != oTestBox.sCpuVendor \
401 or sCpuArch != oTestBox.sCpuArch \
402 or sCpuName != oTestBox.sCpuName \
403 or lCpuRevision != oTestBox.lCpuRevision \
404 or cCpus != oTestBox.cCpus \
405 or fCpuHwVirt != oTestBox.fCpuHwVirt \
406 or fCpuNestedPaging != oTestBox.fCpuNestedPaging \
407 or fCpu64BitGuest != oTestBox.fCpu64BitGuest \
408 or fChipsetIoMmu != oTestBox.fChipsetIoMmu \
409 or cMbMemory != oTestBox.cMbMemory \
410 or cMbScratch != oTestBox.cMbScratch \
411 or sReport != oTestBox.sReport \
412 or iTestBoxScriptRev != oTestBox.iTestBoxScriptRev \
413 or iPythonHexVersion != oTestBox.iPythonHexVersion:
414 oTestBoxLogic.updateOnSignOn(oTestBox.idTestBox,
415 oTestBox.idGenTestBox,
416 sTestBoxAddr = self._sTestBoxAddr,
417 sOs = sOs,
418 sOsVersion = sOsVersion,
419 sCpuVendor = sCpuVendor,
420 sCpuArch = sCpuArch,
421 sCpuName = sCpuName,
422 lCpuRevision = lCpuRevision,
423 cCpus = cCpus,
424 fCpuHwVirt = fCpuHwVirt,
425 fCpuNestedPaging = fCpuNestedPaging,
426 fCpu64BitGuest = fCpu64BitGuest,
427 fChipsetIoMmu = fChipsetIoMmu,
428 cMbMemory = cMbMemory,
429 cMbScratch = cMbScratch,
430 sReport = sReport,
431 iTestBoxScriptRev = iTestBoxScriptRev,
432 iPythonHexVersion = iPythonHexVersion);
433
434 #
435 # Update the testbox status, making sure there is a status.
436 #
437 oStatusLogic = TestBoxStatusLogic(oDb);
438 oStatusData = oStatusLogic.tryFetchStatus(oTestBox.idTestBox);
439 if oStatusData is not None:
440 self._cleanupOldTest(oDb, oStatusData);
441 else:
442 oStatusLogic.insertIdleStatus(oTestBox.idTestBox, oTestBox.idGenTestBox, fCommit = True);
443
444 #
445 # ACK the request.
446 #
447 dResponse = \
448 {
449 constants.tbresp.ALL_PARAM_RESULT: constants.tbresp.STATUS_ACK,
450 constants.tbresp.SIGNON_PARAM_ID: oTestBox.idTestBox,
451 constants.tbresp.SIGNON_PARAM_NAME: oTestBox.sName,
452 }
453 return self._writeResponse(dResponse);
454
455 def _doGangCleanup(self, oDb, oStatusData):
456 """
457 _doRequestCommand worker for handling a box in gang-cleanup.
458 This will check if all testboxes has completed their run, pretending to
459 be busy until that happens. Once all are completed, resources will be
460 freed and the testbox returns to idle state (we update oStatusData).
461 """
462 oStatusLogic = TestBoxStatusLogic(oDb)
463 oTestSet = TestSetData().initFromDbWithId(oDb, oStatusData.idTestSet);
464 if oStatusLogic.isWholeGangDoneTesting(oTestSet.idTestSetGangLeader):
465 oDb.begin();
466
467 GlobalResourceLogic(oDb).freeGlobalResourcesByTestBox(self._idTestBox, fCommit = False);
468 TestBoxStatusLogic(oDb).updateState(self._idTestBox, TestBoxStatusData.ksTestBoxState_Idle, fCommit = False);
469
470 oStatusData.tsUpdated = oDb.getCurrentTimestamp();
471 oStatusData.enmState = TestBoxStatusData.ksTestBoxState_Idle;
472
473 oDb.commit();
474 return None;
475
476 def _doGangGatheringTimedOut(self, oDb, oStatusData):
477 """
478 _doRequestCommand worker for handling a box in gang-gathering-timed-out state.
479 This will do clean-ups similar to _cleanupOldTest and update the state likewise.
480 """
481 oDb.begin();
482
483 TestSetLogic(oDb).completeAsGangGatheringTimeout(oStatusData.idTestSet, fCommit = False);
484 GlobalResourceLogic(oDb).freeGlobalResourcesByTestBox(self._idTestBox, fCommit = False);
485 TestBoxStatusLogic(oDb).updateState(self._idTestBox, TestBoxStatusData.ksTestBoxState_Idle, fCommit = False);
486
487 oStatusData.tsUpdated = oDb.getCurrentTimestamp();
488 oStatusData.enmState = TestBoxStatusData.ksTestBoxState_Idle;
489
490 oDb.commit();
491 return None;
492
493 def _doGangGathering(self, oDb, oStatusData):
494 """
495 _doRequestCommand worker for handling a box in gang-gathering state.
496 This only checks for timeout. It will update the oStatusData if a
497 timeout is detected, so that the box will be idle upon return.
498 """
499 oStatusLogic = TestBoxStatusLogic(oDb);
500 if oStatusLogic.timeSinceLastChangeInSecs(oStatusData) > config.g_kcSecGangGathering \
501 and SchedulerBase.tryCancelGangGathering(oDb, oStatusData): # <-- Updates oStatusData.
502 self._doGangGatheringTimedOut(oDb, oStatusData);
503 return None;
504
505 def _doRequestCommand(self, fIdle):
506 """
507 Common code for handling command request.
508 """
509
510 (oDb, oStatusData, oTestBoxData) = self._connectToDbAndValidateTb();
511 if oDb is None:
512 return False;
513
514 #
515 # Status clean up.
516 #
517 # Only when BUSY will the TestBox Script request and execute commands
518 # concurrently. So, it must be idle when sending REQUEST_COMMAND_IDLE.
519 #
520 if fIdle:
521 if oStatusData.enmState == TestBoxStatusData.ksTestBoxState_GangGathering:
522 self._doGangGathering(oDb, oStatusData);
523 elif oStatusData.enmState == TestBoxStatusData.ksTestBoxState_GangGatheringTimedOut:
524 self._doGangGatheringTimedOut(oDb, oStatusData);
525 elif oStatusData.enmState == TestBoxStatusData.ksTestBoxState_GangTesting:
526 dResponse = SchedulerBase.composeExecResponse(oDb, oTestBoxData.idTestBox, self._oSrvGlue.getBaseUrl());
527 if dResponse is not None:
528 return dResponse;
529 elif oStatusData.enmState == TestBoxStatusData.ksTestBoxState_GangCleanup:
530 self._doGangCleanup(oDb, oStatusData);
531 elif oStatusData.enmState != TestBoxStatusData.ksTestBoxState_Idle: # (includes ksTestBoxState_GangGatheringTimedOut)
532 self._cleanupOldTest(oDb, oStatusData);
533
534 #
535 # Check for pending command.
536 #
537 if oTestBoxData.enmPendingCmd != TestBoxData.ksTestBoxCmd_None:
538 asValidCmds = TestBoxController.kasIdleCmds if fIdle else TestBoxController.kasBusyCmds;
539 if oTestBoxData.enmPendingCmd in asValidCmds:
540 dResponse = { constants.tbresp.ALL_PARAM_RESULT: TestBoxController.kdCmdToTbRespCmd[oTestBoxData.enmPendingCmd] };
541 if oTestBoxData.enmPendingCmd in [TestBoxData.ksTestBoxCmd_Upgrade, TestBoxData.ksTestBoxCmd_UpgradeAndReboot]:
542 dResponse[constants.tbresp.UPGRADE_PARAM_URL] = self._oSrvGlue.getBaseUrl() + TestBoxController.ksUpgradeZip;
543 return self._writeResponse(dResponse);
544
545 if oTestBoxData.enmPendingCmd == TestBoxData.ksTestBoxCmd_Abort and fIdle:
546 TestBoxLogic(oDb).setCommand(self._idTestBox, sOldCommand = oTestBoxData.enmPendingCmd,
547 sNewCommand = TestBoxData.ksTestBoxCmd_None, fCommit = True);
548
549 #
550 # If doing gang stuff, return 'CMD_WAIT'.
551 #
552 ## @todo r=bird: Why is GangTesting included here? Figure out when testing gang testing.
553 if oStatusData.enmState in [TestBoxStatusData.ksTestBoxState_GangGathering,
554 TestBoxStatusData.ksTestBoxState_GangTesting,
555 TestBoxStatusData.ksTestBoxState_GangCleanup]:
556 return self._resultResponse(constants.tbresp.CMD_WAIT);
557
558 #
559 # If idling and enabled try schedule a new task.
560 #
561 if fIdle \
562 and oTestBoxData.fEnabled \
563 and oStatusData.enmState == TestBoxStatusData.ksTestBoxState_Idle: # (paranoia)
564 dResponse = SchedulerBase.scheduleNewTask(oDb, oTestBoxData, self._oSrvGlue.getBaseUrl());
565 if dResponse is not None:
566 return self._writeResponse(dResponse);
567
568 #
569 # Touch the status row every couple of mins so we can tell that the box is alive.
570 #
571 oStatusLogic = TestBoxStatusLogic(oDb);
572 if oStatusData.enmState != TestBoxStatusData.ksTestBoxState_GangGathering \
573 and oStatusLogic.timeSinceLastChangeInSecs(oStatusData) >= TestBoxStatusLogic.kcSecIdleTouchStatus:
574 oStatusLogic.touchStatus(oTestBoxData.idTestBox, fCommit = True);
575
576 return self._idleResponse();
577
578 def _actionRequestCommandBusy(self):
579 """ Implement request for command. """
580 self._checkForUnknownParameters();
581 return self._doRequestCommand(False);
582
583 def _actionRequestCommandIdle(self):
584 """ Implement request for command. """
585 self._checkForUnknownParameters();
586 return self._doRequestCommand(True);
587
588 def _doCommandAckNck(self, sCmd):
589 """ Implements ACK, NACK and NACK(ENOTSUP). """
590
591 (oDb, _, _) = self._connectToDbAndValidateTb();
592 if oDb is None:
593 return False;
594
595 #
596 # If the command maps to a TestBoxCmd_T value, it means we have to
597 # check and update TestBoxes. If it's an ACK, the testbox status will
598 # need updating as well.
599 #
600 sPendingCmd = TestBoxController.kdTbRespCmdToCmd[sCmd];
601 if sPendingCmd is not None:
602 oTestBoxLogic = TestBoxLogic(oDb)
603 oTestBoxLogic.setCommand(self._idTestBox, sOldCommand = sPendingCmd,
604 sNewCommand = TestBoxData.ksTestBoxCmd_None, fCommit = False);
605
606 if self._sAction == constants.tbreq.COMMAND_ACK \
607 and TestBoxController.kdCmdToState[sPendingCmd] is not None:
608 oStatusLogic = TestBoxStatusLogic(oDb);
609 oStatusLogic.updateState(self._idTestBox, TestBoxController.kdCmdToState[sPendingCmd], fCommit = False);
610
611 # Commit the two updates.
612 oDb.commit();
613
614 #
615 # Log NACKs.
616 #
617 if self._sAction != constants.tbreq.COMMAND_ACK:
618 oSysLogLogic = SystemLogLogic(oDb);
619 oSysLogLogic.addEntry(SystemLogData.ksEvent_CmdNacked,
620 'idTestBox=%s sCmd=%s' % (self._idTestBox, sPendingCmd),
621 24, fCommit = True);
622
623 return self._resultResponse(constants.tbresp.STATUS_ACK);
624
625 def _actionCommandAck(self):
626 """ Implement command ACK'ing """
627 sCmd = self._getStringParam(constants.tbreq.COMMAND_ACK_PARAM_CMD_NAME, TestBoxController.kasAckableCmds);
628 self._checkForUnknownParameters();
629 return self._doCommandAckNck(sCmd);
630
631 def _actionCommandNack(self):
632 """ Implement command NACK'ing """
633 sCmd = self._getStringParam(constants.tbreq.COMMAND_ACK_PARAM_CMD_NAME, TestBoxController.kasNackableCmds);
634 self._checkForUnknownParameters();
635 return self._doCommandAckNck(sCmd);
636
637 def _actionCommandNotSup(self):
638 """ Implement command NACK(ENOTSUP)'ing """
639 sCmd = self._getStringParam(constants.tbreq.COMMAND_ACK_PARAM_CMD_NAME, TestBoxController.kasNackableCmds);
640 self._checkForUnknownParameters();
641 return self._doCommandAckNck(sCmd);
642
643 def _actionLogMain(self):
644 """ Implement submitting log entries to the main log file. """
645 #
646 # Parameter validation.
647 #
648 sBody = self._getStringParam(constants.tbreq.LOG_PARAM_BODY, fStrip = False);
649 if len(sBody) == 0:
650 return self._resultResponse(constants.tbresp.STATUS_NACK);
651 self._checkForUnknownParameters();
652
653 (oDb, oStatusData, _) = self._connectToDbAndValidateTb([TestBoxStatusData.ksTestBoxState_Testing,
654 TestBoxStatusData.ksTestBoxState_GangTesting]);
655 if oStatusData is None:
656 return False;
657
658 #
659 # Write the text to the log file.
660 #
661 oTestSet = TestSetData().initFromDbWithId(oDb, oStatusData.idTestSet);
662 self.writeToMainLog(oTestSet, sBody);
663 ## @todo Overflow is a hanging offence, need to note it and fail whatever is going on...
664
665 # Done.
666 return self._resultResponse(constants.tbresp.STATUS_ACK);
667
668 def _actionUpload(self):
669 """ Implement uploading of files. """
670 #
671 # Parameter validation.
672 #
673 sName = self._getStringParam(constants.tbreq.UPLOAD_PARAM_NAME);
674 sMime = self._getStringParam(constants.tbreq.UPLOAD_PARAM_MIME);
675 sKind = self._getStringParam(constants.tbreq.UPLOAD_PARAM_KIND);
676 sDesc = self._getStringParam(constants.tbreq.UPLOAD_PARAM_DESC);
677 self._checkForUnknownParameters();
678
679 (oDb, oStatusData, _) = self._connectToDbAndValidateTb([TestBoxStatusData.ksTestBoxState_Testing,
680 TestBoxStatusData.ksTestBoxState_GangTesting]);
681 if oStatusData is None:
682 return False;
683
684 if len(sName) > 128 or len(sName) < 3:
685 raise TestBoxControllerException('Invalid file name "%s"' % (sName,));
686 if re.match(r'^[a-zA-Z0-9_\-(){}#@+,.=]*$', sName) is None:
687 raise TestBoxControllerException('Invalid file name "%s"' % (sName,));
688
689 if sMime not in [ 'text/plain', #'text/html', 'text/xml',
690 'application/octet-stream',
691 'image/png', #'image/gif', 'image/jpeg',
692 #'video/webm', 'video/mpeg', 'video/mpeg4-generic',
693 ]:
694 raise TestBoxControllerException('Invalid MIME type "%s"' % (sMime,));
695
696 if sKind not in [ 'log/release/vm',
697 'log/debug/vm',
698 'log/release/svc',
699 'log/debug/svc',
700 'log/release/client',
701 'log/debug/client',
702 'log/installer',
703 'log/uninstaller',
704 'crash/report/vm',
705 'crash/dump/vm',
706 'crash/report/svc',
707 'crash/dump/svc',
708 'crash/report/client',
709 'crash/dump/client',
710 'misc/other',
711 'screenshot/failure',
712 'screenshot/success',
713 #'screencapture/failure',
714 ]:
715 raise TestBoxControllerException('Invalid kind "%s"' % (sKind,));
716
717 if len(sDesc) > 256:
718 raise TestBoxControllerException('Invalid description "%s"' % (sDesc,));
719 if not set(sDesc).issubset(set(string.printable)):
720 raise TestBoxControllerException('Invalid description "%s"' % (sDesc,));
721
722 if ('application/octet-stream', {}) != self._oSrvGlue.getContentType():
723 raise TestBoxControllerException('Unexpected content type: %s; %s' % self._oSrvGlue.getContentType());
724
725 cbFile = self._oSrvGlue.getContentLength();
726 if cbFile <= 0:
727 raise TestBoxControllerException('File "%s" is empty or negative in size (%s)' % (sName, cbFile));
728 if (cbFile + 1048575) / 1048576 > config.g_kcMbMaxUploadSingle:
729 raise TestBoxControllerException('File "%s" is too big %u bytes (max %u MiB)'
730 % (sName, cbFile, config.g_kcMbMaxUploadSingle,));
731
732 #
733 # Write the text to the log file.
734 #
735 oTestSet = TestSetData().initFromDbWithId(oDb, oStatusData.idTestSet);
736 oDstFile = TestSetLogic(oDb).createFile(oTestSet, sName = sName, sMime = sMime, sKind = sKind, sDesc = sDesc,
737 cbFile = cbFile, fCommit = True);
738
739 offFile = 0;
740 oSrcFile = self._oSrvGlue.getBodyIoStream();
741 while offFile < cbFile:
742 cbToRead = cbFile - offFile;
743 if cbToRead > 256*1024:
744 cbToRead = 256*1024;
745 offFile += cbToRead;
746
747 abBuf = oSrcFile.read(cbToRead);
748 oDstFile.write(abBuf); # pylint: disable=E1103
749 del abBuf;
750
751 oDstFile.close(); # pylint: disable=E1103
752
753 # Done.
754 return self._resultResponse(constants.tbresp.STATUS_ACK);
755
756 def _actionXmlResults(self):
757 """ Implement submitting "XML" like test result stream. """
758 #
759 # Parameter validation.
760 #
761 sXml = self._getStringParam(constants.tbreq.XML_RESULT_PARAM_BODY, fStrip = False);
762 self._checkForUnknownParameters();
763 if len(sXml) == 0: # Used for link check by vboxinstaller.py on Windows.
764 return self._resultResponse(constants.tbresp.STATUS_ACK);
765
766 (oDb, oStatusData, _) = self._connectToDbAndValidateTb([TestBoxStatusData.ksTestBoxState_Testing,
767 TestBoxStatusData.ksTestBoxState_GangTesting]);
768 if oStatusData is None:
769 return False;
770
771 #
772 # Process the XML.
773 #
774 (sError, fUnforgivable) = TestResultLogic(oDb).processXmlStream(sXml, self._idTestSet);
775 if sError is not None:
776 oTestSet = TestSetData().initFromDbWithId(oDb, oStatusData.idTestSet);
777 self.writeToMainLog(oTestSet, '\n!!XML error: %s\n%s\n\n' % (sError, sXml,));
778 if fUnforgivable:
779 return self._resultResponse(constants.tbresp.STATUS_NACK);
780 return self._resultResponse(constants.tbresp.STATUS_ACK);
781
782
783 def _actionExecCompleted(self):
784 """
785 Implement EXEC completion.
786
787 Because the action is request by the worker thread of the testbox
788 script we cannot pass pending commands back to it like originally
789 planned. So, we just complete the test set and update the status.
790 """
791 #
792 # Parameter validation.
793 #
794 sStatus = self._getStringParam(constants.tbreq.EXEC_COMPLETED_PARAM_RESULT, TestBoxController.kasValidResults);
795 self._checkForUnknownParameters();
796
797 (oDb, oStatusData, _) = self._connectToDbAndValidateTb([TestBoxStatusData.ksTestBoxState_Testing,
798 TestBoxStatusData.ksTestBoxState_GangTesting]);
799 if oStatusData is None:
800 return False;
801
802 #
803 # Complete the status.
804 #
805 oDb.rollback();
806 oDb.begin();
807 oTestSetLogic = TestSetLogic(oDb);
808 idTestSetGangLeader = oTestSetLogic.complete(oStatusData.idTestSet, self.kadTbResultToStatus[sStatus], fCommit = False);
809
810 oStatusLogic = TestBoxStatusLogic(oDb);
811 if oStatusData.enmState == TestBoxStatusData.ksTestBoxState_Testing:
812 assert idTestSetGangLeader is None;
813 GlobalResourceLogic(oDb).freeGlobalResourcesByTestBox(self._idTestBox);
814 oStatusLogic.updateState(self._idTestBox, TestBoxStatusData.ksTestBoxState_Idle, fCommit = False);
815 else:
816 assert idTestSetGangLeader is not None;
817 oStatusLogic.updateState(self._idTestBox, TestBoxStatusData.ksTestBoxState_GangCleanup, oStatusData.idTestSet,
818 fCommit = False);
819 if oStatusLogic.isWholeGangDoneTesting(idTestSetGangLeader):
820 GlobalResourceLogic(oDb).freeGlobalResourcesByTestBox(self._idTestBox);
821 oStatusLogic.updateState(self._idTestBox, TestBoxStatusData.ksTestBoxState_Idle, fCommit = False);
822
823 oDb.commit();
824 return self._resultResponse(constants.tbresp.STATUS_ACK);
825
826
827
828 def _getStandardParams(self, dParams):
829 """
830 Gets the standard parameters and validates them.
831
832 The parameters are returned as a tuple: sAction, idTestBox, sTestBoxUuid.
833 Note! the sTextBoxId can be None if it's a SIGNON request.
834
835 Raises TestBoxControllerException on invalid input.
836 """
837 #
838 # Get the action parameter and validate it.
839 #
840 if constants.tbreq.ALL_PARAM_ACTION not in dParams:
841 raise TestBoxControllerException('No "%s" parameter in request (params: %s)' \
842 % (constants.tbreq.ALL_PARAM_ACTION, dParams,));
843 sAction = dParams[constants.tbreq.ALL_PARAM_ACTION];
844
845 if sAction not in self._dActions:
846 raise TestBoxControllerException('Unknown action "%s" in request (params: %s; action: %s)' \
847 % (sAction, dParams, self._dActions));
848
849 #
850 # TestBox UUID.
851 #
852 if constants.tbreq.ALL_PARAM_TESTBOX_UUID not in dParams:
853 raise TestBoxControllerException('No "%s" parameter in request (params: %s)' \
854 % (constants.tbreq.ALL_PARAM_TESTBOX_UUID, dParams,));
855 sTestBoxUuid = dParams[constants.tbreq.ALL_PARAM_TESTBOX_UUID];
856 try:
857 sTestBoxUuid = str(uuid.UUID(sTestBoxUuid));
858 except Exception as oXcpt:
859 raise TestBoxControllerException('Invalid %s parameter value "%s": %s ' \
860 % (constants.tbreq.ALL_PARAM_TESTBOX_UUID, sTestBoxUuid, oXcpt));
861 if sTestBoxUuid == '00000000-0000-0000-0000-000000000000':
862 raise TestBoxControllerException('Invalid %s parameter value "%s": NULL UUID not allowed.' \
863 % (constants.tbreq.ALL_PARAM_TESTBOX_UUID, sTestBoxUuid));
864
865 #
866 # TestBox ID.
867 #
868 if constants.tbreq.ALL_PARAM_TESTBOX_ID in dParams:
869 sTestBoxId = dParams[constants.tbreq.ALL_PARAM_TESTBOX_ID];
870 try:
871 idTestBox = int(sTestBoxId);
872 if idTestBox <= 0 or idTestBox >= 0x7fffffff:
873 raise Exception;
874 except:
875 raise TestBoxControllerException('Bad value for "%s": "%s"' \
876 % (constants.tbreq.ALL_PARAM_TESTBOX_ID, sTestBoxId));
877 elif sAction == constants.tbreq.SIGNON:
878 idTestBox = None;
879 else:
880 raise TestBoxControllerException('No "%s" parameter in request (params: %s)' \
881 % (constants.tbreq.ALL_PARAM_TESTBOX_ID, dParams,));
882
883 #
884 # Test Set ID.
885 #
886 if constants.tbreq.RESULT_PARAM_TEST_SET_ID in dParams:
887 sTestSetId = dParams[constants.tbreq.RESULT_PARAM_TEST_SET_ID];
888 try:
889 idTestSet = int(sTestSetId);
890 if idTestSet <= 0 or idTestSet >= 0x7fffffff:
891 raise Exception;
892 except:
893 raise TestBoxControllerException('Bad value for "%s": "%s"' \
894 % (constants.tbreq.RESULT_PARAM_TEST_SET_ID, sTestSetId));
895 elif sAction not in [ constants.tbreq.XML_RESULTS, ]: ## More later.
896 idTestSet = None;
897 else:
898 raise TestBoxControllerException('No "%s" parameter in request (params: %s)' \
899 % (constants.tbreq.RESULT_PARAM_TEST_SET_ID, dParams,));
900
901 #
902 # The testbox address.
903 #
904 sTestBoxAddr = self._oSrvGlue.getClientAddr();
905 if sTestBoxAddr is None or sTestBoxAddr.strip() == '':
906 raise TestBoxControllerException('Invalid client address "%s"' % (sTestBoxAddr,));
907
908 #
909 # Update the list of checked parameters.
910 #
911 self._asCheckedParams.extend([constants.tbreq.ALL_PARAM_TESTBOX_UUID, constants.tbreq.ALL_PARAM_ACTION]);
912 if idTestBox is not None:
913 self._asCheckedParams.append(constants.tbreq.ALL_PARAM_TESTBOX_ID);
914 if idTestSet is not None:
915 self._asCheckedParams.append(constants.tbreq.RESULT_PARAM_TEST_SET_ID);
916
917 return (sAction, idTestBox, sTestBoxUuid, sTestBoxAddr, idTestSet);
918
919 def dispatchRequest(self):
920 """
921 Dispatches the incoming request.
922
923 Will raise TestBoxControllerException on failure.
924 """
925
926 #
927 # Must be a POST request.
928 #
929 try:
930 sMethod = self._oSrvGlue.getMethod();
931 except Exception as oXcpt:
932 raise TestBoxControllerException('Error retriving request method: %s' % (oXcpt,));
933 if sMethod != 'POST':
934 raise TestBoxControllerException('Error expected POST request not "%s"' % (sMethod,));
935
936 #
937 # Get the parameters and checks for duplicates.
938 #
939 try:
940 dParams = self._oSrvGlue.getParameters();
941 except Exception as oXcpt:
942 raise TestBoxControllerException('Error retriving parameters: %s' % (oXcpt,));
943 for sKey in dParams.keys():
944 if len(dParams[sKey]) > 1:
945 raise TestBoxControllerException('Parameter "%s" is given multiple times: %s' % (sKey, dParams[sKey]));
946 dParams[sKey] = dParams[sKey][0];
947 self._dParams = dParams;
948
949 #
950 # Get+validate the standard action parameters and dispatch the request.
951 #
952 (self._sAction, self._idTestBox, self._sTestBoxUuid, self._sTestBoxAddr, self._idTestSet) = \
953 self._getStandardParams(dParams);
954 return self._dActions[self._sAction]();
955
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