VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/testmanager/core/testbox.py

Last change on this file was 106061, checked in by vboxsync, 8 weeks ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 55.7 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: testbox.py 106061 2024-09-16 14:03:52Z vboxsync $
3
4"""
5Test Manager - TestBox.
6"""
7
8__copyright__ = \
9"""
10Copyright (C) 2012-2024 Oracle and/or its affiliates.
11
12This file is part of VirtualBox base platform packages, as
13available from https://www.virtualbox.org.
14
15This program is free software; you can redistribute it and/or
16modify it under the terms of the GNU General Public License
17as published by the Free Software Foundation, in version 3 of the
18License.
19
20This program is distributed in the hope that it will be useful, but
21WITHOUT ANY WARRANTY; without even the implied warranty of
22MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
23General Public License for more details.
24
25You should have received a copy of the GNU General Public License
26along with this program; if not, see <https://www.gnu.org/licenses>.
27
28The contents of this file may alternatively be used under the terms
29of the Common Development and Distribution License Version 1.0
30(CDDL), a copy of it is provided in the "COPYING.CDDL" file included
31in the VirtualBox distribution, in which case the provisions of the
32CDDL are applicable instead of those of the GPL.
33
34You may elect to license modified versions of this file under the
35terms and conditions of either the GPL or the CDDL or both.
36
37SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
38"""
39__version__ = "$Revision: 106061 $"
40
41
42# Standard python imports.
43import copy;
44import sys;
45import unittest;
46
47# Validation Kit imports.
48from testmanager.core import db;
49from testmanager.core.base import ModelDataBase, ModelDataBaseTestCase, ModelLogicBase, TMInFligthCollision, \
50 TMInvalidData, TMTooManyRows, TMRowNotFound, \
51 ChangeLogEntry, AttributeChangeEntry, AttributeChangeEntryPre;
52from testmanager.core.useraccount import UserAccountLogic;
53
54# Python 3 hacks:
55if sys.version_info[0] >= 3:
56 xrange = range; # pylint: disable=redefined-builtin,invalid-name
57
58
59class TestBoxInSchedGroupData(ModelDataBase):
60 """
61 TestBox in SchedGroup data.
62 """
63
64 ksParam_idTestBox = 'TestBoxInSchedGroup_idTestBox';
65 ksParam_idSchedGroup = 'TestBoxInSchedGroup_idSchedGroup';
66 ksParam_tsEffective = 'TestBoxInSchedGroup_tsEffective';
67 ksParam_tsExpire = 'TestBoxInSchedGroup_tsExpire';
68 ksParam_uidAuthor = 'TestBoxInSchedGroup_uidAuthor';
69 ksParam_iSchedPriority = 'TestBoxInSchedGroup_iSchedPriority';
70
71 kasAllowNullAttributes = [ 'tsEffective', 'tsExpire', 'uidAuthor', ]
72
73 kiMin_iSchedPriority = 0;
74 kiMax_iSchedPriority = 32;
75
76 kcDbColumns = 6;
77
78 def __init__(self):
79 ModelDataBase.__init__(self);
80 self.idTestBox = None;
81 self.idSchedGroup = None;
82 self.tsEffective = None;
83 self.tsExpire = None;
84 self.uidAuthor = None;
85 self.iSchedPriority = 16;
86
87 def initFromDbRow(self, aoRow):
88 """
89 Expecting the result from a query like this:
90 SELECT * FROM TestBoxesInSchedGroups
91 """
92 if aoRow is None:
93 raise TMRowNotFound('TestBox/SchedGroup not found.');
94
95 self.idTestBox = aoRow[0];
96 self.idSchedGroup = aoRow[1];
97 self.tsEffective = aoRow[2];
98 self.tsExpire = aoRow[3];
99 self.uidAuthor = aoRow[4];
100 self.iSchedPriority = aoRow[5];
101
102 return self;
103
104class TestBoxInSchedGroupDataEx(TestBoxInSchedGroupData):
105 """
106 Extended version of TestBoxInSchedGroupData that contains the scheduling group.
107 """
108
109 def __init__(self):
110 TestBoxInSchedGroupData.__init__(self);
111 self.oSchedGroup = None # type: SchedGroupData
112
113 def initFromDbRowEx(self, aoRow, oDb, tsNow = None, sPeriodBack = None):
114 """
115 Extended version of initFromDbRow that fills in the rest from the database.
116 """
117 from testmanager.core.schedgroup import SchedGroupData;
118 self.initFromDbRow(aoRow);
119 self.oSchedGroup = SchedGroupData().initFromDbWithId(oDb, self.idSchedGroup, tsNow, sPeriodBack);
120 return self;
121
122class TestBoxDataForSchedGroup(TestBoxInSchedGroupData):
123 """
124 Extended version of TestBoxInSchedGroupData that adds the testbox data (if available).
125 Used by TestBoxLogic.fetchForSchedGroup
126 """
127
128 def __init__(self):
129 TestBoxInSchedGroupData.__init__(self);
130 self.oTestBox = None # type: TestBoxData
131
132 def initFromDbRow(self, aoRow):
133 """
134 The row is: TestBoxesInSchedGroups.*, TestBoxesWithStrings.*
135 """
136 TestBoxInSchedGroupData.initFromDbRow(self, aoRow);
137 if aoRow[self.kcDbColumns]:
138 self.oTestBox = TestBoxData().initFromDbRow(aoRow[self.kcDbColumns:]);
139 else:
140 self.oTestBox = None;
141 return self;
142
143 def getDataAttributes(self):
144 asAttributes = TestBoxInSchedGroupData.getDataAttributes(self);
145 asAttributes.remove('oTestBox');
146 return asAttributes;
147
148 def _validateAndConvertWorker(self, asAllowNullAttributes, oDb, enmValidateFor = ModelDataBase.ksValidateFor_Other):
149 dErrors = TestBoxInSchedGroupData._validateAndConvertWorker(self, asAllowNullAttributes, oDb, enmValidateFor);
150 if self.ksParam_idTestBox not in dErrors:
151 self.oTestBox = TestBoxData();
152 try:
153 self.oTestBox.initFromDbWithId(oDb, self.idTestBox);
154 except Exception as oXcpt:
155 self.oTestBox = TestBoxData()
156 dErrors[self.ksParam_idTestBox] = str(oXcpt);
157 return dErrors;
158
159
160# pylint: disable=invalid-name
161class TestBoxData(ModelDataBase): # pylint: disable=too-many-instance-attributes
162 """
163 TestBox Data.
164 """
165
166 ## LomKind_T
167 ksLomKind_None = 'none';
168 ksLomKind_ILOM = 'ilom';
169 ksLomKind_ELOM = 'elom';
170 ksLomKind_AppleXserveLom = 'apple-xserver-lom';
171 kasLomKindValues = [ ksLomKind_None, ksLomKind_ILOM, ksLomKind_ELOM, ksLomKind_AppleXserveLom];
172 kaoLomKindDescs = \
173 [
174 ( ksLomKind_None, 'None', ''),
175 ( ksLomKind_ILOM, 'ILOM', ''),
176 ( ksLomKind_ELOM, 'ELOM', ''),
177 ( ksLomKind_AppleXserveLom, 'Apple Xserve LOM', ''),
178 ];
179
180
181 ## TestBoxCmd_T
182 ksTestBoxCmd_None = 'none';
183 ksTestBoxCmd_Abort = 'abort';
184 ksTestBoxCmd_Reboot = 'reboot';
185 ksTestBoxCmd_Upgrade = 'upgrade';
186 ksTestBoxCmd_UpgradeAndReboot = 'upgrade-and-reboot';
187 ksTestBoxCmd_Special = 'special';
188 kasTestBoxCmdValues = [ ksTestBoxCmd_None, ksTestBoxCmd_Abort, ksTestBoxCmd_Reboot, ksTestBoxCmd_Upgrade,
189 ksTestBoxCmd_UpgradeAndReboot, ksTestBoxCmd_Special];
190 kaoTestBoxCmdDescs = \
191 [
192 ( ksTestBoxCmd_None, 'None', ''),
193 ( ksTestBoxCmd_Abort, 'Abort current test', ''),
194 ( ksTestBoxCmd_Reboot, 'Reboot TestBox', ''),
195 ( ksTestBoxCmd_Upgrade, 'Upgrade TestBox Script', ''),
196 ( ksTestBoxCmd_UpgradeAndReboot, 'Upgrade TestBox Script and reboot', ''),
197 ( ksTestBoxCmd_Special, 'Special (reserved)', ''),
198 ];
199
200
201 ksIdAttr = 'idTestBox';
202 ksIdGenAttr = 'idGenTestBox';
203
204 ksParam_idTestBox = 'TestBox_idTestBox';
205 ksParam_tsEffective = 'TestBox_tsEffective';
206 ksParam_tsExpire = 'TestBox_tsExpire';
207 ksParam_uidAuthor = 'TestBox_uidAuthor';
208 ksParam_idGenTestBox = 'TestBox_idGenTestBox';
209 ksParam_ip = 'TestBox_ip';
210 ksParam_uuidSystem = 'TestBox_uuidSystem';
211 ksParam_sName = 'TestBox_sName';
212 ksParam_sDescription = 'TestBox_sDescription';
213 ksParam_fEnabled = 'TestBox_fEnabled';
214 ksParam_enmLomKind = 'TestBox_enmLomKind';
215 ksParam_ipLom = 'TestBox_ipLom';
216 ksParam_pctScaleTimeout = 'TestBox_pctScaleTimeout';
217 ksParam_sComment = 'TestBox_sComment';
218 ksParam_sOs = 'TestBox_sOs';
219 ksParam_sOsVersion = 'TestBox_sOsVersion';
220 ksParam_sCpuVendor = 'TestBox_sCpuVendor';
221 ksParam_sCpuArch = 'TestBox_sCpuArch';
222 ksParam_sCpuName = 'TestBox_sCpuName';
223 ksParam_lCpuRevision = 'TestBox_lCpuRevision';
224 ksParam_cCpus = 'TestBox_cCpus';
225 ksParam_fCpuHwVirt = 'TestBox_fCpuHwVirt';
226 ksParam_fCpuNestedPaging = 'TestBox_fCpuNestedPaging';
227 ksParam_fCpu64BitGuest = 'TestBox_fCpu64BitGuest';
228 ksParam_fChipsetIoMmu = 'TestBox_fChipsetIoMmu';
229 ksParam_fRawMode = 'TestBox_fRawMode';
230 ksParam_fNativeApi = 'TestBox_fNativeApi';
231 ksParam_cMbMemory = 'TestBox_cMbMemory';
232 ksParam_cMbScratch = 'TestBox_cMbScratch';
233 ksParam_sReport = 'TestBox_sReport';
234 ksParam_iTestBoxScriptRev = 'TestBox_iTestBoxScriptRev';
235 ksParam_iPythonHexVersion = 'TestBox_iPythonHexVersion';
236 ksParam_enmPendingCmd = 'TestBox_enmPendingCmd';
237
238 kasInternalAttributes = [ 'idStrDescription', 'idStrComment', 'idStrOs', 'idStrOsVersion', 'idStrCpuVendor',
239 'idStrCpuArch', 'idStrCpuName', 'idStrReport', ];
240 kasMachineSettableOnly = [ 'sOs', 'sOsVersion', 'sCpuVendor', 'sCpuArch', 'sCpuName', 'lCpuRevision', 'cCpus',
241 'fCpuHwVirt', 'fCpuNestedPaging', 'fCpu64BitGuest', 'fChipsetIoMmu', 'fRawMode',
242 'fNativeApi', 'cMbMemory', 'cMbScratch', 'sReport', 'iTestBoxScriptRev',
243 'iPythonHexVersion', ];
244 kasAllowNullAttributes = ['idTestBox', 'tsEffective', 'tsExpire', 'uidAuthor', 'idGenTestBox', 'sDescription',
245 'ipLom', 'sComment', ] + kasMachineSettableOnly + kasInternalAttributes;
246
247 kasValidValues_enmLomKind = kasLomKindValues;
248 kasValidValues_enmPendingCmd = kasTestBoxCmdValues;
249 kiMin_pctScaleTimeout = 11;
250 kiMax_pctScaleTimeout = 19999;
251 kcchMax_sReport = 65535;
252
253 kcDbColumns = 41; # including the 7 string joins columns
254
255
256 def __init__(self):
257 ModelDataBase.__init__(self);
258
259 #
260 # Initialize with defaults.
261 # See the database for explanations of each of these fields.
262 #
263 self.idTestBox = None;
264 self.tsEffective = None;
265 self.tsExpire = None;
266 self.uidAuthor = None;
267 self.idGenTestBox = None;
268 self.ip = None;
269 self.uuidSystem = None;
270 self.sName = None;
271 self.idStrDescription = None;
272 self.fEnabled = False;
273 self.enmLomKind = self.ksLomKind_None;
274 self.ipLom = None;
275 self.pctScaleTimeout = 100;
276 self.idStrComment = None;
277 self.idStrOs = None;
278 self.idStrOsVersion = None;
279 self.idStrCpuVendor = None;
280 self.idStrCpuArch = None;
281 self.idStrCpuName = None;
282 self.lCpuRevision = None;
283 self.cCpus = 1;
284 self.fCpuHwVirt = False;
285 self.fCpuNestedPaging = False;
286 self.fCpu64BitGuest = False;
287 self.fChipsetIoMmu = False;
288 self.fRawMode = None;
289 self.fNativeApi = None;
290 self.cMbMemory = 1;
291 self.cMbScratch = 0;
292 self.idStrReport = None;
293 self.iTestBoxScriptRev = 0;
294 self.iPythonHexVersion = 0;
295 self.enmPendingCmd = self.ksTestBoxCmd_None;
296 # String table values.
297 self.sDescription = None;
298 self.sComment = None;
299 self.sOs = None;
300 self.sOsVersion = None;
301 self.sCpuVendor = None;
302 self.sCpuArch = None;
303 self.sCpuName = None;
304 self.sReport = None;
305
306 def initFromDbRow(self, aoRow):
307 """
308 Internal worker for initFromDbWithId and initFromDbWithGenId as well as
309 from TestBoxLogic. Expecting the result from a query like this:
310 SELECT TestBoxesWithStrings.* FROM TestBoxesWithStrings
311 """
312 if aoRow is None:
313 raise TMRowNotFound('TestBox not found.');
314
315 self.idTestBox = aoRow[0];
316 self.tsEffective = aoRow[1];
317 self.tsExpire = aoRow[2];
318 self.uidAuthor = aoRow[3];
319 self.idGenTestBox = aoRow[4];
320 self.ip = aoRow[5];
321 self.uuidSystem = aoRow[6];
322 self.sName = aoRow[7];
323 self.idStrDescription = aoRow[8];
324 self.fEnabled = aoRow[9];
325 self.enmLomKind = aoRow[10];
326 self.ipLom = aoRow[11];
327 self.pctScaleTimeout = aoRow[12];
328 self.idStrComment = aoRow[13];
329 self.idStrOs = aoRow[14];
330 self.idStrOsVersion = aoRow[15];
331 self.idStrCpuVendor = aoRow[16];
332 self.idStrCpuArch = aoRow[17];
333 self.idStrCpuName = aoRow[18];
334 self.lCpuRevision = aoRow[19];
335 self.cCpus = aoRow[20];
336 self.fCpuHwVirt = aoRow[21];
337 self.fCpuNestedPaging = aoRow[22];
338 self.fCpu64BitGuest = aoRow[23];
339 self.fChipsetIoMmu = aoRow[24];
340 self.fRawMode = aoRow[25];
341 self.fNativeApi = aoRow[26];
342 self.cMbMemory = aoRow[27];
343 self.cMbScratch = aoRow[28];
344 self.idStrReport = aoRow[29];
345 self.iTestBoxScriptRev = aoRow[30];
346 self.iPythonHexVersion = aoRow[31];
347 self.enmPendingCmd = aoRow[32];
348
349 # String table values.
350 if len(aoRow) > 33:
351 self.sDescription = aoRow[33];
352 self.sComment = aoRow[34];
353 self.sOs = aoRow[35];
354 self.sOsVersion = aoRow[36];
355 self.sCpuVendor = aoRow[37];
356 self.sCpuArch = aoRow[38];
357 self.sCpuName = aoRow[39];
358 self.sReport = aoRow[40];
359
360 return self;
361
362 def initFromDbWithId(self, oDb, idTestBox, tsNow = None, sPeriodBack = None):
363 """
364 Initialize the object from the database.
365 """
366 oDb.execute(self.formatSimpleNowAndPeriodQuery(oDb,
367 'SELECT TestBoxesWithStrings.*\n'
368 'FROM TestBoxesWithStrings\n'
369 'WHERE idTestBox = %s\n'
370 , ( idTestBox, ), tsNow, sPeriodBack));
371 aoRow = oDb.fetchOne()
372 if aoRow is None:
373 raise TMRowNotFound('idTestBox=%s not found (tsNow=%s sPeriodBack=%s)' % (idTestBox, tsNow, sPeriodBack,));
374 return self.initFromDbRow(aoRow);
375
376 def initFromDbWithGenId(self, oDb, idGenTestBox, tsNow = None):
377 """
378 Initialize the object from the database.
379 """
380 _ = tsNow; # Only useful for extended data classes.
381 oDb.execute('SELECT TestBoxesWithStrings.*\n'
382 'FROM TestBoxesWithStrings\n'
383 'WHERE idGenTestBox = %s\n'
384 , (idGenTestBox, ) );
385 return self.initFromDbRow(oDb.fetchOne());
386
387 def _validateAndConvertWorker(self, asAllowNullAttributes, oDb, enmValidateFor = ModelDataBase.ksValidateFor_Other):
388 # Override to do extra ipLom checks.
389 dErrors = ModelDataBase._validateAndConvertWorker(self, asAllowNullAttributes, oDb, enmValidateFor);
390 if self.ksParam_ipLom not in dErrors \
391 and self.ksParam_enmLomKind not in dErrors \
392 and self.enmLomKind != self.ksLomKind_None \
393 and self.ipLom is None:
394 dErrors[self.ksParam_ipLom] = 'Light-out-management IP is mandatory and a LOM is selected.'
395 return dErrors;
396
397 @staticmethod
398 def formatPythonVersionEx(iPythonHexVersion):
399 """ Unbuttons the version number and formats it as a version string. """
400 if iPythonHexVersion is None:
401 return 'N/A';
402 return 'v%d.%d.%d.%d' \
403 % ( iPythonHexVersion >> 24,
404 (iPythonHexVersion >> 16) & 0xff,
405 (iPythonHexVersion >> 8) & 0xff,
406 iPythonHexVersion & 0xff);
407
408 def formatPythonVersion(self):
409 """ Unbuttons the version number and formats it as a version string. """
410 return self.formatPythonVersionEx(self.iPythonHexVersion);
411
412
413 @staticmethod
414 def getCpuFamilyEx(lCpuRevision):
415 """ Returns the CPU family for a x86 or amd64 testboxes."""
416 if lCpuRevision is None:
417 return 0;
418 return (lCpuRevision >> 24 & 0xff);
419
420 def getCpuFamily(self):
421 """ Returns the CPU family for a x86 or amd64 testboxes."""
422 return self.getCpuFamilyEx(self.lCpuRevision);
423
424 @staticmethod
425 def getCpuModelEx(lCpuRevision):
426 """ Returns the CPU model for a x86 or amd64 testboxes."""
427 if lCpuRevision is None:
428 return 0;
429 return (lCpuRevision >> 8 & 0xffff);
430
431 def getCpuModel(self):
432 """ Returns the CPU model for a x86 or amd64 testboxes."""
433 return self.getCpuModelEx(self.lCpuRevision);
434
435 @staticmethod
436 def getCpuSteppingEx(lCpuRevision):
437 """ Returns the CPU stepping for a x86 or amd64 testboxes."""
438 if lCpuRevision is None:
439 return 0;
440 return (lCpuRevision & 0xff);
441
442 def getCpuStepping(self):
443 """ Returns the CPU stepping for a x86 or amd64 testboxes."""
444 return self.getCpuSteppingEx(self.lCpuRevision);
445
446
447 # The following is a translation of the g_aenmIntelFamily06 array in CPUMR3CpuId.cpp:
448 kdIntelFamily06 = {
449 0x00: 'P6',
450 0x01: 'P6',
451 0x03: 'P6_II',
452 0x05: 'P6_II',
453 0x06: 'P6_II',
454 0x07: 'P6_III',
455 0x08: 'P6_III',
456 0x09: 'P6_M_Banias',
457 0x0a: 'P6_III',
458 0x0b: 'P6_III',
459 0x0d: 'P6_M_Dothan',
460 0x0e: 'Core_Yonah',
461 0x0f: 'Core2_Merom',
462 0x15: 'P6_M_Dothan',
463 0x16: 'Core2_Merom',
464 0x17: 'Core2_Penryn',
465 0x1a: 'Core7_Nehalem',
466 0x1c: 'Atom_Bonnell',
467 0x1d: 'Core2_Penryn',
468 0x1e: 'Core7_Nehalem',
469 0x1f: 'Core7_Nehalem',
470 0x25: 'Core7_Westmere',
471 0x26: 'Atom_Lincroft',
472 0x27: 'Atom_Saltwell',
473 0x2a: 'Core7_SandyBridge',
474 0x2c: 'Core7_Westmere',
475 0x2d: 'Core7_SandyBridge',
476 0x2e: 'Core7_Nehalem',
477 0x2f: 'Core7_Westmere',
478 0x35: 'Atom_Saltwell',
479 0x36: 'Atom_Saltwell',
480 0x37: 'Atom_Silvermont',
481 0x3a: 'Core7_IvyBridge',
482 0x3c: 'Core7_Haswell',
483 0x3d: 'Core7_Broadwell',
484 0x3e: 'Core7_IvyBridge',
485 0x3f: 'Core7_Haswell',
486 0x45: 'Core7_Haswell',
487 0x46: 'Core7_Haswell',
488 0x47: 'Core7_Broadwell',
489 0x4a: 'Atom_Silvermont',
490 0x4c: 'Atom_Airmount',
491 0x4d: 'Atom_Silvermont',
492 0x4e: 'Core7_Skylake',
493 0x4f: 'Core7_Broadwell',
494 0x55: 'Core7_Skylake',
495 0x56: 'Core7_Broadwell',
496 0x5a: 'Atom_Silvermont',
497 0x5c: 'Atom_Goldmont',
498 0x5d: 'Atom_Silvermont',
499 0x5e: 'Core7_Skylake',
500 0x66: 'Core7_Cannonlake',
501 };
502 # Also from CPUMR3CpuId.cpp, but the switch.
503 kdIntelFamily15 = {
504 0x00: 'NB_Willamette',
505 0x01: 'NB_Willamette',
506 0x02: 'NB_Northwood',
507 0x03: 'NB_Prescott',
508 0x04: 'NB_Prescott2M',
509 0x05: 'NB_Unknown',
510 0x06: 'NB_CedarMill',
511 0x07: 'NB_Gallatin',
512 };
513
514 @staticmethod
515 def queryCpuMicroarchEx(lCpuRevision, sCpuVendor):
516 """ Try guess the microarch name for the cpu. Returns None if we cannot. """
517 if lCpuRevision is None or sCpuVendor is None:
518 return None;
519 uFam = TestBoxData.getCpuFamilyEx(lCpuRevision);
520 uMod = TestBoxData.getCpuModelEx(lCpuRevision);
521 if sCpuVendor == 'GenuineIntel':
522 if uFam == 6:
523 return TestBoxData.kdIntelFamily06.get(uMod, None);
524 if uFam == 15:
525 return TestBoxData.kdIntelFamily15.get(uMod, None);
526 elif sCpuVendor == 'AuthenticAMD':
527 if uFam == 0xf:
528 if uMod < 0x10: return 'K8_130nm';
529 if 0x60 <= uMod < 0x80: return 'K8_65nm';
530 if uMod >= 0x40: return 'K8_90nm_AMDV';
531 if uMod in [0x21, 0x23, 0x2b, 0x37, 0x3f]: return 'K8_90nm_DualCore';
532 return 'AMD_K8_90nm';
533 if uFam == 0x10: return 'K10';
534 if uFam == 0x11: return 'K10_Lion';
535 if uFam == 0x12: return 'K10_Llano';
536 if uFam == 0x14: return 'Bobcat';
537 if uFam == 0x15:
538 if uMod <= 0x01: return 'Bulldozer';
539 if uMod in [0x02, 0x10, 0x13]: return 'Piledriver';
540 return None;
541 if uFam == 0x16:
542 return 'Jaguar';
543 elif sCpuVendor == 'CentaurHauls':
544 if uFam == 0x05:
545 if uMod == 0x01: return 'Centaur_C6';
546 if uMod == 0x04: return 'Centaur_C6';
547 if uMod == 0x08: return 'Centaur_C2';
548 if uMod == 0x09: return 'Centaur_C3';
549 if uFam == 0x06:
550 if uMod == 0x05: return 'VIA_C3_M2';
551 if uMod == 0x06: return 'VIA_C3_C5A';
552 if uMod == 0x07: return 'VIA_C3_C5B' if TestBoxData.getCpuSteppingEx(lCpuRevision) < 8 else 'VIA_C3_C5C';
553 if uMod == 0x08: return 'VIA_C3_C5N';
554 if uMod == 0x09: return 'VIA_C3_C5XL' if TestBoxData.getCpuSteppingEx(lCpuRevision) < 8 else 'VIA_C3_C5P';
555 if uMod == 0x0a: return 'VIA_C7_C5J';
556 if uMod == 0x0f: return 'VIA_Isaiah';
557 elif sCpuVendor == ' Shanghai ':
558 if uFam == 0x07:
559 if uMod == 0x0b: return 'Shanghai_KX-5000';
560 return None;
561
562 def queryCpuMicroarch(self):
563 """ Try guess the microarch name for the cpu. Returns None if we cannot. """
564 return self.queryCpuMicroarchEx(self.lCpuRevision, self.sCpuVendor);
565
566 @staticmethod
567 def getPrettyCpuVersionEx(lCpuRevision, sCpuVendor):
568 """ Pretty formatting of the family/model/stepping with microarch optimizations. """
569 if lCpuRevision is None or sCpuVendor is None:
570 return u'<none>';
571 sMarch = TestBoxData.queryCpuMicroarchEx(lCpuRevision, sCpuVendor);
572 if sMarch is not None:
573 return '%s %02x:%x' \
574 % (sMarch, TestBoxData.getCpuModelEx(lCpuRevision), TestBoxData.getCpuSteppingEx(lCpuRevision));
575 return 'fam%02X m%02X s%02X' \
576 % ( TestBoxData.getCpuFamilyEx(lCpuRevision), TestBoxData.getCpuModelEx(lCpuRevision),
577 TestBoxData.getCpuSteppingEx(lCpuRevision));
578
579 def getPrettyCpuVersion(self):
580 """ Pretty formatting of the family/model/stepping with microarch optimizations. """
581 return self.getPrettyCpuVersionEx(self.lCpuRevision, self.sCpuVendor);
582
583 def getArchBitString(self):
584 """ Returns 32-bit, 64-bit, <none>, or sCpuArch. """
585 if self.sCpuArch is None:
586 return '<none>';
587 if self.sCpuArch in [ 'x86',]:
588 return '32-bit';
589 if self.sCpuArch in [ 'amd64',]:
590 return '64-bit';
591 return self.sCpuArch;
592
593 def getPrettyCpuVendor(self):
594 """ Pretty vendor name."""
595 if self.sCpuVendor is None:
596 return '<none>';
597 if self.sCpuVendor == 'GenuineIntel': return 'Intel';
598 if self.sCpuVendor == 'AuthenticAMD': return 'AMD';
599 if self.sCpuVendor == 'CentaurHauls': return 'VIA';
600 if self.sCpuVendor == ' Shanghai ': return 'Shanghai';
601 return self.sCpuVendor;
602
603
604class TestBoxDataEx(TestBoxData):
605 """
606 TestBox data.
607 """
608
609 ksParam_aoInSchedGroups = 'TestBox_aoInSchedGroups';
610
611 # Use [] instead of None.
612 kasAltArrayNull = [ 'aoInSchedGroups', ];
613
614 ## Helper parameter containing the comma separated list with the IDs of
615 # potential members found in the parameters.
616 ksParam_aidSchedGroups = 'TestBoxDataEx_aidSchedGroups';
617
618 def __init__(self):
619 TestBoxData.__init__(self);
620 self.aoInSchedGroups = [] # type: list[TestBoxInSchedGroupData]
621
622 def _initExtraMembersFromDb(self, oDb, tsNow = None, sPeriodBack = None):
623 """
624 Worker shared by the initFromDb* methods.
625 Returns self. Raises exception if no row or database error.
626 """
627 oDb.execute(self.formatSimpleNowAndPeriodQuery(oDb,
628 'SELECT *\n'
629 'FROM TestBoxesInSchedGroups\n'
630 'WHERE idTestBox = %s\n'
631 , (self.idTestBox,), tsNow, sPeriodBack)
632 + 'ORDER BY idSchedGroup\n' );
633 self.aoInSchedGroups = [];
634 for aoRow in oDb.fetchAll():
635 self.aoInSchedGroups.append(TestBoxInSchedGroupDataEx().initFromDbRowEx(aoRow, oDb, tsNow, sPeriodBack));
636 return self;
637
638 def initFromDbRowEx(self, aoRow, oDb, tsNow = None):
639 """
640 Reinitialize from a SELECT * FROM TestBoxesWithStrings row. Will query the
641 necessary additional data from oDb using tsNow.
642 Returns self. Raises exception if no row or database error.
643 """
644 TestBoxData.initFromDbRow(self, aoRow);
645 return self._initExtraMembersFromDb(oDb, tsNow);
646
647 def initFromDbWithId(self, oDb, idTestBox, tsNow = None, sPeriodBack = None):
648 """
649 Initialize the object from the database.
650 """
651 TestBoxData.initFromDbWithId(self, oDb, idTestBox, tsNow, sPeriodBack);
652 return self._initExtraMembersFromDb(oDb, tsNow, sPeriodBack);
653
654 def initFromDbWithGenId(self, oDb, idGenTestBox, tsNow = None):
655 """
656 Initialize the object from the database.
657 """
658 TestBoxData.initFromDbWithGenId(self, oDb, idGenTestBox);
659 if tsNow is None and not oDb.isTsInfinity(self.tsExpire):
660 tsNow = self.tsEffective;
661 return self._initExtraMembersFromDb(oDb, tsNow);
662
663 def getAttributeParamNullValues(self, sAttr): # Necessary?
664 if sAttr in ['aoInSchedGroups', ]:
665 return [[], ''];
666 return TestBoxData.getAttributeParamNullValues(self, sAttr);
667
668 def convertParamToAttribute(self, sAttr, sParam, oValue, oDisp, fStrict):
669 """
670 For dealing with the in-scheduling-group list.
671 """
672 if sAttr != 'aoInSchedGroups':
673 return TestBoxData.convertParamToAttribute(self, sAttr, sParam, oValue, oDisp, fStrict);
674
675 aoNewValues = [];
676 aidSelected = oDisp.getListOfIntParams(sParam, iMin = 1, iMax = 0x7ffffffe, aiDefaults = []);
677 asIds = oDisp.getStringParam(self.ksParam_aidSchedGroups, sDefault = '').split(',');
678 for idSchedGroup in asIds:
679 try: idSchedGroup = int(idSchedGroup);
680 except: pass;
681 oDispWrapper = self.DispWrapper(oDisp, '%s[%s][%%s]' % (TestBoxDataEx.ksParam_aoInSchedGroups, idSchedGroup,))
682 oMember = TestBoxInSchedGroupData().initFromParams(oDispWrapper, fStrict = False);
683 if idSchedGroup in aidSelected:
684 aoNewValues.append(oMember);
685 return aoNewValues;
686
687 def _validateAndConvertAttribute(self, sAttr, sParam, oValue, aoNilValues, fAllowNull, oDb): # pylint: disable=too-many-locals
688 """
689 Validate special arrays and requirement expressions.
690
691 Some special needs for the in-scheduling-group list.
692 """
693 if sAttr != 'aoInSchedGroups':
694 return TestBoxData._validateAndConvertAttribute(self, sAttr, sParam, oValue, aoNilValues, fAllowNull, oDb);
695
696 asErrors = [];
697 aoNewValues = [];
698
699 # Note! We'll be returning an error dictionary instead of an string here.
700 dErrors = {};
701
702 # HACK ALERT! idTestBox might not have been validated and converted yet, but we need detect
703 # adding so we can ignore idTestBox being NIL when validating group memberships.
704 ## @todo make base.py pass us the ksValidateFor_Xxxx value.
705 fIsAdding = bool(self.idTestBox in [ None, -1, '-1', 'None', '' ])
706
707 for iInGrp, oInSchedGroup in enumerate(self.aoInSchedGroups):
708 oInSchedGroup = copy.copy(oInSchedGroup);
709 oInSchedGroup.idTestBox = self.idTestBox;
710 if fIsAdding:
711 dCurErrors = oInSchedGroup.validateAndConvertEx(['idTestBox',] + oInSchedGroup.kasAllowNullAttributes,
712 oDb, ModelDataBase.ksValidateFor_Add);
713 else:
714 dCurErrors = oInSchedGroup.validateAndConvert(oDb, ModelDataBase.ksValidateFor_Other);
715 if not dCurErrors:
716 pass; ## @todo figure out the ID?
717 else:
718 asErrors = [];
719 for sKey in dCurErrors:
720 asErrors.append('%s: %s' % (sKey[len('TestBoxInSchedGroup_'):],
721 dCurErrors[sKey] + ('{%s}' % self.idTestBox)))
722 dErrors[iInGrp] = '<br>\n'.join(asErrors)
723 aoNewValues.append(oInSchedGroup);
724
725 for iInGrp, oInSchedGroup in enumerate(self.aoInSchedGroups):
726 for iInGrp2 in xrange(iInGrp + 1, len(self.aoInSchedGroups)):
727 if self.aoInSchedGroups[iInGrp2].idSchedGroup == oInSchedGroup.idSchedGroup:
728 sMsg = 'Duplicate scheduling group #%s".' % (oInSchedGroup.idSchedGroup,);
729 if iInGrp in dErrors: dErrors[iInGrp] += '<br>\n' + sMsg;
730 else: dErrors[iInGrp] = sMsg;
731 if iInGrp2 in dErrors: dErrors[iInGrp2] += '<br>\n' + sMsg;
732 else: dErrors[iInGrp2] = sMsg;
733 break;
734
735 return (aoNewValues, dErrors if dErrors else None);
736
737
738class TestBoxLogic(ModelLogicBase):
739 """
740 TestBox logic.
741 """
742
743 kiSortColumn_sName = 1;
744 kiSortColumn_sOs = 2;
745 kiSortColumn_sOsVersion = 3;
746 kiSortColumn_sCpuVendor = 4;
747 kiSortColumn_sCpuArch = 5;
748 kiSortColumn_lCpuRevision = 6;
749 kiSortColumn_cCpus = 7;
750 kiSortColumn_cMbMemory = 8;
751 kiSortColumn_cMbScratch = 9;
752 kiSortColumn_fCpuNestedPaging = 10;
753 kiSortColumn_fNativeApi = 11;
754 kiSortColumn_iTestBoxScriptRev = 12;
755 kiSortColumn_iPythonHexVersion = 13;
756 kiSortColumn_enmPendingCmd = 14;
757 kiSortColumn_fEnabled = 15;
758 kiSortColumn_enmState = 16;
759 kiSortColumn_tsUpdated = 17;
760 kcMaxSortColumns = 18;
761 kdSortColumnMap = {
762 0: 'TestBoxesWithStrings.sName',
763 kiSortColumn_sName: "regexp_replace(TestBoxesWithStrings.sName,'[0-9]*', '', 'g'), " \
764 "RIGHT(CONCAT(regexp_replace(TestBoxesWithStrings.sName,'[^0-9]*','', 'g'),'0'),8)::int",
765 -kiSortColumn_sName: "regexp_replace(TestBoxesWithStrings.sName,'[0-9]*', '', 'g') DESC, " \
766 "RIGHT(CONCAT(regexp_replace(TestBoxesWithStrings.sName,'[^0-9]*','', 'g'),'0'),8)::int DESC",
767 kiSortColumn_sOs: 'TestBoxesWithStrings.sOs',
768 -kiSortColumn_sOs: 'TestBoxesWithStrings.sOs DESC',
769 kiSortColumn_sOsVersion: 'TestBoxesWithStrings.sOsVersion',
770 -kiSortColumn_sOsVersion: 'TestBoxesWithStrings.sOsVersion DESC',
771 kiSortColumn_sCpuVendor: 'TestBoxesWithStrings.sCpuVendor',
772 -kiSortColumn_sCpuVendor: 'TestBoxesWithStrings.sCpuVendor DESC',
773 kiSortColumn_sCpuArch: 'TestBoxesWithStrings.sCpuArch',
774 -kiSortColumn_sCpuArch: 'TestBoxesWithStrings.sCpuArch DESC',
775 kiSortColumn_lCpuRevision: 'TestBoxesWithStrings.lCpuRevision',
776 -kiSortColumn_lCpuRevision: 'TestBoxesWithStrings.lCpuRevision DESC',
777 kiSortColumn_cCpus: 'TestBoxesWithStrings.cCpus',
778 -kiSortColumn_cCpus: 'TestBoxesWithStrings.cCpus DESC',
779 kiSortColumn_cMbMemory: 'TestBoxesWithStrings.cMbMemory',
780 -kiSortColumn_cMbMemory: 'TestBoxesWithStrings.cMbMemory DESC',
781 kiSortColumn_cMbScratch: 'TestBoxesWithStrings.cMbScratch',
782 -kiSortColumn_cMbScratch: 'TestBoxesWithStrings.cMbScratch DESC',
783 kiSortColumn_fCpuNestedPaging: 'TestBoxesWithStrings.fCpuNestedPaging',
784 -kiSortColumn_fCpuNestedPaging: 'TestBoxesWithStrings.fCpuNestedPaging DESC',
785 kiSortColumn_fNativeApi: 'TestBoxesWithStrings.fNativeApi',
786 -kiSortColumn_fNativeApi: 'TestBoxesWithStrings.fNativeApi DESC',
787 kiSortColumn_iTestBoxScriptRev: 'TestBoxesWithStrings.iTestBoxScriptRev',
788 -kiSortColumn_iTestBoxScriptRev: 'TestBoxesWithStrings.iTestBoxScriptRev DESC',
789 kiSortColumn_iPythonHexVersion: 'TestBoxesWithStrings.iPythonHexVersion',
790 -kiSortColumn_iPythonHexVersion: 'TestBoxesWithStrings.iPythonHexVersion DESC',
791 kiSortColumn_enmPendingCmd: 'TestBoxesWithStrings.enmPendingCmd',
792 -kiSortColumn_enmPendingCmd: 'TestBoxesWithStrings.enmPendingCmd DESC',
793 kiSortColumn_fEnabled: 'TestBoxesWithStrings.fEnabled',
794 -kiSortColumn_fEnabled: 'TestBoxesWithStrings.fEnabled DESC',
795 kiSortColumn_enmState: 'TestBoxStatuses.enmState',
796 -kiSortColumn_enmState: 'TestBoxStatuses.enmState DESC',
797 kiSortColumn_tsUpdated: 'TestBoxStatuses.tsUpdated',
798 -kiSortColumn_tsUpdated: 'TestBoxStatuses.tsUpdated DESC',
799 };
800
801 def __init__(self, oDb):
802 ModelLogicBase.__init__(self, oDb);
803 self.dCache = None;
804
805 def tryFetchTestBoxByUuid(self, sTestBoxUuid):
806 """
807 Tries to fetch a testbox by its UUID alone.
808 """
809 self._oDb.execute('SELECT TestBoxesWithStrings.*\n'
810 'FROM TestBoxesWithStrings\n'
811 'WHERE uuidSystem = %s\n'
812 ' AND tsExpire = \'infinity\'::timestamp\n'
813 'ORDER BY tsEffective DESC\n',
814 (sTestBoxUuid,));
815 if self._oDb.getRowCount() == 0:
816 return None;
817 if self._oDb.getRowCount() != 1:
818 raise TMTooManyRows('Database integrity error: %u hits' % (self._oDb.getRowCount(),));
819 oData = TestBoxData();
820 oData.initFromDbRow(self._oDb.fetchOne());
821 return oData;
822
823 def fetchForListing(self, iStart, cMaxRows, tsNow, aiSortColumns = None):
824 """
825 Fetches testboxes for listing.
826
827 Returns an array (list) of TestBoxDataForListing items, empty list if none.
828 The TestBoxDataForListing instances are just TestBoxData with two extra
829 members, an extra oStatus member that is either None or a TestBoxStatusData
830 instance, and a member tsCurrent holding CURRENT_TIMESTAMP.
831
832 Raises exception on error.
833 """
834 class TestBoxDataForListing(TestBoxDataEx):
835 """ We add two members for the listing. """
836 def __init__(self):
837 TestBoxDataEx.__init__(self);
838 self.tsCurrent = None; # CURRENT_TIMESTAMP
839 self.oStatus = None # type: TestBoxStatusData
840
841 from testmanager.core.testboxstatus import TestBoxStatusData;
842
843 if not aiSortColumns:
844 aiSortColumns = [self.kiSortColumn_sName,];
845
846 if tsNow is None:
847 self._oDb.execute('SELECT TestBoxesWithStrings.*,\n'
848 ' TestBoxStatuses.*\n'
849 'FROM TestBoxesWithStrings\n'
850 ' LEFT OUTER JOIN TestBoxStatuses\n'
851 ' ON TestBoxStatuses.idTestBox = TestBoxesWithStrings.idTestBox\n'
852 'WHERE TestBoxesWithStrings.tsExpire = \'infinity\'::TIMESTAMP\n'
853 'ORDER BY ' + (', '.join([self.kdSortColumnMap[i] for i in aiSortColumns])) + '\n'
854 'LIMIT %s OFFSET %s\n'
855 , (cMaxRows, iStart,));
856 else:
857 self._oDb.execute('SELECT TestBoxesWithStrings.*,\n'
858 ' TestBoxStatuses.*\n'
859 'FROM TestBoxesWithStrings\n'
860 ' LEFT OUTER JOIN TestBoxStatuses\n'
861 ' ON TestBoxStatuses.idTestBox = TestBoxesWithStrings.idTestBox\n'
862 'WHERE tsExpire > %s\n'
863 ' AND tsEffective <= %s\n'
864 'ORDER BY ' + (', '.join([self.kdSortColumnMap[i] for i in aiSortColumns])) + '\n'
865 'LIMIT %s OFFSET %s\n'
866 , ( tsNow, tsNow, cMaxRows, iStart,));
867
868 aoRows = [];
869 for aoOne in self._oDb.fetchAll():
870 oTestBox = TestBoxDataForListing().initFromDbRowEx(aoOne, self._oDb, tsNow);
871 oTestBox.tsCurrent = self._oDb.getCurrentTimestamp();
872 if aoOne[TestBoxData.kcDbColumns] is not None:
873 oTestBox.oStatus = TestBoxStatusData().initFromDbRow(aoOne[TestBoxData.kcDbColumns:]);
874 aoRows.append(oTestBox);
875 return aoRows;
876
877 def fetchForSchedGroup(self, idSchedGroup, tsNow, aiSortColumns = None):
878 """
879 Fetches testboxes for listing.
880
881 Returns an array (list) of TestBoxDataForSchedGroup items, empty list if none.
882
883 Raises exception on error.
884 """
885 if not aiSortColumns:
886 aiSortColumns = [self.kiSortColumn_sName,];
887 asSortColumns = [self.kdSortColumnMap[i] for i in aiSortColumns];
888 asSortColumns.append('TestBoxesInSchedGroups.idTestBox');
889
890 if tsNow is None:
891 self._oDb.execute('''
892SELECT TestBoxesInSchedGroups.*,
893 TestBoxesWithStrings.*
894FROM TestBoxesInSchedGroups
895 LEFT OUTER JOIN TestBoxesWithStrings
896 ON TestBoxesWithStrings.idTestBox = TestBoxesInSchedGroups.idTestBox
897 AND TestBoxesWithStrings.tsExpire = 'infinity'::TIMESTAMP
898WHERE TestBoxesInSchedGroups.idSchedGroup = %s
899 AND TestBoxesInSchedGroups.tsExpire = 'infinity'::TIMESTAMP
900ORDER BY ''' + ', '.join(asSortColumns), (idSchedGroup, ));
901 else:
902 self._oDb.execute('''
903SELECT TestBoxesInSchedGroups.*,
904 TestBoxesWithStrings.*
905FROM TestBoxesInSchedGroups
906 LEFT OUTER JOIN TestBoxesWithStrings
907 ON TestBoxesWithStrings.idTestBox = TestBoxesInSchedGroups.idTestBox
908 AND TestBoxesWithStrings.tsExpire > %s
909 AND TestBoxesWithStrings.tsEffective <= %s
910WHERE TestBoxesInSchedGroups.idSchedGroup = %s
911 AND TestBoxesInSchedGroups.tsExpire > %s
912 AND TestBoxesInSchedGroups.tsEffective <= %s
913ORDER BY ''' + ', '.join(asSortColumns), (tsNow, tsNow, idSchedGroup, tsNow, tsNow, ));
914
915 aoRows = [];
916 for aoOne in self._oDb.fetchAll():
917 aoRows.append(TestBoxDataForSchedGroup().initFromDbRow(aoOne));
918 return aoRows;
919
920 def fetchForChangeLog(self, idTestBox, iStart, cMaxRows, tsNow): # pylint: disable=too-many-locals
921 """
922 Fetches change log entries for a testbox.
923
924 Returns an array of ChangeLogEntry instance and an indicator whether
925 there are more entries.
926 Raises exception on error.
927 """
928
929 ## @todo calc changes to scheduler group!
930
931 if tsNow is None:
932 tsNow = self._oDb.getCurrentTimestamp();
933
934 self._oDb.execute('SELECT TestBoxesWithStrings.*\n'
935 'FROM TestBoxesWithStrings\n'
936 'WHERE TestBoxesWithStrings.tsEffective <= %s\n'
937 ' AND TestBoxesWithStrings.idTestBox = %s\n'
938 'ORDER BY TestBoxesWithStrings.tsExpire DESC\n'
939 'LIMIT %s OFFSET %s\n'
940 , (tsNow, idTestBox, cMaxRows + 1, iStart,));
941
942 aoRows = [];
943 for aoDbRow in self._oDb.fetchAll():
944 aoRows.append(TestBoxData().initFromDbRow(aoDbRow));
945
946 # Calculate the changes.
947 aoEntries = [];
948 for i in xrange(0, len(aoRows) - 1):
949 oNew = aoRows[i];
950 oOld = aoRows[i + 1];
951 aoChanges = [];
952 for sAttr in oNew.getDataAttributes():
953 if sAttr not in [ 'tsEffective', 'tsExpire', 'uidAuthor', ]:
954 oOldAttr = getattr(oOld, sAttr);
955 oNewAttr = getattr(oNew, sAttr);
956 if oOldAttr != oNewAttr:
957 if sAttr == 'sReport':
958 aoChanges.append(AttributeChangeEntryPre(sAttr, oNewAttr, oOldAttr, str(oNewAttr), str(oOldAttr)));
959 else:
960 aoChanges.append(AttributeChangeEntry(sAttr, oNewAttr, oOldAttr, str(oNewAttr), str(oOldAttr)));
961 aoEntries.append(ChangeLogEntry(oNew.uidAuthor, None, oNew.tsEffective, oNew.tsExpire, oNew, oOld, aoChanges));
962
963 # If we're at the end of the log, add the initial entry.
964 if len(aoRows) <= cMaxRows and aoRows:
965 oNew = aoRows[-1];
966 aoEntries.append(ChangeLogEntry(oNew.uidAuthor, None, oNew.tsEffective, oNew.tsExpire, oNew, None, []));
967
968 UserAccountLogic(self._oDb).resolveChangeLogAuthors(aoEntries);
969 return (aoEntries, len(aoRows) > cMaxRows);
970
971 def _validateAndConvertData(self, oData, enmValidateFor):
972 # type: (TestBoxDataEx, str) -> None
973 """
974 Helper for addEntry and editEntry that validates the scheduling group IDs in
975 addtion to what's covered by the default validateAndConvert of the data object.
976
977 Raises exception on invalid input.
978 """
979 dDataErrors = oData.validateAndConvert(self._oDb, enmValidateFor);
980 if dDataErrors:
981 raise TMInvalidData('TestBoxLogic.addEntry: %s' % (dDataErrors,));
982 if isinstance(oData, TestBoxDataEx):
983 if oData.aoInSchedGroups:
984 sSchedGrps = ', '.join('(%s)' % oCur.idSchedGroup for oCur in oData.aoInSchedGroups);
985 self._oDb.execute('SELECT SchedGroupIDs.idSchedGroup\n'
986 'FROM (VALUES ' + sSchedGrps + ' ) AS SchedGroupIDs(idSchedGroup)\n'
987 ' LEFT OUTER JOIN SchedGroups\n'
988 ' ON SchedGroupIDs.idSchedGroup = SchedGroups.idSchedGroup\n'
989 ' AND SchedGroups.tsExpire = \'infinity\'::TIMESTAMP\n'
990 'WHERE SchedGroups.idSchedGroup IS NULL\n');
991 aaoRows = self._oDb.fetchAll();
992 if aaoRows:
993 raise TMInvalidData('TestBoxLogic.addEntry missing scheduling groups: %s'
994 % (', '.join(str(aoRow[0]) for aoRow in aaoRows),));
995 return None;
996
997 def addEntry(self, oData, uidAuthor, fCommit = False):
998 # type: (TestBoxDataEx, int, bool) -> (int, int, datetime.datetime)
999 """
1000 Creates a testbox in the database.
1001 Returns the testbox ID, testbox generation ID and effective timestamp
1002 of the created testbox on success. Throws error on failure.
1003 """
1004
1005 #
1006 # Validate. Extra work because of missing foreign key (due to history).
1007 #
1008 self._validateAndConvertData(oData, oData.ksValidateFor_Add);
1009
1010 #
1011 # Do it.
1012 #
1013 self._oDb.callProc('TestBoxLogic_addEntry'
1014 , ( uidAuthor,
1015 oData.ip, # Should we allow setting the IP?
1016 oData.uuidSystem,
1017 oData.sName,
1018 oData.sDescription,
1019 oData.fEnabled,
1020 oData.enmLomKind,
1021 oData.ipLom,
1022 oData.pctScaleTimeout,
1023 oData.sComment,
1024 oData.enmPendingCmd, ) );
1025 (idTestBox, idGenTestBox, tsEffective) = self._oDb.fetchOne();
1026
1027 for oInSchedGrp in oData.aoInSchedGroups:
1028 self._oDb.callProc('TestBoxLogic_addGroupEntry',
1029 ( uidAuthor, idTestBox, oInSchedGrp.idSchedGroup, oInSchedGrp.iSchedPriority,) );
1030
1031 self._oDb.maybeCommit(fCommit);
1032 return (idTestBox, idGenTestBox, tsEffective);
1033
1034
1035 def editEntry(self, oData, uidAuthor, fCommit = False):
1036 """
1037 Data edit update, web UI is the primary user.
1038
1039 oData is either TestBoxDataEx or TestBoxData. The latter is for enabling
1040 Returns the new generation ID and effective date.
1041 """
1042
1043 #
1044 # Validate.
1045 #
1046 self._validateAndConvertData(oData, oData.ksValidateFor_Edit);
1047
1048 #
1049 # Get current data.
1050 #
1051 oOldData = TestBoxDataEx().initFromDbWithId(self._oDb, oData.idTestBox);
1052
1053 #
1054 # Do it.
1055 #
1056 if not oData.isEqualEx(oOldData, [ 'tsEffective', 'tsExpire', 'uidAuthor', 'aoInSchedGroups', ]
1057 + TestBoxData.kasMachineSettableOnly ):
1058 self._oDb.callProc('TestBoxLogic_editEntry'
1059 , ( uidAuthor,
1060 oData.idTestBox,
1061 oData.ip, # Should we allow setting the IP?
1062 oData.uuidSystem,
1063 oData.sName,
1064 oData.sDescription,
1065 oData.fEnabled,
1066 oData.enmLomKind,
1067 oData.ipLom,
1068 oData.pctScaleTimeout,
1069 oData.sComment,
1070 oData.enmPendingCmd, ));
1071 (idGenTestBox, tsEffective) = self._oDb.fetchOne();
1072 else:
1073 idGenTestBox = oOldData.idGenTestBox;
1074 tsEffective = oOldData.tsEffective;
1075
1076 if isinstance(oData, TestBoxDataEx):
1077 # Calc in-group changes.
1078 aoRemoved = list(oOldData.aoInSchedGroups);
1079 aoNew = [];
1080 aoUpdated = [];
1081 for oNewInGroup in oData.aoInSchedGroups:
1082 oOldInGroup = None;
1083 for iCur, oCur in enumerate(aoRemoved):
1084 if oCur.idSchedGroup == oNewInGroup.idSchedGroup:
1085 oOldInGroup = aoRemoved.pop(iCur);
1086 break;
1087 if oOldInGroup is None:
1088 aoNew.append(oNewInGroup);
1089 elif oNewInGroup.iSchedPriority != oOldInGroup.iSchedPriority:
1090 aoUpdated.append(oNewInGroup);
1091
1092 # Remove in-groups.
1093 for oInGroup in aoRemoved:
1094 self._oDb.callProc('TestBoxLogic_removeGroupEntry', (uidAuthor, oData.idTestBox, oInGroup.idSchedGroup, ));
1095
1096 # Add new ones.
1097 for oInGroup in aoNew:
1098 self._oDb.callProc('TestBoxLogic_addGroupEntry',
1099 ( uidAuthor, oData.idTestBox, oInGroup.idSchedGroup, oInGroup.iSchedPriority, ) );
1100
1101 # Edit existing ones.
1102 for oInGroup in aoUpdated:
1103 self._oDb.callProc('TestBoxLogic_editGroupEntry',
1104 ( uidAuthor, oData.idTestBox, oInGroup.idSchedGroup, oInGroup.iSchedPriority, ) );
1105 else:
1106 assert isinstance(oData, TestBoxData);
1107
1108 self._oDb.maybeCommit(fCommit);
1109 return (idGenTestBox, tsEffective);
1110
1111
1112 def removeEntry(self, uidAuthor, idTestBox, fCascade = False, fCommit = False):
1113 """
1114 Delete test box and scheduling group associations.
1115 """
1116 self._oDb.callProc('TestBoxLogic_removeEntry'
1117 , ( uidAuthor, idTestBox, fCascade,));
1118 self._oDb.maybeCommit(fCommit);
1119 return True;
1120
1121
1122 def updateOnSignOn(self, idTestBox, idGenTestBox, sTestBoxAddr, sOs, sOsVersion, # pylint: disable=too-many-arguments,too-many-locals
1123 sCpuVendor, sCpuArch, sCpuName, lCpuRevision, cCpus, fCpuHwVirt, fCpuNestedPaging, fCpu64BitGuest,
1124 fChipsetIoMmu, fRawMode, fNativeApi, cMbMemory, cMbScratch, sReport, iTestBoxScriptRev, iPythonHexVersion):
1125 """
1126 Update the testbox attributes automatically on behalf of the testbox script.
1127 Returns the new generation id on success, raises an exception on failure.
1128 """
1129 _ = idGenTestBox;
1130 self._oDb.callProc('TestBoxLogic_updateOnSignOn'
1131 , ( idTestBox,
1132 sTestBoxAddr,
1133 sOs,
1134 sOsVersion,
1135 sCpuVendor,
1136 sCpuArch,
1137 sCpuName,
1138 lCpuRevision,
1139 cCpus,
1140 fCpuHwVirt,
1141 fCpuNestedPaging,
1142 fCpu64BitGuest,
1143 fChipsetIoMmu,
1144 fRawMode,
1145 fNativeApi,
1146 cMbMemory,
1147 cMbScratch,
1148 sReport,
1149 iTestBoxScriptRev,
1150 iPythonHexVersion,));
1151 return self._oDb.fetchOne()[0];
1152
1153
1154 def setCommand(self, idTestBox, sOldCommand, sNewCommand, uidAuthor = None, fCommit = False, sComment = None):
1155 """
1156 Sets or resets the pending command on a testbox.
1157 Returns (idGenTestBox, tsEffective) of the new row.
1158 """
1159 ## @todo throw TMInFligthCollision again...
1160 self._oDb.callProc('TestBoxLogic_setCommand'
1161 , ( uidAuthor, idTestBox, sOldCommand, sNewCommand, sComment,));
1162 aoRow = self._oDb.fetchOne();
1163 self._oDb.maybeCommit(fCommit);
1164 return (aoRow[0], aoRow[1]);
1165
1166
1167 def getAll(self):
1168 """
1169 Retrieve list of all registered Test Box records from DB.
1170 """
1171 self._oDb.execute('SELECT *\n'
1172 'FROM TestBoxesWithStrings\n'
1173 'WHERE tsExpire=\'infinity\'::timestamp\n'
1174 'ORDER BY sName')
1175
1176 aaoRows = self._oDb.fetchAll()
1177 aoRet = []
1178 for aoRow in aaoRows:
1179 aoRet.append(TestBoxData().initFromDbRow(aoRow))
1180 return aoRet
1181
1182
1183 def cachedLookup(self, idTestBox):
1184 # type: (int) -> TestBoxDataEx
1185 """
1186 Looks up the most recent TestBoxData object for idTestBox via
1187 an object cache.
1188
1189 Returns a shared TestBoxDataEx object. None if not found.
1190 Raises exception on DB error.
1191 """
1192 if self.dCache is None:
1193 self.dCache = self._oDb.getCache('TestBoxData');
1194 oEntry = self.dCache.get(idTestBox, None);
1195 if oEntry is None:
1196 fNeedNow = False;
1197 self._oDb.execute('SELECT TestBoxesWithStrings.*\n'
1198 'FROM TestBoxesWithStrings\n'
1199 'WHERE idTestBox = %s\n'
1200 ' AND tsExpire = \'infinity\'::TIMESTAMP\n'
1201 , (idTestBox, ));
1202 if self._oDb.getRowCount() == 0:
1203 # Maybe it was deleted, try get the last entry.
1204 self._oDb.execute('SELECT TestBoxesWithStrings.*\n'
1205 'FROM TestBoxesWithStrings\n'
1206 'WHERE idTestBox = %s\n'
1207 'ORDER BY tsExpire DESC\n'
1208 'LIMIT 1\n'
1209 , (idTestBox, ));
1210 fNeedNow = True;
1211 elif self._oDb.getRowCount() > 1:
1212 raise self._oDb.integrityException('%s infinity rows for %s' % (self._oDb.getRowCount(), idTestBox));
1213
1214 if self._oDb.getRowCount() == 1:
1215 aaoRow = self._oDb.fetchOne();
1216 if not fNeedNow:
1217 oEntry = TestBoxDataEx().initFromDbRowEx(aaoRow, self._oDb);
1218 else:
1219 oEntry = TestBoxDataEx().initFromDbRow(aaoRow);
1220 oEntry.initFromDbRowEx(aaoRow, self._oDb, tsNow = db.dbTimestampMinusOneTick(oEntry.tsExpire));
1221 self.dCache[idTestBox] = oEntry;
1222 return oEntry;
1223
1224
1225
1226 #
1227 # The virtual test sheriff interface.
1228 #
1229
1230 def hasTestBoxRecentlyBeenRebooted(self, idTestBox, cHoursBack = 2, tsNow = None):
1231 """
1232 Checks if the testbox has been rebooted in the specified time period.
1233
1234 This does not include already pending reboots, though under some
1235 circumstances it may. These being the test box entry being edited for
1236 other reasons.
1237
1238 Returns True / False.
1239 """
1240 if tsNow is None:
1241 tsNow = self._oDb.getCurrentTimestamp();
1242 self._oDb.execute('SELECT COUNT(idTestBox)\n'
1243 'FROM TestBoxes\n'
1244 'WHERE idTestBox = %s\n'
1245 ' AND tsExpire < %s\n'
1246 ' AND tsExpire >= %s - interval \'%s hours\'\n'
1247 ' AND enmPendingCmd IN (%s, %s)\n'
1248 , ( idTestBox, tsNow, tsNow, cHoursBack,
1249 TestBoxData.ksTestBoxCmd_Reboot, TestBoxData.ksTestBoxCmd_UpgradeAndReboot, ));
1250 return self._oDb.fetchOne()[0] > 0;
1251
1252
1253 def rebootTestBox(self, idTestBox, uidAuthor, sComment, sOldCommand = TestBoxData.ksTestBoxCmd_None, fCommit = False):
1254 """
1255 Issues a reboot command for the given test box.
1256 Return True on succes, False on in-flight collision.
1257 May raise DB exception on other trouble.
1258 """
1259 try:
1260 self.setCommand(idTestBox, sOldCommand, TestBoxData.ksTestBoxCmd_Reboot,
1261 uidAuthor = uidAuthor, fCommit = fCommit, sComment = sComment);
1262 except TMInFligthCollision:
1263 return False;
1264 return True;
1265
1266
1267 def disableTestBox(self, idTestBox, uidAuthor, sComment, fCommit = False):
1268 """
1269 Disables the given test box.
1270
1271 Raises exception on trouble, without rollback.
1272 """
1273 oTestBox = TestBoxData().initFromDbWithId(self._oDb, idTestBox);
1274 if oTestBox.fEnabled:
1275 oTestBox.fEnabled = False;
1276 if sComment is not None:
1277 oTestBox.sComment = sComment;
1278 self.editEntry(oTestBox, uidAuthor = uidAuthor, fCommit = fCommit);
1279 return None;
1280
1281
1282#
1283# Unit testing.
1284#
1285
1286# pylint: disable=missing-docstring
1287class TestBoxDataTestCase(ModelDataBaseTestCase):
1288 def setUp(self):
1289 self.aoSamples = [TestBoxData(),];
1290
1291if __name__ == '__main__':
1292 unittest.main();
1293 # not reached.
1294
Note: See TracBrowser for help on using the repository browser.

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