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