VirtualBox

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

Last change on this file since 98103 was 98103, checked in by vboxsync, 2 years ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 43.8 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: testboxcontroller.py 98103 2023-01-17 14:15:46Z vboxsync $
3
4"""
5Test Manager Core - Web Server Abstraction Base Class.
6"""
7
8__copyright__ = \
9"""
10Copyright (C) 2012-2023 Oracle and/or its affiliates.
11
12This file is part of VirtualBox base platform packages, as
13available from https://www.virtualbox.org.
14
15This program is free software; you can redistribute it and/or
16modify it under the terms of the GNU General Public License
17as published by the Free Software Foundation, in version 3 of the
18License.
19
20This program is distributed in the hope that it will be useful, but
21WITHOUT ANY WARRANTY; without even the implied warranty of
22MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
23General Public License for more details.
24
25You should have received a copy of the GNU General Public License
26along with this program; if not, see <https://www.gnu.org/licenses>.
27
28The contents of this file may alternatively be used under the terms
29of the Common Development and Distribution License Version 1.0
30(CDDL), a copy of it is provided in the "COPYING.CDDL" file included
31in the VirtualBox distribution, in which case the provisions of the
32CDDL are applicable instead of those of the GPL.
33
34You may elect to license modified versions of this file under the
35terms and conditions of either the GPL or the CDDL or both.
36
37SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
38"""
39__version__ = "$Revision: 98103 $"
40
41
42# Standard python imports.
43import re;
44import os;
45import string; # pylint: disable=deprecated-module
46import sys;
47import uuid;
48
49# Validation Kit imports.
50from common import constants;
51from testmanager import config;
52from testmanager.core import coreconsts;
53from testmanager.core.db import TMDatabaseConnection;
54from testmanager.core.base import TMExceptionBase;
55from testmanager.core.globalresource import GlobalResourceLogic;
56from testmanager.core.testboxstatus import TestBoxStatusData, TestBoxStatusLogic;
57from testmanager.core.testbox import TestBoxData, TestBoxLogic;
58from testmanager.core.testresults import TestResultLogic, TestResultFileData;
59from testmanager.core.testset import TestSetData, TestSetLogic;
60from testmanager.core.systemlog import SystemLogData, SystemLogLogic;
61from testmanager.core.schedulerbase import SchedulerBase;
62
63# Python 3 hacks:
64if sys.version_info[0] >= 3:
65 long = int; # pylint: disable=redefined-builtin,invalid-name
66
67
68class TestBoxControllerException(TMExceptionBase):
69 """
70 Exception class for TestBoxController.
71 """
72 pass; # pylint: disable=unnecessary-pass
73
74
75class TestBoxController(object): # pylint: disable=too-few-public-methods
76 """
77 TestBox Controller class.
78 """
79
80 ## Applicable testbox commands to an idle TestBox.
81 kasIdleCmds = [TestBoxData.ksTestBoxCmd_Reboot,
82 TestBoxData.ksTestBoxCmd_Upgrade,
83 TestBoxData.ksTestBoxCmd_UpgradeAndReboot,
84 TestBoxData.ksTestBoxCmd_Special];
85 ## Applicable testbox commands to a busy TestBox.
86 kasBusyCmds = [TestBoxData.ksTestBoxCmd_Abort, TestBoxData.ksTestBoxCmd_Reboot];
87 ## Commands that can be ACK'ed.
88 kasAckableCmds = [constants.tbresp.CMD_EXEC, constants.tbresp.CMD_ABORT, constants.tbresp.CMD_REBOOT,
89 constants.tbresp.CMD_UPGRADE, constants.tbresp.CMD_UPGRADE_AND_REBOOT, constants.tbresp.CMD_SPECIAL];
90 ## Commands that can be NACK'ed or NOTSUP'ed.
91 kasNackableCmds = kasAckableCmds + [kasAckableCmds, constants.tbresp.CMD_IDLE, constants.tbresp.CMD_WAIT];
92
93 ## Mapping from TestBoxCmd_T to TestBoxState_T
94 kdCmdToState = \
95 { \
96 TestBoxData.ksTestBoxCmd_Abort: None,
97 TestBoxData.ksTestBoxCmd_Reboot: TestBoxStatusData.ksTestBoxState_Rebooting,
98 TestBoxData.ksTestBoxCmd_Upgrade: TestBoxStatusData.ksTestBoxState_Upgrading,
99 TestBoxData.ksTestBoxCmd_UpgradeAndReboot: TestBoxStatusData.ksTestBoxState_UpgradingAndRebooting,
100 TestBoxData.ksTestBoxCmd_Special: TestBoxStatusData.ksTestBoxState_DoingSpecialCmd,
101 };
102
103 ## Mapping from TestBoxCmd_T to TestBox responses commands.
104 kdCmdToTbRespCmd = \
105 {
106 TestBoxData.ksTestBoxCmd_Abort: constants.tbresp.CMD_ABORT,
107 TestBoxData.ksTestBoxCmd_Reboot: constants.tbresp.CMD_REBOOT,
108 TestBoxData.ksTestBoxCmd_Upgrade: constants.tbresp.CMD_UPGRADE,
109 TestBoxData.ksTestBoxCmd_UpgradeAndReboot: constants.tbresp.CMD_UPGRADE_AND_REBOOT,
110 TestBoxData.ksTestBoxCmd_Special: constants.tbresp.CMD_SPECIAL,
111 };
112
113 ## Mapping from TestBox responses to TestBoxCmd_T commands.
114 kdTbRespCmdToCmd = \
115 {
116 constants.tbresp.CMD_IDLE: None,
117 constants.tbresp.CMD_WAIT: None,
118 constants.tbresp.CMD_EXEC: None,
119 constants.tbresp.CMD_ABORT: TestBoxData.ksTestBoxCmd_Abort,
120 constants.tbresp.CMD_REBOOT: TestBoxData.ksTestBoxCmd_Reboot,
121 constants.tbresp.CMD_UPGRADE: TestBoxData.ksTestBoxCmd_Upgrade,
122 constants.tbresp.CMD_UPGRADE_AND_REBOOT: TestBoxData.ksTestBoxCmd_UpgradeAndReboot,
123 constants.tbresp.CMD_SPECIAL: TestBoxData.ksTestBoxCmd_Special,
124 };
125
126
127 ## The path to the upgrade zip, relative WebServerGlueBase.getBaseUrl().
128 ksUpgradeZip = 'htdocs/upgrade/VBoxTestBoxScript.zip';
129
130 ## Valid TestBox result values.
131 kasValidResults = list(constants.result.g_kasValidResults);
132 ## Mapping TestBox result values to TestStatus_T values.
133 kadTbResultToStatus = \
134 {
135 constants.result.PASSED: TestSetData.ksTestStatus_Success,
136 constants.result.SKIPPED: TestSetData.ksTestStatus_Skipped,
137 constants.result.ABORTED: TestSetData.ksTestStatus_Aborted,
138 constants.result.BAD_TESTBOX: TestSetData.ksTestStatus_BadTestBox,
139 constants.result.FAILED: TestSetData.ksTestStatus_Failure,
140 constants.result.TIMED_OUT: TestSetData.ksTestStatus_TimedOut,
141 constants.result.REBOOTED: TestSetData.ksTestStatus_Rebooted,
142 };
143
144
145 def __init__(self, oSrvGlue):
146 """
147 Won't raise exceptions.
148 """
149 self._oSrvGlue = oSrvGlue;
150 self._sAction = None; # _getStandardParams / dispatchRequest sets this later on.
151 self._idTestBox = None; # _getStandardParams / dispatchRequest sets this later on.
152 self._sTestBoxUuid = None; # _getStandardParams / dispatchRequest sets this later on.
153 self._sTestBoxAddr = None; # _getStandardParams / dispatchRequest sets this later on.
154 self._idTestSet = None; # _getStandardParams / dispatchRequest sets this later on.
155 self._dParams = None; # _getStandardParams / dispatchRequest sets this later on.
156 self._asCheckedParams = [];
157 self._dActions = \
158 { \
159 constants.tbreq.SIGNON : self._actionSignOn,
160 constants.tbreq.REQUEST_COMMAND_BUSY: self._actionRequestCommandBusy,
161 constants.tbreq.REQUEST_COMMAND_IDLE: self._actionRequestCommandIdle,
162 constants.tbreq.COMMAND_ACK : self._actionCommandAck,
163 constants.tbreq.COMMAND_NACK : self._actionCommandNack,
164 constants.tbreq.COMMAND_NOTSUP : self._actionCommandNotSup,
165 constants.tbreq.LOG_MAIN : self._actionLogMain,
166 constants.tbreq.UPLOAD : self._actionUpload,
167 constants.tbreq.XML_RESULTS : self._actionXmlResults,
168 constants.tbreq.EXEC_COMPLETED : self._actionExecCompleted,
169 };
170
171 def _getStringParam(self, sName, asValidValues = None, fStrip = False, sDefValue = None):
172 """
173 Gets a string parameter (stripped).
174
175 Raises exception if not found and no default is provided, or if the
176 value isn't found in asValidValues.
177 """
178 if sName not in self._dParams:
179 if sDefValue is None:
180 raise TestBoxControllerException('%s parameter %s is missing' % (self._sAction, sName));
181 return sDefValue;
182 sValue = self._dParams[sName];
183 if fStrip:
184 sValue = sValue.strip();
185
186 if sName not in self._asCheckedParams:
187 self._asCheckedParams.append(sName);
188
189 if asValidValues is not None and sValue not in asValidValues:
190 raise TestBoxControllerException('%s parameter %s value "%s" not in %s ' \
191 % (self._sAction, sName, sValue, asValidValues));
192 return sValue;
193
194 def _getBoolParam(self, sName, fDefValue = None):
195 """
196 Gets a boolean parameter.
197
198 Raises exception if not found and no default is provided, or if not a
199 valid boolean.
200 """
201 sValue = self._getStringParam(sName, [ 'True', 'true', '1', 'False', 'false', '0'], sDefValue = str(fDefValue));
202 return sValue in ('True', 'true', '1',);
203
204 def _getIntParam(self, sName, iMin = None, iMax = None):
205 """
206 Gets a string parameter.
207 Raises exception if not found, not a valid integer, or if the value
208 isn't in the range defined by iMin and iMax.
209 """
210 sValue = self._getStringParam(sName);
211 try:
212 iValue = int(sValue, 0);
213 except:
214 raise TestBoxControllerException('%s parameter %s value "%s" cannot be convert to an integer' \
215 % (self._sAction, sName, sValue));
216
217 if (iMin is not None and iValue < iMin) \
218 or (iMax is not None and iValue > iMax):
219 raise TestBoxControllerException('%s parameter %s value %d is out of range [%s..%s]' \
220 % (self._sAction, sName, iValue, iMin, iMax));
221 return iValue;
222
223 def _getLongParam(self, sName, lMin = None, lMax = None, lDefValue = None):
224 """
225 Gets a string parameter.
226 Raises exception if not found, not a valid long integer, or if the value
227 isn't in the range defined by lMin and lMax.
228 """
229 sValue = self._getStringParam(sName, sDefValue = (str(lDefValue) if lDefValue is not None else None));
230 try:
231 lValue = long(sValue, 0);
232 except Exception as oXcpt:
233 raise TestBoxControllerException('%s parameter %s value "%s" cannot be convert to an integer (%s)' \
234 % (self._sAction, sName, sValue, oXcpt));
235
236 if (lMin is not None and lValue < lMin) \
237 or (lMax is not None and lValue > lMax):
238 raise TestBoxControllerException('%s parameter %s value %d is out of range [%s..%s]' \
239 % (self._sAction, sName, lValue, lMin, lMax));
240 return lValue;
241
242 def _checkForUnknownParameters(self):
243 """
244 Check if we've handled all parameters, raises exception if anything
245 unknown was found.
246 """
247
248 if len(self._asCheckedParams) != len(self._dParams):
249 sUnknownParams = '';
250 for sKey in self._dParams:
251 if sKey not in self._asCheckedParams:
252 sUnknownParams += ' ' + sKey + '=' + self._dParams[sKey];
253 raise TestBoxControllerException('Unknown parameters: ' + sUnknownParams);
254
255 return True;
256
257 def _writeResponse(self, dParams):
258 """
259 Makes a reply to the testbox script.
260 Will raise exception on failure.
261 """
262 self._oSrvGlue.writeParams(dParams);
263 self._oSrvGlue.flush();
264 return True;
265
266 def _resultResponse(self, sResultValue):
267 """
268 Makes a simple reply to the testbox script.
269 Will raise exception on failure.
270 """
271 return self._writeResponse({constants.tbresp.ALL_PARAM_RESULT: sResultValue});
272
273
274 def _idleResponse(self):
275 """
276 Makes an IDLE reply to the testbox script.
277 Will raise exception on failure.
278 """
279 return self._writeResponse({ constants.tbresp.ALL_PARAM_RESULT: constants.tbresp.CMD_IDLE });
280
281 def _cleanupOldTest(self, oDb, oStatusData):
282 """
283 Cleans up any old test set that may be left behind and changes the
284 state to 'idle'. See scenario #9:
285 file://../../docs/AutomaticTestingRevamp.html#cleaning-up-abandoned-testcase
286
287 Note. oStatusData.enmState is set to idle, but tsUpdated is not changed.
288 """
289
290 # Cleanup any abandoned test.
291 if oStatusData.idTestSet is not None:
292 SystemLogLogic(oDb).addEntry(SystemLogData.ksEvent_TestSetAbandoned,
293 "idTestSet=%u idTestBox=%u enmState=%s %s"
294 % (oStatusData.idTestSet, oStatusData.idTestBox,
295 oStatusData.enmState, self._sAction),
296 fCommit = False);
297 TestSetLogic(oDb).completeAsAbandoned(oStatusData.idTestSet, fCommit = False);
298 GlobalResourceLogic(oDb).freeGlobalResourcesByTestBox(self._idTestBox, fCommit = False);
299
300 # Change to idle status
301 if oStatusData.enmState != TestBoxStatusData.ksTestBoxState_Idle:
302 TestBoxStatusLogic(oDb).updateState(self._idTestBox, TestBoxStatusData.ksTestBoxState_Idle, fCommit = False);
303 oStatusData.tsUpdated = oDb.getCurrentTimestamp();
304 oStatusData.enmState = TestBoxStatusData.ksTestBoxState_Idle;
305
306 # Commit.
307 oDb.commit();
308
309 return True;
310
311 def _connectToDbAndValidateTb(self, asValidStates = None):
312 """
313 Connects to the database and validates the testbox.
314
315 Returns (TMDatabaseConnection, TestBoxStatusData, TestBoxData) on success.
316 Returns (None, None, None) on failure after sending the box an appropriate response.
317 May raise exception on DB error.
318 """
319 oDb = TMDatabaseConnection(self._oSrvGlue.dprint);
320 oLogic = TestBoxStatusLogic(oDb);
321 (oStatusData, oTestBoxData) = oLogic.tryFetchStatusAndConfig(self._idTestBox, self._sTestBoxUuid, self._sTestBoxAddr);
322 if oStatusData is None:
323 self._resultResponse(constants.tbresp.STATUS_DEAD);
324 elif asValidStates is not None and oStatusData.enmState not in asValidStates:
325 self._resultResponse(constants.tbresp.STATUS_NACK);
326 elif self._idTestSet is not None and self._idTestSet != oStatusData.idTestSet:
327 self._resultResponse(constants.tbresp.STATUS_NACK);
328 else:
329 return (oDb, oStatusData, oTestBoxData);
330 return (None, None, None);
331
332 def writeToMainLog(self, oTestSet, sText, fIgnoreSizeCheck = False):
333 """ Writes the text to the main log file. """
334
335 # Calc the file name and open the file.
336 sFile = os.path.join(config.g_ksFileAreaRootDir, oTestSet.sBaseFilename + '-main.log');
337 if not os.path.exists(os.path.dirname(sFile)):
338 os.makedirs(os.path.dirname(sFile), 0o755);
339
340 with open(sFile, 'ab') as oFile:
341 # Check the size.
342 fSizeOk = True;
343 if not fIgnoreSizeCheck:
344 oStat = os.fstat(oFile.fileno());
345 fSizeOk = oStat.st_size / (1024 * 1024) < config.g_kcMbMaxMainLog;
346
347 # Write the text.
348 if fSizeOk:
349 if sys.version_info[0] >= 3:
350 oFile.write(bytes(sText, 'utf-8'));
351 else:
352 oFile.write(sText);
353
354 return fSizeOk;
355
356 def _actionSignOn(self): # pylint: disable=too-many-locals
357 """ Implement sign-on """
358
359 #
360 # Validate parameters (raises exception on failure).
361 #
362 sOs = self._getStringParam(constants.tbreq.SIGNON_PARAM_OS, coreconsts.g_kasOses);
363 sOsVersion = self._getStringParam(constants.tbreq.SIGNON_PARAM_OS_VERSION);
364 sCpuVendor = self._getStringParam(constants.tbreq.SIGNON_PARAM_CPU_VENDOR);
365 sCpuArch = self._getStringParam(constants.tbreq.SIGNON_PARAM_CPU_ARCH, coreconsts.g_kasCpuArches);
366 sCpuName = self._getStringParam(constants.tbreq.SIGNON_PARAM_CPU_NAME, fStrip = True, sDefValue = ''); # new
367 lCpuRevision = self._getLongParam( constants.tbreq.SIGNON_PARAM_CPU_REVISION, lMin = 0, lDefValue = 0); # new
368 cCpus = self._getIntParam( constants.tbreq.SIGNON_PARAM_CPU_COUNT, 1, 16384);
369 fCpuHwVirt = self._getBoolParam( constants.tbreq.SIGNON_PARAM_HAS_HW_VIRT);
370 fCpuNestedPaging = self._getBoolParam( constants.tbreq.SIGNON_PARAM_HAS_NESTED_PAGING);
371 fCpu64BitGuest = self._getBoolParam( constants.tbreq.SIGNON_PARAM_HAS_64_BIT_GUEST, fDefValue = True);
372 fChipsetIoMmu = self._getBoolParam( constants.tbreq.SIGNON_PARAM_HAS_IOMMU);
373 fRawMode = self._getBoolParam( constants.tbreq.SIGNON_PARAM_WITH_RAW_MODE, fDefValue = None);
374 cMbMemory = self._getLongParam( constants.tbreq.SIGNON_PARAM_MEM_SIZE, 8, 1073741823); # 8MB..1PB
375 cMbScratch = self._getLongParam( constants.tbreq.SIGNON_PARAM_SCRATCH_SIZE, 0, 1073741823); # 0..1PB
376 sReport = self._getStringParam(constants.tbreq.SIGNON_PARAM_REPORT, fStrip = True, sDefValue = ''); # new
377 iTestBoxScriptRev = self._getIntParam( constants.tbreq.SIGNON_PARAM_SCRIPT_REV, 1, 100000000);
378 iPythonHexVersion = self._getIntParam( constants.tbreq.SIGNON_PARAM_PYTHON_VERSION, 0x020300f0, 0x030f00f0);
379 self._checkForUnknownParameters();
380
381 # Null conversions for new parameters.
382 if not sReport:
383 sReport = None;
384 if not sCpuName:
385 sCpuName = None;
386 if lCpuRevision <= 0:
387 lCpuRevision = None;
388
389 #
390 # Connect to the database and validate the testbox.
391 #
392 oDb = TMDatabaseConnection(self._oSrvGlue.dprint);
393 oTestBoxLogic = TestBoxLogic(oDb);
394 oTestBox = oTestBoxLogic.tryFetchTestBoxByUuid(self._sTestBoxUuid);
395 if oTestBox is None:
396 oSystemLogLogic = SystemLogLogic(oDb);
397 oSystemLogLogic.addEntry(SystemLogData.ksEvent_TestBoxUnknown,
398 'addr=%s uuid=%s os=%s %d cpus' \
399 % (self._sTestBoxAddr, self._sTestBoxUuid, sOs, cCpus),
400 24, fCommit = True);
401 return self._resultResponse(constants.tbresp.STATUS_NACK);
402
403 #
404 # Update the row in TestBoxes if something changed.
405 #
406 if oTestBox.cMbScratch is not None and oTestBox.cMbScratch != 0:
407 cPctScratchDiff = (cMbScratch - oTestBox.cMbScratch) * 100 / oTestBox.cMbScratch;
408 else:
409 cPctScratchDiff = 100;
410
411 # pylint: disable=too-many-boolean-expressions
412 if self._sTestBoxAddr != oTestBox.ip \
413 or sOs != oTestBox.sOs \
414 or sOsVersion != oTestBox.sOsVersion \
415 or sCpuVendor != oTestBox.sCpuVendor \
416 or sCpuArch != oTestBox.sCpuArch \
417 or sCpuName != oTestBox.sCpuName \
418 or lCpuRevision != oTestBox.lCpuRevision \
419 or cCpus != oTestBox.cCpus \
420 or fCpuHwVirt != oTestBox.fCpuHwVirt \
421 or fCpuNestedPaging != oTestBox.fCpuNestedPaging \
422 or fCpu64BitGuest != oTestBox.fCpu64BitGuest \
423 or fChipsetIoMmu != oTestBox.fChipsetIoMmu \
424 or fRawMode != oTestBox.fRawMode \
425 or cMbMemory != oTestBox.cMbMemory \
426 or abs(cPctScratchDiff) >= min(4 + cMbScratch / 10240, 12) \
427 or sReport != oTestBox.sReport \
428 or iTestBoxScriptRev != oTestBox.iTestBoxScriptRev \
429 or iPythonHexVersion != oTestBox.iPythonHexVersion:
430 oTestBoxLogic.updateOnSignOn(oTestBox.idTestBox,
431 oTestBox.idGenTestBox,
432 sTestBoxAddr = self._sTestBoxAddr,
433 sOs = sOs,
434 sOsVersion = sOsVersion,
435 sCpuVendor = sCpuVendor,
436 sCpuArch = sCpuArch,
437 sCpuName = sCpuName,
438 lCpuRevision = lCpuRevision,
439 cCpus = cCpus,
440 fCpuHwVirt = fCpuHwVirt,
441 fCpuNestedPaging = fCpuNestedPaging,
442 fCpu64BitGuest = fCpu64BitGuest,
443 fChipsetIoMmu = fChipsetIoMmu,
444 fRawMode = fRawMode,
445 cMbMemory = cMbMemory,
446 cMbScratch = cMbScratch,
447 sReport = sReport,
448 iTestBoxScriptRev = iTestBoxScriptRev,
449 iPythonHexVersion = iPythonHexVersion);
450
451 #
452 # Update the testbox status, making sure there is a status.
453 #
454 oStatusLogic = TestBoxStatusLogic(oDb);
455 oStatusData = oStatusLogic.tryFetchStatus(oTestBox.idTestBox);
456 if oStatusData is not None:
457 self._cleanupOldTest(oDb, oStatusData);
458 else:
459 oStatusLogic.insertIdleStatus(oTestBox.idTestBox, oTestBox.idGenTestBox, fCommit = True);
460
461 #
462 # ACK the request.
463 #
464 dResponse = \
465 {
466 constants.tbresp.ALL_PARAM_RESULT: constants.tbresp.STATUS_ACK,
467 constants.tbresp.SIGNON_PARAM_ID: oTestBox.idTestBox,
468 constants.tbresp.SIGNON_PARAM_NAME: oTestBox.sName,
469 }
470 return self._writeResponse(dResponse);
471
472 def _doGangCleanup(self, oDb, oStatusData):
473 """
474 _doRequestCommand worker for handling a box in gang-cleanup.
475 This will check if all testboxes has completed their run, pretending to
476 be busy until that happens. Once all are completed, resources will be
477 freed and the testbox returns to idle state (we update oStatusData).
478 """
479 oStatusLogic = TestBoxStatusLogic(oDb)
480 oTestSet = TestSetData().initFromDbWithId(oDb, oStatusData.idTestSet);
481 if oStatusLogic.isWholeGangDoneTesting(oTestSet.idTestSetGangLeader):
482 oDb.begin();
483
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 _doGangGatheringTimedOut(self, oDb, oStatusData):
494 """
495 _doRequestCommand worker for handling a box in gang-gathering-timed-out state.
496 This will do clean-ups similar to _cleanupOldTest and update the state likewise.
497 """
498 oDb.begin();
499
500 TestSetLogic(oDb).completeAsGangGatheringTimeout(oStatusData.idTestSet, fCommit = False);
501 GlobalResourceLogic(oDb).freeGlobalResourcesByTestBox(self._idTestBox, fCommit = False);
502 TestBoxStatusLogic(oDb).updateState(self._idTestBox, TestBoxStatusData.ksTestBoxState_Idle, fCommit = False);
503
504 oStatusData.tsUpdated = oDb.getCurrentTimestamp();
505 oStatusData.enmState = TestBoxStatusData.ksTestBoxState_Idle;
506
507 oDb.commit();
508 return None;
509
510 def _doGangGathering(self, oDb, oStatusData):
511 """
512 _doRequestCommand worker for handling a box in gang-gathering state.
513 This only checks for timeout. It will update the oStatusData if a
514 timeout is detected, so that the box will be idle upon return.
515 """
516 oStatusLogic = TestBoxStatusLogic(oDb);
517 if oStatusLogic.timeSinceLastChangeInSecs(oStatusData) > config.g_kcSecGangGathering \
518 and SchedulerBase.tryCancelGangGathering(oDb, oStatusData): # <-- Updates oStatusData.
519 self._doGangGatheringTimedOut(oDb, oStatusData);
520 return None;
521
522 def _doRequestCommand(self, fIdle):
523 """
524 Common code for handling command request.
525 """
526
527 (oDb, oStatusData, oTestBoxData) = self._connectToDbAndValidateTb();
528 if oDb is None:
529 return False;
530
531 #
532 # Status clean up.
533 #
534 # Only when BUSY will the TestBox Script request and execute commands
535 # concurrently. So, it must be idle when sending REQUEST_COMMAND_IDLE.
536 #
537 if fIdle:
538 if oStatusData.enmState == TestBoxStatusData.ksTestBoxState_GangGathering:
539 self._doGangGathering(oDb, oStatusData);
540 elif oStatusData.enmState == TestBoxStatusData.ksTestBoxState_GangGatheringTimedOut:
541 self._doGangGatheringTimedOut(oDb, oStatusData);
542 elif oStatusData.enmState == TestBoxStatusData.ksTestBoxState_GangTesting:
543 dResponse = SchedulerBase.composeExecResponse(oDb, oTestBoxData.idTestBox, self._oSrvGlue.getBaseUrl());
544 if dResponse is not None:
545 return dResponse;
546 elif oStatusData.enmState == TestBoxStatusData.ksTestBoxState_GangCleanup:
547 self._doGangCleanup(oDb, oStatusData);
548 elif oStatusData.enmState != TestBoxStatusData.ksTestBoxState_Idle: # (includes ksTestBoxState_GangGatheringTimedOut)
549 self._cleanupOldTest(oDb, oStatusData);
550
551 #
552 # Check for pending command.
553 #
554 if oTestBoxData.enmPendingCmd != TestBoxData.ksTestBoxCmd_None:
555 asValidCmds = TestBoxController.kasIdleCmds if fIdle else TestBoxController.kasBusyCmds;
556 if oTestBoxData.enmPendingCmd in asValidCmds:
557 dResponse = { constants.tbresp.ALL_PARAM_RESULT: TestBoxController.kdCmdToTbRespCmd[oTestBoxData.enmPendingCmd] };
558 if oTestBoxData.enmPendingCmd in [TestBoxData.ksTestBoxCmd_Upgrade, TestBoxData.ksTestBoxCmd_UpgradeAndReboot]:
559 dResponse[constants.tbresp.UPGRADE_PARAM_URL] = self._oSrvGlue.getBaseUrl() + TestBoxController.ksUpgradeZip;
560 return self._writeResponse(dResponse);
561
562 if oTestBoxData.enmPendingCmd == TestBoxData.ksTestBoxCmd_Abort and fIdle:
563 TestBoxLogic(oDb).setCommand(self._idTestBox, sOldCommand = oTestBoxData.enmPendingCmd,
564 sNewCommand = TestBoxData.ksTestBoxCmd_None, fCommit = True);
565
566 #
567 # If doing gang stuff, return 'CMD_WAIT'.
568 #
569 ## @todo r=bird: Why is GangTesting included here? Figure out when testing gang testing.
570 if oStatusData.enmState in [TestBoxStatusData.ksTestBoxState_GangGathering,
571 TestBoxStatusData.ksTestBoxState_GangTesting,
572 TestBoxStatusData.ksTestBoxState_GangCleanup]:
573 return self._resultResponse(constants.tbresp.CMD_WAIT);
574
575 #
576 # If idling and enabled try schedule a new task.
577 #
578 if fIdle \
579 and oTestBoxData.fEnabled \
580 and not TestSetLogic(oDb).isTestBoxExecutingTooRapidly(oTestBoxData.idTestBox) \
581 and oStatusData.enmState == TestBoxStatusData.ksTestBoxState_Idle: # (paranoia)
582 dResponse = SchedulerBase.scheduleNewTask(oDb, oTestBoxData, oStatusData.iWorkItem, self._oSrvGlue.getBaseUrl());
583 if dResponse is not None:
584 return self._writeResponse(dResponse);
585
586 #
587 # Touch the status row every couple of mins so we can tell that the box is alive.
588 #
589 oStatusLogic = TestBoxStatusLogic(oDb);
590 if oStatusData.enmState != TestBoxStatusData.ksTestBoxState_GangGathering \
591 and oStatusLogic.timeSinceLastChangeInSecs(oStatusData) >= TestBoxStatusLogic.kcSecIdleTouchStatus:
592 oStatusLogic.touchStatus(oTestBoxData.idTestBox, fCommit = True);
593
594 return self._idleResponse();
595
596 def _actionRequestCommandBusy(self):
597 """ Implement request for command. """
598 self._checkForUnknownParameters();
599 return self._doRequestCommand(False);
600
601 def _actionRequestCommandIdle(self):
602 """ Implement request for command. """
603 self._checkForUnknownParameters();
604 return self._doRequestCommand(True);
605
606 def _doCommandAckNck(self, sCmd):
607 """ Implements ACK, NACK and NACK(ENOTSUP). """
608
609 (oDb, _, _) = self._connectToDbAndValidateTb();
610 if oDb is None:
611 return False;
612
613 #
614 # If the command maps to a TestBoxCmd_T value, it means we have to
615 # check and update TestBoxes. If it's an ACK, the testbox status will
616 # need updating as well.
617 #
618 sPendingCmd = TestBoxController.kdTbRespCmdToCmd[sCmd];
619 if sPendingCmd is not None:
620 oTestBoxLogic = TestBoxLogic(oDb)
621 oTestBoxLogic.setCommand(self._idTestBox, sOldCommand = sPendingCmd,
622 sNewCommand = TestBoxData.ksTestBoxCmd_None, fCommit = False);
623
624 if self._sAction == constants.tbreq.COMMAND_ACK \
625 and TestBoxController.kdCmdToState[sPendingCmd] is not None:
626 oStatusLogic = TestBoxStatusLogic(oDb);
627 oStatusLogic.updateState(self._idTestBox, TestBoxController.kdCmdToState[sPendingCmd], fCommit = False);
628
629 # Commit the two updates.
630 oDb.commit();
631
632 #
633 # Log NACKs.
634 #
635 if self._sAction != constants.tbreq.COMMAND_ACK:
636 oSysLogLogic = SystemLogLogic(oDb);
637 oSysLogLogic.addEntry(SystemLogData.ksEvent_CmdNacked,
638 'idTestBox=%s sCmd=%s' % (self._idTestBox, sPendingCmd),
639 24, fCommit = True);
640
641 return self._resultResponse(constants.tbresp.STATUS_ACK);
642
643 def _actionCommandAck(self):
644 """ Implement command ACK'ing """
645 sCmd = self._getStringParam(constants.tbreq.COMMAND_ACK_PARAM_CMD_NAME, TestBoxController.kasAckableCmds);
646 self._checkForUnknownParameters();
647 return self._doCommandAckNck(sCmd);
648
649 def _actionCommandNack(self):
650 """ Implement command NACK'ing """
651 sCmd = self._getStringParam(constants.tbreq.COMMAND_ACK_PARAM_CMD_NAME, TestBoxController.kasNackableCmds);
652 self._checkForUnknownParameters();
653 return self._doCommandAckNck(sCmd);
654
655 def _actionCommandNotSup(self):
656 """ Implement command NACK(ENOTSUP)'ing """
657 sCmd = self._getStringParam(constants.tbreq.COMMAND_ACK_PARAM_CMD_NAME, TestBoxController.kasNackableCmds);
658 self._checkForUnknownParameters();
659 return self._doCommandAckNck(sCmd);
660
661 def _actionLogMain(self):
662 """ Implement submitting log entries to the main log file. """
663 #
664 # Parameter validation.
665 #
666 sBody = self._getStringParam(constants.tbreq.LOG_PARAM_BODY, fStrip = False);
667 if not sBody:
668 return self._resultResponse(constants.tbresp.STATUS_NACK);
669 self._checkForUnknownParameters();
670
671 (oDb, oStatusData, _) = self._connectToDbAndValidateTb([TestBoxStatusData.ksTestBoxState_Testing,
672 TestBoxStatusData.ksTestBoxState_GangTesting]);
673 if oStatusData is None:
674 return False;
675
676 #
677 # Write the text to the log file.
678 #
679 oTestSet = TestSetData().initFromDbWithId(oDb, oStatusData.idTestSet);
680 self.writeToMainLog(oTestSet, sBody);
681 ## @todo Overflow is a hanging offence, need to note it and fail whatever is going on...
682
683 # Done.
684 return self._resultResponse(constants.tbresp.STATUS_ACK);
685
686 def _actionUpload(self):
687 """ Implement uploading of files. """
688 #
689 # Parameter validation.
690 #
691 sName = self._getStringParam(constants.tbreq.UPLOAD_PARAM_NAME);
692 sMime = self._getStringParam(constants.tbreq.UPLOAD_PARAM_MIME);
693 sKind = self._getStringParam(constants.tbreq.UPLOAD_PARAM_KIND);
694 sDesc = self._getStringParam(constants.tbreq.UPLOAD_PARAM_DESC);
695 self._checkForUnknownParameters();
696
697 (oDb, oStatusData, _) = self._connectToDbAndValidateTb([TestBoxStatusData.ksTestBoxState_Testing,
698 TestBoxStatusData.ksTestBoxState_GangTesting]);
699 if oStatusData is None:
700 return False;
701
702 if len(sName) > 128 or len(sName) < 3:
703 raise TestBoxControllerException('Invalid file name "%s"' % (sName,));
704 if re.match(r'^[a-zA-Z0-9_\-(){}#@+,.=]*$', sName) is None:
705 raise TestBoxControllerException('Invalid file name "%s"' % (sName,));
706
707 if sMime not in [ 'text/plain', #'text/html', 'text/xml',
708 'application/octet-stream',
709 'image/png', #'image/gif', 'image/jpeg',
710 'video/webm', #'video/mpeg', 'video/mpeg4-generic',
711 ]:
712 raise TestBoxControllerException('Invalid MIME type "%s"' % (sMime,));
713
714 if sKind not in TestResultFileData.kasKinds:
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.getBodyIoStreamBinary();
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=maybe-no-member
749 del abBuf;
750
751 oDstFile.close(); # pylint: disable=maybe-no-member
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 not sXml: # 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]();
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