VirtualBox

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

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

pylint birn fix

  • 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 84110 2020-04-30 15:04:56Z 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: 84110 $"
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 = bool(self.idTestBox in [ None, -1, '-1', 'None', '' ])
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_'):],
707 dCurErrors[sKey] + ('{%s}' % self.idTestBox)))
708 dErrors[iInGrp] = '<br>\n'.join(asErrors)
709 aoNewValues.append(oInSchedGroup);
710
711 for iInGrp, oInSchedGroup in enumerate(self.aoInSchedGroups):
712 for iInGrp2 in xrange(iInGrp + 1, len(self.aoInSchedGroups)):
713 if self.aoInSchedGroups[iInGrp2].idSchedGroup == oInSchedGroup.idSchedGroup:
714 sMsg = 'Duplicate scheduling group #%s".' % (oInSchedGroup.idSchedGroup,);
715 if iInGrp in dErrors: dErrors[iInGrp] += '<br>\n' + sMsg;
716 else: dErrors[iInGrp] = sMsg;
717 if iInGrp2 in dErrors: dErrors[iInGrp2] += '<br>\n' + sMsg;
718 else: dErrors[iInGrp2] = sMsg;
719 break;
720
721 return (aoNewValues, dErrors if dErrors else None);
722
723
724class TestBoxLogic(ModelLogicBase):
725 """
726 TestBox logic.
727 """
728
729 kiSortColumn_sName = 1;
730 kiSortColumn_sOs = 2;
731 kiSortColumn_sOsVersion = 3;
732 kiSortColumn_sCpuVendor = 4;
733 kiSortColumn_sCpuArch = 5;
734 kiSortColumn_lCpuRevision = 6;
735 kiSortColumn_cCpus = 7;
736 kiSortColumn_cMbMemory = 8;
737 kiSortColumn_cMbScratch = 9;
738 kiSortColumn_fCpuNestedPaging = 10;
739 kiSortColumn_iTestBoxScriptRev = 11;
740 kiSortColumn_iPythonHexVersion = 12;
741 kiSortColumn_enmPendingCmd = 13;
742 kiSortColumn_fEnabled = 14;
743 kiSortColumn_enmState = 15;
744 kiSortColumn_tsUpdated = 16;
745 kcMaxSortColumns = 17;
746 kdSortColumnMap = {
747 0: 'TestBoxesWithStrings.sName',
748 kiSortColumn_sName: "regexp_replace(TestBoxesWithStrings.sName,'[0-9]*', '', 'g'), " \
749 "RIGHT(CONCAT(regexp_replace(TestBoxesWithStrings.sName,'[^0-9]*','', 'g'),'0'),8)::int",
750 -kiSortColumn_sName: "regexp_replace(TestBoxesWithStrings.sName,'[0-9]*', '', 'g') DESC, " \
751 "RIGHT(CONCAT(regexp_replace(TestBoxesWithStrings.sName,'[^0-9]*','', 'g'),'0'),8)::int DESC",
752 kiSortColumn_sOs: 'TestBoxesWithStrings.sOs',
753 -kiSortColumn_sOs: 'TestBoxesWithStrings.sOs DESC',
754 kiSortColumn_sOsVersion: 'TestBoxesWithStrings.sOsVersion',
755 -kiSortColumn_sOsVersion: 'TestBoxesWithStrings.sOsVersion DESC',
756 kiSortColumn_sCpuVendor: 'TestBoxesWithStrings.sCpuVendor',
757 -kiSortColumn_sCpuVendor: 'TestBoxesWithStrings.sCpuVendor DESC',
758 kiSortColumn_sCpuArch: 'TestBoxesWithStrings.sCpuArch',
759 -kiSortColumn_sCpuArch: 'TestBoxesWithStrings.sCpuArch DESC',
760 kiSortColumn_lCpuRevision: 'TestBoxesWithStrings.lCpuRevision',
761 -kiSortColumn_lCpuRevision: 'TestBoxesWithStrings.lCpuRevision DESC',
762 kiSortColumn_cCpus: 'TestBoxesWithStrings.cCpus',
763 -kiSortColumn_cCpus: 'TestBoxesWithStrings.cCpus DESC',
764 kiSortColumn_cMbMemory: 'TestBoxesWithStrings.cMbMemory',
765 -kiSortColumn_cMbMemory: 'TestBoxesWithStrings.cMbMemory DESC',
766 kiSortColumn_cMbScratch: 'TestBoxesWithStrings.cMbScratch',
767 -kiSortColumn_cMbScratch: 'TestBoxesWithStrings.cMbScratch DESC',
768 kiSortColumn_fCpuNestedPaging: 'TestBoxesWithStrings.fCpuNestedPaging',
769 -kiSortColumn_fCpuNestedPaging: 'TestBoxesWithStrings.fCpuNestedPaging DESC',
770 kiSortColumn_iTestBoxScriptRev: 'TestBoxesWithStrings.iTestBoxScriptRev',
771 -kiSortColumn_iTestBoxScriptRev: 'TestBoxesWithStrings.iTestBoxScriptRev DESC',
772 kiSortColumn_iPythonHexVersion: 'TestBoxesWithStrings.iPythonHexVersion',
773 -kiSortColumn_iPythonHexVersion: 'TestBoxesWithStrings.iPythonHexVersion DESC',
774 kiSortColumn_enmPendingCmd: 'TestBoxesWithStrings.enmPendingCmd',
775 -kiSortColumn_enmPendingCmd: 'TestBoxesWithStrings.enmPendingCmd DESC',
776 kiSortColumn_fEnabled: 'TestBoxesWithStrings.fEnabled',
777 -kiSortColumn_fEnabled: 'TestBoxesWithStrings.fEnabled DESC',
778 kiSortColumn_enmState: 'TestBoxStatuses.enmState',
779 -kiSortColumn_enmState: 'TestBoxStatuses.enmState DESC',
780 kiSortColumn_tsUpdated: 'TestBoxStatuses.tsUpdated',
781 -kiSortColumn_tsUpdated: 'TestBoxStatuses.tsUpdated DESC',
782 };
783
784 def __init__(self, oDb):
785 ModelLogicBase.__init__(self, oDb);
786 self.dCache = None;
787
788 def tryFetchTestBoxByUuid(self, sTestBoxUuid):
789 """
790 Tries to fetch a testbox by its UUID alone.
791 """
792 self._oDb.execute('SELECT TestBoxesWithStrings.*\n'
793 'FROM TestBoxesWithStrings\n'
794 'WHERE uuidSystem = %s\n'
795 ' AND tsExpire = \'infinity\'::timestamp\n'
796 'ORDER BY tsEffective DESC\n',
797 (sTestBoxUuid,));
798 if self._oDb.getRowCount() == 0:
799 return None;
800 if self._oDb.getRowCount() != 1:
801 raise TMTooManyRows('Database integrity error: %u hits' % (self._oDb.getRowCount(),));
802 oData = TestBoxData();
803 oData.initFromDbRow(self._oDb.fetchOne());
804 return oData;
805
806 def fetchForListing(self, iStart, cMaxRows, tsNow, aiSortColumns = None):
807 """
808 Fetches testboxes for listing.
809
810 Returns an array (list) of TestBoxDataForListing items, empty list if none.
811 The TestBoxDataForListing instances are just TestBoxData with two extra
812 members, an extra oStatus member that is either None or a TestBoxStatusData
813 instance, and a member tsCurrent holding CURRENT_TIMESTAMP.
814
815 Raises exception on error.
816 """
817 class TestBoxDataForListing(TestBoxDataEx):
818 """ We add two members for the listing. """
819 def __init__(self):
820 TestBoxDataEx.__init__(self);
821 self.tsCurrent = None; # CURRENT_TIMESTAMP
822 self.oStatus = None # type: TestBoxStatusData
823
824 from testmanager.core.testboxstatus import TestBoxStatusData;
825
826 if not aiSortColumns:
827 aiSortColumns = [self.kiSortColumn_sName,];
828
829 if tsNow is None:
830 self._oDb.execute('SELECT TestBoxesWithStrings.*,\n'
831 ' TestBoxStatuses.*\n'
832 'FROM TestBoxesWithStrings\n'
833 ' LEFT OUTER JOIN TestBoxStatuses\n'
834 ' ON TestBoxStatuses.idTestBox = TestBoxesWithStrings.idTestBox\n'
835 'WHERE TestBoxesWithStrings.tsExpire = \'infinity\'::TIMESTAMP\n'
836 'ORDER BY ' + (', '.join([self.kdSortColumnMap[i] for i in aiSortColumns])) + '\n'
837 'LIMIT %s OFFSET %s\n'
838 , (cMaxRows, iStart,));
839 else:
840 self._oDb.execute('SELECT TestBoxesWithStrings.*,\n'
841 ' TestBoxStatuses.*\n'
842 'FROM TestBoxesWithStrings\n'
843 ' LEFT OUTER JOIN TestBoxStatuses\n'
844 ' ON TestBoxStatuses.idTestBox = TestBoxesWithStrings.idTestBox\n'
845 'WHERE tsExpire > %s\n'
846 ' AND tsEffective <= %s\n'
847 'ORDER BY ' + (', '.join([self.kdSortColumnMap[i] for i in aiSortColumns])) + '\n'
848 'LIMIT %s OFFSET %s\n'
849 , ( tsNow, tsNow, cMaxRows, iStart,));
850
851 aoRows = [];
852 for aoOne in self._oDb.fetchAll():
853 oTestBox = TestBoxDataForListing().initFromDbRowEx(aoOne, self._oDb, tsNow);
854 oTestBox.tsCurrent = self._oDb.getCurrentTimestamp();
855 if aoOne[TestBoxData.kcDbColumns] is not None:
856 oTestBox.oStatus = TestBoxStatusData().initFromDbRow(aoOne[TestBoxData.kcDbColumns:]);
857 aoRows.append(oTestBox);
858 return aoRows;
859
860 def fetchForSchedGroup(self, idSchedGroup, tsNow, aiSortColumns = None):
861 """
862 Fetches testboxes for listing.
863
864 Returns an array (list) of TestBoxDataForSchedGroup items, empty list if none.
865
866 Raises exception on error.
867 """
868 if not aiSortColumns:
869 aiSortColumns = [self.kiSortColumn_sName,];
870 asSortColumns = [self.kdSortColumnMap[i] for i in aiSortColumns];
871 asSortColumns.append('TestBoxesInSchedGroups.idTestBox');
872
873 if tsNow is None:
874 self._oDb.execute('''
875SELECT TestBoxesInSchedGroups.*,
876 TestBoxesWithStrings.*
877FROM TestBoxesInSchedGroups
878 LEFT OUTER JOIN TestBoxesWithStrings
879 ON TestBoxesWithStrings.idTestBox = TestBoxesInSchedGroups.idTestBox
880 AND TestBoxesWithStrings.tsExpire = 'infinity'::TIMESTAMP
881WHERE TestBoxesInSchedGroups.idSchedGroup = %s
882 AND TestBoxesInSchedGroups.tsExpire = 'infinity'::TIMESTAMP
883ORDER BY ''' + ', '.join(asSortColumns), (idSchedGroup, ));
884 else:
885 self._oDb.execute('''
886SELECT TestBoxesInSchedGroups.*,
887 TestBoxesWithStrings.*
888FROM TestBoxesInSchedGroups
889 LEFT OUTER JOIN TestBoxesWithStrings
890 ON TestBoxesWithStrings.idTestBox = TestBoxesInSchedGroups.idTestBox
891 AND TestBoxesWithStrings.tsExpire > %s
892 AND TestBoxesWithStrings.tsEffective <= %s
893WHERE TestBoxesInSchedGroups.idSchedGroup = %s
894 AND TestBoxesInSchedGroups.tsExpire > %s
895 AND TestBoxesInSchedGroups.tsEffective <= %s
896ORDER BY ''' + ', '.join(asSortColumns), (tsNow, tsNow, idSchedGroup, tsNow, tsNow, ));
897
898 aoRows = [];
899 for aoOne in self._oDb.fetchAll():
900 aoRows.append(TestBoxDataForSchedGroup().initFromDbRow(aoOne));
901 return aoRows;
902
903 def fetchForChangeLog(self, idTestBox, iStart, cMaxRows, tsNow): # pylint: disable=too-many-locals
904 """
905 Fetches change log entries for a testbox.
906
907 Returns an array of ChangeLogEntry instance and an indicator whether
908 there are more entries.
909 Raises exception on error.
910 """
911
912 ## @todo calc changes to scheduler group!
913
914 if tsNow is None:
915 tsNow = self._oDb.getCurrentTimestamp();
916
917 self._oDb.execute('SELECT TestBoxesWithStrings.*\n'
918 'FROM TestBoxesWithStrings\n'
919 'WHERE TestBoxesWithStrings.tsEffective <= %s\n'
920 ' AND TestBoxesWithStrings.idTestBox = %s\n'
921 'ORDER BY TestBoxesWithStrings.tsExpire DESC\n'
922 'LIMIT %s OFFSET %s\n'
923 , (tsNow, idTestBox, cMaxRows + 1, iStart,));
924
925 aoRows = [];
926 for aoDbRow in self._oDb.fetchAll():
927 aoRows.append(TestBoxData().initFromDbRow(aoDbRow));
928
929 # Calculate the changes.
930 aoEntries = [];
931 for i in xrange(0, len(aoRows) - 1):
932 oNew = aoRows[i];
933 oOld = aoRows[i + 1];
934 aoChanges = [];
935 for sAttr in oNew.getDataAttributes():
936 if sAttr not in [ 'tsEffective', 'tsExpire', 'uidAuthor', ]:
937 oOldAttr = getattr(oOld, sAttr);
938 oNewAttr = getattr(oNew, sAttr);
939 if oOldAttr != oNewAttr:
940 if sAttr == 'sReport':
941 aoChanges.append(AttributeChangeEntryPre(sAttr, oNewAttr, oOldAttr, str(oNewAttr), str(oOldAttr)));
942 else:
943 aoChanges.append(AttributeChangeEntry(sAttr, oNewAttr, oOldAttr, str(oNewAttr), str(oOldAttr)));
944 aoEntries.append(ChangeLogEntry(oNew.uidAuthor, None, oNew.tsEffective, oNew.tsExpire, oNew, oOld, aoChanges));
945
946 # If we're at the end of the log, add the initial entry.
947 if len(aoRows) <= cMaxRows and aoRows:
948 oNew = aoRows[-1];
949 aoEntries.append(ChangeLogEntry(oNew.uidAuthor, None, oNew.tsEffective, oNew.tsExpire, oNew, None, []));
950
951 UserAccountLogic(self._oDb).resolveChangeLogAuthors(aoEntries);
952 return (aoEntries, len(aoRows) > cMaxRows);
953
954 def _validateAndConvertData(self, oData, enmValidateFor):
955 # type: (TestBoxDataEx, str) -> None
956 """
957 Helper for addEntry and editEntry that validates the scheduling group IDs in
958 addtion to what's covered by the default validateAndConvert of the data object.
959
960 Raises exception on invalid input.
961 """
962 dDataErrors = oData.validateAndConvert(self._oDb, enmValidateFor);
963 if dDataErrors:
964 raise TMInvalidData('TestBoxLogic.addEntry: %s' % (dDataErrors,));
965 if isinstance(oData, TestBoxDataEx):
966 if oData.aoInSchedGroups:
967 sSchedGrps = ', '.join('(%s)' % oCur.idSchedGroup for oCur in oData.aoInSchedGroups);
968 self._oDb.execute('SELECT SchedGroupIDs.idSchedGroup\n'
969 'FROM (VALUES ' + sSchedGrps + ' ) AS SchedGroupIDs(idSchedGroup)\n'
970 ' LEFT OUTER JOIN SchedGroups\n'
971 ' ON SchedGroupIDs.idSchedGroup = SchedGroups.idSchedGroup\n'
972 ' AND SchedGroups.tsExpire = \'infinity\'::TIMESTAMP\n'
973 'WHERE SchedGroups.idSchedGroup IS NULL\n');
974 aaoRows = self._oDb.fetchAll();
975 if aaoRows:
976 raise TMInvalidData('TestBoxLogic.addEntry missing scheduling groups: %s'
977 % (', '.join(str(aoRow[0]) for aoRow in aaoRows),));
978 return None;
979
980 def addEntry(self, oData, uidAuthor, fCommit = False):
981 # type: (TestBoxDataEx, int, bool) -> (int, int, datetime.datetime)
982 """
983 Creates a testbox in the database.
984 Returns the testbox ID, testbox generation ID and effective timestamp
985 of the created testbox on success. Throws error on failure.
986 """
987
988 #
989 # Validate. Extra work because of missing foreign key (due to history).
990 #
991 self._validateAndConvertData(oData, oData.ksValidateFor_Add);
992
993 #
994 # Do it.
995 #
996 self._oDb.callProc('TestBoxLogic_addEntry'
997 , ( uidAuthor,
998 oData.ip, # Should we allow setting the IP?
999 oData.uuidSystem,
1000 oData.sName,
1001 oData.sDescription,
1002 oData.fEnabled,
1003 oData.enmLomKind,
1004 oData.ipLom,
1005 oData.pctScaleTimeout,
1006 oData.sComment,
1007 oData.enmPendingCmd, ) );
1008 (idTestBox, idGenTestBox, tsEffective) = self._oDb.fetchOne();
1009
1010 for oInSchedGrp in oData.aoInSchedGroups:
1011 self._oDb.callProc('TestBoxLogic_addGroupEntry',
1012 ( uidAuthor, idTestBox, oInSchedGrp.idSchedGroup, oInSchedGrp.iSchedPriority,) );
1013
1014 self._oDb.maybeCommit(fCommit);
1015 return (idTestBox, idGenTestBox, tsEffective);
1016
1017
1018 def editEntry(self, oData, uidAuthor, fCommit = False):
1019 """
1020 Data edit update, web UI is the primary user.
1021
1022 oData is either TestBoxDataEx or TestBoxData. The latter is for enabling
1023 Returns the new generation ID and effective date.
1024 """
1025
1026 #
1027 # Validate.
1028 #
1029 self._validateAndConvertData(oData, oData.ksValidateFor_Edit);
1030
1031 #
1032 # Get current data.
1033 #
1034 oOldData = TestBoxDataEx().initFromDbWithId(self._oDb, oData.idTestBox);
1035
1036 #
1037 # Do it.
1038 #
1039 if not oData.isEqualEx(oOldData, [ 'tsEffective', 'tsExpire', 'uidAuthor', 'aoInSchedGroups', ]
1040 + TestBoxData.kasMachineSettableOnly ):
1041 self._oDb.callProc('TestBoxLogic_editEntry'
1042 , ( uidAuthor,
1043 oData.idTestBox,
1044 oData.ip, # Should we allow setting the IP?
1045 oData.uuidSystem,
1046 oData.sName,
1047 oData.sDescription,
1048 oData.fEnabled,
1049 oData.enmLomKind,
1050 oData.ipLom,
1051 oData.pctScaleTimeout,
1052 oData.sComment,
1053 oData.enmPendingCmd, ));
1054 (idGenTestBox, tsEffective) = self._oDb.fetchOne();
1055 else:
1056 idGenTestBox = oOldData.idGenTestBox;
1057 tsEffective = oOldData.tsEffective;
1058
1059 if isinstance(oData, TestBoxDataEx):
1060 # Calc in-group changes.
1061 aoRemoved = list(oOldData.aoInSchedGroups);
1062 aoNew = [];
1063 aoUpdated = [];
1064 for oNewInGroup in oData.aoInSchedGroups:
1065 oOldInGroup = None;
1066 for iCur, oCur in enumerate(aoRemoved):
1067 if oCur.idSchedGroup == oNewInGroup.idSchedGroup:
1068 oOldInGroup = aoRemoved.pop(iCur);
1069 break;
1070 if oOldInGroup is None:
1071 aoNew.append(oNewInGroup);
1072 elif oNewInGroup.iSchedPriority != oOldInGroup.iSchedPriority:
1073 aoUpdated.append(oNewInGroup);
1074
1075 # Remove in-groups.
1076 for oInGroup in aoRemoved:
1077 self._oDb.callProc('TestBoxLogic_removeGroupEntry', (uidAuthor, oData.idTestBox, oInGroup.idSchedGroup, ));
1078
1079 # Add new ones.
1080 for oInGroup in aoNew:
1081 self._oDb.callProc('TestBoxLogic_addGroupEntry',
1082 ( uidAuthor, oData.idTestBox, oInGroup.idSchedGroup, oInGroup.iSchedPriority, ) );
1083
1084 # Edit existing ones.
1085 for oInGroup in aoUpdated:
1086 self._oDb.callProc('TestBoxLogic_editGroupEntry',
1087 ( uidAuthor, oData.idTestBox, oInGroup.idSchedGroup, oInGroup.iSchedPriority, ) );
1088 else:
1089 assert isinstance(oData, TestBoxData);
1090
1091 self._oDb.maybeCommit(fCommit);
1092 return (idGenTestBox, tsEffective);
1093
1094
1095 def removeEntry(self, uidAuthor, idTestBox, fCascade = False, fCommit = False):
1096 """
1097 Delete test box and scheduling group associations.
1098 """
1099 self._oDb.callProc('TestBoxLogic_removeEntry'
1100 , ( uidAuthor, idTestBox, fCascade,));
1101 self._oDb.maybeCommit(fCommit);
1102 return True;
1103
1104
1105 def updateOnSignOn(self, idTestBox, idGenTestBox, sTestBoxAddr, sOs, sOsVersion, # pylint: disable=too-many-arguments,too-many-locals
1106 sCpuVendor, sCpuArch, sCpuName, lCpuRevision, cCpus, fCpuHwVirt, fCpuNestedPaging, fCpu64BitGuest,
1107 fChipsetIoMmu, fRawMode, cMbMemory, cMbScratch, sReport, iTestBoxScriptRev, iPythonHexVersion):
1108 """
1109 Update the testbox attributes automatically on behalf of the testbox script.
1110 Returns the new generation id on success, raises an exception on failure.
1111 """
1112 _ = idGenTestBox;
1113 self._oDb.callProc('TestBoxLogic_updateOnSignOn'
1114 , ( idTestBox,
1115 sTestBoxAddr,
1116 sOs,
1117 sOsVersion,
1118 sCpuVendor,
1119 sCpuArch,
1120 sCpuName,
1121 lCpuRevision,
1122 cCpus,
1123 fCpuHwVirt,
1124 fCpuNestedPaging,
1125 fCpu64BitGuest,
1126 fChipsetIoMmu,
1127 fRawMode,
1128 cMbMemory,
1129 cMbScratch,
1130 sReport,
1131 iTestBoxScriptRev,
1132 iPythonHexVersion,));
1133 return self._oDb.fetchOne()[0];
1134
1135
1136 def setCommand(self, idTestBox, sOldCommand, sNewCommand, uidAuthor = None, fCommit = False, sComment = None):
1137 """
1138 Sets or resets the pending command on a testbox.
1139 Returns (idGenTestBox, tsEffective) of the new row.
1140 """
1141 ## @todo throw TMInFligthCollision again...
1142 self._oDb.callProc('TestBoxLogic_setCommand'
1143 , ( uidAuthor, idTestBox, sOldCommand, sNewCommand, sComment,));
1144 aoRow = self._oDb.fetchOne();
1145 self._oDb.maybeCommit(fCommit);
1146 return (aoRow[0], aoRow[1]);
1147
1148
1149 def getAll(self):
1150 """
1151 Retrieve list of all registered Test Box records from DB.
1152 """
1153 self._oDb.execute('SELECT *\n'
1154 'FROM TestBoxesWithStrings\n'
1155 'WHERE tsExpire=\'infinity\'::timestamp\n'
1156 'ORDER BY sName')
1157
1158 aaoRows = self._oDb.fetchAll()
1159 aoRet = []
1160 for aoRow in aaoRows:
1161 aoRet.append(TestBoxData().initFromDbRow(aoRow))
1162 return aoRet
1163
1164
1165 def cachedLookup(self, idTestBox):
1166 # type: (int) -> TestBoxDataEx
1167 """
1168 Looks up the most recent TestBoxData object for idTestBox via
1169 an object cache.
1170
1171 Returns a shared TestBoxDataEx object. None if not found.
1172 Raises exception on DB error.
1173 """
1174 if self.dCache is None:
1175 self.dCache = self._oDb.getCache('TestBoxData');
1176 oEntry = self.dCache.get(idTestBox, None);
1177 if oEntry is None:
1178 fNeedNow = False;
1179 self._oDb.execute('SELECT TestBoxesWithStrings.*\n'
1180 'FROM TestBoxesWithStrings\n'
1181 'WHERE idTestBox = %s\n'
1182 ' AND tsExpire = \'infinity\'::TIMESTAMP\n'
1183 , (idTestBox, ));
1184 if self._oDb.getRowCount() == 0:
1185 # Maybe it was deleted, try get the last entry.
1186 self._oDb.execute('SELECT TestBoxesWithStrings.*\n'
1187 'FROM TestBoxesWithStrings\n'
1188 'WHERE idTestBox = %s\n'
1189 'ORDER BY tsExpire DESC\n'
1190 'LIMIT 1\n'
1191 , (idTestBox, ));
1192 fNeedNow = True;
1193 elif self._oDb.getRowCount() > 1:
1194 raise self._oDb.integrityException('%s infinity rows for %s' % (self._oDb.getRowCount(), idTestBox));
1195
1196 if self._oDb.getRowCount() == 1:
1197 aaoRow = self._oDb.fetchOne();
1198 if not fNeedNow:
1199 oEntry = TestBoxDataEx().initFromDbRowEx(aaoRow, self._oDb);
1200 else:
1201 oEntry = TestBoxDataEx().initFromDbRow(aaoRow);
1202 oEntry.initFromDbRowEx(aaoRow, self._oDb, tsNow = db.dbTimestampMinusOneTick(oEntry.tsExpire));
1203 self.dCache[idTestBox] = oEntry;
1204 return oEntry;
1205
1206
1207
1208 #
1209 # The virtual test sheriff interface.
1210 #
1211
1212 def hasTestBoxRecentlyBeenRebooted(self, idTestBox, cHoursBack = 2, tsNow = None):
1213 """
1214 Checks if the testbox has been rebooted in the specified time period.
1215
1216 This does not include already pending reboots, though under some
1217 circumstances it may. These being the test box entry being edited for
1218 other reasons.
1219
1220 Returns True / False.
1221 """
1222 if tsNow is None:
1223 tsNow = self._oDb.getCurrentTimestamp();
1224 self._oDb.execute('SELECT COUNT(idTestBox)\n'
1225 'FROM TestBoxes\n'
1226 'WHERE idTestBox = %s\n'
1227 ' AND tsExpire < %s\n'
1228 ' AND tsExpire >= %s - interval \'%s hours\'\n'
1229 ' AND enmPendingCmd IN (%s, %s)\n'
1230 , ( idTestBox, tsNow, tsNow, cHoursBack,
1231 TestBoxData.ksTestBoxCmd_Reboot, TestBoxData.ksTestBoxCmd_UpgradeAndReboot, ));
1232 return self._oDb.fetchOne()[0] > 0;
1233
1234
1235 def rebootTestBox(self, idTestBox, uidAuthor, sComment, sOldCommand = TestBoxData.ksTestBoxCmd_None, fCommit = False):
1236 """
1237 Issues a reboot command for the given test box.
1238 Return True on succes, False on in-flight collision.
1239 May raise DB exception on other trouble.
1240 """
1241 try:
1242 self.setCommand(idTestBox, sOldCommand, TestBoxData.ksTestBoxCmd_Reboot,
1243 uidAuthor = uidAuthor, fCommit = fCommit, sComment = sComment);
1244 except TMInFligthCollision:
1245 return False;
1246 return True;
1247
1248
1249 def disableTestBox(self, idTestBox, uidAuthor, sComment, fCommit = False):
1250 """
1251 Disables the given test box.
1252
1253 Raises exception on trouble, without rollback.
1254 """
1255 oTestBox = TestBoxData().initFromDbWithId(self._oDb, idTestBox);
1256 if oTestBox.fEnabled:
1257 oTestBox.fEnabled = False;
1258 if sComment is not None:
1259 oTestBox.sComment = sComment;
1260 self.editEntry(oTestBox, uidAuthor = uidAuthor, fCommit = fCommit);
1261 return None;
1262
1263
1264#
1265# Unit testing.
1266#
1267
1268# pylint: disable=missing-docstring
1269class TestBoxDataTestCase(ModelDataBaseTestCase):
1270 def setUp(self):
1271 self.aoSamples = [TestBoxData(),];
1272
1273if __name__ == '__main__':
1274 unittest.main();
1275 # not reached.
1276
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