VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/testmanager/core/schedgroup.py

Last change on this file was 106061, checked in by vboxsync, 3 months ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 60.5 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: schedgroup.py 106061 2024-09-16 14:03:52Z vboxsync $
3
4"""
5Test Manager - Scheduling Group.
6"""
7
8__copyright__ = \
9"""
10Copyright (C) 2012-2024 Oracle and/or its affiliates.
11
12This file is part of VirtualBox base platform packages, as
13available from https://www.virtualbox.org.
14
15This program is free software; you can redistribute it and/or
16modify it under the terms of the GNU General Public License
17as published by the Free Software Foundation, in version 3 of the
18License.
19
20This program is distributed in the hope that it will be useful, but
21WITHOUT ANY WARRANTY; without even the implied warranty of
22MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
23General Public License for more details.
24
25You should have received a copy of the GNU General Public License
26along with this program; if not, see <https://www.gnu.org/licenses>.
27
28The contents of this file may alternatively be used under the terms
29of the Common Development and Distribution License Version 1.0
30(CDDL), a copy of it is provided in the "COPYING.CDDL" file included
31in the VirtualBox distribution, in which case the provisions of the
32CDDL are applicable instead of those of the GPL.
33
34You may elect to license modified versions of this file under the
35terms and conditions of either the GPL or the CDDL or both.
36
37SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
38"""
39__version__ = "$Revision: 106061 $"
40
41
42# Standard python imports.
43import unittest;
44
45# Validation Kit imports.
46from testmanager.core.base import ModelDataBase, ModelDataBaseTestCase, ModelLogicBase, TMExceptionBase, \
47 TMRowInUse, TMInvalidData, TMRowAlreadyExists, TMRowNotFound, \
48 ChangeLogEntry, AttributeChangeEntry, AttributeChangeEntryPre;
49from testmanager.core.buildsource import BuildSourceData;
50from testmanager.core import db;
51from testmanager.core.testcase import TestCaseData;
52from testmanager.core.testcaseargs import TestCaseArgsData;
53from testmanager.core.testbox import TestBoxLogic, TestBoxDataForSchedGroup;
54from testmanager.core.testgroup import TestGroupData;
55from testmanager.core.useraccount import UserAccountLogic;
56
57
58
59class SchedGroupMemberData(ModelDataBase):
60 """
61 SchedGroupMember Data.
62 """
63
64 ksIdAttr = 'idSchedGroup';
65
66 ksParam_idSchedGroup = 'SchedGroupMember_idSchedGroup';
67 ksParam_idTestGroup = 'SchedGroupMember_idTestGroup';
68 ksParam_tsEffective = 'SchedGroupMember_tsEffective';
69 ksParam_tsExpire = 'SchedGroupMember_tsExpire';
70 ksParam_uidAuthor = 'SchedGroupMember_uidAuthor';
71 ksParam_iSchedPriority = 'SchedGroupMember_iSchedPriority';
72 ksParam_bmHourlySchedule = 'SchedGroupMember_bmHourlySchedule';
73 ksParam_idTestGroupPreReq = 'SchedGroupMember_idTestGroupPreReq';
74
75 kasAllowNullAttributes = [ 'idSchedGroup', 'idTestGroup', 'tsEffective', 'tsExpire',
76 'uidAuthor', 'bmHourlySchedule', 'idTestGroupPreReq' ];
77 kiMin_iSchedPriority = 0;
78 kiMax_iSchedPriority = 32;
79
80 kcDbColumns = 8
81
82 def __init__(self):
83 ModelDataBase.__init__(self);
84
85 #
86 # Initialize with defaults.
87 # See the database for explanations of each of these fields.
88 #
89 self.idSchedGroup = None;
90 self.idTestGroup = None;
91 self.tsEffective = None;
92 self.tsExpire = None;
93 self.uidAuthor = None;
94 self.iSchedPriority = 16;
95 self.bmHourlySchedule = None;
96 self.idTestGroupPreReq = None;
97
98 def initFromDbRow(self, aoRow):
99 """
100 Re-initializes the data with a row from a SELECT * FROM SchedGroupMembers.
101
102 Returns self. Raises exception if the row is None or otherwise invalid.
103 """
104
105 if aoRow is None:
106 raise TMRowNotFound('SchedGroupMember not found.');
107
108 self.idSchedGroup = aoRow[0];
109 self.idTestGroup = aoRow[1];
110 self.tsEffective = aoRow[2];
111 self.tsExpire = aoRow[3];
112 self.uidAuthor = aoRow[4];
113 self.iSchedPriority = aoRow[5];
114 self.bmHourlySchedule = aoRow[6]; ## @todo figure out how bitmaps are returned...
115 self.idTestGroupPreReq = aoRow[7];
116 return self;
117
118
119class SchedGroupMemberDataEx(SchedGroupMemberData):
120 """
121 Extended SchedGroupMember data class.
122 This adds the testgroups.
123 """
124
125 def __init__(self):
126 SchedGroupMemberData.__init__(self);
127 self.oTestGroup = None;
128
129 def initFromDbRow(self, aoRow):
130 """
131 Re-initializes the data with a row from a query like this:
132
133 SELECT SchedGroupMembers.*, TestGroups.*
134 FROM SchedGroupMembers
135 JOIN TestGroups
136 ON (SchedGroupMembers.idTestGroup = TestGroups.idTestGroup);
137
138 Returns self. Raises exception if the row is None or otherwise invalid.
139 """
140 SchedGroupMemberData.initFromDbRow(self, aoRow);
141 self.oTestGroup = TestGroupData().initFromDbRow(aoRow[SchedGroupMemberData.kcDbColumns:]);
142 return self;
143
144 def getDataAttributes(self):
145 asAttributes = SchedGroupMemberData.getDataAttributes(self);
146 asAttributes.remove('oTestGroup');
147 return asAttributes;
148
149 def _validateAndConvertWorker(self, asAllowNullAttributes, oDb, enmValidateFor = ModelDataBase.ksValidateFor_Other):
150 dErrors = SchedGroupMemberData._validateAndConvertWorker(self, asAllowNullAttributes, oDb, enmValidateFor);
151 if self.ksParam_idTestGroup not in dErrors:
152 self.oTestGroup = TestGroupData();
153 try:
154 self.oTestGroup.initFromDbWithId(oDb, self.idTestGroup);
155 except Exception as oXcpt:
156 self.oTestGroup = TestGroupData()
157 dErrors[self.ksParam_idTestGroup] = str(oXcpt);
158 return dErrors;
159
160
161
162
163class SchedGroupData(ModelDataBase):
164 """
165 SchedGroup Data.
166 """
167
168 ## @name TestBoxState_T
169 # @{
170 ksScheduler_BestEffortContinuousIntegration = 'bestEffortContinousItegration'; # sic*2
171 ksScheduler_Reserved = 'reserved';
172 ## @}
173
174
175 ksIdAttr = 'idSchedGroup';
176
177 ksParam_idSchedGroup = 'SchedGroup_idSchedGroup';
178 ksParam_tsEffective = 'SchedGroup_tsEffective';
179 ksParam_tsExpire = 'SchedGroup_tsExpire';
180 ksParam_uidAuthor = 'SchedGroup_uidAuthor';
181 ksParam_sName = 'SchedGroup_sName';
182 ksParam_sDescription = 'SchedGroup_sDescription';
183 ksParam_fEnabled = 'SchedGroup_fEnabled';
184 ksParam_enmScheduler = 'SchedGroup_enmScheduler';
185 ksParam_idBuildSrc = 'SchedGroup_idBuildSrc';
186 ksParam_idBuildSrcTestSuite = 'SchedGroup_idBuildSrcTestSuite';
187 ksParam_sComment = 'SchedGroup_sComment';
188
189 kasAllowNullAttributes = ['idSchedGroup', 'tsEffective', 'tsExpire', 'uidAuthor', 'sDescription',
190 'idBuildSrc', 'idBuildSrcTestSuite', 'sComment' ];
191 kasValidValues_enmScheduler = [ ksScheduler_BestEffortContinuousIntegration, ];
192
193 kcDbColumns = 11;
194
195 # Scheduler types
196 kasSchedulerDesc = \
197 [
198 ( ksScheduler_BestEffortContinuousIntegration, 'Best-Effort-Continuous-Integration (BECI) scheduler.', ''),
199 ]
200
201 def __init__(self):
202 ModelDataBase.__init__(self);
203
204 #
205 # Initialize with defaults.
206 # See the database for explanations of each of these fields.
207 #
208 self.idSchedGroup = None;
209 self.tsEffective = None;
210 self.tsExpire = None;
211 self.uidAuthor = None;
212 self.sName = None;
213 self.sDescription = None;
214 self.fEnabled = None;
215 self.enmScheduler = SchedGroupData.ksScheduler_BestEffortContinuousIntegration;
216 self.idBuildSrc = None;
217 self.idBuildSrcTestSuite = None;
218 self.sComment = None;
219
220 def initFromDbRow(self, aoRow):
221 """
222 Re-initializes the data with a row from a SELECT * FROM SchedGroups.
223
224 Returns self. Raises exception if the row is None or otherwise invalid.
225 """
226
227 if aoRow is None:
228 raise TMRowNotFound('SchedGroup not found.');
229
230 self.idSchedGroup = aoRow[0];
231 self.tsEffective = aoRow[1];
232 self.tsExpire = aoRow[2];
233 self.uidAuthor = aoRow[3];
234 self.sName = aoRow[4];
235 self.sDescription = aoRow[5];
236 self.fEnabled = aoRow[6];
237 self.enmScheduler = aoRow[7];
238 self.idBuildSrc = aoRow[8];
239 self.idBuildSrcTestSuite = aoRow[9];
240 self.sComment = aoRow[10];
241 return self;
242
243 def initFromDbWithId(self, oDb, idSchedGroup, tsNow = None, sPeriodBack = None):
244 """
245 Initialize the object from the database.
246 """
247 oDb.execute(self.formatSimpleNowAndPeriodQuery(oDb,
248 'SELECT *\n'
249 'FROM SchedGroups\n'
250 'WHERE idSchedGroup = %s\n'
251 , ( idSchedGroup,), tsNow, sPeriodBack));
252 aoRow = oDb.fetchOne()
253 if aoRow is None:
254 raise TMRowNotFound('idSchedGroup=%s not found (tsNow=%s, sPeriodBack=%s)' % (idSchedGroup, tsNow, sPeriodBack));
255 return self.initFromDbRow(aoRow);
256
257
258class SchedGroupDataEx(SchedGroupData):
259 """
260 Extended scheduling group data.
261
262 Note! Similar to TestGroupDataEx.
263 """
264
265 ksParam_aoMembers = 'SchedGroup_aoMembers';
266 ksParam_aoTestBoxes = 'SchedGroup_aoTestboxes';
267 kasAltArrayNull = [ 'aoMembers', 'aoTestboxes' ];
268
269 ## Helper parameter containing the comma separated list with the IDs of
270 # potential members found in the parameters.
271 ksParam_aidTestGroups = 'TestGroupDataEx_aidTestGroups';
272 ## Ditto for testbox meembers.
273 ksParam_aidTestBoxes = 'TestGroupDataEx_aidTestBoxes';
274
275
276 def __init__(self):
277 SchedGroupData.__init__(self);
278 self.aoMembers = [] # type: list[SchedGroupMemberDataEx]
279 self.aoTestBoxes = [] # type: list[TestBoxDataForSchedGroup]
280
281 # The two build sources for the sake of convenience.
282 self.oBuildSrc = None # type: BuildSourceData
283 self.oBuildSrcValidationKit = None # type: BuildSourceData
284
285 def _initExtraMembersFromDb(self, oDb, tsNow = None, sPeriodBack = None):
286 """
287 Worker shared by the initFromDb* methods.
288 Returns self. Raises exception if no row or database error.
289 """
290 #
291 # Clear all members upfront so the object has some kind of consistency
292 # if anything below raises exceptions.
293 #
294 self.oBuildSrc = None;
295 self.oBuildSrcValidationKit = None;
296 self.aoTestBoxes = [];
297 self.aoMembers = [];
298
299 #
300 # Build source.
301 #
302 if self.idBuildSrc:
303 self.oBuildSrc = BuildSourceData().initFromDbWithId(oDb, self.idBuildSrc, tsNow, sPeriodBack);
304
305 if self.idBuildSrcTestSuite:
306 self.oBuildSrcValidationKit = BuildSourceData().initFromDbWithId(oDb, self.idBuildSrcTestSuite,
307 tsNow, sPeriodBack);
308
309 #
310 # Test Boxes.
311 #
312 self.aoTestBoxes = TestBoxLogic(oDb).fetchForSchedGroup(self.idSchedGroup, tsNow);
313
314 #
315 # Test groups.
316 # The fetchForChangeLog method makes ASSUMPTIONS about sorting!
317 #
318 oDb.execute('SELECT SchedGroupMembers.*, TestGroups.*\n'
319 'FROM SchedGroupMembers\n'
320 'LEFT OUTER JOIN TestGroups ON (SchedGroupMembers.idTestGroup = TestGroups.idTestGroup)\n'
321 'WHERE SchedGroupMembers.idSchedGroup = %s\n'
322 + self.formatSimpleNowAndPeriod(oDb, tsNow, sPeriodBack, sTablePrefix = 'SchedGroupMembers.')
323 + self.formatSimpleNowAndPeriod(oDb, tsNow, sPeriodBack, sTablePrefix = 'TestGroups.') +
324 'ORDER BY SchedGroupMembers.idTestGroupPreReq ASC NULLS FIRST,\n'
325 ' TestGroups.sName,\n'
326 ' SchedGroupMembers.idTestGroup\n'
327 , (self.idSchedGroup,));
328 for aoRow in oDb.fetchAll():
329 self.aoMembers.append(SchedGroupMemberDataEx().initFromDbRow(aoRow));
330 return self;
331
332 def initFromDbRowEx(self, aoRow, oDb, tsNow = None):
333 """
334 Reinitialize from a SELECT * FROM SchedGroups row. Will query the
335 necessary additional data from oDb using tsNow.
336 Returns self. Raises exception if no row or database error.
337 """
338 SchedGroupData.initFromDbRow(self, aoRow);
339 return self._initExtraMembersFromDb(oDb, tsNow);
340
341 def initFromDbWithId(self, oDb, idSchedGroup, tsNow = None, sPeriodBack = None):
342 """
343 Initialize the object from the database.
344 """
345 SchedGroupData.initFromDbWithId(self, oDb, idSchedGroup, tsNow, sPeriodBack);
346 return self._initExtraMembersFromDb(oDb, tsNow, sPeriodBack);
347
348 def getDataAttributes(self):
349 asAttributes = SchedGroupData.getDataAttributes(self);
350 asAttributes.remove('oBuildSrc');
351 asAttributes.remove('oBuildSrcValidationKit');
352 return asAttributes;
353
354 def getAttributeParamNullValues(self, sAttr):
355 if sAttr not in [ 'aoMembers', 'aoTestBoxes' ]:
356 return SchedGroupData.getAttributeParamNullValues(self, sAttr);
357 return ['', [], None];
358
359 def convertParamToAttribute(self, sAttr, sParam, oValue, oDisp, fStrict):
360 aoNewValue = [];
361 if sAttr == 'aoMembers':
362 aidSelected = oDisp.getListOfIntParams(sParam, iMin = 1, iMax = 0x7ffffffe, aiDefaults = [])
363 sIds = oDisp.getStringParam(self.ksParam_aidTestGroups, sDefault = '');
364 for idTestGroup in sIds.split(','):
365 try: idTestGroup = int(idTestGroup);
366 except: pass;
367 oDispWrapper = self.DispWrapper(oDisp, '%s[%s][%%s]' % (SchedGroupDataEx.ksParam_aoMembers, idTestGroup,))
368 oMember = SchedGroupMemberDataEx().initFromParams(oDispWrapper, fStrict = False);
369 if idTestGroup in aidSelected:
370 oMember.idTestGroup = idTestGroup;
371 aoNewValue.append(oMember);
372 elif sAttr == 'aoTestBoxes':
373 aidSelected = oDisp.getListOfIntParams(sParam, iMin = 1, iMax = 0x7ffffffe, aiDefaults = [])
374 sIds = oDisp.getStringParam(self.ksParam_aidTestBoxes, sDefault = '');
375 for idTestBox in sIds.split(','):
376 try: idTestBox = int(idTestBox);
377 except: pass;
378 oDispWrapper = self.DispWrapper(oDisp, '%s[%s][%%s]' % (SchedGroupDataEx.ksParam_aoTestBoxes, idTestBox,))
379 oBoxInGrp = TestBoxDataForSchedGroup().initFromParams(oDispWrapper, fStrict = False);
380 if idTestBox in aidSelected:
381 oBoxInGrp.idTestBox = idTestBox;
382 aoNewValue.append(oBoxInGrp);
383 else:
384 return SchedGroupData.convertParamToAttribute(self, sAttr, sParam, oValue, oDisp, fStrict);
385 return aoNewValue;
386
387 def _validateAndConvertAttribute(self, sAttr, sParam, oValue, aoNilValues, fAllowNull, oDb):
388 if sAttr not in [ 'aoMembers', 'aoTestBoxes' ]:
389 return SchedGroupData._validateAndConvertAttribute(self, sAttr, sParam, oValue, aoNilValues, fAllowNull, oDb);
390
391 if oValue in aoNilValues:
392 return ([], None);
393
394 asErrors = [];
395 aoNewMembers = [];
396 if sAttr == 'aoMembers':
397 asAllowNulls = ['bmHourlySchedule', 'idTestGroupPreReq', 'tsEffective', 'tsExpire', 'uidAuthor', ];
398 if self.idSchedGroup in [None, '-1', -1]:
399 asAllowNulls.append('idSchedGroup'); # Probably new group, so allow null scheduling group.
400
401 for oOldMember in oValue:
402 oNewMember = SchedGroupMemberDataEx().initFromOther(oOldMember);
403 aoNewMembers.append(oNewMember);
404
405 dErrors = oNewMember.validateAndConvertEx(asAllowNulls, oDb, ModelDataBase.ksValidateFor_Other);
406 if dErrors:
407 asErrors.append(str(dErrors));
408
409 if not asErrors:
410 for i, idTestGroup in enumerate(aoNewMembers):
411 for j in range(i + 1, len(aoNewMembers)):
412 if aoNewMembers[j].idTestGroup == idTestGroup:
413 asErrors.append('Duplicate test group #%d!' % (idTestGroup, ));
414 break;
415 else:
416 asAllowNulls = list(TestBoxDataForSchedGroup.kasAllowNullAttributes);
417 if self.idSchedGroup in [None, '-1', -1]:
418 asAllowNulls.append('idSchedGroup'); # Probably new group, so allow null scheduling group.
419
420 for oOldMember in oValue:
421 oNewMember = TestBoxDataForSchedGroup().initFromOther(oOldMember);
422 aoNewMembers.append(oNewMember);
423
424 dErrors = oNewMember.validateAndConvertEx(asAllowNulls, oDb, ModelDataBase.ksValidateFor_Other);
425 if dErrors:
426 asErrors.append(str(dErrors));
427
428 if not asErrors:
429 for i, idTestBox in enumerate(aoNewMembers):
430 for j in range(i + 1, len(aoNewMembers)):
431 if aoNewMembers[j].idTestBox == idTestBox:
432 asErrors.append('Duplicate test box #%d!' % (idTestBox, ));
433 break;
434
435 return (aoNewMembers, None if not asErrors else '<br>\n'.join(asErrors));
436
437 def _validateAndConvertWorker(self, asAllowNullAttributes, oDb, enmValidateFor = ModelDataBase.ksValidateFor_Other):
438 dErrors = SchedGroupData._validateAndConvertWorker(self, asAllowNullAttributes, oDb, enmValidateFor);
439
440 #
441 # Fetch the extended build source bits.
442 #
443 if self.ksParam_idBuildSrc not in dErrors:
444 if self.idBuildSrc in self.getAttributeParamNullValues('idBuildSrc') \
445 or self.idBuildSrc is None:
446 self.oBuildSrc = None;
447 else:
448 try:
449 self.oBuildSrc = BuildSourceData().initFromDbWithId(oDb, self.idBuildSrc);
450 except Exception as oXcpt:
451 self.oBuildSrc = BuildSourceData();
452 dErrors[self.ksParam_idBuildSrc] = str(oXcpt);
453
454 if self.ksParam_idBuildSrcTestSuite not in dErrors:
455 if self.idBuildSrcTestSuite in self.getAttributeParamNullValues('idBuildSrcTestSuite') \
456 or self.idBuildSrcTestSuite is None:
457 self.oBuildSrcValidationKit = None;
458 else:
459 try:
460 self.oBuildSrcValidationKit = BuildSourceData().initFromDbWithId(oDb, self.idBuildSrcTestSuite);
461 except Exception as oXcpt:
462 self.oBuildSrcValidationKit = BuildSourceData();
463 dErrors[self.ksParam_idBuildSrcTestSuite] = str(oXcpt);
464
465 return dErrors;
466
467
468
469class SchedGroupLogic(ModelLogicBase): # pylint: disable=too-few-public-methods
470 """
471 SchedGroup logic.
472 """
473
474 def __init__(self, oDb):
475 ModelLogicBase.__init__(self, oDb);
476 self.dCache = None;
477
478 #
479 # Standard methods.
480 #
481
482 def fetchForListing(self, iStart, cMaxRows, tsNow, aiSortColumns = None):
483 """
484 Fetches build sources.
485
486 Returns an array (list) of BuildSourceData items, empty list if none.
487 Raises exception on error.
488 """
489 _ = aiSortColumns;
490
491 if tsNow is None:
492 self._oDb.execute('SELECT *\n'
493 'FROM SchedGroups\n'
494 'WHERE tsExpire = \'infinity\'::TIMESTAMP\n'
495 'ORDER BY fEnabled DESC, sName DESC\n'
496 'LIMIT %s OFFSET %s\n'
497 , (cMaxRows, iStart,));
498 else:
499 self._oDb.execute('SELECT *\n'
500 'FROM SchedGroups\n'
501 'WHERE tsExpire > %s\n'
502 ' AND tsEffective <= %s\n'
503 'ORDER BY fEnabled DESC, sName DESC\n'
504 'LIMIT %s OFFSET %s\n'
505 , (tsNow, tsNow, cMaxRows, iStart,));
506
507 aoRet = [];
508 for aoRow in self._oDb.fetchAll():
509 aoRet.append(SchedGroupDataEx().initFromDbRowEx(aoRow, self._oDb, tsNow));
510 return aoRet;
511
512 def fetchForChangeLog(self, idSchedGroup, iStart, cMaxRows, tsNow): # pylint: disable=too-many-locals,too-many-statements
513 """
514 Fetches change log entries for a scheduling group.
515
516 Returns an array of ChangeLogEntry instance and an indicator whether
517 there are more entries.
518 Raises exception on error.
519 """
520
521 ## @todo calc changes to scheduler group!
522
523 if tsNow is None:
524 tsNow = self._oDb.getCurrentTimestamp();
525
526 #
527 # First gather the change log timeline using the effective dates.
528 # (ASSUMES that we'll always have a separate delete entry, rather
529 # than just setting tsExpire.)
530 #
531 self._oDb.execute('''
532(
533SELECT tsEffective,
534 uidAuthor
535FROM SchedGroups
536WHERE idSchedGroup = %s
537 AND tsEffective <= %s
538ORDER BY tsEffective DESC
539) UNION (
540SELECT CASE WHEN tsEffective + %s::INTERVAL = tsExpire THEN tsExpire ELSE tsEffective END,
541 uidAuthor
542FROM SchedGroupMembers
543WHERE idSchedGroup = %s
544 AND tsEffective <= %s
545ORDER BY tsEffective DESC
546) UNION (
547SELECT CASE WHEN tsEffective + %s::INTERVAL = tsExpire THEN tsExpire ELSE tsEffective END,
548 uidAuthor
549FROM TestBoxesInSchedGroups
550WHERE idSchedGroup = %s
551 AND tsEffective <= %s
552ORDER BY tsEffective DESC
553)
554ORDER BY tsEffective DESC
555LIMIT %s OFFSET %s
556''', (idSchedGroup, tsNow,
557 db.dbOneTickIntervalString(), idSchedGroup, tsNow,
558 db.dbOneTickIntervalString(), idSchedGroup, tsNow,
559 cMaxRows + 1, iStart, ));
560
561 aoEntries = [] # type: list[ChangeLogEntry]
562 tsPrevious = tsNow;
563 for aoDbRow in self._oDb.fetchAll():
564 (tsEffective, uidAuthor) = aoDbRow;
565 aoEntries.append(ChangeLogEntry(uidAuthor, None, tsEffective, tsPrevious, None, None, []));
566 tsPrevious = db.dbTimestampPlusOneTick(tsEffective);
567
568 if True: # pylint: disable=using-constant-test
569 #
570 # Fetch data for each for each change log entry point.
571 #
572 # We add one tick to the timestamp here to skip past delete records
573 # that only there to record the user doing the deletion.
574 #
575 for iEntry, oEntry in enumerate(aoEntries):
576 oEntry.oNewRaw = SchedGroupDataEx().initFromDbWithId(self._oDb, idSchedGroup, oEntry.tsEffective);
577 if iEntry > 0:
578 aoEntries[iEntry - 1].oOldRaw = oEntry.oNewRaw;
579
580 # Chop off the +1 entry, if any.
581 fMore = len(aoEntries) > cMaxRows;
582 if fMore:
583 aoEntries = aoEntries[:-1];
584
585 # Figure out the changes.
586 for oEntry in aoEntries:
587 oOld = oEntry.oOldRaw;
588 if not oOld:
589 break;
590 oNew = oEntry.oNewRaw;
591 aoChanges = oEntry.aoChanges;
592 for sAttr in oNew.getDataAttributes():
593 if sAttr in [ 'tsEffective', 'tsExpire', 'uidAuthor', ]:
594 continue;
595 oOldAttr = getattr(oOld, sAttr);
596 oNewAttr = getattr(oNew, sAttr);
597 if oOldAttr == oNewAttr:
598 continue;
599 if sAttr in [ 'aoMembers', 'aoTestBoxes', ]:
600 iNew = 0;
601 iOld = 0;
602 asNewAttr = [];
603 asOldAttr = [];
604 if sAttr == 'aoMembers':
605 # ASSUMES aoMembers is sorted by idTestGroupPreReq (nulls first), oTestGroup.sName, idTestGroup!
606 while iNew < len(oNewAttr) and iOld < len(oOldAttr):
607 if oNewAttr[iNew].idTestGroup == oOldAttr[iOld].idTestGroup:
608 if oNewAttr[iNew].idTestGroupPreReq != oOldAttr[iOld].idTestGroupPreReq:
609 if oNewAttr[iNew].idTestGroupPreReq is None:
610 asOldAttr.append('Dropped test group #%s (%s) dependency on #%s'
611 % (oNewAttr[iNew].idTestGroup, oNewAttr[iNew].oTestGroup.sName,
612 oOldAttr[iOld].idTestGroupPreReq));
613 elif oOldAttr[iOld].idTestGroupPreReq is None:
614 asNewAttr.append('Added test group #%s (%s) dependency on #%s'
615 % (oNewAttr[iNew].idTestGroup, oNewAttr[iNew].oTestGroup.sName,
616 oNewAttr[iOld].idTestGroupPreReq));
617 else:
618 asNewAttr.append('Test group #%s (%s) dependency on #%s'
619 % (oNewAttr[iNew].idTestGroup, oNewAttr[iNew].oTestGroup.sName,
620 oNewAttr[iNew].idTestGroupPreReq));
621 asOldAttr.append('Test group #%s (%s) dependency on #%s'
622 % (oNewAttr[iNew].idTestGroup, oNewAttr[iNew].oTestGroup.sName,
623 oOldAttr[iOld].idTestGroupPreReq));
624 if oNewAttr[iNew].iSchedPriority != oOldAttr[iOld].iSchedPriority:
625 asNewAttr.append('Test group #%s (%s) priority %s'
626 % (oNewAttr[iNew].idTestGroup, oNewAttr[iNew].oTestGroup.sName,
627 oNewAttr[iNew].iSchedPriority));
628 asOldAttr.append('Test group #%s (%s) priority %s'
629 % (oNewAttr[iNew].idTestGroup, oNewAttr[iNew].oTestGroup.sName,
630 oOldAttr[iOld].iSchedPriority));
631 iNew += 1;
632 iOld += 1;
633 elif oNewAttr[iNew].oTestGroup.sName < oOldAttr[iOld].oTestGroup.sName \
634 or ( oNewAttr[iNew].oTestGroup.sName == oOldAttr[iOld].oTestGroup.sName
635 and oNewAttr[iNew].idTestGroup < oOldAttr[iOld].idTestGroup):
636 asNewAttr.append('New test group #%s - %s'
637 % (oNewAttr[iNew].idTestGroup, oNewAttr[iNew].oTestGroup.sName));
638 iNew += 1;
639 else:
640 asOldAttr.append('Removed test group #%s - %s'
641 % (oOldAttr[iOld].idTestGroup, oOldAttr[iOld].oTestGroup.sName));
642 iOld += 1;
643 while iNew < len(oNewAttr):
644 asNewAttr.append('New test group #%s - %s'
645 % (oNewAttr[iNew].idTestGroup, oNewAttr[iNew].oTestGroup.sName));
646 iNew += 1;
647 while iOld < len(oOldAttr):
648 asOldAttr.append('Removed test group #%s - %s'
649 % (oOldAttr[iOld].idTestGroup, oOldAttr[iOld].oTestGroup.sName));
650 iOld += 1;
651 else:
652 dNewIds = { oBoxInGrp.idTestBox: oBoxInGrp for oBoxInGrp in oNewAttr };
653 dOldIds = { oBoxInGrp.idTestBox: oBoxInGrp for oBoxInGrp in oOldAttr };
654 hCommonIds = set(dNewIds.keys()) & set(dOldIds.keys());
655 for idTestBox in hCommonIds:
656 oNewBoxInGrp = dNewIds[idTestBox];
657 oOldBoxInGrp = dOldIds[idTestBox];
658 if oNewBoxInGrp.iSchedPriority != oOldBoxInGrp.iSchedPriority:
659 asNewAttr.append('Test box \'%s\' (#%s) priority %s'
660 % (getattr(oNewBoxInGrp.oTestBox, 'sName', '[Partial DB]'),
661 oNewBoxInGrp.idTestBox, oNewBoxInGrp.iSchedPriority));
662 asOldAttr.append('Test box \'%s\' (#%s) priority %s'
663 % (getattr(oOldBoxInGrp.oTestBox, 'sName', '[Partial DB]'),
664 oOldBoxInGrp.idTestBox, oOldBoxInGrp.iSchedPriority));
665 asNewAttr = sorted(asNewAttr);
666 asOldAttr = sorted(asOldAttr);
667 for idTestBox in set(dNewIds.keys()) - hCommonIds:
668 oNewBoxInGrp = dNewIds[idTestBox];
669 asNewAttr.append('New test box \'%s\' (#%s) priority %s'
670 % (getattr(oNewBoxInGrp.oTestBox, 'sName', '[Partial DB]'),
671 oNewBoxInGrp.idTestBox, oNewBoxInGrp.iSchedPriority));
672 for idTestBox in set(dOldIds.keys()) - hCommonIds:
673 oOldBoxInGrp = dOldIds[idTestBox];
674 asOldAttr.append('Removed test box \'%s\' (#%s) priority %s'
675 % (getattr(oOldBoxInGrp.oTestBox, 'sName', '[Partial DB]'),
676 oOldBoxInGrp.idTestBox, oOldBoxInGrp.iSchedPriority));
677
678 if asNewAttr or asOldAttr:
679 aoChanges.append(AttributeChangeEntryPre(sAttr, oNewAttr, oOldAttr,
680 '\n'.join(asNewAttr), '\n'.join(asOldAttr)));
681 else:
682 aoChanges.append(AttributeChangeEntry(sAttr, oNewAttr, oOldAttr, str(oNewAttr), str(oOldAttr)));
683
684 else:
685 ##
686 ## @todo Incomplete: A more complicate apporach, probably faster though.
687 ##
688 def findEntry(tsEffective, iPrev = 0):
689 """ Find entry with matching effective + expiration time """
690 self._oDb.dprint('findEntry: iPrev=%s len(aoEntries)=%s tsEffective=%s' % (iPrev, len(aoEntries), tsEffective));
691 while iPrev < len(aoEntries):
692 self._oDb.dprint('%s iPrev=%u' % (aoEntries[iPrev].tsEffective, iPrev, ));
693 if aoEntries[iPrev].tsEffective > tsEffective:
694 iPrev += 1;
695 elif aoEntries[iPrev].tsEffective == tsEffective:
696 self._oDb.dprint('hit %u' % (iPrev,));
697 return iPrev;
698 else:
699 break;
700 self._oDb.dprint('%s not found!' % (tsEffective,));
701 return -1;
702
703 fMore = True;
704
705 #
706 # Track scheduling group changes. Not terribly efficient for large cMaxRows
707 # values, but not in the mood for figure out if there is any way to optimize that.
708 #
709 self._oDb.execute('''
710SELECT *
711FROM SchedGroups
712WHERE idSchedGroup = %s
713 AND tsEffective <= %s
714ORDER BY tsEffective DESC
715LIMIT %s''', (idSchedGroup, aoEntries[0].tsEffective, cMaxRows + 1,));
716
717 iEntry = 0;
718 aaoRows = self._oDb.fetchAll();
719 for iRow, oRow in enumerate(aaoRows):
720 oNew = SchedGroupDataEx().initFromDbRow(oRow);
721 iEntry = findEntry(oNew.tsEffective, iEntry);
722 self._oDb.dprint('iRow=%s iEntry=%s' % (iRow, iEntry));
723 if iEntry < 0:
724 break;
725 oEntry = aoEntries[iEntry];
726 aoChanges = oEntry.aoChanges;
727 oEntry.oNewRaw = oNew;
728 if iRow + 1 < len(aaoRows):
729 oOld = SchedGroupDataEx().initFromDbRow(aaoRows[iRow + 1]);
730 self._oDb.dprint('oOld=%s' % (oOld,));
731 for sAttr in oNew.getDataAttributes():
732 if sAttr not in [ 'tsEffective', 'tsExpire', 'uidAuthor', ]:
733 oOldAttr = getattr(oOld, sAttr);
734 oNewAttr = getattr(oNew, sAttr);
735 if oOldAttr != oNewAttr:
736 aoChanges.append(AttributeChangeEntry(sAttr, oNewAttr, oOldAttr, str(oNewAttr), str(oOldAttr)));
737 else:
738 self._oDb.dprint('New');
739
740 #
741 # ...
742 #
743
744 # FInally
745 UserAccountLogic(self._oDb).resolveChangeLogAuthors(aoEntries);
746 return (aoEntries, fMore);
747
748
749 def addEntry(self, oData, uidAuthor, fCommit = False):
750 """Add Scheduling Group record"""
751
752 #
753 # Validate.
754 #
755 dDataErrors = oData.validateAndConvert(self._oDb, oData.ksValidateFor_Add);
756 if dDataErrors:
757 raise TMInvalidData('Invalid data passed to addEntry: %s' % (dDataErrors,));
758 if self.exists(oData.sName):
759 raise TMRowAlreadyExists('Scheduling group "%s" already exists.' % (oData.sName,));
760
761 #
762 # Add it.
763 #
764 self._oDb.execute('INSERT INTO SchedGroups (\n'
765 ' uidAuthor,\n'
766 ' sName,\n'
767 ' sDescription,\n'
768 ' fEnabled,\n'
769 ' enmScheduler,\n'
770 ' idBuildSrc,\n'
771 ' idBuildSrcTestSuite,\n'
772 ' sComment)\n'
773 'VALUES (%s, %s, %s, %s, %s, %s, %s, %s)\n'
774 'RETURNING idSchedGroup\n'
775 , ( uidAuthor,
776 oData.sName,
777 oData.sDescription,
778 oData.fEnabled,
779 oData.enmScheduler,
780 oData.idBuildSrc,
781 oData.idBuildSrcTestSuite,
782 oData.sComment ));
783 idSchedGroup = self._oDb.fetchOne()[0];
784 oData.idSchedGroup = idSchedGroup;
785
786 for oBoxInGrp in oData.aoTestBoxes:
787 oBoxInGrp.idSchedGroup = idSchedGroup;
788 self._addSchedGroupTestBox(uidAuthor, oBoxInGrp);
789
790 for oMember in oData.aoMembers:
791 oMember.idSchedGroup = idSchedGroup;
792 self._addSchedGroupMember(uidAuthor, oMember);
793
794 self._oDb.maybeCommit(fCommit);
795 return True;
796
797 def editEntry(self, oData, uidAuthor, fCommit = False):
798 """Edit Scheduling Group record"""
799
800 #
801 # Validate input and retrieve the old data.
802 #
803 dErrors = oData.validateAndConvert(self._oDb, oData.ksValidateFor_Edit);
804 if dErrors:
805 raise TMInvalidData('editEntry got invalid data: %s' % (dErrors,));
806 self._assertUnique(oData.sName, oData.idSchedGroup);
807 oOldData = SchedGroupDataEx().initFromDbWithId(self._oDb, oData.idSchedGroup);
808
809 #
810 # Make the changes.
811 #
812 if not oData.isEqualEx(oOldData, [ 'tsEffective', 'tsExpire', 'uidAuthor', 'aoMembers', 'aoTestBoxes',
813 'oBuildSrc', 'oBuildSrcValidationKit', ]):
814 self._historizeEntry(oData.idSchedGroup);
815 self._readdEntry(uidAuthor, oData);
816
817 # Remove groups.
818 for oOld in oOldData.aoMembers:
819 fRemove = True;
820 for oNew in oData.aoMembers:
821 if oNew.idTestGroup == oOld.idTestGroup:
822 fRemove = False;
823 break;
824 if fRemove:
825 self._removeSchedGroupMember(uidAuthor, oOld);
826
827 # Add / modify groups.
828 for oMember in oData.aoMembers:
829 oOldMember = None;
830 for oOld in oOldData.aoMembers:
831 if oOld.idTestGroup == oMember.idTestGroup:
832 oOldMember = oOld;
833 break;
834
835 oMember.idSchedGroup = oData.idSchedGroup;
836 if oOldMember is None:
837 self._addSchedGroupMember(uidAuthor, oMember);
838 elif not oMember.isEqualEx(oOldMember, ['tsEffective', 'tsExpire', 'uidAuthor', 'oTestGroup']):
839 self._historizeSchedGroupMember(oMember);
840 self._addSchedGroupMember(uidAuthor, oMember);
841
842 # Remove testboxes.
843 for oOld in oOldData.aoTestBoxes:
844 fRemove = True;
845 for oNew in oData.aoTestBoxes:
846 if oNew.idTestBox == oOld.idTestBox:
847 fRemove = False;
848 break;
849 if fRemove:
850 self._removeSchedGroupTestBox(uidAuthor, oOld);
851
852 # Add / modify testboxes.
853 for oBoxInGrp in oData.aoTestBoxes:
854 oOldBoxInGrp = None;
855 for oOld in oOldData.aoTestBoxes:
856 if oOld.idTestBox == oBoxInGrp.idTestBox:
857 oOldBoxInGrp = oOld;
858 break;
859
860 oBoxInGrp.idSchedGroup = oData.idSchedGroup;
861 if oOldBoxInGrp is None:
862 self._addSchedGroupTestBox(uidAuthor, oBoxInGrp);
863 elif not oBoxInGrp.isEqualEx(oOldBoxInGrp, ['tsEffective', 'tsExpire', 'uidAuthor', 'oTestBox']):
864 self._historizeSchedGroupTestBox(oBoxInGrp);
865 self._addSchedGroupTestBox(uidAuthor, oBoxInGrp);
866
867 self._oDb.maybeCommit(fCommit);
868 return True;
869
870 def removeEntry(self, uidAuthor, idSchedGroup, fCascade = False, fCommit = False):
871 """
872 Deletes a scheduling group.
873 """
874 _ = fCascade;
875
876 #
877 # Input validation and retrival of current data.
878 #
879 if idSchedGroup == 1:
880 raise TMRowInUse('Cannot remove the default scheduling group (id 1).');
881 oData = SchedGroupDataEx().initFromDbWithId(self._oDb, idSchedGroup);
882
883 #
884 # Remove the test box member records.
885 #
886 for oBoxInGrp in oData.aoTestBoxes:
887 self._removeSchedGroupTestBox(uidAuthor, oBoxInGrp);
888 self._oDb.execute('UPDATE TestBoxesInSchedGroups\n'
889 'SET tsExpire = CURRENT_TIMESTAMP\n'
890 'WHERE idSchedGroup = %s\n'
891 ' AND tsExpire = \'infinity\'::TIMESTAMP\n'
892 , (idSchedGroup,));
893
894 #
895 # Remove the test group member records.
896 #
897 for oMember in oData.aoMembers:
898 self._removeSchedGroupMember(uidAuthor, oMember);
899 self._oDb.execute('UPDATE SchedGroupMembers\n'
900 'SET tsExpire = CURRENT_TIMESTAMP\n'
901 'WHERE idSchedGroup = %s\n'
902 ' AND tsExpire = \'infinity\'::TIMESTAMP\n'
903 , (idSchedGroup,));
904
905 #
906 # Now the SchedGroups entry.
907 #
908 (tsCur, tsCurMinusOne) = self._oDb.getCurrentTimestamps();
909 if oData.tsEffective not in (tsCur, tsCurMinusOne):
910 self._historizeEntry(idSchedGroup, tsCurMinusOne);
911 self._readdEntry(uidAuthor, oData, tsCurMinusOne);
912 self._historizeEntry(idSchedGroup);
913 self._oDb.execute('UPDATE SchedGroups\n'
914 'SET tsExpire = CURRENT_TIMESTAMP\n'
915 'WHERE idSchedGroup = %s\n'
916 ' AND tsExpire = \'infinity\'::TIMESTAMP\n'
917 , (idSchedGroup,))
918
919 self._oDb.maybeCommit(fCommit)
920 return True;
921
922
923 def cachedLookup(self, idSchedGroup):
924 """
925 Looks up the most recent SchedGroupData object for idSchedGroup
926 via an object cache.
927
928 Returns a shared SchedGroupData object. None if not found.
929 Raises exception on DB error.
930 """
931 if self.dCache is None:
932 self.dCache = self._oDb.getCache('SchedGroup');
933
934 oEntry = self.dCache.get(idSchedGroup, None);
935 if oEntry is None:
936 self._oDb.execute('SELECT *\n'
937 'FROM SchedGroups\n'
938 'WHERE idSchedGroup = %s\n'
939 ' AND tsExpire = \'infinity\'::TIMESTAMP\n'
940 , (idSchedGroup, ));
941 if self._oDb.getRowCount() == 0:
942 # Maybe it was deleted, try get the last entry.
943 self._oDb.execute('SELECT *\n'
944 'FROM SchedGroups\n'
945 'WHERE idSchedGroup = %s\n'
946 'ORDER BY tsExpire DESC\n'
947 'LIMIT 1\n'
948 , (idSchedGroup, ));
949 elif self._oDb.getRowCount() > 1:
950 raise self._oDb.integrityException('%s infinity rows for %s' % (self._oDb.getRowCount(), idSchedGroup));
951
952 if self._oDb.getRowCount() == 1:
953 oEntry = SchedGroupData().initFromDbRow(self._oDb.fetchOne());
954 self.dCache[idSchedGroup] = oEntry;
955 return oEntry;
956
957
958 #
959 # Other methods.
960 #
961
962 def fetchOrderedByName(self, tsNow = None):
963 """
964 Return list of objects of type SchedGroups ordered by name.
965 May raise exception on database error.
966 """
967 if tsNow is None:
968 self._oDb.execute('SELECT *\n'
969 'FROM SchedGroups\n'
970 'WHERE tsExpire = \'infinity\'::TIMESTAMP\n'
971 'ORDER BY sName ASC\n');
972 else:
973 self._oDb.execute('SELECT *\n'
974 'FROM SchedGroups\n'
975 'WHERE tsExpire > %s\n'
976 ' AND tsEffective <= %s\n'
977 'ORDER BY sName ASC\n'
978 , (tsNow, tsNow,));
979 aoRet = []
980 for _ in range(self._oDb.getRowCount()):
981 aoRet.append(SchedGroupData().initFromDbRow(self._oDb.fetchOne()));
982 return aoRet;
983
984
985 def getAll(self, tsEffective = None):
986 """
987 Gets the list of all scheduling groups.
988 Returns an array of SchedGroupData instances.
989 """
990 if tsEffective is None:
991 self._oDb.execute('SELECT *\n'
992 'FROM SchedGroups\n'
993 'WHERE tsExpire = \'infinity\'::TIMESTAMP\n');
994 else:
995 self._oDb.execute('SELECT *\n'
996 'FROM SchedGroups\n'
997 'WHERE tsExpire > %s\n'
998 ' AND tsEffective <= %s\n'
999 , (tsEffective, tsEffective));
1000 aoRet = [];
1001 for aoRow in self._oDb.fetchAll():
1002 aoRet.append(SchedGroupData().initFromDbRow(aoRow));
1003 return aoRet;
1004
1005 def getSchedGroupsForCombo(self, tsEffective = None):
1006 """
1007 Gets the list of active scheduling groups for a combo box.
1008 Returns an array of (value [idSchedGroup], drop-down-name [sName],
1009 hover-text [sDescription]) tuples.
1010 """
1011 if tsEffective is None:
1012 self._oDb.execute('SELECT idSchedGroup, sName, sDescription\n'
1013 'FROM SchedGroups\n'
1014 'WHERE tsExpire = \'infinity\'::TIMESTAMP\n'
1015 'ORDER BY sName');
1016 else:
1017 self._oDb.execute('SELECT idSchedGroup, sName, sDescription\n'
1018 'FROM SchedGroups\n'
1019 'WHERE tsExpire > %s\n'
1020 ' AND tsEffective <= %s\n'
1021 'ORDER BY sName'
1022 , (tsEffective, tsEffective));
1023 return self._oDb.fetchAll();
1024
1025
1026 def getMembers(self, idSchedGroup, tsEffective = None):
1027 """
1028 Gets the scheduling groups members for the given scheduling group.
1029
1030 Returns an array of SchedGroupMemberDataEx instances (sorted by
1031 priority (descending) and idTestGroup). May raise exception DB error.
1032 """
1033
1034 if tsEffective is None:
1035 self._oDb.execute('SELECT *\n'
1036 'FROM SchedGroupMembers, TestGroups\n'
1037 'WHERE SchedGroupMembers.idSchedGroup = %s\n'
1038 ' AND SchedGroupMembers.tsExpire = \'infinity\'::TIMESTAMP\n'
1039 ' AND TestGroups.idTestGroup = SchedGroupMembers.idTestGroup\n'
1040 ' AND TestGroups.tsExpire = \'infinity\'::TIMESTAMP\n'
1041 'ORDER BY SchedGroupMembers.iSchedPriority DESC, SchedGroupMembers.idTestGroup\n'
1042 , (idSchedGroup,));
1043 else:
1044 self._oDb.execute('SELECT *\n'
1045 'FROM SchedGroupMembers, TestGroups\n'
1046 'WHERE SchedGroupMembers.idSchedGroup = %s\n'
1047 ' AND SchedGroupMembers.tsExpire < %s\n'
1048 ' AND SchedGroupMembers.tsEffective >= %s\n'
1049 ' AND TestGroups.idTestGroup = SchedGroupMembers.idTestGroup\n'
1050 ' AND TestGroups.tsExpire < %s\n'
1051 ' AND TestGroups.tsEffective >= %s\n'
1052 'ORDER BY SchedGroupMembers.iSchedPriority DESC, SchedGroupMembers.idTestGroup\n'
1053 , (idSchedGroup, tsEffective, tsEffective, tsEffective, tsEffective, ));
1054 aaoRows = self._oDb.fetchAll();
1055 aoRet = [];
1056 for aoRow in aaoRows:
1057 aoRet.append(SchedGroupMemberDataEx().initFromDbRow(aoRow));
1058 return aoRet;
1059
1060 def getTestCasesForGroup(self, idSchedGroup, cMax = None):
1061 """
1062 Gets the enabled testcases w/ testgroup+priority for the given scheduling group.
1063
1064 Returns an array of TestCaseData instances (ordered by group id, descending
1065 testcase priority, and testcase IDs) with an extra iSchedPriority member.
1066 May raise exception on DB error or if the result exceeds cMax.
1067 """
1068
1069 self._oDb.execute('SELECT TestGroupMembers.idTestGroup, TestGroupMembers.iSchedPriority, TestCases.*\n'
1070 'FROM SchedGroupMembers, TestGroups, TestGroupMembers, TestCases\n'
1071 'WHERE SchedGroupMembers.idSchedGroup = %s\n'
1072 ' AND SchedGroupMembers.tsExpire = \'infinity\'::TIMESTAMP\n'
1073 ' AND TestGroups.idTestGroup = SchedGroupMembers.idTestGroup\n'
1074 ' AND TestGroups.tsExpire = \'infinity\'::TIMESTAMP\n'
1075 ' AND TestGroupMembers.idTestGroup = TestGroups.idTestGroup\n'
1076 ' AND TestGroupMembers.tsExpire = \'infinity\'::TIMESTAMP\n'
1077 ' AND TestCases.idTestCase = TestGroupMembers.idTestCase\n'
1078 ' AND TestCases.tsExpire = \'infinity\'::TIMESTAMP\n'
1079 ' AND TestCases.fEnabled = TRUE\n'
1080 'ORDER BY TestGroupMembers.idTestGroup, TestGroupMembers.iSchedPriority DESC, TestCases.idTestCase\n'
1081 , (idSchedGroup,));
1082
1083 if cMax is not None and self._oDb.getRowCount() > cMax:
1084 raise TMExceptionBase('Too many testcases for scheduling group %s: %s, max %s'
1085 % (idSchedGroup, cMax, self._oDb.getRowCount(),));
1086
1087 aoRet = [];
1088 for aoRow in self._oDb.fetchAll():
1089 oTestCase = TestCaseData().initFromDbRow(aoRow[2:]);
1090 oTestCase.idTestGroup = aoRow[0];
1091 oTestCase.iSchedPriority = aoRow[1];
1092 aoRet.append(oTestCase);
1093 return aoRet;
1094
1095 def getTestCaseArgsForGroup(self, idSchedGroup, cMax = None):
1096 """
1097 Gets the testcase argument variation w/ testgroup+priority for the given scheduling group.
1098
1099 Returns an array TestCaseArgsData instance (sorted by group and
1100 variation id) with an extra iSchedPriority member.
1101 May raise exception on DB error or if the result exceeds cMax.
1102 """
1103
1104 self._oDb.execute('SELECT TestGroupMembers.idTestGroup, TestGroupMembers.iSchedPriority, TestCaseArgs.*\n'
1105 'FROM SchedGroupMembers, TestGroups, TestGroupMembers, TestCaseArgs, TestCases\n'
1106 'WHERE SchedGroupMembers.idSchedGroup = %s\n'
1107 ' AND SchedGroupMembers.tsExpire = \'infinity\'::TIMESTAMP\n'
1108 ' AND TestGroups.idTestGroup = SchedGroupMembers.idTestGroup\n'
1109 ' AND TestGroups.tsExpire = \'infinity\'::TIMESTAMP\n'
1110 ' AND TestGroupMembers.idTestGroup = TestGroups.idTestGroup\n'
1111 ' AND TestGroupMembers.tsExpire = \'infinity\'::TIMESTAMP\n'
1112 ' AND TestCaseArgs.idTestCase = TestGroupMembers.idTestCase\n'
1113 ' AND TestCaseArgs.tsExpire = \'infinity\'::TIMESTAMP\n'
1114 ' AND ( TestGroupMembers.aidTestCaseArgs is NULL\n'
1115 ' OR TestCaseArgs.idTestCaseArgs = ANY(TestGroupMembers.aidTestCaseArgs) )\n'
1116 ' AND TestCases.idTestCase = TestCaseArgs.idTestCase\n'
1117 ' AND TestCases.tsExpire = \'infinity\'::TIMESTAMP\n'
1118 ' AND TestCases.fEnabled = TRUE\n'
1119 'ORDER BY TestGroupMembers.idTestGroup, TestGroupMembers.idTestCase, TestCaseArgs.idTestCaseArgs\n'
1120 , (idSchedGroup,));
1121
1122 if cMax is not None and self._oDb.getRowCount() > cMax:
1123 raise TMExceptionBase('Too many argument variations for scheduling group %s: %s, max %s'
1124 % (idSchedGroup, cMax, self._oDb.getRowCount(),));
1125
1126 aoRet = [];
1127 for aoRow in self._oDb.fetchAll():
1128 oVariation = TestCaseArgsData().initFromDbRow(aoRow[2:]);
1129 oVariation.idTestGroup = aoRow[0];
1130 oVariation.iSchedPriority = aoRow[1];
1131 aoRet.append(oVariation);
1132 return aoRet;
1133
1134 def exists(self, sName):
1135 """Checks if a group with the given name exists."""
1136 self._oDb.execute('SELECT idSchedGroup\n'
1137 'FROM SchedGroups\n'
1138 'WHERE tsExpire = \'infinity\'::TIMESTAMP\n'
1139 ' AND sName = %s\n'
1140 'LIMIT 1\n'
1141 , (sName,));
1142 return self._oDb.getRowCount() > 0;
1143
1144 def getById(self, idSchedGroup):
1145 """Get Scheduling Group data by idSchedGroup"""
1146 self._oDb.execute('SELECT *\n'
1147 'FROM SchedGroups\n'
1148 'WHERE tsExpire = \'infinity\'::timestamp\n'
1149 ' AND idSchedGroup = %s;', (idSchedGroup,))
1150 aRows = self._oDb.fetchAll()
1151 if len(aRows) not in (0, 1):
1152 raise self._oDb.integrityException(
1153 'Found more than one scheduling groups with the same credentials. Database structure is corrupted.')
1154 try:
1155 return SchedGroupData().initFromDbRow(aRows[0])
1156 except IndexError:
1157 return None
1158
1159
1160 #
1161 # Internal helpers.
1162 #
1163
1164 def _assertUnique(self, sName, idSchedGroupIgnore = None):
1165 """
1166 Checks that the scheduling group name is unique.
1167 Raises exception if the name is already in use.
1168 """
1169 if idSchedGroupIgnore is None:
1170 self._oDb.execute('SELECT idSchedGroup\n'
1171 'FROM SchedGroups\n'
1172 'WHERE tsExpire = \'infinity\'::TIMESTAMP\n'
1173 ' AND sName = %s\n'
1174 , ( sName, ) );
1175 else:
1176 self._oDb.execute('SELECT idSchedGroup\n'
1177 'FROM SchedGroups\n'
1178 'WHERE tsExpire = \'infinity\'::TIMESTAMP\n'
1179 ' AND sName = %s\n'
1180 ' AND idSchedGroup <> %s\n'
1181 , ( sName, idSchedGroupIgnore, ) );
1182 if self._oDb.getRowCount() > 0:
1183 raise TMRowInUse('Scheduling group name (%s) is already in use.' % (sName,));
1184 return True;
1185
1186 def _readdEntry(self, uidAuthor, oData, tsEffective = None):
1187 """
1188 Re-adds the SchedGroups entry. Used by editEntry and removeEntry.
1189 """
1190 if tsEffective is None:
1191 tsEffective = self._oDb.getCurrentTimestamp();
1192 self._oDb.execute('INSERT INTO SchedGroups (\n'
1193 ' uidAuthor,\n'
1194 ' tsEffective,\n'
1195 ' idSchedGroup,\n'
1196 ' sName,\n'
1197 ' sDescription,\n'
1198 ' fEnabled,\n'
1199 ' enmScheduler,\n'
1200 ' idBuildSrc,\n'
1201 ' idBuildSrcTestSuite,\n'
1202 ' sComment )\n'
1203 'VALUES ( %s, %s, %s, %s, %s, %s, %s, %s, %s, %s )\n'
1204 , ( uidAuthor,
1205 tsEffective,
1206 oData.idSchedGroup,
1207 oData.sName,
1208 oData.sDescription,
1209 oData.fEnabled,
1210 oData.enmScheduler,
1211 oData.idBuildSrc,
1212 oData.idBuildSrcTestSuite,
1213 oData.sComment, ));
1214 return True;
1215
1216 def _historizeEntry(self, idSchedGroup, tsExpire = None):
1217 """
1218 Historizes the current entry for the given scheduling group.
1219 """
1220 if tsExpire is None:
1221 tsExpire = self._oDb.getCurrentTimestamp();
1222 self._oDb.execute('UPDATE SchedGroups\n'
1223 'SET tsExpire = %s\n'
1224 'WHERE idSchedGroup = %s\n'
1225 ' AND tsExpire = \'infinity\'::TIMESTAMP\n'
1226 , ( tsExpire, idSchedGroup, ));
1227 return True;
1228
1229 def _addSchedGroupMember(self, uidAuthor, oMember, tsEffective = None):
1230 """
1231 addEntry worker for adding a scheduling group member.
1232 """
1233 if tsEffective is None:
1234 tsEffective = self._oDb.getCurrentTimestamp();
1235 self._oDb.execute('INSERT INTO SchedGroupMembers(\n'
1236 ' idSchedGroup,\n'
1237 ' idTestGroup,\n'
1238 ' tsEffective,\n'
1239 ' uidAuthor,\n'
1240 ' iSchedPriority,\n'
1241 ' bmHourlySchedule,\n'
1242 ' idTestGroupPreReq)\n'
1243 'VALUES (%s, %s, %s, %s, %s, %s, %s)\n'
1244 , ( oMember.idSchedGroup,
1245 oMember.idTestGroup,
1246 tsEffective,
1247 uidAuthor,
1248 oMember.iSchedPriority,
1249 oMember.bmHourlySchedule,
1250 oMember.idTestGroupPreReq, ));
1251 return True;
1252
1253 def _removeSchedGroupMember(self, uidAuthor, oMember):
1254 """
1255 Removes a scheduling group member.
1256 """
1257
1258 # Try record who removed it by adding an dummy entry that expires immediately.
1259 (tsCur, tsCurMinusOne) = self._oDb.getCurrentTimestamps();
1260 if oMember.tsEffective not in (tsCur, tsCurMinusOne):
1261 self._historizeSchedGroupMember(oMember, tsCurMinusOne);
1262 self._addSchedGroupMember(uidAuthor, oMember, tsCurMinusOne); # lazy bird.
1263 self._historizeSchedGroupMember(oMember);
1264 else:
1265 self._historizeSchedGroupMember(oMember);
1266 return True;
1267
1268 def _historizeSchedGroupMember(self, oMember, tsExpire = None):
1269 """
1270 Historizes the current entry for the given scheduling group.
1271 """
1272 if tsExpire is None:
1273 tsExpire = self._oDb.getCurrentTimestamp();
1274 self._oDb.execute('UPDATE SchedGroupMembers\n'
1275 'SET tsExpire = %s\n'
1276 'WHERE idSchedGroup = %s\n'
1277 ' AND idTestGroup = %s\n'
1278 ' AND tsExpire = \'infinity\'::TIMESTAMP\n'
1279 , ( tsExpire, oMember.idSchedGroup, oMember.idTestGroup, ));
1280 return True;
1281
1282 #
1283 def _addSchedGroupTestBox(self, uidAuthor, oBoxInGroup, tsEffective = None):
1284 """
1285 addEntry worker for adding a test box to a scheduling group.
1286 """
1287 if tsEffective is None:
1288 tsEffective = self._oDb.getCurrentTimestamp();
1289 self._oDb.execute('INSERT INTO TestBoxesInSchedGroups(\n'
1290 ' idSchedGroup,\n'
1291 ' idTestBox,\n'
1292 ' tsEffective,\n'
1293 ' uidAuthor,\n'
1294 ' iSchedPriority)\n'
1295 'VALUES (%s, %s, %s, %s, %s)\n'
1296 , ( oBoxInGroup.idSchedGroup,
1297 oBoxInGroup.idTestBox,
1298 tsEffective,
1299 uidAuthor,
1300 oBoxInGroup.iSchedPriority, ));
1301 return True;
1302
1303 def _removeSchedGroupTestBox(self, uidAuthor, oBoxInGroup):
1304 """
1305 Removes a testbox from a scheduling group.
1306 """
1307
1308 # Try record who removed it by adding an dummy entry that expires immediately.
1309 (tsCur, tsCurMinusOne) = self._oDb.getCurrentTimestamps();
1310 if oBoxInGroup.tsEffective not in (tsCur, tsCurMinusOne):
1311 self._historizeSchedGroupTestBox(oBoxInGroup, tsCurMinusOne);
1312 self._addSchedGroupTestBox(uidAuthor, oBoxInGroup, tsCurMinusOne); # lazy bird.
1313 self._historizeSchedGroupTestBox(oBoxInGroup);
1314 else:
1315 self._historizeSchedGroupTestBox(oBoxInGroup);
1316 return True;
1317
1318 def _historizeSchedGroupTestBox(self, oBoxInGroup, tsExpire = None):
1319 """
1320 Historizes the current entry for the given scheduling group.
1321 """
1322 if tsExpire is None:
1323 tsExpire = self._oDb.getCurrentTimestamp();
1324 self._oDb.execute('UPDATE TestBoxesInSchedGroups\n'
1325 'SET tsExpire = %s\n'
1326 'WHERE idSchedGroup = %s\n'
1327 ' AND idTestBox = %s\n'
1328 ' AND tsExpire = \'infinity\'::TIMESTAMP\n'
1329 , ( tsExpire, oBoxInGroup.idSchedGroup, oBoxInGroup.idTestBox, ));
1330 return True;
1331
1332
1333
1334#
1335# Unit testing.
1336#
1337
1338# pylint: disable=missing-docstring
1339class SchedGroupMemberDataTestCase(ModelDataBaseTestCase):
1340 def setUp(self):
1341 self.aoSamples = [SchedGroupMemberData(),];
1342
1343class SchedGroupDataTestCase(ModelDataBaseTestCase):
1344 def setUp(self):
1345 self.aoSamples = [SchedGroupData(),];
1346
1347if __name__ == '__main__':
1348 unittest.main();
1349 # not reached.
1350
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