VirtualBox

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

Last change on this file since 65350 was 65350, checked in by vboxsync, 8 years ago

TestManager/webui: Implemented simple column sorting direction toggle during the meeting. (Still testboxes only.)

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

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