VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/testmanager/core/build.py@ 62532

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

(C) 2016

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 32.1 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: build.py 62484 2016-07-22 18:35:33Z vboxsync $
3
4"""
5Test Manager - Builds.
6"""
7
8__copyright__ = \
9"""
10Copyright (C) 2012-2016 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: 62484 $"
30
31
32# Standard python imports.
33import os;
34import unittest;
35
36# Validation Kit imports.
37from testmanager import config;
38from testmanager.core import coreconsts;
39from testmanager.core.base import ModelDataBase, ModelDataBaseTestCase, ModelLogicBase, TMExceptionBase, \
40 TMTooManyRows, TMInvalidData, TMRowNotFound, TMRowInUse;
41
42
43class BuildCategoryData(ModelDataBase):
44 """
45 A build category.
46 """
47
48 ksIdAttr = 'idBuildCategory';
49
50 ksParam_idBuildCategory = 'BuildCategory_idBuildCategory';
51 ksParam_sProduct = 'BuildCategory_sProduct';
52 ksParam_sRepository = 'BuildCategory_sRepository';
53 ksParam_sBranch = 'BuildCategory_sBranch';
54 ksParam_sType = 'BuildCategory_sType';
55 ksParam_asOsArches = 'BuildCategory_asOsArches';
56
57 kasAllowNullAttributes = ['idBuildCategory', ];
58
59 def __init__(self):
60 ModelDataBase.__init__(self);
61
62 #
63 # Initialize with defaults.
64 # See the database for explanations of each of these fields.
65 #
66 self.idBuildCategory = None;
67 self.sProduct = None;
68 self.sRepository = None;
69 self.sBranch = None;
70 self.sType = None;
71 self.asOsArches = None;
72
73 def initFromDbRow(self, aoRow):
74 """
75 Re-initializes the object from a SELECT * FROM BuildCategories row.
76 Returns self. Raises exception if aoRow is None.
77 """
78 if aoRow is None:
79 raise TMRowNotFound('BuildCategory not found.');
80
81 self.idBuildCategory = aoRow[0];
82 self.sProduct = aoRow[1];
83 self.sRepository = aoRow[2];
84 self.sBranch = aoRow[3];
85 self.sType = aoRow[4];
86 self.asOsArches = sorted(aoRow[5]);
87 return self;
88
89 def initFromDbWithId(self, oDb, idBuildCategory, tsNow = None, sPeriodBack = None):
90 """
91 Initialize from the database, given the ID of a row.
92 """
93 _ = tsNow; _ = sPeriodBack; # No history in this table.
94 oDb.execute('SELECT * FROM BuildCategories WHERE idBuildCategory = %s', (idBuildCategory,));
95 aoRow = oDb.fetchOne()
96 if aoRow is None:
97 raise TMRowNotFound('idBuildCategory=%s not found' % (idBuildCategory, ));
98 return self.initFromDbRow(aoRow);
99
100 def initFromValues(self, sProduct, sRepository, sBranch, sType, asOsArches, idBuildCategory = None):
101 """
102 Reinitializes form a set of values.
103 return self.
104 """
105 self.idBuildCategory = idBuildCategory;
106 self.sProduct = sProduct;
107 self.sRepository = sRepository;
108 self.sBranch = sBranch;
109 self.sType = sType;
110 self.asOsArches = asOsArches;
111 return self;
112
113 def _validateAndConvertAttribute(self, sAttr, sParam, oValue, aoNilValues, fAllowNull, oDb):
114 # Handle sType and asOsArches specially.
115 if sAttr == 'sType':
116 (oNewValue, sError) = ModelDataBase._validateAndConvertAttribute(self, sAttr, sParam, oValue,
117 aoNilValues, fAllowNull, oDb);
118 if sError is None and self.sType.lower() != self.sType:
119 sError = 'Invalid build type value';
120
121 elif sAttr == 'asOsArches':
122 (oNewValue, sError) = self.validateListOfStr(oValue, aoNilValues = aoNilValues, fAllowNull = fAllowNull,
123 asValidValues = coreconsts.g_kasOsDotCpusAll);
124 if sError is not None and oNewValue is not None:
125 oNewValue = sorted(oNewValue); # Must be sorted!
126
127 else:
128 return ModelDataBase._validateAndConvertAttribute(self, sAttr, sParam, oValue, aoNilValues, fAllowNull, oDb);
129
130 return (oNewValue, sError);
131
132 def matchesOsArch(self, sOs, sArch):
133 """ Checks if the build matches the given OS and architecture. """
134 if sOs + '.' + sArch in self.asOsArches:
135 return True;
136 if sOs + '.noarch' in self.asOsArches:
137 return True;
138 if 'os-agnostic.' + sArch in self.asOsArches:
139 return True;
140 if 'os-agnostic.noarch' in self.asOsArches:
141 return True;
142 return False;
143
144
145class BuildCategoryLogic(ModelLogicBase): # pylint: disable=R0903
146 """
147 Build categories database logic.
148 """
149
150 def fetchForListing(self, iStart, cMaxRows, tsNow):
151 """
152 Fetches testboxes for listing.
153
154 Returns an array (list) of UserAccountData items, empty list if none.
155 Raises exception on error.
156 """
157 _ = tsNow;
158 self._oDb.execute('SELECT *\n'
159 'FROM BuildCategories\n'
160 'ORDER BY sProduct, sRepository, sBranch, sType, idBuildCategory\n'
161 'LIMIT %s OFFSET %s\n'
162 , (cMaxRows, iStart,));
163
164 aoRows = [];
165 for _ in range(self._oDb.getRowCount()):
166 aoRows.append(BuildCategoryData().initFromDbRow(self._oDb.fetchOne()));
167 return aoRows;
168
169 def fetchForCombo(self):
170 """
171 Gets the list of Build Categories for a combo box.
172 Returns an array of (value [idBuildCategory], drop-down-name [info],
173 hover-text [info]) tuples.
174 """
175 self._oDb.execute('SELECT *\n'
176 'FROM BuildCategories\n'
177 'ORDER BY sProduct, sBranch, sType, asOsArches')
178
179 aaoRows = self._oDb.fetchAll()
180 aoRet = []
181 for aoRow in aaoRows:
182 oData = BuildCategoryData().initFromDbRow(aoRow)
183
184 sInfo = '%s / %s / %s / %s' % \
185 (oData.sProduct,
186 oData.sBranch,
187 oData.sType,
188 ', '.join(oData.asOsArches))
189
190 # Make short info string if necessary
191 sInfo = sInfo if len(sInfo) < 70 else (sInfo[:70] + '...')
192
193 oInfoItem = (oData.idBuildCategory, sInfo, sInfo)
194 aoRet.append(oInfoItem)
195
196 return aoRet
197
198 def addEntry(self, oData, uidAuthor = None, fCommit = False):
199 """
200 Standard method for adding a build category.
201 """
202
203 # Lazy bird warning! Reuse the soft addBuildCategory method.
204 self.addBuildCategory(oData, fCommit);
205 _ = uidAuthor;
206 return True;
207
208 def removeEntry(self, uidAuthor, idBuildCategory, fCascade = False, fCommit = False):
209 """
210 Tries to delete the build category.
211 Note! Does not implement cascading. This is intentional!
212 """
213
214 #
215 # Check that the build category isn't used by anyone.
216 #
217 self._oDb.execute('SELECT COUNT(idBuild)\n'
218 'FROM Builds\n'
219 'WHERE idBuildCategory = %s\n'
220 , (idBuildCategory,));
221 cBuilds = self._oDb.fetchOne()[0];
222 if cBuilds > 0:
223 raise TMRowInUse('Build category #%d is used by %d builds and can therefore not be deleted.'
224 % (idBuildCategory, cBuilds,));
225
226 #
227 # Ok, it's not used, so just delete it.
228 # (No history on this table. This code is for typos.)
229 #
230 self._oDb.execute('DELETE FROM Builds\n'
231 'WHERE idBuildCategory = %s\n'
232 , (idBuildCategory,));
233
234 self._oDb.maybeCommit(fCommit);
235 _ = uidAuthor; _ = fCascade;
236 return True;
237
238 #
239 # Other methods.
240 #
241
242 def tryFetch(self, idBuildCategory):
243 """
244 Try fetch the build category with the given ID.
245 Returns BuildCategoryData instance if found, None if not found.
246 May raise exception on database error.
247 """
248 self._oDb.execute('SELECT *\n'
249 'FROM BuildCategories\n'
250 'WHERE idBuildCategory = %s\n'
251 , (idBuildCategory,))
252 aaoRows = self._oDb.fetchAll()
253 if len(aaoRows) == 0:
254 return None;
255 if len(aaoRows) != 1:
256 raise self._oDb.integrityException('Duplicates in BuildCategories: %s' % (aaoRows,));
257 return BuildCategoryData().initFromDbRow(aaoRows[0])
258
259 def tryFindByData(self, oData):
260 """
261 Tries to find the matching build category from the sProduct, sBranch,
262 sType and asOsArches members of oData.
263
264 Returns a valid build category ID and an updated oData object if found.
265 Returns None and unmodified oData object if not found.
266 May raise exception on database error.
267 """
268 self._oDb.execute('SELECT *\n'
269 'FROM BuildCategories\n'
270 'WHERE sProduct = %s\n'
271 ' AND sRepository = %s\n'
272 ' AND sBranch = %s\n'
273 ' AND sType = %s\n'
274 ' AND asOsArches = %s\n'
275 , ( oData.sProduct,
276 oData.sRepository,
277 oData.sBranch,
278 oData.sType,
279 sorted(oData.asOsArches),
280 ));
281 aaoRows = self._oDb.fetchAll();
282 if len(aaoRows) == 0:
283 return None;
284 if len(aaoRows) > 1:
285 raise self._oDb.integrityException('Duplicates in BuildCategories: %s' % (aaoRows,));
286
287 oData.initFromDbRow(aaoRows[0]);
288 return oData.idBuildCategory;
289
290 def addBuildCategory(self, oData, fCommit = False):
291 """
292 Add Build Category record into the database if needed, returning updated oData.
293 Raises exception on input and database errors.
294 """
295
296 # Check BuildCategoryData before do anything
297 dDataErrors = oData.validateAndConvert(self._oDb, oData.ksValidateFor_Add);
298 if len(dDataErrors) > 0:
299 raise TMInvalidData('Invalid data passed to addBuildCategory(): %s' % (dDataErrors,));
300
301 # Does it already exist?
302 if self.tryFindByData(oData) is None:
303 # No, We'll have to add it.
304 self._oDb.execute('INSERT INTO BuildCategories (sProduct, sRepository, sBranch, sType, asOsArches)\n'
305 'VALUES (%s, %s, %s, %s, %s)\n'
306 'RETURNING idBuildCategory'
307 , ( oData.sProduct,
308 oData.sRepository,
309 oData.sBranch,
310 oData.sType,
311 sorted(oData.asOsArches),
312 ));
313 oData.idBuildCategory = self._oDb.fetchOne()[0];
314
315 self._oDb.maybeCommit(fCommit);
316 return oData;
317
318
319class BuildData(ModelDataBase):
320 """
321 A build.
322 """
323
324 ksIdAttr = 'idBuild';
325
326 ksParam_idBuild = 'Build_idBuild';
327 ksParam_tsCreated = 'Build_tsCreated';
328 ksParam_tsEffective = 'Build_tsEffective';
329 ksParam_tsExpire = 'Build_tsExpire';
330 ksParam_uidAuthor = 'Build_uidAuthor';
331 ksParam_idBuildCategory = 'Build_idBuildCategory';
332 ksParam_iRevision = 'Build_iRevision';
333 ksParam_sVersion = 'Build_sVersion';
334 ksParam_sLogUrl = 'Build_sLogUrl';
335 ksParam_sBinaries = 'Build_sBinaries';
336 ksParam_fBinariesDeleted = 'Build_fBinariesDeleted';
337
338 kasAllowNullAttributes = ['idBuild', 'tsCreated', 'tsEffective', 'tsExpire', 'uidAuthor', 'tsCreated', 'sLogUrl'];
339
340
341 def __init__(self):
342 ModelDataBase.__init__(self);
343
344 #
345 # Initialize with defaults.
346 # See the database for explanations of each of these fields.
347 #
348 self.idBuild = None;
349 self.tsCreated = None;
350 self.tsEffective = None;
351 self.tsExpire = None;
352 self.uidAuthor = None;
353 self.idBuildCategory = None;
354 self.iRevision = None;
355 self.sVersion = None;
356 self.sLogUrl = None;
357 self.sBinaries = None;
358 self.fBinariesDeleted = False;
359
360 def initFromDbRow(self, aoRow):
361 """
362 Re-initializes the object from a SELECT * FROM Builds row.
363 Returns self. Raises exception if aoRow is None.
364 """
365 if aoRow is None:
366 raise TMRowNotFound('Build not found.');
367
368 self.idBuild = aoRow[0];
369 self.tsCreated = aoRow[1];
370 self.tsEffective = aoRow[2];
371 self.tsExpire = aoRow[3];
372 self.uidAuthor = aoRow[4];
373 self.idBuildCategory = aoRow[5];
374 self.iRevision = aoRow[6];
375 self.sVersion = aoRow[7];
376 self.sLogUrl = aoRow[8];
377 self.sBinaries = aoRow[9];
378 self.fBinariesDeleted = aoRow[10];
379 return self;
380
381 def initFromDbWithId(self, oDb, idBuild, tsNow = None, sPeriodBack = None):
382 """
383 Initialize from the database, given the ID of a row.
384 """
385 oDb.execute(self.formatSimpleNowAndPeriodQuery(oDb,
386 'SELECT *\n'
387 'FROM Builds\n'
388 'WHERE idBuild = %s\n'
389 , ( idBuild,), tsNow, sPeriodBack));
390 aoRow = oDb.fetchOne()
391 if aoRow is None:
392 raise TMRowNotFound('idBuild=%s not found (tsNow=%s sPeriodBack=%s)' % (idBuild, tsNow, sPeriodBack,));
393 return self.initFromDbRow(aoRow);
394
395 def areFilesStillThere(self):
396 """
397 Try check if the build files are still there.
398
399 Returns True if they are, None if we cannot tell, and False if one or
400 more are missing.
401 """
402 if self.fBinariesDeleted:
403 return False;
404
405 for sBinary in self.sBinaries.split(','):
406 sBinary = sBinary.strip();
407 if len(sBinary) == 0:
408 continue;
409 # Same URL tests as in webutils.downloadFile().
410 if sBinary.startswith('http://') \
411 or sBinary.startswith('https://') \
412 or sBinary.startswith('ftp://'):
413 # URL - don't bother trying to verify that (we don't use it atm).
414 fRc = None;
415 else:
416 # File.
417 if config.g_ksBuildBinRootDir is not None:
418 sFullPath = os.path.join(config.g_ksBuildBinRootDir, sBinary);
419 fRc = os.path.isfile(sFullPath);
420 if not fRc \
421 and not os.path.isfile(os.path.join(config.g_ksBuildBinRootDir, config.g_ksBuildBinRootFile)):
422 fRc = None; # Root file missing, so the share might not be mounted correctly.
423 else:
424 fRc = None;
425 if fRc is not True:
426 return fRc;
427
428 return True;
429
430
431class BuildDataEx(BuildData):
432 """
433 Complete data set.
434 """
435
436 def __init__(self):
437 BuildData.__init__(self);
438 self.oCat = None;
439
440 def initFromDbRow(self, aoRow):
441 """
442 Reinitialize from a SELECT Builds.*, BuildCategories.* FROM Builds, BuildCategories query.
443 Returns self. Raises exception if aoRow is None.
444 """
445 if aoRow is None:
446 raise TMRowNotFound('Build not found.');
447 BuildData.initFromDbRow(self, aoRow);
448 self.oCat = BuildCategoryData().initFromDbRow(aoRow[11:]);
449 return self;
450
451 def initFromDbWithId(self, oDb, idBuild, tsNow = None, sPeriodBack = None):
452 """
453 Reinitialize from database given a row ID.
454 Returns self. Raises exception on database error or if the ID is invalid.
455 """
456 oDb.execute(self.formatSimpleNowAndPeriodQuery(oDb,
457 'SELECT Builds.*, BuildCategories.*\n'
458 'FROM Builds, BuildCategories\n'
459 'WHERE idBuild = %s\n'
460 ' AND Builds.idBuildCategory = BuildCategories.idBuildCategory\n'
461 , ( idBuild,), tsNow, sPeriodBack, 'Builds.'));
462 aoRow = oDb.fetchOne()
463 if aoRow is None:
464 raise TMRowNotFound('idBuild=%s not found (tsNow=%s sPeriodBack=%s)' % (idBuild, tsNow, sPeriodBack,));
465 return self.initFromDbRow(aoRow);
466
467 def convertFromParamNull(self):
468 raise TMExceptionBase('Not implemented');
469
470 def isEqual(self, oOther):
471 raise TMExceptionBase('Not implemented');
472
473
474
475class BuildLogic(ModelLogicBase): # pylint: disable=R0903
476 """
477 Build database logic (covers build categories as well as builds).
478 """
479
480 #
481 # Standard methods.
482 #
483
484 def fetchForListing(self, iStart, cMaxRows, tsNow):
485 """
486 Fetches builds for listing.
487
488 Returns an array (list) of BuildDataEx items, empty list if none.
489 Raises exception on error.
490 """
491 if tsNow is None:
492 self._oDb.execute('SELECT *\n'
493 'FROM Builds, BuildCategories\n'
494 'WHERE Builds.idBuildCategory = BuildCategories.idBuildCategory\n'
495 ' AND Builds.tsExpire = \'infinity\'::TIMESTAMP\n'
496 'ORDER BY tsCreated DESC\n'
497 'LIMIT %s OFFSET %s\n'
498 , (cMaxRows, iStart,));
499 else:
500 self._oDb.execute('SELECT *\n'
501 'FROM Builds, BuildCategories\n'
502 'WHERE Builds.idBuildCategory = BuildCategories.idBuildCategory\n'
503 ' AND Builds.tsExpire > %s\n'
504 ' AND Builds.tsEffective <= %s\n'
505 'ORDER BY tsCreated DESC\n'
506 'LIMIT %s OFFSET %s\n'
507 , (tsNow, tsNow, cMaxRows, iStart,));
508
509 aoRows = [];
510 for _ in range(self._oDb.getRowCount()):
511 aoRows.append(BuildDataEx().initFromDbRow(self._oDb.fetchOne()));
512 return aoRows;
513
514 def addEntry(self, oBuildData, uidAuthor = None, fCommit = False):
515 """
516 Adds the build to the database, optionally adding the build category if
517 a BuildDataEx object used and it's necessary.
518
519 Returns updated data object. Raises exception on failure.
520 """
521
522 # Find/Add the build category if specified.
523 if isinstance(oBuildData, BuildDataEx) \
524 and oBuildData.idBuildCategory is None:
525 BuildCategoryLogic(self._oDb).addBuildCategory(oBuildData.oCat, fCommit = False);
526 oBuildData.idBuildCategory = oBuildData.oCat.idBuildCategory;
527
528 # Add the build.
529 self._oDb.execute('INSERT INTO Builds (uidAuthor,\n'
530 ' idBuildCategory,\n'
531 ' iRevision,\n'
532 ' sVersion,\n'
533 ' sLogUrl,\n'
534 ' sBinaries,\n'
535 ' fBinariesDeleted)\n'
536 'VALUES (%s, %s, %s, %s, %s, %s, %s)\n'
537 'RETURNING idBuild, tsCreated\n'
538 , ( uidAuthor,
539 oBuildData.idBuildCategory,
540 oBuildData.iRevision,
541 oBuildData.sVersion,
542 oBuildData.sLogUrl,
543 oBuildData.sBinaries,
544 oBuildData.fBinariesDeleted,
545 ));
546 aoRow = self._oDb.fetchOne();
547 oBuildData.idBuild = aoRow[0];
548 oBuildData.tsCreated = aoRow[1];
549
550 self._oDb.maybeCommit(fCommit);
551 return oBuildData;
552
553 def editEntry(self, oData, uidAuthor = None, fCommit = False):
554 """Modify database record"""
555
556 #
557 # Validate input and get current data.
558 #
559 dErrors = oData.validateAndConvert(self._oDb, oData.ksValidateFor_Edit);
560 if len(dErrors) > 0:
561 raise TMInvalidData('editEntry invalid input: %s' % (dErrors,));
562 oOldData = BuildData().initFromDbWithId(self._oDb, oData.idBuild);
563
564 #
565 # Do the work.
566 #
567 if not oData.isEqualEx(oOldData, [ 'tsEffective', 'tsExpire', 'uidAuthor' ]):
568 self._historizeBuild(oData.idBuild);
569 self._oDb.execute('INSERT INTO Builds (uidAuthor,\n'
570 ' idBuild,\n'
571 ' tsCreated,\n'
572 ' idBuildCategory,\n'
573 ' iRevision,\n'
574 ' sVersion,\n'
575 ' sLogUrl,\n'
576 ' sBinaries,\n'
577 ' fBinariesDeleted)\n'
578 'VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)\n'
579 'RETURNING idBuild, tsCreated\n'
580 , ( uidAuthor,
581 oData.idBuild,
582 oData.tsCreated,
583 oData.idBuildCategory,
584 oData.iRevision,
585 oData.sVersion,
586 oData.sLogUrl,
587 oData.sBinaries,
588 oData.fBinariesDeleted,
589 ));
590
591 self._oDb.maybeCommit(fCommit);
592 return True;
593
594 def removeEntry(self, uidAuthor, idBuild, fCascade = False, fCommit = False):
595 """
596 Historize record
597 """
598
599 #
600 # No non-historic refs here, so just go ahead and expire the build.
601 #
602 _ = fCascade;
603 _ = uidAuthor; ## @todo record deleter.
604
605 self._historizeBuild(idBuild, None);
606
607 self._oDb.maybeCommit(fCommit);
608 return True;
609
610
611 #
612 # Other methods.
613 #
614
615 def tryFindSameBuildForOsArch(self, oBuildEx, sOs, sCpuArch):
616 """
617 Attempts to find a matching build for the given OS.ARCH. May return
618 the input build if if matches.
619
620 Returns BuildDataEx instance if found, None if none. May raise
621 exception on database error.
622 """
623
624 if oBuildEx.oCat.matchesOsArch(sOs, sCpuArch):
625 return oBuildEx;
626
627 self._oDb.execute('SELECT Builds.*, BuildCategories.*\n'
628 'FROM Builds, BuildCategories\n'
629 'WHERE BuildCategories.sProduct = %s\n'
630 ' AND BuildCategories.sBranch = %s\n'
631 ' AND BuildCategories.sType = %s\n'
632 ' AND ( %s = ANY(BuildCategories.asOsArches)\n'
633 ' OR %s = ANY(BuildCategories.asOsArches)\n'
634 ' OR %s = ANY(BuildCategories.asOsArches))\n'
635 ' AND Builds.idBuildCategory = BuildCategories.idBuildCategory\n'
636 ' AND Builds.tsExpire = \'infinity\'::TIMESTAMP\n'
637 ' AND Builds.iRevision = %s\n'
638 ' AND Builds.sRelease = %s\n'
639 ' AND Builds.fBinariesDeleted IS FALSE\n'
640 'ORDER BY tsCreated DESC\n'
641 'LIMIT 4096\n' # stay sane.
642 , (oBuildEx.oCat.sProduct,
643 oBuildEx.oCat.sBranch,
644 oBuildEx.oCat.sType,
645 '%s.%s' % (sOs, sCpuArch),
646 '%s.noarch' % (sOs,),
647 'os-agnostic.%s' % (sCpuArch,),
648 'os-agnostic.noarch',
649 oBuildEx.iRevision,
650 oBuildEx.sRelease,
651 ) );
652 aaoRows = self._oDb.fetchAll();
653
654 for aoRow in aaoRows:
655 oBuildExRet = BuildDataEx().initFromDbRow(self, aoRow);
656 if not self.isBuildBlacklisted(oBuildExRet):
657 return oBuildExRet;
658
659 return None;
660
661 def isBuildBlacklisted(self, oBuildEx):
662 """
663 Checks if the given build is blacklisted
664 Returns True/False. May raise exception on database error.
665 """
666
667 asOsAgnosticArch = [];
668 asOsNoArch = [];
669 for i in range(len(oBuildEx.oCat.asOsArches)):
670 asParts = oBuildEx.oCat.asOsArches[i].split('.');
671 if len(asParts) != 2 or len(asParts[0]) == 0 or len(asParts[1]) == 0:
672 raise self._oDb.integrityException('Bad build asOsArches value: %s (idBuild=%s idBuildCategory=%s)'
673 % (oBuildEx.asOsArches[i], oBuildEx.idBuild, oBuildEx.idBuildCategory));
674 asOsNoArch.append(asParts[0] + '.noarch');
675 asOsNoArch.append('os-agnostic.' + asParts[1]);
676
677 self._oDb.execute('SELECT COUNT(*)\n'
678 'FROM BuildBlacklist\n'
679 'WHERE BuildBlacklist.tsExpire > CURRENT_TIMESTAMP\n'
680 ' AND BuildBlacklist.tsEffective <= CURRENT_TIMESTAMP\n'
681 ' AND BuildBlacklist.sProduct = %s\n'
682 ' AND BuildBlacklist.sBranch = %s\n'
683 ' AND ( BuildBlacklist.asTypes is NULL\n'
684 ' OR %s = ANY(BuildBlacklist.asTypes))\n'
685 ' AND ( BuildBlacklist.asOsArches is NULL\n'
686 ' OR %s && BuildBlacklist.asOsArches\n' ## @todo check array rep! Need overload?
687 ' OR %s && BuildBlacklist.asOsArches\n'
688 ' OR %s && BuildBlacklist.asOsArches\n'
689 ' OR %s = ANY(BuildBlacklist.asOsArches))\n'
690 ' AND BuildBlacklist.iFirstRevision <= %s\n'
691 ' AND BuildBlacklist.iLastRevision >= %s\n'
692 , (oBuildEx.oCat.sProduct,
693 oBuildEx.oCat.sBranch,
694 oBuildEx.oCat.sType,
695 oBuildEx.oCat.asOsArches,
696 asOsAgnosticArch,
697 asOsNoArch,
698 'os-agnostic.noarch',
699 oBuildEx.iRevision,
700 oBuildEx.iRevision,
701 ) );
702 return self._oDb.fetchOne()[0] > 0;
703
704
705 def getById(self, idBuild):
706 """
707 Get build record by its id
708 """
709 self._oDb.execute('SELECT Builds.*, BuildCategories.*\n'
710 'FROM Builds, BuildCategories\n'
711 'WHERE Builds.idBuild=%s\n'
712 ' AND Builds.idBuildCategory=BuildCategories.idBuildCategory\n'
713 ' AND Builds.tsExpire = \'infinity\'::TIMESTAMP\n', (idBuild,))
714
715 aRows = self._oDb.fetchAll()
716 if len(aRows) not in (0, 1):
717 raise TMTooManyRows('Found more than one build with the same credentials. Database structure is corrupted.')
718 try:
719 return BuildDataEx().initFromDbRow(aRows[0])
720 except IndexError:
721 return None
722
723
724 def getAll(self, tsEffective = None):
725 """
726 Gets the list of all builds.
727 Returns an array of BuildDataEx instances.
728 """
729 if tsEffective is None:
730 self._oDb.execute('SELECT Builds.*, BuildCategories.*\n'
731 'FROM Builds, BuildCategories\n'
732 'WHERE Builds.tsExpire = \'infinity\'::TIMESTAMP\n'
733 ' AND Builds.idBuildCategory=BuildCategories.idBuildCategory')
734 else:
735 self._oDb.execute('SELECT Builds.*, BuildCategories.*\n'
736 'FROM Builds, BuildCategories\n'
737 'WHERE Builds.tsExpire > %s\n'
738 ' AND Builds.tsEffective <= %s'
739 ' AND Builds.idBuildCategory=BuildCategories.idBuildCategory'
740 , (tsEffective, tsEffective))
741 aoRet = []
742 for aoRow in self._oDb.fetchAll():
743 aoRet.append(BuildDataEx().initFromDbRow(aoRow))
744 return aoRet
745
746
747 def markDeletedByBinaries(self, sBinaries, fCommit = False):
748 """
749 Marks zero or more builds deleted given the build binaries.
750
751 Returns the number of affected builds.
752 """
753 # Fetch a list of affected build IDs (generally 1 build), and used the
754 # editEntry method to do the rest. This isn't 100% optimal, but it's
755 # short and simple, the main effort is anyway the first query.
756 self._oDb.execute('SELECT idBuild\n'
757 'FROM Builds\n'
758 'WHERE sBinaries = %s\n'
759 ' AND fBinariesDeleted = FALSE\n'
760 ' AND tsExpire = \'infinity\'::TIMESTAMP\n'
761 , (sBinaries,));
762 aaoRows = self._oDb.fetchAll();
763 for aoRow in aaoRows:
764 oData = BuildData().initFromDbWithId(self._oDb, aoRow[0]);
765 assert not oData.fBinariesDeleted;
766 oData.fBinariesDeleted = True;
767 self.editEntry(oData, fCommit = False);
768 self._oDb.maybeCommit(fCommit);
769 return len(aaoRows);
770
771
772
773 #
774 # Internal helpers.
775 #
776
777 def _historizeBuild(self, idBuild, tsExpire = None):
778 """ Historizes the current entry for the specified build. """
779 if tsExpire is None:
780 self._oDb.execute('UPDATE Builds\n'
781 'SET tsExpire = CURRENT_TIMESTAMP\n'
782 'WHERE idBuild = %s\n'
783 ' AND tsExpire = \'infinity\'::TIMESTAMP\n'
784 , (idBuild,));
785 else:
786 self._oDb.execute('UPDATE Builds\n'
787 'SET tsExpire = %s\n'
788 'WHERE idBuild = %s\n'
789 ' AND tsExpire = \'infinity\'::TIMESTAMP\n'
790 , (tsExpire, idBuild,));
791 return True;
792
793#
794# Unit testing.
795#
796
797# pylint: disable=C0111
798class BuildCategoryDataTestCase(ModelDataBaseTestCase):
799 def setUp(self):
800 self.aoSamples = [BuildCategoryData(),];
801
802class BuildDataTestCase(ModelDataBaseTestCase):
803 def setUp(self):
804 self.aoSamples = [BuildData(),];
805
806if __name__ == '__main__':
807 unittest.main();
808 # not reached.
809
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