VirtualBox

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

Last change on this file since 78138 was 76886, checked in by vboxsync, 6 years ago

VMM (and related changes): Add support for Shanghai/Zhaoxin CPUs. Modified and improved contribution by Journey Ren submitted under MIT license. Thank you!

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