VirtualBox

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

Last change on this file since 94129 was 94129, checked in by vboxsync, 3 years ago

testmanager: pylint 2.9.6 adjustments (mostly about using sub-optimal looping and 'with' statements).

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