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