VirtualBox

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

Last change on this file since 61468 was 61468, checked in by vboxsync, 9 years ago

testmanager: Adding sComment and fRawMode fields to TestBoxes and moves the strings into a separate shared string table.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 33.3 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: testbox.py 61468 2016-06-05 02:55:32Z vboxsync $
3
4"""
5Test Manager - TestBox.
6"""
7
8__copyright__ = \
9"""
10Copyright (C) 2012-2015 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: 61468 $"
30
31
32# Standard python imports.
33import unittest;
34
35# Validation Kit imports.
36from testmanager.core.base import ModelDataBase, ModelDataBaseTestCase, ModelLogicBase, TMInFligthCollision, \
37 TMInvalidData, TMTooManyRows, TMRowNotFound, ChangeLogEntry, AttributeChangeEntry;
38from testmanager.core.useraccount import UserAccountLogic;
39
40
41# pylint: disable=C0103
42class TestBoxData(ModelDataBase): # pylint: disable=R0902
43 """
44 TestBox Data.
45 """
46
47 ## LomKind_T
48 ksLomKind_None = 'none';
49 ksLomKind_ILOM = 'ilom';
50 ksLomKind_ELOM = 'elom';
51 ksLomKind_AppleXserveLom = 'apple-xserver-lom';
52 kasLomKindValues = [ ksLomKind_None, ksLomKind_ILOM, ksLomKind_ELOM, ksLomKind_AppleXserveLom];
53 kaoLomKindDescs = \
54 [
55 ( ksLomKind_None, 'None', ''),
56 ( ksLomKind_ILOM, 'ILOM', ''),
57 ( ksLomKind_ELOM, 'ELOM', ''),
58 ( ksLomKind_AppleXserveLom, 'Apple Xserve LOM', ''),
59 ];
60
61
62 ## TestBoxCmd_T
63 ksTestBoxCmd_None = 'none';
64 ksTestBoxCmd_Abort = 'abort';
65 ksTestBoxCmd_Reboot = 'reboot';
66 ksTestBoxCmd_Upgrade = 'upgrade';
67 ksTestBoxCmd_UpgradeAndReboot = 'upgrade-and-reboot';
68 ksTestBoxCmd_Special = 'special';
69 kasTestBoxCmdValues = [ ksTestBoxCmd_None, ksTestBoxCmd_Abort, ksTestBoxCmd_Reboot, ksTestBoxCmd_Upgrade,
70 ksTestBoxCmd_UpgradeAndReboot, ksTestBoxCmd_Special];
71 kaoTestBoxCmdDescs = \
72 [
73 ( ksTestBoxCmd_None, 'None', ''),
74 ( ksTestBoxCmd_Abort, 'Abort current test', ''),
75 ( ksTestBoxCmd_Reboot, 'Reboot TestBox', ''),
76 ( ksTestBoxCmd_Upgrade, 'Upgrade TestBox Script', ''),
77 ( ksTestBoxCmd_UpgradeAndReboot, 'Upgrade TestBox Script and reboot', ''),
78 ( ksTestBoxCmd_Special, 'Special (reserved)', ''),
79 ];
80
81
82 ksIdAttr = 'idTestBox';
83 ksIdGenAttr = 'idGenTestBox';
84
85 ksParam_idTestBox = 'TestBox_idTestBox';
86 ksParam_tsEffective = 'TestBox_tsEffective';
87 ksParam_tsExpire = 'TestBox_tsExpire';
88 ksParam_uidAuthor = 'TestBox_uidAuthor';
89 ksParam_idGenTestBox = 'TestBox_idGenTestBox';
90 ksParam_ip = 'TestBox_ip';
91 ksParam_uuidSystem = 'TestBox_uuidSystem';
92 ksParam_sName = 'TestBox_sName';
93 ksParam_sDescription = 'TestBox_sDescription';
94 ksParam_idSchedGroup = 'TestBox_idSchedGroup';
95 ksParam_fEnabled = 'TestBox_fEnabled';
96 ksParam_enmLomKind = 'TestBox_enmLomKind';
97 ksParam_ipLom = 'TestBox_ipLom';
98 ksParam_pctScaleTimeout = 'TestBox_pctScaleTimeout';
99 ksParam_sComment = 'TestBox_sComment';
100 ksParam_sOs = 'TestBox_sOs';
101 ksParam_sOsVersion = 'TestBox_sOsVersion';
102 ksParam_sCpuVendor = 'TestBox_sCpuVendor';
103 ksParam_sCpuArch = 'TestBox_sCpuArch';
104 ksParam_sCpuName = 'TestBox_sCpuName';
105 ksParam_lCpuRevision = 'TestBox_lCpuRevision';
106 ksParam_cCpus = 'TestBox_cCpus';
107 ksParam_fCpuHwVirt = 'TestBox_fCpuHwVirt';
108 ksParam_fCpuNestedPaging = 'TestBox_fCpuNestedPaging';
109 ksParam_fCpu64BitGuest = 'TestBox_fCpu64BitGuest';
110 ksParam_fChipsetIoMmu = 'TestBox_fChipsetIoMmu';
111 ksParam_fRawMode = 'TestBox_fRawMode';
112 ksParam_cMbMemory = 'TestBox_cMbMemory';
113 ksParam_cMbScratch = 'TestBox_cMbScratch';
114 ksParam_sReport = 'TestBox_sReport';
115 ksParam_iTestBoxScriptRev = 'TestBox_iTestBoxScriptRev';
116 ksParam_iPythonHexVersion = 'TestBox_iPythonHexVersion';
117 ksParam_enmPendingCmd = 'TestBox_enmPendingCmd';
118
119 kasInternalAttributes = [ 'idStrDescription', 'idStrComment', 'idStrOs', 'idStrOsVersion', 'idStrCpuVendor',
120 'idStrCpuArch', 'idStrCpuName', 'idStrReport', ];
121 kasAllowNullAttributes = ['idTestBox', 'tsEffective', 'tsExpire', 'uidAuthor', 'idGenTestBox', 'sDescription',
122 'ipLom', 'sComment', 'sOs', 'sOsVersion', 'sCpuVendor', 'sCpuArch', 'sCpuName',
123 'lCpuRevision', 'cCpus', 'fCpuHwVirt', 'fCpuNestedPaging', 'fCpu64BitGuest', 'fChipsetIoMmu',
124 'fRawMode', 'cMbMemory', 'cMbScratch', 'sReport', 'iTestBoxScriptRev', 'iPythonHexVersion',
125 ] + kasInternalAttributes;
126
127 kasValidValues_enmLomKind = kasLomKindValues;
128 kasValidValues_enmPendingCmd = kasTestBoxCmdValues;
129 kiMin_pctScaleTimeout = 11;
130 kiMax_pctScaleTimeout = 19999;
131 kcchMax_sReport = 65535;
132
133
134 kcDbColumns = 41; # including the 7 string joins columns
135
136 def __init__(self):
137 ModelDataBase.__init__(self);
138
139 #
140 # Initialize with defaults.
141 # See the database for explanations of each of these fields.
142 #
143 self.idTestBox = None;
144 self.tsEffective = None;
145 self.tsExpire = None;
146 self.uidAuthor = None;
147 self.idGenTestBox = None;
148 self.ip = None;
149 self.uuidSystem = None;
150 self.sName = None;
151 self.idStrDescription = None;
152 self.idSchedGroup = 1;
153 self.fEnabled = False;
154 self.enmLomKind = self.ksLomKind_None;
155 self.ipLom = None;
156 self.pctScaleTimeout = 100;
157 self.idStrComment = None;
158 self.idStrOs = None;
159 self.idStrOsVersion = None;
160 self.idStrCpuVendor = None;
161 self.idStrCpuArch = None;
162 self.idStrCpuName = None;
163 self.lCpuRevision = None;
164 self.cCpus = 1;
165 self.fCpuHwVirt = False;
166 self.fCpuNestedPaging = False;
167 self.fCpu64BitGuest = False;
168 self.fChipsetIoMmu = False;
169 self.fRawMode = None;
170 self.cMbMemory = 1;
171 self.cMbScratch = 0;
172 self.idStrReport = None;
173 self.iTestBoxScriptRev = 0;
174 self.iPythonHexVersion = 0;
175 self.enmPendingCmd = self.ksTestBoxCmd_None;
176 # String table values.
177 self.sDescription = None;
178 self.sComment = None;
179 self.sOs = None;
180 self.sOsVersion = None;
181 self.sCpuVendor = None;
182 self.sCpuArch = None;
183 self.sCpuName = None;
184 self.sReport = None;
185
186 def initFromDbRow(self, aoRow):
187 """
188 Internal worker for initFromDbWithId and initFromDbWithGenId as well as
189 from TestBoxLogic. Expecting the result from a query like this:
190 SELECT TestBoxesWithStrings.* FROM TestBoxesWithStrings
191 """
192 if aoRow is None:
193 raise TMRowNotFound('TestBox not found.');
194
195 self.idTestBox = aoRow[0];
196 self.tsEffective = aoRow[1];
197 self.tsExpire = aoRow[2];
198 self.uidAuthor = aoRow[3];
199 self.idGenTestBox = aoRow[4];
200 self.ip = aoRow[5];
201 self.uuidSystem = aoRow[6];
202 self.sName = aoRow[7];
203 self.idStrDescription = aoRow[8];
204 self.idSchedGroup = aoRow[9];
205 self.fEnabled = aoRow[10];
206 self.enmLomKind = aoRow[11];
207 self.ipLom = aoRow[12];
208 self.pctScaleTimeout = aoRow[13];
209 self.idStrComment = aoRow[14];
210 self.idStrOs = aoRow[15];
211 self.idStrOsVersion = aoRow[16];
212 self.idStrCpuVendor = aoRow[17];
213 self.idStrCpuArch = aoRow[18];
214 self.idStrCpuName = aoRow[19];
215 self.lCpuRevision = aoRow[20];
216 self.cCpus = aoRow[21];
217 self.fCpuHwVirt = aoRow[22];
218 self.fCpuNestedPaging = aoRow[23];
219 self.fCpu64BitGuest = aoRow[24];
220 self.fChipsetIoMmu = aoRow[25];
221 self.fRawMode = aoRow[26];
222 self.cMbMemory = aoRow[27];
223 self.cMbScratch = aoRow[28];
224 self.idStrReport = aoRow[29];
225 self.iTestBoxScriptRev = aoRow[30];
226 self.iPythonHexVersion = aoRow[31];
227 self.enmPendingCmd = aoRow[32];
228
229 # String table values.
230 if len(aoRow) > 32:
231 self.sDescription = aoRow[33];
232 self.sComment = aoRow[34];
233 self.sOs = aoRow[35];
234 self.sOsVersion = aoRow[36];
235 self.sCpuVendor = aoRow[37];
236 self.sCpuArch = aoRow[38];
237 self.sCpuName = aoRow[39];
238 self.sReport = aoRow[40];
239
240 return self;
241
242 def initFromDbWithId(self, oDb, idTestBox, tsNow = None, sPeriodBack = None):
243 """
244 Initialize the object from the database.
245 """
246 oDb.execute(self.formatSimpleNowAndPeriodQuery(oDb,
247 'SELECT TestBoxesWithStrings.*\n'
248 'FROM TestBoxesWithStrings\n'
249 'WHERE idTestBox = %s\n'
250 , ( idTestBox, ), tsNow, sPeriodBack));
251 aoRow = oDb.fetchOne()
252 if aoRow is None:
253 raise TMRowNotFound('idTestBox=%s not found (tsNow=%s sPeriodBack=%s)' % (idTestBox, tsNow, sPeriodBack,));
254 return self.initFromDbRow(aoRow);
255
256 def initFromDbWithGenId(self, oDb, idGenTestBox):
257 """
258 Initialize the object from the database.
259 """
260 oDb.execute('SELECT TestBoxesWithStrings.*\n'
261 'FROM TestBoxesWithStrings\n'
262 'WHERE idGenTestBox = %s\n'
263 , (idGenTestBox, ) );
264 return self.initFromDbRow(oDb.fetchOne());
265
266 def _validateAndConvertWorker(self, asAllowNullAttributes, oDb, enmValidateFor = ModelDataBase.ksValidateFor_Other):
267 # Override to do extra ipLom checks.
268 dErrors = ModelDataBase._validateAndConvertWorker(self, asAllowNullAttributes, oDb, enmValidateFor);
269 if self.ksParam_ipLom not in dErrors \
270 and self.ksParam_enmLomKind not in dErrors \
271 and self.enmLomKind != self.ksLomKind_None \
272 and self.ipLom is None:
273 dErrors[self.ksParam_ipLom] = 'Light-out-management IP is mandatory and a LOM is selected.'
274 return dErrors;
275
276 def formatPythonVersion(self):
277 """
278 Unbuttons the version number and formats it as a version string.
279 """
280 if self.iPythonHexVersion is None:
281 return 'N/A';
282 return 'v%d.%d.%d.%d' \
283 % ( self.iPythonHexVersion >> 24,
284 (self.iPythonHexVersion >> 16) & 0xff,
285 (self.iPythonHexVersion >> 8) & 0xff,
286 self.iPythonHexVersion & 0xff);
287
288 def getCpuFamily(self):
289 """ Returns the CPU family for a x86 or amd64 testboxes."""
290 if self.lCpuRevision is None:
291 return 0;
292 return (self.lCpuRevision >> 24 & 0xff);
293
294 def getCpuModel(self):
295 """ Returns the CPU model for a x86 or amd64 testboxes."""
296 if self.lCpuRevision is None:
297 return 0;
298 return (self.lCpuRevision >> 8 & 0xffff);
299
300 def getCpuStepping(self):
301 """ Returns the CPU stepping for a x86 or amd64 testboxes."""
302 if self.lCpuRevision is None:
303 return 0;
304 return (self.lCpuRevision & 0xff);
305
306 # The following is a translation of the g_aenmIntelFamily06 array in CPUMR3CpuId.cpp:
307 kdIntelFamily06 = {
308 0x00: 'P6',
309 0x01: 'P6',
310 0x03: 'P6_II',
311 0x05: 'P6_II',
312 0x06: 'P6_II',
313 0x07: 'P6_III',
314 0x08: 'P6_III',
315 0x09: 'P6_M_Banias',
316 0x0a: 'P6_III',
317 0x0b: 'P6_III',
318 0x0d: 'P6_M_Dothan',
319 0x0e: 'Core_Yonah',
320 0x0f: 'Core2_Merom',
321 0x15: 'P6_M_Dothan',
322 0x16: 'Core2_Merom',
323 0x17: 'Core2_Penryn',
324 0x1a: 'Core7_Nehalem',
325 0x1c: 'Atom_Bonnell',
326 0x1d: 'Core2_Penryn',
327 0x1e: 'Core7_Nehalem',
328 0x1f: 'Core7_Nehalem',
329 0x25: 'Core7_Westmere',
330 0x26: 'Atom_Lincroft',
331 0x27: 'Atom_Saltwell',
332 0x2a: 'Core7_SandyBridge',
333 0x2c: 'Core7_Westmere',
334 0x2d: 'Core7_SandyBridge',
335 0x2e: 'Core7_Nehalem',
336 0x2f: 'Core7_Westmere',
337 0x35: 'Atom_Saltwell',
338 0x36: 'Atom_Saltwell',
339 0x37: 'Atom_Silvermont',
340 0x3a: 'Core7_IvyBridge',
341 0x3c: 'Core7_Haswell',
342 0x3d: 'Core7_Broadwell',
343 0x3e: 'Core7_IvyBridge',
344 0x3f: 'Core7_Haswell',
345 0x45: 'Core7_Haswell',
346 0x46: 'Core7_Haswell',
347 0x47: 'Core7_Broadwell',
348 0x4a: 'Atom_Silvermont',
349 0x4c: 'Atom_Airmount',
350 0x4d: 'Atom_Silvermont',
351 0x4e: 'Core7_Skylake',
352 0x4f: 'Core7_Broadwell',
353 0x55: 'Core7_Skylake',
354 0x56: 'Core7_Broadwell',
355 0x5a: 'Atom_Silvermont',
356 0x5c: 'Atom_Goldmont',
357 0x5d: 'Atom_Silvermont',
358 0x5e: 'Core7_Skylake',
359 0x66: 'Core7_Cannonlake',
360 };
361 # Also from CPUMR3CpuId.cpp, but the switch.
362 kdIntelFamily15 = {
363 0x00: 'NB_Willamette',
364 0x01: 'NB_Willamette',
365 0x02: 'NB_Northwood',
366 0x03: 'NB_Prescott',
367 0x04: 'NB_Prescott2M',
368 0x05: 'NB_Unknown',
369 0x06: 'NB_CedarMill',
370 0x07: 'NB_Gallatin',
371 };
372
373 def queryCpuMicroarch(self):
374 """ Try guess the microarch name for the cpu. Returns None if we cannot. """
375 if self.lCpuRevision is None or self.sCpuVendor is None:
376 return None;
377 uFam = self.getCpuFamily();
378 uMod = self.getCpuModel();
379 if self.sCpuVendor == 'GenuineIntel':
380 if uFam == 6:
381 return self.kdIntelFamily06.get(uMod, None);
382 if uFam == 15:
383 return self.kdIntelFamily15.get(uMod, None);
384 elif self.sCpuVendor == 'AuthenticAMD':
385 if uFam == 0xf:
386 if uMod < 0x10: return 'K8_130nm';
387 if uMod >= 0x60 and uMod < 0x80: return 'K8_65nm';
388 if uMod >= 0x40: return 'K8_90nm_AMDV';
389 if uMod in [0x21, 0x23, 0x2b, 0x37, 0x3f]: return 'K8_90nm_DualCore';
390 return 'AMD_K8_90nm';
391 if uFam == 0x10: return 'K10';
392 if uFam == 0x11: return 'K10_Lion';
393 if uFam == 0x12: return 'K10_Llano';
394 if uFam == 0x14: return 'Bobcat';
395 if uFam == 0x15:
396 if uMod <= 0x01: return 'Bulldozer';
397 if uMod in [0x02, 0x10, 0x13]: return 'Piledriver';
398 return None;
399 if uFam == 0x16:
400 return 'Jaguar';
401 elif self.sCpuVendor == 'CentaurHauls':
402 if uFam == 0x05:
403 if uMod == 0x01: return 'Centaur_C6';
404 if uMod == 0x04: return 'Centaur_C6';
405 if uMod == 0x08: return 'Centaur_C2';
406 if uMod == 0x09: return 'Centaur_C3';
407 if uFam == 0x06:
408 if uMod == 0x05: return 'VIA_C3_M2';
409 if uMod == 0x06: return 'VIA_C3_C5A';
410 if uMod == 0x07: return 'VIA_C3_C5B' if self.getCpuStepping() < 8 else 'VIA_C3_C5C';
411 if uMod == 0x08: return 'VIA_C3_C5N';
412 if uMod == 0x09: return 'VIA_C3_C5XL' if self.getCpuStepping() < 8 else 'VIA_C3_C5P';
413 if uMod == 0x0a: return 'VIA_C7_C5J';
414 if uMod == 0x0f: return 'VIA_Isaiah';
415 return None;
416
417 def getPrettyCpuVersion(self):
418 """ Pretty formatting of the family/model/stepping with microarch optimizations. """
419 if self.lCpuRevision is None or self.sCpuVendor is None:
420 return u'<none>';
421 sMarch = self.queryCpuMicroarch();
422 if sMarch is not None:
423 return '%s m%02X s%02X' % (sMarch, self.getCpuModel(), self.getCpuStepping());
424 return 'fam%02X m%02X s%02X' % (self.getCpuFamily(), self.getCpuModel(), self.getCpuStepping());
425
426 def getArchBitString(self):
427 """ Returns 32-bit, 64-bit, <none>, or sCpuArch. """
428 if self.sCpuArch is None:
429 return '<none>';
430 if self.sCpuArch in [ 'x86',]:
431 return '32-bit';
432 if self.sCpuArch in [ 'amd64',]:
433 return '64-bit';
434 return self.sCpuArch;
435
436 def getPrettyCpuVendor(self):
437 """ Pretty vendor name."""
438 if self.sCpuVendor is None:
439 return '<none>';
440 if self.sCpuVendor == 'GenuineIntel': return 'Intel';
441 if self.sCpuVendor == 'AuthenticAMD': return 'AMD';
442 if self.sCpuVendor == 'CentaurHauls': return 'VIA';
443 return self.sCpuVendor;
444
445
446
447class TestBoxLogic(ModelLogicBase):
448 """
449 TestBox logic.
450 """
451
452
453 def __init__(self, oDb):
454 ModelLogicBase.__init__(self, oDb);
455 self.dCache = None;
456
457 def tryFetchTestBoxByUuid(self, sTestBoxUuid):
458 """
459 Tries to fetch a testbox by its UUID alone.
460 """
461 self._oDb.execute('SELECT TestBoxesWithStrings.*\n'
462 'FROM TestBoxesWithStrings\n'
463 'WHERE uuidSystem = %s\n'
464 ' AND tsExpire = \'infinity\'::timestamp\n'
465 'ORDER BY tsEffective DESC\n',
466 (sTestBoxUuid,));
467 if self._oDb.getRowCount() == 0:
468 return None;
469 if self._oDb.getRowCount() != 1:
470 raise TMTooManyRows('Database integrity error: %u hits' % (self._oDb.getRowCount(),));
471 oData = TestBoxData();
472 oData.initFromDbRow(self._oDb.fetchOne());
473 return oData;
474
475 def fetchForListing(self, iStart, cMaxRows, tsNow):
476 """
477 Fetches testboxes for listing.
478
479 Returns an array (list) of TestBoxDataForListing items, empty list if none.
480 The TestBoxDataForListing instances are just TestBoxData with two extra
481 members, an extra oStatus member that is either None or a TestBoxStatusData
482 instance, and a member tsCurrent holding CURRENT_TIMESTAMP.
483
484 Raises exception on error.
485 """
486 class TestBoxDataForListing(TestBoxData):
487 """ We add two members for the listing. """
488 def __init__(self):
489 TestBoxData.__init__(self);
490 self.tsCurrent = None; # CURRENT_TIMESTAMP
491 self.oStatus = None; # TestBoxStatusData
492
493 from testmanager.core.testboxstatus import TestBoxStatusData;
494
495 if tsNow is None:
496 self._oDb.execute('SELECT TestBoxesWithStrings.*,\n'
497 ' TestBoxStatuses.*\n'
498 'FROM TestBoxesWithStrings\n'
499 ' LEFT OUTER JOIN TestBoxStatuses\n'
500 ' ON TestBoxStatuses.idTestBox = TestBoxesWithStrings.idTestBox\n'
501 'WHERE TestBoxesWithStrings.tsExpire = \'infinity\'::TIMESTAMP\n'
502 'ORDER BY TestBoxesWithStrings.sName\n'
503 'LIMIT %s OFFSET %s\n'
504 , (cMaxRows, iStart,));
505 else:
506 self._oDb.execute('SELECT TestBoxesWithStrings.*,\n'
507 ' TestBoxStatuses.*\n'
508 'FROM TestBoxesWithStrings\n'
509 ' LEFT OUTER JOIN TestBoxStatuses\n'
510 ' ON TestBoxStatuses.idTestBox = TestBoxesWithStrings.idTestBox\n'
511 'WHERE tsExpire > %s\n'
512 ' AND tsEffective <= %s\n'
513 'ORDER BY TestBoxesWithStrings.sName\n'
514 'LIMIT %s OFFSET %s\n'
515 , ( tsNow, tsNow, cMaxRows, iStart,));
516
517 aoRows = [];
518 for aoOne in self._oDb.fetchAll():
519 oTestBox = TestBoxDataForListing().initFromDbRow(aoOne);
520 oTestBox.tsCurrent = self._oDb.getCurrentTimestamp();
521 if aoOne[TestBoxData.kcDbColumns] is not None:
522 oTestBox.oStatus = TestBoxStatusData().initFromDbRow(aoOne[TestBoxData.kcDbColumns:]);
523 aoRows.append(oTestBox);
524 return aoRows;
525
526 def fetchForChangeLog(self, idTestBox, iStart, cMaxRows, tsNow): # pylint: disable=R0914
527 """
528 Fetches change log entries for a testbox.
529
530 Returns an array of ChangeLogEntry instance and an indicator whether
531 there are more entries.
532 Raises exception on error.
533 """
534
535 if tsNow is None:
536 tsNow = self._oDb.getCurrentTimestamp();
537
538 self._oDb.execute('SELECT TestBoxesWithStrings.*\n'
539 'FROM TestBoxesWithStrings\n'
540 'WHERE TestBoxesWithStrings.tsEffective <= %s\n'
541 ' AND TestBoxesWithStrings.idTestBox = %s\n'
542 'ORDER BY TestBoxesWithStrings.tsExpire DESC\n'
543 'LIMIT %s OFFSET %s\n'
544 , (tsNow, idTestBox, cMaxRows + 1, iStart,));
545
546 aoRows = [];
547 for aoDbRow in self._oDb.fetchAll():
548 aoRows.append(TestBoxData().initFromDbRow(aoDbRow));
549
550 # Calculate the changes.
551 aoEntries = [];
552 for i in xrange(0, len(aoRows) - 1):
553 oNew = aoRows[i];
554 oOld = aoRows[i + 1];
555 aoChanges = [];
556 for sAttr in oNew.getDataAttributes():
557 if sAttr not in [ 'tsEffective', 'tsExpire', 'uidAuthor', ]:
558 oOldAttr = getattr(oOld, sAttr);
559 oNewAttr = getattr(oNew, sAttr);
560 if oOldAttr != oNewAttr:
561 aoChanges.append(AttributeChangeEntry(sAttr, oNewAttr, oOldAttr, str(oNewAttr), str(oOldAttr)));
562 aoEntries.append(ChangeLogEntry(oNew.uidAuthor, None, oNew.tsEffective, oNew.tsExpire, oNew, oOld, aoChanges));
563
564 # If we're at the end of the log, add the initial entry.
565 if len(aoRows) <= cMaxRows and len(aoRows) > 0:
566 oNew = aoRows[-1];
567 aoEntries.append(ChangeLogEntry(oNew.uidAuthor, None, oNew.tsEffective, oNew.tsExpire, oNew, None, []));
568
569 UserAccountLogic(self._oDb).resolveChangeLogAuthors(aoEntries);
570 return (aoEntries, len(aoRows) > cMaxRows);
571
572
573 def addEntry(self, oData, uidAuthor, fCommit = False):
574 """
575 Creates a testbox in the database.
576 Returns the testbox ID, testbox generation ID and effective timestamp
577 of the created testbox on success. Throws error on failure.
578 """
579 dDataErrors = oData.validateAndConvert(self._oDb, oData.ksValidateFor_Add);
580 if len(dDataErrors) > 0:
581 raise TMInvalidData('Invalid data passed to create(): %s' % (dDataErrors,));
582
583 self._oDb.callProc('TestBoxLogic_addEntry'
584 , ( uidAuthor,
585 oData.ip, # Should we allow setting the IP?
586 oData.uuidSystem,
587 oData.sName,
588 oData.sDescription,
589 oData.idSchedGroup,
590 oData.fEnabled,
591 oData.enmLomKind,
592 oData.ipLom,
593 oData.pctScaleTimeout,
594 oData.sComment,
595 oData.enmPendingCmd, ) );
596 aoRow = self._oDb.fetchOne();
597 self._oDb.maybeCommit(fCommit);
598 return (aoRow[0], aoRow[1], aoRow[2]);
599
600
601 def editEntry(self, oData, uidAuthor, fCommit = False):
602 """
603 Data edit update, web UI is the primary user.
604 Returns the new generation ID and effective date.
605 """
606
607 dDataErrors = oData.validateAndConvert(self._oDb, oData.ksValidateFor_Edit);
608 if len(dDataErrors) > 0:
609 raise TMInvalidData('Invalid data passed to create(): %s' % (dDataErrors,));
610
611 self._oDb.callProc('TestBoxLogic_editEntry'
612 , ( uidAuthor,
613 oData.idTestBox,
614 oData.ip, # Should we allow setting the IP?
615 oData.uuidSystem,
616 oData.sName,
617 oData.sDescription,
618 oData.idSchedGroup,
619 oData.fEnabled,
620 oData.enmLomKind,
621 oData.ipLom,
622 oData.pctScaleTimeout,
623 oData.sComment,
624 oData.enmPendingCmd, ));
625 aoRow = self._oDb.fetchOne();
626 self._oDb.maybeCommit(fCommit);
627 return (aoRow[0], aoRow[1]);
628
629
630 def removeEntry(self, uidAuthor, idTestBox, fCascade = False, fCommit = False):
631 """
632 Delete user account
633 """
634 self._oDb.callProc('TestBoxLogic_removeEntry'
635 , ( uidAuthor, idTestBox, fCascade,));
636 self._oDb.maybeCommit(fCommit);
637 return True;
638
639
640 def updateOnSignOn(self, idTestBox, idGenTestBox, sTestBoxAddr, sOs, sOsVersion, # pylint: disable=R0913,R0914
641 sCpuVendor, sCpuArch, sCpuName, lCpuRevision, cCpus, fCpuHwVirt, fCpuNestedPaging, fCpu64BitGuest,
642 fChipsetIoMmu, fRawMode, cMbMemory, cMbScratch, sReport, iTestBoxScriptRev, iPythonHexVersion):
643 """
644 Update the testbox attributes automatically on behalf of the testbox script.
645 Returns the new generation id on success, raises an exception on failure.
646 """
647 _ = idGenTestBox;
648 self._oDb.callProc('TestBoxLogic_updateOnSignOn'
649 , ( idTestBox,
650 sTestBoxAddr,
651 sOs,
652 sOsVersion,
653 sCpuVendor,
654 sCpuArch,
655 sCpuName,
656 lCpuRevision,
657 cCpus,
658 fCpuHwVirt,
659 fCpuNestedPaging,
660 fCpu64BitGuest,
661 fChipsetIoMmu,
662 fRawMode,
663 cMbMemory,
664 cMbScratch,
665 sReport,
666 iTestBoxScriptRev,
667 iPythonHexVersion,));
668 return self._oDb.fetchOne()[0];
669
670
671 def setCommand(self, idTestBox, sOldCommand, sNewCommand, uidAuthor = None, fCommit = False, sComment = None):
672 """
673 Sets or resets the pending command on a testbox.
674 Returns (idGenTestBox, tsEffective) of the new row.
675 """
676 ## @todo throw TMInFligthCollision again...
677 self._oDb.callProc('TestBoxLogic_setCommand'
678 , ( uidAuthor, idTestBox, sOldCommand, sNewCommand, sComment,));
679 aoRow = self._oDb.fetchOne();
680 self._oDb.maybeCommit(fCommit);
681 return (aoRow[0], aoRow[1]);
682
683
684 def getAll(self):
685 """
686 Retrieve list of all registered Test Box records from DB.
687 """
688 self._oDb.execute('SELECT *\n'
689 'FROM TestBoxesWithStrings\n'
690 'WHERE tsExpire=\'infinity\'::timestamp;')
691
692 aaoRows = self._oDb.fetchAll()
693 aoRet = []
694 for aoRow in aaoRows:
695 aoRet.append(TestBoxData().initFromDbRow(aoRow))
696 return aoRet
697
698
699 def cachedLookup(self, idTestBox):
700 """
701 Looks up the most recent TestBoxData object for idTestBox via
702 an object cache.
703
704 Returns a shared TestBoxData object. None if not found.
705 Raises exception on DB error.
706 """
707 if self.dCache is None:
708 self.dCache = self._oDb.getCache('TestBoxData');
709 oEntry = self.dCache.get(idTestBox, None);
710 if oEntry is None:
711 self._oDb.execute('SELECT TestBoxesWithStrings.*\n'
712 'FROM TestBoxesWithStrings\n'
713 'WHERE idTestBox = %s\n'
714 ' AND tsExpire = \'infinity\'::TIMESTAMP\n'
715 , (idTestBox, ));
716 if self._oDb.getRowCount() == 0:
717 # Maybe it was deleted, try get the last entry.
718 self._oDb.execute('SELECT TestBoxesWithStrings.*\n'
719 'FROM TestBoxes\n'
720 'WHERE idTestBox = %s\n'
721 'ORDER BY tsExpire DESC\n'
722 'LIMIT 1\n'
723 , (idTestBox, ));
724 elif self._oDb.getRowCount() > 1:
725 raise self._oDb.integrityException('%s infinity rows for %s' % (self._oDb.getRowCount(), idTestBox));
726
727 if self._oDb.getRowCount() == 1:
728 aaoRow = self._oDb.fetchOne();
729 oEntry = TestBoxData().initFromDbRow(aaoRow);
730 self.dCache[idTestBox] = oEntry;
731 return oEntry;
732
733
734
735 #
736 # The virtual test sheriff interface.
737 #
738
739 def hasTestBoxRecentlyBeenRebooted(self, idTestBox, cHoursBack = 2, tsNow = None):
740 """
741 Checks if the testbox has been rebooted in the specified time period.
742
743 This does not include already pending reboots, though under some
744 circumstances it may. These being the test box entry being edited for
745 other reasons.
746
747 Returns True / False.
748 """
749 if tsNow is None:
750 tsNow = self._oDb.getCurrentTimestamp();
751 self._oDb.execute('SELECT COUNT(idTestBox)\n'
752 'FROM TestBoxes\n'
753 'WHERE idTestBox = %s\n'
754 ' AND tsExpire < %s\n'
755 ' AND tsExpire >= %s - interval \'%s hours\'\n'
756 ' AND enmPendingCmd IN (%s, %s)\n'
757 , ( idTestBox, tsNow, tsNow, cHoursBack,
758 TestBoxData.ksTestBoxCmd_Reboot, TestBoxData.ksTestBoxCmd_UpgradeAndReboot, ));
759 return self._oDb.fetchOne()[0] > 0;
760
761
762 def rebootTestBox(self, idTestBox, uidAuthor, sComment, sOldCommand = TestBoxData.ksTestBoxCmd_None, fCommit = False):
763 """
764 Issues a reboot command for the given test box.
765 Return True on succes, False on in-flight collision.
766 May raise DB exception on other trouble.
767 """
768 try:
769 self.setCommand(idTestBox, sOldCommand, TestBoxData.ksTestBoxCmd_Reboot,
770 uidAuthor = uidAuthor, fCommit = fCommit, sComment = sComment);
771 except TMInFligthCollision:
772 return False;
773 except:
774 raise;
775 return True;
776
777
778 def disableTestBox(self, idTestBox, uidAuthor, sComment, fCommit = False):
779 """
780 Disables the given test box.
781
782 Raises exception on trouble, without rollback.
783 """
784 oTestBox = TestBoxData().initFromDbWithId(self._oDb, idTestBox);
785 if oTestBox.fEnabled:
786 oTestBox.fEnabled = False;
787 if sComment is not None:
788 _ = sComment; # oTestBox.sComment = sComment;
789 self.editEntry(oTestBox, uidAuthor = uidAuthor, fCommit = fCommit);
790 return None;
791
792
793#
794# Unit testing.
795#
796
797# pylint: disable=C0111
798class TestBoxDataTestCase(ModelDataBaseTestCase):
799 def setUp(self):
800 self.aoSamples = [TestBoxData(),];
801
802if __name__ == '__main__':
803 unittest.main();
804 # not reached.
805
Note: See TracBrowser for help on using the repository browser.

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