VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/testmanager/core/schedgroup.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: 46.3 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: schedgroup.py 83364 2020-03-23 09:47:01Z vboxsync $
3
4"""
5Test Manager - Scheduling Group.
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 unittest;
34
35# Validation Kit imports.
36from testmanager.core.base import ModelDataBase, ModelDataBaseTestCase, ModelLogicBase, TMExceptionBase, \
37 TMRowInUse, TMInvalidData, TMRowAlreadyExists, TMRowNotFound;
38from testmanager.core.buildsource import BuildSourceData;
39from testmanager.core.testcase import TestCaseData;
40from testmanager.core.testcaseargs import TestCaseArgsData;
41from testmanager.core.testbox import TestBoxLogic, TestBoxDataForSchedGroup;
42from testmanager.core.testgroup import TestGroupData;
43
44
45
46class SchedGroupMemberData(ModelDataBase):
47 """
48 SchedGroupMember Data.
49 """
50
51 ksIdAttr = 'idSchedGroup';
52
53 ksParam_idSchedGroup = 'SchedGroupMember_idSchedGroup';
54 ksParam_idTestGroup = 'SchedGroupMember_idTestGroup';
55 ksParam_tsEffective = 'SchedGroupMember_tsEffective';
56 ksParam_tsExpire = 'SchedGroupMember_tsExpire';
57 ksParam_uidAuthor = 'SchedGroupMember_uidAuthor';
58 ksParam_iSchedPriority = 'SchedGroupMember_iSchedPriority';
59 ksParam_bmHourlySchedule = 'SchedGroupMember_bmHourlySchedule';
60 ksParam_idTestGroupPreReq = 'SchedGroupMember_idTestGroupPreReq';
61
62 kasAllowNullAttributes = [ 'idSchedGroup', 'idTestGroup', 'tsEffective', 'tsExpire',
63 'uidAuthor', 'bmHourlySchedule', 'idTestGroupPreReq' ];
64 kiMin_iSchedPriority = 0;
65 kiMax_iSchedPriority = 32;
66
67 kcDbColumns = 8
68
69 def __init__(self):
70 ModelDataBase.__init__(self);
71
72 #
73 # Initialize with defaults.
74 # See the database for explanations of each of these fields.
75 #
76 self.idSchedGroup = None;
77 self.idTestGroup = None;
78 self.tsEffective = None;
79 self.tsExpire = None;
80 self.uidAuthor = None;
81 self.iSchedPriority = 16;
82 self.bmHourlySchedule = None;
83 self.idTestGroupPreReq = None;
84
85 def initFromDbRow(self, aoRow):
86 """
87 Re-initializes the data with a row from a SELECT * FROM SchedGroupMembers.
88
89 Returns self. Raises exception if the row is None or otherwise invalid.
90 """
91
92 if aoRow is None:
93 raise TMRowNotFound('SchedGroupMember not found.');
94
95 self.idSchedGroup = aoRow[0];
96 self.idTestGroup = aoRow[1];
97 self.tsEffective = aoRow[2];
98 self.tsExpire = aoRow[3];
99 self.uidAuthor = aoRow[4];
100 self.iSchedPriority = aoRow[5];
101 self.bmHourlySchedule = aoRow[6]; ## @todo figure out how bitmaps are returned...
102 self.idTestGroupPreReq = aoRow[7];
103 return self;
104
105
106class SchedGroupMemberDataEx(SchedGroupMemberData):
107 """
108 Extended SchedGroupMember data class.
109 This adds the testgroups.
110 """
111
112 def __init__(self):
113 SchedGroupMemberData.__init__(self);
114 self.oTestGroup = None;
115
116 def initFromDbRow(self, aoRow):
117 """
118 Re-initializes the data with a row from a query like this:
119
120 SELECT SchedGroupMembers.*, TestGroups.*
121 FROM SchedGroupMembers
122 JOIN TestGroups
123 ON (SchedGroupMembers.idTestGroup = TestGroups.idTestGroup);
124
125 Returns self. Raises exception if the row is None or otherwise invalid.
126 """
127 SchedGroupMemberData.initFromDbRow(self, aoRow);
128 self.oTestGroup = TestGroupData().initFromDbRow(aoRow[SchedGroupMemberData.kcDbColumns:]);
129 return self;
130
131 def getDataAttributes(self):
132 asAttributes = SchedGroupMemberData.getDataAttributes(self);
133 asAttributes.remove('oTestGroup');
134 return asAttributes;
135
136 def _validateAndConvertWorker(self, asAllowNullAttributes, oDb, enmValidateFor = ModelDataBase.ksValidateFor_Other):
137 dErrors = SchedGroupMemberData._validateAndConvertWorker(self, asAllowNullAttributes, oDb, enmValidateFor);
138 if self.ksParam_idTestGroup not in dErrors:
139 self.oTestGroup = TestGroupData();
140 try:
141 self.oTestGroup.initFromDbWithId(oDb, self.idTestGroup);
142 except Exception as oXcpt:
143 self.oTestGroup = TestGroupData()
144 dErrors[self.ksParam_idTestGroup] = str(oXcpt);
145 return dErrors;
146
147
148
149class SchedGroupData(ModelDataBase):
150 """
151 SchedGroup Data.
152 """
153
154 ## @name TestBoxState_T
155 # @{
156 ksScheduler_BestEffortContinuousIntegration = 'bestEffortContinousItegration'; # sic*2
157 ksScheduler_Reserved = 'reserved';
158 ## @}
159
160
161 ksIdAttr = 'idSchedGroup';
162
163 ksParam_idSchedGroup = 'SchedGroup_idSchedGroup';
164 ksParam_tsEffective = 'SchedGroup_tsEffective';
165 ksParam_tsExpire = 'SchedGroup_tsExpire';
166 ksParam_uidAuthor = 'SchedGroup_uidAuthor';
167 ksParam_sName = 'SchedGroup_sName';
168 ksParam_sDescription = 'SchedGroup_sDescription';
169 ksParam_fEnabled = 'SchedGroup_fEnabled';
170 ksParam_enmScheduler = 'SchedGroup_enmScheduler';
171 ksParam_idBuildSrc = 'SchedGroup_idBuildSrc';
172 ksParam_idBuildSrcTestSuite = 'SchedGroup_idBuildSrcTestSuite';
173 ksParam_sComment = 'SchedGroup_sComment';
174
175 kasAllowNullAttributes = ['idSchedGroup', 'tsEffective', 'tsExpire', 'uidAuthor', 'sDescription',
176 'idBuildSrc', 'idBuildSrcTestSuite', 'sComment' ];
177 kasValidValues_enmScheduler = [ ksScheduler_BestEffortContinuousIntegration, ];
178
179 kcDbColumns = 11;
180
181 # Scheduler types
182 kasSchedulerDesc = \
183 [
184 ( ksScheduler_BestEffortContinuousIntegration, 'Best-Effort-Continuous-Integration (BECI) scheduler.', ''),
185 ]
186
187 def __init__(self):
188 ModelDataBase.__init__(self);
189
190 #
191 # Initialize with defaults.
192 # See the database for explanations of each of these fields.
193 #
194 self.idSchedGroup = None;
195 self.tsEffective = None;
196 self.tsExpire = None;
197 self.uidAuthor = None;
198 self.sName = None;
199 self.sDescription = None;
200 self.fEnabled = None;
201 self.enmScheduler = SchedGroupData.ksScheduler_BestEffortContinuousIntegration;
202 self.idBuildSrc = None;
203 self.idBuildSrcTestSuite = None;
204 self.sComment = None;
205
206 def initFromDbRow(self, aoRow):
207 """
208 Re-initializes the data with a row from a SELECT * FROM SchedGroups.
209
210 Returns self. Raises exception if the row is None or otherwise invalid.
211 """
212
213 if aoRow is None:
214 raise TMRowNotFound('SchedGroup not found.');
215
216 self.idSchedGroup = aoRow[0];
217 self.tsEffective = aoRow[1];
218 self.tsExpire = aoRow[2];
219 self.uidAuthor = aoRow[3];
220 self.sName = aoRow[4];
221 self.sDescription = aoRow[5];
222 self.fEnabled = aoRow[6];
223 self.enmScheduler = aoRow[7];
224 self.idBuildSrc = aoRow[8];
225 self.idBuildSrcTestSuite = aoRow[9];
226 self.sComment = aoRow[10];
227 return self;
228
229 def initFromDbWithId(self, oDb, idSchedGroup, tsNow = None, sPeriodBack = None):
230 """
231 Initialize the object from the database.
232 """
233 oDb.execute(self.formatSimpleNowAndPeriodQuery(oDb,
234 'SELECT *\n'
235 'FROM SchedGroups\n'
236 'WHERE idSchedGroup = %s\n'
237 , ( idSchedGroup,), tsNow, sPeriodBack));
238 aoRow = oDb.fetchOne()
239 if aoRow is None:
240 raise TMRowNotFound('idSchedGroup=%s not found (tsNow=%s, sPeriodBack=%s)' % (idSchedGroup, tsNow, sPeriodBack));
241 return self.initFromDbRow(aoRow);
242
243
244class SchedGroupDataEx(SchedGroupData):
245 """
246 Extended scheduling group data.
247
248 Note! Similar to TestGroupDataEx.
249 """
250
251 ksParam_aoMembers = 'SchedGroup_aoMembers';
252 ksParam_aoTestBoxes = 'SchedGroup_aoTestboxes';
253 kasAltArrayNull = [ 'aoMembers', 'aoTestboxes' ];
254
255 ## Helper parameter containing the comma separated list with the IDs of
256 # potential members found in the parameters.
257 ksParam_aidTestGroups = 'TestGroupDataEx_aidTestGroups';
258 ## Ditto for testbox meembers.
259 ksParam_aidTestBoxes = 'TestGroupDataEx_aidTestBoxes';
260
261
262 def __init__(self):
263 SchedGroupData.__init__(self);
264 self.aoMembers = [] # type: list[SchedGroupMemberDataEx]
265 self.aoTestBoxes = [] # type: list[TestBoxDataForSchedGroup]
266
267 # The two build sources for the sake of convenience.
268 self.oBuildSrc = None # type: BuildSourceData
269 self.oBuildSrcValidationKit = None # type: BuildSourceData
270
271 def _initExtraMembersFromDb(self, oDb, tsNow = None, sPeriodBack = None):
272 """
273 Worker shared by the initFromDb* methods.
274 Returns self. Raises exception if no row or database error.
275 """
276 #
277 # Clear all members upfront so the object has some kind of consistency
278 # if anything below raises exceptions.
279 #
280 self.oBuildSrc = None;
281 self.oBuildSrcValidationKit = None;
282 self.aoTestBoxes = [];
283 self.aoMembers = [];
284
285 #
286 # Build source.
287 #
288 if self.idBuildSrc:
289 self.oBuildSrc = BuildSourceData().initFromDbWithId(oDb, self.idBuildSrc, tsNow, sPeriodBack);
290
291 if self.idBuildSrcTestSuite:
292 self.oBuildSrcValidationKit = BuildSourceData().initFromDbWithId(oDb, self.idBuildSrcTestSuite,
293 tsNow, sPeriodBack);
294
295 #
296 # Test Boxes.
297 #
298 self.aoTestBoxes = TestBoxLogic(oDb).fetchForSchedGroup(self.idSchedGroup, tsNow);
299
300 #
301 # Test groups.
302 #
303 oDb.execute('SELECT SchedGroupMembers.*, TestGroups.*\n'
304 'FROM SchedGroupMembers\n'
305 'LEFT OUTER JOIN TestGroups ON (SchedGroupMembers.idTestGroup = TestGroups.idTestGroup)\n'
306 'WHERE SchedGroupMembers.idSchedGroup = %s\n'
307 + self.formatSimpleNowAndPeriod(oDb, tsNow, sPeriodBack, sTablePrefix = 'SchedGroupMembers.')
308 + self.formatSimpleNowAndPeriod(oDb, tsNow, sPeriodBack, sTablePrefix = 'TestGroups.') +
309 'ORDER BY SchedGroupMembers.idTestGroupPreReq, SchedGroupMembers.idTestGroup\n'
310 , (self.idSchedGroup,));
311 for aoRow in oDb.fetchAll():
312 self.aoMembers.append(SchedGroupMemberDataEx().initFromDbRow(aoRow));
313 return self;
314
315 def initFromDbRowEx(self, aoRow, oDb, tsNow = None):
316 """
317 Reinitialize from a SELECT * FROM SchedGroups row. Will query the
318 necessary additional data from oDb using tsNow.
319 Returns self. Raises exception if no row or database error.
320 """
321 SchedGroupData.initFromDbRow(self, aoRow);
322 return self._initExtraMembersFromDb(oDb, tsNow);
323
324 def initFromDbWithId(self, oDb, idSchedGroup, tsNow = None, sPeriodBack = None):
325 """
326 Initialize the object from the database.
327 """
328 SchedGroupData.initFromDbWithId(self, oDb, idSchedGroup, tsNow, sPeriodBack);
329 return self._initExtraMembersFromDb(oDb, tsNow, sPeriodBack);
330
331 def getDataAttributes(self):
332 asAttributes = SchedGroupData.getDataAttributes(self);
333 asAttributes.remove('oBuildSrc');
334 asAttributes.remove('oBuildSrcValidationKit');
335 return asAttributes;
336
337 def getAttributeParamNullValues(self, sAttr):
338 if sAttr not in [ 'aoMembers', 'aoTestBoxes' ]:
339 return SchedGroupData.getAttributeParamNullValues(self, sAttr);
340 return ['', [], None];
341
342 def convertParamToAttribute(self, sAttr, sParam, oValue, oDisp, fStrict):
343 aoNewValue = [];
344 if sAttr == 'aoMembers':
345 aidSelected = oDisp.getListOfIntParams(sParam, iMin = 1, iMax = 0x7ffffffe, aiDefaults = [])
346 sIds = oDisp.getStringParam(self.ksParam_aidTestGroups, sDefault = '');
347 for idTestGroup in sIds.split(','):
348 try: idTestGroup = int(idTestGroup);
349 except: pass;
350 oDispWrapper = self.DispWrapper(oDisp, '%s[%s][%%s]' % (SchedGroupDataEx.ksParam_aoMembers, idTestGroup,))
351 oMember = SchedGroupMemberDataEx().initFromParams(oDispWrapper, fStrict = False);
352 if idTestGroup in aidSelected:
353 aoNewValue.append(oMember);
354 elif sAttr == 'aoTestBoxes':
355 aidSelected = oDisp.getListOfIntParams(sParam, iMin = 1, iMax = 0x7ffffffe, aiDefaults = [])
356 sIds = oDisp.getStringParam(self.ksParam_aidTestBoxes, sDefault = '');
357 for idTestBox in sIds.split(','):
358 try: idTestBox = int(idTestBox);
359 except: pass;
360 oDispWrapper = self.DispWrapper(oDisp, '%s[%s][%%s]' % (SchedGroupDataEx.ksParam_aoTestBoxes, idTestBox,))
361 oTestBox = TestBoxDataForSchedGroup().initFromParams(oDispWrapper, fStrict = False);
362 if idTestBox in aidSelected:
363 aoNewValue.append(oTestBox);
364 else:
365 return SchedGroupData.convertParamToAttribute(self, sAttr, sParam, oValue, oDisp, fStrict);
366 return aoNewValue;
367
368 def _validateAndConvertAttribute(self, sAttr, sParam, oValue, aoNilValues, fAllowNull, oDb):
369 if sAttr not in [ 'aoMembers', 'aoTestBoxes' ]:
370 return SchedGroupData._validateAndConvertAttribute(self, sAttr, sParam, oValue, aoNilValues, fAllowNull, oDb);
371
372 asErrors = [];
373 aoNewMembers = [];
374 if sAttr == 'aoMembers':
375 for oOldMember in oValue:
376 oNewMember = SchedGroupMemberDataEx().initFromOther(oOldMember);
377 aoNewMembers.append(oNewMember);
378
379 dErrors = oNewMember.validateAndConvert(oDb, ModelDataBase.ksValidateFor_Other);
380 if dErrors:
381 asErrors.append(str(dErrors));
382
383 if not asErrors:
384 for i, _ in enumerate(aoNewMembers):
385 idTestGroup = aoNewMembers[i];
386 for j in range(i + 1, len(aoNewMembers)):
387 if aoNewMembers[j].idTestGroup == idTestGroup:
388 asErrors.append('Duplicate test group #%d!' % (idTestGroup, ));
389 break;
390 else:
391 for oOldMember in oValue:
392 oNewMember = TestBoxDataForSchedGroup().initFromOther(oOldMember);
393 aoNewMembers.append(oNewMember);
394
395 dErrors = oNewMember.validateAndConvert(oDb, ModelDataBase.ksValidateFor_Other);
396 if dErrors:
397 asErrors.append(str(dErrors));
398
399 if not asErrors:
400 for i, _ in enumerate(aoNewMembers):
401 idTestBox = aoNewMembers[i];
402 for j in range(i + 1, len(aoNewMembers)):
403 if aoNewMembers[j].idTestBox == idTestBox:
404 asErrors.append('Duplicate test group #%d!' % (idTestBox, ));
405 break;
406
407
408 return (aoNewMembers, None if not asErrors else '<br>\n'.join(asErrors));
409
410 def _validateAndConvertWorker(self, asAllowNullAttributes, oDb, enmValidateFor = ModelDataBase.ksValidateFor_Other):
411 dErrors = SchedGroupData._validateAndConvertWorker(self, asAllowNullAttributes, oDb, enmValidateFor);
412
413 #
414 # Fetch the extended build source bits.
415 #
416 if self.ksParam_idBuildSrc not in dErrors:
417 if self.idBuildSrc in self.getAttributeParamNullValues('idBuildSrc') \
418 or self.idBuildSrc is None:
419 self.oBuildSrc = None;
420 else:
421 try:
422 self.oBuildSrc = BuildSourceData().initFromDbWithId(oDb, self.idBuildSrc);
423 except Exception as oXcpt:
424 self.oBuildSrc = BuildSourceData();
425 dErrors[self.ksParam_idBuildSrc] = str(oXcpt);
426
427 if self.ksParam_idBuildSrcTestSuite not in dErrors:
428 if self.idBuildSrcTestSuite in self.getAttributeParamNullValues('idBuildSrcTestSuite') \
429 or self.idBuildSrcTestSuite is None:
430 self.oBuildSrcValidationKit = None;
431 else:
432 try:
433 self.oBuildSrcValidationKit = BuildSourceData().initFromDbWithId(oDb, self.idBuildSrcTestSuite);
434 except Exception as oXcpt:
435 self.oBuildSrcValidationKit = BuildSourceData();
436 dErrors[self.ksParam_idBuildSrcTestSuite] = str(oXcpt);
437
438 return dErrors;
439
440
441
442class SchedGroupLogic(ModelLogicBase): # pylint: disable=too-few-public-methods
443 """
444 SchedGroup logic.
445 """
446
447 def __init__(self, oDb):
448 ModelLogicBase.__init__(self, oDb);
449 self.dCache = None;
450
451 #
452 # Standard methods.
453 #
454
455 def fetchForListing(self, iStart, cMaxRows, tsNow, aiSortColumns = None):
456 """
457 Fetches build sources.
458
459 Returns an array (list) of BuildSourceData items, empty list if none.
460 Raises exception on error.
461 """
462 _ = aiSortColumns;
463
464 if tsNow is None:
465 self._oDb.execute('SELECT *\n'
466 'FROM SchedGroups\n'
467 'WHERE tsExpire = \'infinity\'::TIMESTAMP\n'
468 'ORDER BY fEnabled DESC, sName DESC\n'
469 'LIMIT %s OFFSET %s\n'
470 , (cMaxRows, iStart,));
471 else:
472 self._oDb.execute('SELECT *\n'
473 'FROM SchedGroups\n'
474 'WHERE tsExpire > %s\n'
475 ' AND tsEffective <= %s\n'
476 'ORDER BY fEnabled DESC, sName DESC\n'
477 'LIMIT %s OFFSET %s\n'
478 , (tsNow, tsNow, cMaxRows, iStart,));
479
480 aoRet = [];
481 for aoRow in self._oDb.fetchAll():
482 aoRet.append(SchedGroupDataEx().initFromDbRowEx(aoRow, self._oDb, tsNow));
483 return aoRet;
484
485 def addEntry(self, oData, uidAuthor, fCommit = False):
486 """Add Scheduling Group record"""
487
488 #
489 # Validate.
490 #
491 dDataErrors = oData.validateAndConvert(self._oDb, oData.ksValidateFor_Add);
492 if dDataErrors:
493 raise TMInvalidData('Invalid data passed to addEntry: %s' % (dDataErrors,));
494 if self.exists(oData.sName):
495 raise TMRowAlreadyExists('Scheduling group "%s" already exists.' % (oData.sName,));
496
497 #
498 # Add it.
499 #
500 self._oDb.execute('INSERT INTO SchedGroups (\n'
501 ' uidAuthor,\n'
502 ' sName,\n'
503 ' sDescription,\n'
504 ' fEnabled,\n'
505 ' enmScheduler,\n'
506 ' idBuildSrc,\n'
507 ' idBuildSrcTestSuite,\n'
508 ' sComment)\n'
509 'VALUES (%s, %s, %s, %s, %s, %s, %s, %s)\n'
510 'RETURNING idSchedGroup\n'
511 , ( uidAuthor,
512 oData.sName,
513 oData.sDescription,
514 oData.fEnabled,
515 oData.enmScheduler,
516 oData.idBuildSrc,
517 oData.idBuildSrcTestSuite,
518 oData.sComment ));
519 idSchedGroup = self._oDb.fetchOne()[0];
520 oData.idSchedGroup = idSchedGroup;
521
522 for oBoxInGrp in oData.aoTestBoxes:
523 oBoxInGrp.idSchedGroup = idSchedGroup;
524 self._addSchedGroupTestBox(uidAuthor, oBoxInGrp);
525
526 for oMember in oData.aoMembers:
527 oMember.idSchedGroup = idSchedGroup;
528 self._addSchedGroupMember(uidAuthor, oMember);
529
530 self._oDb.maybeCommit(fCommit);
531 return True;
532
533 def editEntry(self, oData, uidAuthor, fCommit = False):
534 """Edit Scheduling Group record"""
535
536 #
537 # Validate input and retrieve the old data.
538 #
539 dErrors = oData.validateAndConvert(self._oDb, oData.ksValidateFor_Edit);
540 if dErrors:
541 raise TMInvalidData('editEntry got invalid data: %s' % (dErrors,));
542 self._assertUnique(oData.sName, oData.idSchedGroup);
543 oOldData = SchedGroupDataEx().initFromDbWithId(self._oDb, oData.idSchedGroup);
544
545 #
546 # Make the changes.
547 #
548 if not oData.isEqualEx(oOldData, [ 'tsEffective', 'tsExpire', 'uidAuthor', 'aoMembers', 'aoTestBoxes',
549 'oBuildSrc', 'oBuildSrcValidationKit', ]):
550 self._historizeEntry(oData.idSchedGroup);
551 self._readdEntry(uidAuthor, oData);
552
553 # Remove groups.
554 for oOld in oOldData.aoMembers:
555 fRemove = True;
556 for oNew in oData.aoMembers:
557 if oNew.idTestGroup == oOld.idTestGroup:
558 fRemove = False;
559 break;
560 if fRemove:
561 self._removeSchedGroupMember(uidAuthor, oOld);
562
563 # Add / modify groups.
564 for oMember in oData.aoMembers:
565 oOldMember = None;
566 for oOld in oOldData.aoMembers:
567 if oOld.idTestGroup == oMember.idTestGroup:
568 oOldMember = oOld;
569 break;
570
571 oMember.idSchedGroup = oData.idSchedGroup;
572 if oOldMember is None:
573 self._addSchedGroupMember(uidAuthor, oMember);
574 elif not oMember.isEqualEx(oOldMember, ['tsEffective', 'tsExpire', 'uidAuthor', 'oTestGroup']):
575 self._historizeSchedGroupMember(oMember);
576 self._addSchedGroupMember(uidAuthor, oMember);
577
578 # Remove testboxes.
579 for oOld in oOldData.aoTestBoxes:
580 fRemove = True;
581 for oNew in oData.aoTestBoxes:
582 if oNew.idTestBox == oOld.idTestBox:
583 fRemove = False;
584 break;
585 if fRemove:
586 self._removeSchedGroupTestBox(uidAuthor, oOld);
587
588 # Add / modify testboxes.
589 for oBoxInGrp in oData.aoTestBoxes:
590 oOldBoxInGrp = None;
591 for oOld in oOldData.aoTestBoxes:
592 if oOld.idTestBox == oBoxInGrp.idTestBox:
593 oOldBoxInGrp = oOld;
594 break;
595
596 oBoxInGrp.idSchedGroup = oData.idSchedGroup;
597 if oOldBoxInGrp is None:
598 self._addSchedGroupTestBox(uidAuthor, oBoxInGrp);
599 elif not oBoxInGrp.isEqualEx(oOldBoxInGrp, ['tsEffective', 'tsExpire', 'uidAuthor', 'oTestBox']):
600 self._historizeSchedGroupTestBox(oBoxInGrp);
601 self._addSchedGroupTestBox(uidAuthor, oBoxInGrp);
602
603 self._oDb.maybeCommit(fCommit);
604 return True;
605
606 def removeEntry(self, uidAuthor, idSchedGroup, fCascade = False, fCommit = False):
607 """
608 Deletes a scheduling group.
609 """
610 _ = fCascade;
611
612 #
613 # Input validation and retrival of current data.
614 #
615 if idSchedGroup == 1:
616 raise TMRowInUse('Cannot remove the default scheduling group (id 1).');
617 oData = SchedGroupDataEx().initFromDbWithId(self._oDb, idSchedGroup);
618
619 #
620 # Remove the test box member records.
621 #
622 for oBoxInGrp in oData.aoTestBoxes:
623 self._removeSchedGroupTestBox(uidAuthor, oBoxInGrp);
624 self._oDb.execute('UPDATE TestBoxesInSchedGroups\n'
625 'SET tsExpire = CURRENT_TIMESTAMP\n'
626 'WHERE idSchedGroup = %s\n'
627 ' AND tsExpire = \'infinity\'::TIMESTAMP\n'
628 , (idSchedGroup,));
629
630 #
631 # Remove the test group member records.
632 #
633 for oMember in oData.aoMembers:
634 self._removeSchedGroupMember(uidAuthor, oMember);
635 self._oDb.execute('UPDATE SchedGroupMembers\n'
636 'SET tsExpire = CURRENT_TIMESTAMP\n'
637 'WHERE idSchedGroup = %s\n'
638 ' AND tsExpire = \'infinity\'::TIMESTAMP\n'
639 , (idSchedGroup,));
640
641 #
642 # Now the SchedGroups entry.
643 #
644 (tsCur, tsCurMinusOne) = self._oDb.getCurrentTimestamps();
645 if oData.tsEffective != tsCur and oData.tsEffective != tsCurMinusOne:
646 self._historizeEntry(idSchedGroup, tsCurMinusOne);
647 self._readdEntry(uidAuthor, oData, tsCurMinusOne);
648 self._historizeEntry(idSchedGroup);
649 self._oDb.execute('UPDATE SchedGroups\n'
650 'SET tsExpire = CURRENT_TIMESTAMP\n'
651 'WHERE idSchedGroup = %s\n'
652 ' AND tsExpire = \'infinity\'::TIMESTAMP\n'
653 , (idSchedGroup,))
654
655 self._oDb.maybeCommit(fCommit)
656 return True;
657
658
659 def cachedLookup(self, idSchedGroup):
660 """
661 Looks up the most recent SchedGroupData object for idSchedGroup
662 via an object cache.
663
664 Returns a shared SchedGroupData object. None if not found.
665 Raises exception on DB error.
666 """
667 if self.dCache is None:
668 self.dCache = self._oDb.getCache('SchedGroup');
669
670 oEntry = self.dCache.get(idSchedGroup, None);
671 if oEntry is None:
672 self._oDb.execute('SELECT *\n'
673 'FROM SchedGroups\n'
674 'WHERE idSchedGroup = %s\n'
675 ' AND tsExpire = \'infinity\'::TIMESTAMP\n'
676 , (idSchedGroup, ));
677 if self._oDb.getRowCount() == 0:
678 # Maybe it was deleted, try get the last entry.
679 self._oDb.execute('SELECT *\n'
680 'FROM SchedGroups\n'
681 'WHERE idSchedGroup = %s\n'
682 'ORDER BY tsExpire DESC\n'
683 'LIMIT 1\n'
684 , (idSchedGroup, ));
685 elif self._oDb.getRowCount() > 1:
686 raise self._oDb.integrityException('%s infinity rows for %s' % (self._oDb.getRowCount(), idSchedGroup));
687
688 if self._oDb.getRowCount() == 1:
689 oEntry = SchedGroupData().initFromDbRow(self._oDb.fetchOne());
690 self.dCache[idSchedGroup] = oEntry;
691 return oEntry;
692
693
694 #
695 # Other methods.
696 #
697
698 def fetchOrderedByName(self, tsNow = None):
699 """
700 Return list of objects of type SchedGroups ordered by name.
701 May raise exception on database error.
702 """
703 if tsNow is None:
704 self._oDb.execute('SELECT *\n'
705 'FROM SchedGroups\n'
706 'WHERE tsExpire = \'infinity\'::TIMESTAMP\n'
707 'ORDER BY sName ASC\n');
708 else:
709 self._oDb.execute('SELECT *\n'
710 'FROM SchedGroups\n'
711 'WHERE tsExpire > %s\n'
712 ' AND tsEffective <= %s\n'
713 'ORDER BY sName ASC\n'
714 , (tsNow, tsNow,));
715 aoRet = []
716 for _ in range(self._oDb.getRowCount()):
717 aoRet.append(SchedGroupData().initFromDbRow(self._oDb.fetchOne()));
718 return aoRet;
719
720
721 def getAll(self, tsEffective = None):
722 """
723 Gets the list of all scheduling groups.
724 Returns an array of SchedGroupData instances.
725 """
726 if tsEffective is None:
727 self._oDb.execute('SELECT *\n'
728 'FROM SchedGroups\n'
729 'WHERE tsExpire = \'infinity\'::TIMESTAMP\n');
730 else:
731 self._oDb.execute('SELECT *\n'
732 'FROM SchedGroups\n'
733 'WHERE tsExpire > %s\n'
734 ' AND tsEffective <= %s\n'
735 , (tsEffective, tsEffective));
736 aoRet = [];
737 for aoRow in self._oDb.fetchAll():
738 aoRet.append(SchedGroupData().initFromDbRow(aoRow));
739 return aoRet;
740
741 def getSchedGroupsForCombo(self, tsEffective = None):
742 """
743 Gets the list of active scheduling groups for a combo box.
744 Returns an array of (value [idSchedGroup], drop-down-name [sName],
745 hover-text [sDescription]) tuples.
746 """
747 if tsEffective is None:
748 self._oDb.execute('SELECT idSchedGroup, sName, sDescription\n'
749 'FROM SchedGroups\n'
750 'WHERE tsExpire = \'infinity\'::TIMESTAMP\n'
751 'ORDER BY sName');
752 else:
753 self._oDb.execute('SELECT idSchedGroup, sName, sDescription\n'
754 'FROM SchedGroups\n'
755 'WHERE tsExpire > %s\n'
756 ' AND tsEffective <= %s\n'
757 'ORDER BY sName'
758 , (tsEffective, tsEffective));
759 return self._oDb.fetchAll();
760
761
762 def getMembers(self, idSchedGroup, tsEffective = None):
763 """
764 Gets the scheduling groups members for the given scheduling group.
765
766 Returns an array of SchedGroupMemberDataEx instances (sorted by
767 priority (descending) and idTestGroup). May raise exception DB error.
768 """
769
770 if tsEffective is None:
771 self._oDb.execute('SELECT *\n'
772 'FROM SchedGroupMembers, TestGroups\n'
773 'WHERE SchedGroupMembers.idSchedGroup = %s\n'
774 ' AND SchedGroupMembers.tsExpire = \'infinity\'::TIMESTAMP\n'
775 ' AND TestGroups.idTestGroup = SchedGroupMembers.idTestGroup\n'
776 ' AND TestGroups.tsExpire = \'infinity\'::TIMESTAMP\n'
777 'ORDER BY SchedGroupMembers.iSchedPriority DESC, SchedGroupMembers.idTestGroup\n'
778 , (idSchedGroup,));
779 else:
780 self._oDb.execute('SELECT *\n'
781 'FROM SchedGroupMembers, TestGroups\n'
782 'WHERE SchedGroupMembers.idSchedGroup = %s\n'
783 ' AND SchedGroupMembers.tsExpire < %s\n'
784 ' AND SchedGroupMembers.tsEffective >= %s\n'
785 ' AND TestGroups.idTestGroup = SchedGroupMembers.idTestGroup\n'
786 ' AND TestGroups.tsExpire < %s\n'
787 ' AND TestGroups.tsEffective >= %s\n'
788 'ORDER BY SchedGroupMembers.iSchedPriority DESC, SchedGroupMembers.idTestGroup\n'
789 , (idSchedGroup, tsEffective, tsEffective, tsEffective, tsEffective, ));
790 aaoRows = self._oDb.fetchAll();
791 aoRet = [];
792 for aoRow in aaoRows:
793 aoRet.append(SchedGroupMemberDataEx().initFromDbRow(aoRow));
794 return aoRet;
795
796 def getTestCasesForGroup(self, idSchedGroup, cMax = None):
797 """
798 Gets the enabled testcases w/ testgroup+priority for the given scheduling group.
799
800 Returns an array of TestCaseData instances (ordered by group id, descending
801 testcase priority, and testcase IDs) with an extra iSchedPriority member.
802 May raise exception on DB error or if the result exceeds cMax.
803 """
804
805 self._oDb.execute('SELECT TestGroupMembers.idTestGroup, TestGroupMembers.iSchedPriority, TestCases.*\n'
806 'FROM SchedGroupMembers, TestGroups, TestGroupMembers, TestCases\n'
807 'WHERE SchedGroupMembers.idSchedGroup = %s\n'
808 ' AND SchedGroupMembers.tsExpire = \'infinity\'::TIMESTAMP\n'
809 ' AND TestGroups.idTestGroup = SchedGroupMembers.idTestGroup\n'
810 ' AND TestGroups.tsExpire = \'infinity\'::TIMESTAMP\n'
811 ' AND TestGroupMembers.idTestGroup = TestGroups.idTestGroup\n'
812 ' AND TestGroupMembers.tsExpire = \'infinity\'::TIMESTAMP\n'
813 ' AND TestCases.idTestCase = TestGroupMembers.idTestCase\n'
814 ' AND TestCases.tsExpire = \'infinity\'::TIMESTAMP\n'
815 ' AND TestCases.fEnabled = TRUE\n'
816 'ORDER BY TestGroupMembers.idTestGroup, TestGroupMembers.iSchedPriority DESC, TestCases.idTestCase\n'
817 , (idSchedGroup,));
818
819 if cMax is not None and self._oDb.getRowCount() > cMax:
820 raise TMExceptionBase('Too many testcases for scheduling group %s: %s, max %s'
821 % (idSchedGroup, cMax, self._oDb.getRowCount(),));
822
823 aoRet = [];
824 for aoRow in self._oDb.fetchAll():
825 oTestCase = TestCaseData().initFromDbRow(aoRow[2:]);
826 oTestCase.idTestGroup = aoRow[0];
827 oTestCase.iSchedPriority = aoRow[1];
828 aoRet.append(oTestCase);
829 return aoRet;
830
831 def getTestCaseArgsForGroup(self, idSchedGroup, cMax = None):
832 """
833 Gets the testcase argument variation w/ testgroup+priority for the given scheduling group.
834
835 Returns an array TestCaseArgsData instance (sorted by group and
836 variation id) with an extra iSchedPriority member.
837 May raise exception on DB error or if the result exceeds cMax.
838 """
839
840 self._oDb.execute('SELECT TestGroupMembers.idTestGroup, TestGroupMembers.iSchedPriority, TestCaseArgs.*\n'
841 'FROM SchedGroupMembers, TestGroups, TestGroupMembers, TestCaseArgs, TestCases\n'
842 'WHERE SchedGroupMembers.idSchedGroup = %s\n'
843 ' AND SchedGroupMembers.tsExpire = \'infinity\'::TIMESTAMP\n'
844 ' AND TestGroups.idTestGroup = SchedGroupMembers.idTestGroup\n'
845 ' AND TestGroups.tsExpire = \'infinity\'::TIMESTAMP\n'
846 ' AND TestGroupMembers.idTestGroup = TestGroups.idTestGroup\n'
847 ' AND TestGroupMembers.tsExpire = \'infinity\'::TIMESTAMP\n'
848 ' AND TestCaseArgs.idTestCase = TestGroupMembers.idTestCase\n'
849 ' AND TestCaseArgs.tsExpire = \'infinity\'::TIMESTAMP\n'
850 ' AND ( TestGroupMembers.aidTestCaseArgs is NULL\n'
851 ' OR TestCaseArgs.idTestCaseArgs = ANY(TestGroupMembers.aidTestCaseArgs) )\n'
852 ' AND TestCases.idTestCase = TestCaseArgs.idTestCase\n'
853 ' AND TestCases.tsExpire = \'infinity\'::TIMESTAMP\n'
854 ' AND TestCases.fEnabled = TRUE\n'
855 'ORDER BY TestGroupMembers.idTestGroup, TestGroupMembers.idTestCase, TestCaseArgs.idTestCaseArgs\n'
856 , (idSchedGroup,));
857
858 if cMax is not None and self._oDb.getRowCount() > cMax:
859 raise TMExceptionBase('Too many argument variations for scheduling group %s: %s, max %s'
860 % (idSchedGroup, cMax, self._oDb.getRowCount(),));
861
862 aoRet = [];
863 for aoRow in self._oDb.fetchAll():
864 oVariation = TestCaseArgsData().initFromDbRow(aoRow[2:]);
865 oVariation.idTestGroup = aoRow[0];
866 oVariation.iSchedPriority = aoRow[1];
867 aoRet.append(oVariation);
868 return aoRet;
869
870 def exists(self, sName):
871 """Checks if a group with the given name exists."""
872 self._oDb.execute('SELECT idSchedGroup\n'
873 'FROM SchedGroups\n'
874 'WHERE tsExpire = \'infinity\'::TIMESTAMP\n'
875 ' AND sName = %s\n'
876 'LIMIT 1\n'
877 , (sName,));
878 return self._oDb.getRowCount() > 0;
879
880 def getById(self, idSchedGroup):
881 """Get Scheduling Group data by idSchedGroup"""
882 self._oDb.execute('SELECT *\n'
883 'FROM SchedGroups\n'
884 'WHERE tsExpire = \'infinity\'::timestamp\n'
885 ' AND idSchedGroup = %s;', (idSchedGroup,))
886 aRows = self._oDb.fetchAll()
887 if len(aRows) not in (0, 1):
888 raise self._oDb.integrityException(
889 'Found more than one scheduling groups with the same credentials. Database structure is corrupted.')
890 try:
891 return SchedGroupData().initFromDbRow(aRows[0])
892 except IndexError:
893 return None
894
895
896 #
897 # Internal helpers.
898 #
899
900 def _assertUnique(self, sName, idSchedGroupIgnore = None):
901 """
902 Checks that the scheduling group name is unique.
903 Raises exception if the name is already in use.
904 """
905 if idSchedGroupIgnore is None:
906 self._oDb.execute('SELECT idSchedGroup\n'
907 'FROM SchedGroups\n'
908 'WHERE tsExpire = \'infinity\'::TIMESTAMP\n'
909 ' AND sName = %s\n'
910 , ( sName, ) );
911 else:
912 self._oDb.execute('SELECT idSchedGroup\n'
913 'FROM SchedGroups\n'
914 'WHERE tsExpire = \'infinity\'::TIMESTAMP\n'
915 ' AND sName = %s\n'
916 ' AND idSchedGroup <> %s\n'
917 , ( sName, idSchedGroupIgnore, ) );
918 if self._oDb.getRowCount() > 0:
919 raise TMRowInUse('Scheduling group name (%s) is already in use.' % (sName,));
920 return True;
921
922 def _readdEntry(self, uidAuthor, oData, tsEffective = None):
923 """
924 Re-adds the SchedGroups entry. Used by editEntry and removeEntry.
925 """
926 if tsEffective is None:
927 tsEffective = self._oDb.getCurrentTimestamp();
928 self._oDb.execute('INSERT INTO SchedGroups (\n'
929 ' uidAuthor,\n'
930 ' tsEffective,\n'
931 ' idSchedGroup,\n'
932 ' sName,\n'
933 ' sDescription,\n'
934 ' fEnabled,\n'
935 ' enmScheduler,\n'
936 ' idBuildSrc,\n'
937 ' idBuildSrcTestSuite,\n'
938 ' sComment )\n'
939 'VALUES ( %s, %s, %s, %s, %s, %s, %s, %s, %s, %s )\n'
940 , ( uidAuthor,
941 tsEffective,
942 oData.idSchedGroup,
943 oData.sName,
944 oData.sDescription,
945 oData.fEnabled,
946 oData.enmScheduler,
947 oData.idBuildSrc,
948 oData.idBuildSrcTestSuite,
949 oData.sComment, ));
950 return True;
951
952 def _historizeEntry(self, idSchedGroup, tsExpire = None):
953 """
954 Historizes the current entry for the given scheduling group.
955 """
956 if tsExpire is None:
957 tsExpire = self._oDb.getCurrentTimestamp();
958 self._oDb.execute('UPDATE SchedGroups\n'
959 'SET tsExpire = %s\n'
960 'WHERE idSchedGroup = %s\n'
961 ' AND tsExpire = \'infinity\'::TIMESTAMP\n'
962 , ( tsExpire, idSchedGroup, ));
963 return True;
964
965 def _addSchedGroupMember(self, uidAuthor, oMember, tsEffective = None):
966 """
967 addEntry worker for adding a scheduling group member.
968 """
969 if tsEffective is None:
970 tsEffective = self._oDb.getCurrentTimestamp();
971 self._oDb.execute('INSERT INTO SchedGroupMembers(\n'
972 ' idSchedGroup,\n'
973 ' idTestGroup,\n'
974 ' tsEffective,\n'
975 ' uidAuthor,\n'
976 ' iSchedPriority,\n'
977 ' bmHourlySchedule,\n'
978 ' idTestGroupPreReq)\n'
979 'VALUES (%s, %s, %s, %s, %s, %s, %s)\n'
980 , ( oMember.idSchedGroup,
981 oMember.idTestGroup,
982 tsEffective,
983 uidAuthor,
984 oMember.iSchedPriority,
985 oMember.bmHourlySchedule,
986 oMember.idTestGroupPreReq, ));
987 return True;
988
989 def _removeSchedGroupMember(self, uidAuthor, oMember):
990 """
991 Removes a scheduling group member.
992 """
993
994 # Try record who removed it by adding an dummy entry that expires immediately.
995 (tsCur, tsCurMinusOne) = self._oDb.getCurrentTimestamps();
996 if oMember.tsEffective != tsCur and oMember.tsEffective != tsCurMinusOne:
997 self._historizeSchedGroupMember(oMember, tsCurMinusOne);
998 self._addSchedGroupMember(uidAuthor, oMember, tsCurMinusOne); # lazy bird.
999 self._historizeSchedGroupMember(oMember);
1000 else:
1001 self._historizeSchedGroupMember(oMember);
1002 return True;
1003
1004 def _historizeSchedGroupMember(self, oMember, tsExpire = None):
1005 """
1006 Historizes the current entry for the given scheduling group.
1007 """
1008 if tsExpire is None:
1009 tsExpire = self._oDb.getCurrentTimestamp();
1010 self._oDb.execute('UPDATE SchedGroupMembers\n'
1011 'SET tsExpire = %s\n'
1012 'WHERE idSchedGroup = %s\n'
1013 ' AND idTestGroup = %s\n'
1014 ' AND tsExpire = \'infinity\'::TIMESTAMP\n'
1015 , ( tsExpire, oMember.idSchedGroup, oMember.idTestGroup, ));
1016 return True;
1017
1018 #
1019 def _addSchedGroupTestBox(self, uidAuthor, oBoxInGroup, tsEffective = None):
1020 """
1021 addEntry worker for adding a test box to a scheduling group.
1022 """
1023 if tsEffective is None:
1024 tsEffective = self._oDb.getCurrentTimestamp();
1025 self._oDb.execute('INSERT INTO TestBoxesInSchedGroups(\n'
1026 ' idSchedGroup,\n'
1027 ' idTestBox,\n'
1028 ' tsEffective,\n'
1029 ' uidAuthor,\n'
1030 ' iSchedPriority)\n'
1031 'VALUES (%s, %s, %s, %s, %s)\n'
1032 , ( oBoxInGroup.idSchedGroup,
1033 oBoxInGroup.idTestBox,
1034 tsEffective,
1035 uidAuthor,
1036 oBoxInGroup.iSchedPriority, ));
1037 return True;
1038
1039 def _removeSchedGroupTestBox(self, uidAuthor, oBoxInGroup):
1040 """
1041 Removes a testbox from a scheduling group.
1042 """
1043
1044 # Try record who removed it by adding an dummy entry that expires immediately.
1045 (tsCur, tsCurMinusOne) = self._oDb.getCurrentTimestamps();
1046 if oBoxInGroup.tsEffective != tsCur and oBoxInGroup.tsEffective != tsCurMinusOne:
1047 self._historizeSchedGroupTestBox(oBoxInGroup, tsCurMinusOne);
1048 self._addSchedGroupTestBox(uidAuthor, oBoxInGroup, tsCurMinusOne); # lazy bird.
1049 self._historizeSchedGroupTestBox(oBoxInGroup);
1050 else:
1051 self._historizeSchedGroupTestBox(oBoxInGroup);
1052 return True;
1053
1054 def _historizeSchedGroupTestBox(self, oBoxInGroup, tsExpire = None):
1055 """
1056 Historizes the current entry for the given scheduling group.
1057 """
1058 if tsExpire is None:
1059 tsExpire = self._oDb.getCurrentTimestamp();
1060 self._oDb.execute('UPDATE TestBoxesInSchedGroups\n'
1061 'SET tsExpire = %s\n'
1062 'WHERE idSchedGroup = %s\n'
1063 ' AND idTestBox = %s\n'
1064 ' AND tsExpire = \'infinity\'::TIMESTAMP\n'
1065 , ( tsExpire, oBoxInGroup.idSchedGroup, oBoxInGroup.idTestBox, ));
1066 return True;
1067
1068
1069
1070#
1071# Unit testing.
1072#
1073
1074# pylint: disable=missing-docstring
1075class SchedGroupMemberDataTestCase(ModelDataBaseTestCase):
1076 def setUp(self):
1077 self.aoSamples = [SchedGroupMemberData(),];
1078
1079class SchedGroupDataTestCase(ModelDataBaseTestCase):
1080 def setUp(self):
1081 self.aoSamples = [SchedGroupData(),];
1082
1083if __name__ == '__main__':
1084 unittest.main();
1085 # not reached.
1086
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