VirtualBox

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

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

testmanager: More test result filtering.

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

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