VirtualBox

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

Last change on this file since 84097 was 84097, checked in by vboxsync, 5 years ago

TestManager/testbox.py: Fix for names with lots of digits in them.

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