VirtualBox

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

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

scm copyright and license note update

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