VirtualBox

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

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

TestManager: Adding test box selection to the scheduling group form (left over from r107843 where test boxes gained the ability to service more than one scheduling group).

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