VirtualBox

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

最後變更 在這個檔案從61220是 61220,由 vboxsync 提交於 9 年 前

testmanager: failiure reason fixes, some exception throwing cleanups, delinting with pylint 1.5.5.

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

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