VirtualBox

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

Last change on this file since 62546 was 62546, checked in by vboxsync, 8 years ago

check_for_deleted_builds.py,builds.py: Fixes.

  • 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 62546 2016-07-25 16:56:12Z 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: 62546 $"
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 kasInternalAttributes = [ 'oCat', ];
437
438 def __init__(self):
439 BuildData.__init__(self);
440 self.oCat = None;
441
442 def initFromDbRow(self, aoRow):
443 """
444 Reinitialize from a SELECT Builds.*, BuildCategories.* FROM Builds, BuildCategories query.
445 Returns self. Raises exception if aoRow is None.
446 """
447 if aoRow is None:
448 raise TMRowNotFound('Build not found.');
449 BuildData.initFromDbRow(self, aoRow);
450 self.oCat = BuildCategoryData().initFromDbRow(aoRow[11:]);
451 return self;
452
453 def initFromDbWithId(self, oDb, idBuild, tsNow = None, sPeriodBack = None):
454 """
455 Reinitialize from database given a row ID.
456 Returns self. Raises exception on database error or if the ID is invalid.
457 """
458 oDb.execute(self.formatSimpleNowAndPeriodQuery(oDb,
459 'SELECT Builds.*, BuildCategories.*\n'
460 'FROM Builds, BuildCategories\n'
461 'WHERE idBuild = %s\n'
462 ' AND Builds.idBuildCategory = BuildCategories.idBuildCategory\n'
463 , ( idBuild,), tsNow, sPeriodBack, 'Builds.'));
464 aoRow = oDb.fetchOne()
465 if aoRow is None:
466 raise TMRowNotFound('idBuild=%s not found (tsNow=%s sPeriodBack=%s)' % (idBuild, tsNow, sPeriodBack,));
467 return self.initFromDbRow(aoRow);
468
469 def convertFromParamNull(self):
470 raise TMExceptionBase('Not implemented');
471
472 def isEqual(self, oOther):
473 raise TMExceptionBase('Not implemented');
474
475
476
477class BuildLogic(ModelLogicBase): # pylint: disable=R0903
478 """
479 Build database logic (covers build categories as well as builds).
480 """
481
482 #
483 # Standard methods.
484 #
485
486 def fetchForListing(self, iStart, cMaxRows, tsNow):
487 """
488 Fetches builds for listing.
489
490 Returns an array (list) of BuildDataEx items, empty list if none.
491 Raises exception on error.
492 """
493 if tsNow is None:
494 self._oDb.execute('SELECT *\n'
495 'FROM Builds, BuildCategories\n'
496 'WHERE Builds.idBuildCategory = BuildCategories.idBuildCategory\n'
497 ' AND Builds.tsExpire = \'infinity\'::TIMESTAMP\n'
498 'ORDER BY tsCreated DESC\n'
499 'LIMIT %s OFFSET %s\n'
500 , (cMaxRows, iStart,));
501 else:
502 self._oDb.execute('SELECT *\n'
503 'FROM Builds, BuildCategories\n'
504 'WHERE Builds.idBuildCategory = BuildCategories.idBuildCategory\n'
505 ' AND Builds.tsExpire > %s\n'
506 ' AND Builds.tsEffective <= %s\n'
507 'ORDER BY tsCreated DESC\n'
508 'LIMIT %s OFFSET %s\n'
509 , (tsNow, tsNow, cMaxRows, iStart,));
510
511 aoRows = [];
512 for _ in range(self._oDb.getRowCount()):
513 aoRows.append(BuildDataEx().initFromDbRow(self._oDb.fetchOne()));
514 return aoRows;
515
516 def addEntry(self, oBuildData, uidAuthor = None, fCommit = False):
517 """
518 Adds the build to the database, optionally adding the build category if
519 a BuildDataEx object used and it's necessary.
520
521 Returns updated data object. Raises exception on failure.
522 """
523
524 # Find/Add the build category if specified.
525 if isinstance(oBuildData, BuildDataEx) \
526 and oBuildData.idBuildCategory is None:
527 BuildCategoryLogic(self._oDb).addBuildCategory(oBuildData.oCat, fCommit = False);
528 oBuildData.idBuildCategory = oBuildData.oCat.idBuildCategory;
529
530 # Add the build.
531 self._oDb.execute('INSERT INTO Builds (uidAuthor,\n'
532 ' idBuildCategory,\n'
533 ' iRevision,\n'
534 ' sVersion,\n'
535 ' sLogUrl,\n'
536 ' sBinaries,\n'
537 ' fBinariesDeleted)\n'
538 'VALUES (%s, %s, %s, %s, %s, %s, %s)\n'
539 'RETURNING idBuild, tsCreated\n'
540 , ( uidAuthor,
541 oBuildData.idBuildCategory,
542 oBuildData.iRevision,
543 oBuildData.sVersion,
544 oBuildData.sLogUrl,
545 oBuildData.sBinaries,
546 oBuildData.fBinariesDeleted,
547 ));
548 aoRow = self._oDb.fetchOne();
549 oBuildData.idBuild = aoRow[0];
550 oBuildData.tsCreated = aoRow[1];
551
552 self._oDb.maybeCommit(fCommit);
553 return oBuildData;
554
555 def editEntry(self, oData, uidAuthor = None, fCommit = False):
556 """Modify database record"""
557
558 #
559 # Validate input and get current data.
560 #
561 dErrors = oData.validateAndConvert(self._oDb, oData.ksValidateFor_Edit);
562 if len(dErrors) > 0:
563 raise TMInvalidData('editEntry invalid input: %s' % (dErrors,));
564 oOldData = BuildData().initFromDbWithId(self._oDb, oData.idBuild);
565
566 #
567 # Do the work.
568 #
569 if not oData.isEqualEx(oOldData, [ 'tsEffective', 'tsExpire', 'uidAuthor' ]):
570 self._historizeBuild(oData.idBuild);
571 self._oDb.execute('INSERT INTO Builds (uidAuthor,\n'
572 ' idBuild,\n'
573 ' tsCreated,\n'
574 ' idBuildCategory,\n'
575 ' iRevision,\n'
576 ' sVersion,\n'
577 ' sLogUrl,\n'
578 ' sBinaries,\n'
579 ' fBinariesDeleted)\n'
580 'VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)\n'
581 'RETURNING idBuild, tsCreated\n'
582 , ( uidAuthor,
583 oData.idBuild,
584 oData.tsCreated,
585 oData.idBuildCategory,
586 oData.iRevision,
587 oData.sVersion,
588 oData.sLogUrl,
589 oData.sBinaries,
590 oData.fBinariesDeleted,
591 ));
592
593 self._oDb.maybeCommit(fCommit);
594 return True;
595
596 def removeEntry(self, uidAuthor, idBuild, fCascade = False, fCommit = False):
597 """
598 Historize record
599 """
600
601 #
602 # No non-historic refs here, so just go ahead and expire the build.
603 #
604 _ = fCascade;
605 _ = uidAuthor; ## @todo record deleter.
606
607 self._historizeBuild(idBuild, None);
608
609 self._oDb.maybeCommit(fCommit);
610 return True;
611
612
613 #
614 # Other methods.
615 #
616
617 def tryFindSameBuildForOsArch(self, oBuildEx, sOs, sCpuArch):
618 """
619 Attempts to find a matching build for the given OS.ARCH. May return
620 the input build if if matches.
621
622 Returns BuildDataEx instance if found, None if none. May raise
623 exception on database error.
624 """
625
626 if oBuildEx.oCat.matchesOsArch(sOs, sCpuArch):
627 return oBuildEx;
628
629 self._oDb.execute('SELECT Builds.*, BuildCategories.*\n'
630 'FROM Builds, BuildCategories\n'
631 'WHERE BuildCategories.sProduct = %s\n'
632 ' AND BuildCategories.sBranch = %s\n'
633 ' AND BuildCategories.sType = %s\n'
634 ' AND ( %s = ANY(BuildCategories.asOsArches)\n'
635 ' OR %s = ANY(BuildCategories.asOsArches)\n'
636 ' OR %s = ANY(BuildCategories.asOsArches))\n'
637 ' AND Builds.idBuildCategory = BuildCategories.idBuildCategory\n'
638 ' AND Builds.tsExpire = \'infinity\'::TIMESTAMP\n'
639 ' AND Builds.iRevision = %s\n'
640 ' AND Builds.sRelease = %s\n'
641 ' AND Builds.fBinariesDeleted IS FALSE\n'
642 'ORDER BY tsCreated DESC\n'
643 'LIMIT 4096\n' # stay sane.
644 , (oBuildEx.oCat.sProduct,
645 oBuildEx.oCat.sBranch,
646 oBuildEx.oCat.sType,
647 '%s.%s' % (sOs, sCpuArch),
648 '%s.noarch' % (sOs,),
649 'os-agnostic.%s' % (sCpuArch,),
650 'os-agnostic.noarch',
651 oBuildEx.iRevision,
652 oBuildEx.sRelease,
653 ) );
654 aaoRows = self._oDb.fetchAll();
655
656 for aoRow in aaoRows:
657 oBuildExRet = BuildDataEx().initFromDbRow(self, aoRow);
658 if not self.isBuildBlacklisted(oBuildExRet):
659 return oBuildExRet;
660
661 return None;
662
663 def isBuildBlacklisted(self, oBuildEx):
664 """
665 Checks if the given build is blacklisted
666 Returns True/False. May raise exception on database error.
667 """
668
669 asOsAgnosticArch = [];
670 asOsNoArch = [];
671 for i in range(len(oBuildEx.oCat.asOsArches)):
672 asParts = oBuildEx.oCat.asOsArches[i].split('.');
673 if len(asParts) != 2 or len(asParts[0]) == 0 or len(asParts[1]) == 0:
674 raise self._oDb.integrityException('Bad build asOsArches value: %s (idBuild=%s idBuildCategory=%s)'
675 % (oBuildEx.asOsArches[i], oBuildEx.idBuild, oBuildEx.idBuildCategory));
676 asOsNoArch.append(asParts[0] + '.noarch');
677 asOsNoArch.append('os-agnostic.' + asParts[1]);
678
679 self._oDb.execute('SELECT COUNT(*)\n'
680 'FROM BuildBlacklist\n'
681 'WHERE BuildBlacklist.tsExpire > CURRENT_TIMESTAMP\n'
682 ' AND BuildBlacklist.tsEffective <= CURRENT_TIMESTAMP\n'
683 ' AND BuildBlacklist.sProduct = %s\n'
684 ' AND BuildBlacklist.sBranch = %s\n'
685 ' AND ( BuildBlacklist.asTypes is NULL\n'
686 ' OR %s = ANY(BuildBlacklist.asTypes))\n'
687 ' AND ( BuildBlacklist.asOsArches is NULL\n'
688 ' OR %s && BuildBlacklist.asOsArches\n' ## @todo check array rep! Need overload?
689 ' OR %s && BuildBlacklist.asOsArches\n'
690 ' OR %s && BuildBlacklist.asOsArches\n'
691 ' OR %s = ANY(BuildBlacklist.asOsArches))\n'
692 ' AND BuildBlacklist.iFirstRevision <= %s\n'
693 ' AND BuildBlacklist.iLastRevision >= %s\n'
694 , (oBuildEx.oCat.sProduct,
695 oBuildEx.oCat.sBranch,
696 oBuildEx.oCat.sType,
697 oBuildEx.oCat.asOsArches,
698 asOsAgnosticArch,
699 asOsNoArch,
700 'os-agnostic.noarch',
701 oBuildEx.iRevision,
702 oBuildEx.iRevision,
703 ) );
704 return self._oDb.fetchOne()[0] > 0;
705
706
707 def getById(self, idBuild):
708 """
709 Get build record by its id
710 """
711 self._oDb.execute('SELECT Builds.*, BuildCategories.*\n'
712 'FROM Builds, BuildCategories\n'
713 'WHERE Builds.idBuild=%s\n'
714 ' AND Builds.idBuildCategory=BuildCategories.idBuildCategory\n'
715 ' AND Builds.tsExpire = \'infinity\'::TIMESTAMP\n', (idBuild,))
716
717 aRows = self._oDb.fetchAll()
718 if len(aRows) not in (0, 1):
719 raise TMTooManyRows('Found more than one build with the same credentials. Database structure is corrupted.')
720 try:
721 return BuildDataEx().initFromDbRow(aRows[0])
722 except IndexError:
723 return None
724
725
726 def getAll(self, tsEffective = None):
727 """
728 Gets the list of all builds.
729 Returns an array of BuildDataEx instances.
730 """
731 if tsEffective is None:
732 self._oDb.execute('SELECT Builds.*, BuildCategories.*\n'
733 'FROM Builds, BuildCategories\n'
734 'WHERE Builds.tsExpire = \'infinity\'::TIMESTAMP\n'
735 ' AND Builds.idBuildCategory=BuildCategories.idBuildCategory')
736 else:
737 self._oDb.execute('SELECT Builds.*, BuildCategories.*\n'
738 'FROM Builds, BuildCategories\n'
739 'WHERE Builds.tsExpire > %s\n'
740 ' AND Builds.tsEffective <= %s'
741 ' AND Builds.idBuildCategory=BuildCategories.idBuildCategory'
742 , (tsEffective, tsEffective))
743 aoRet = []
744 for aoRow in self._oDb.fetchAll():
745 aoRet.append(BuildDataEx().initFromDbRow(aoRow))
746 return aoRet
747
748
749 def markDeletedByBinaries(self, sBinaries, fCommit = False):
750 """
751 Marks zero or more builds deleted given the build binaries.
752
753 Returns the number of affected builds.
754 """
755 # Fetch a list of affected build IDs (generally 1 build), and used the
756 # editEntry method to do the rest. This isn't 100% optimal, but it's
757 # short and simple, the main effort is anyway the first query.
758 self._oDb.execute('SELECT idBuild\n'
759 'FROM Builds\n'
760 'WHERE sBinaries = %s\n'
761 ' AND fBinariesDeleted = FALSE\n'
762 ' AND tsExpire = \'infinity\'::TIMESTAMP\n'
763 , (sBinaries,));
764 aaoRows = self._oDb.fetchAll();
765 for aoRow in aaoRows:
766 oData = BuildData().initFromDbWithId(self._oDb, aoRow[0]);
767 assert not oData.fBinariesDeleted;
768 oData.fBinariesDeleted = True;
769 self.editEntry(oData, fCommit = False);
770 self._oDb.maybeCommit(fCommit);
771 return len(aaoRows);
772
773
774
775 #
776 # Internal helpers.
777 #
778
779 def _historizeBuild(self, idBuild, tsExpire = None):
780 """ Historizes the current entry for the specified build. """
781 if tsExpire is None:
782 self._oDb.execute('UPDATE Builds\n'
783 'SET tsExpire = CURRENT_TIMESTAMP\n'
784 'WHERE idBuild = %s\n'
785 ' AND tsExpire = \'infinity\'::TIMESTAMP\n'
786 , (idBuild,));
787 else:
788 self._oDb.execute('UPDATE Builds\n'
789 'SET tsExpire = %s\n'
790 'WHERE idBuild = %s\n'
791 ' AND tsExpire = \'infinity\'::TIMESTAMP\n'
792 , (tsExpire, idBuild,));
793 return True;
794
795#
796# Unit testing.
797#
798
799# pylint: disable=C0111
800class BuildCategoryDataTestCase(ModelDataBaseTestCase):
801 def setUp(self):
802 self.aoSamples = [BuildCategoryData(),];
803
804class BuildDataTestCase(ModelDataBaseTestCase):
805 def setUp(self):
806 self.aoSamples = [BuildData(),];
807
808if __name__ == '__main__':
809 unittest.main();
810 # not reached.
811
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