VirtualBox

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

Last change on this file since 72293 was 69111, checked in by vboxsync, 7 years ago

(C) year

  • 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 69111 2017-10-17 14:26:02Z vboxsync $
3
4"""
5Test Manager - Builds.
6"""
7
8__copyright__ = \
9"""
10Copyright (C) 2012-2017 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: 69111 $"
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, aiSortColumns = None):
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; _ = aiSortColumns;
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 not aaoRows:
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 not aaoRows:
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 dDataErrors:
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 not sBinary:
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, aiSortColumns = None):
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 _ = aiSortColumns;
525
526 if tsNow is None:
527 self._oDb.execute('SELECT *\n'
528 'FROM Builds, BuildCategories\n'
529 'WHERE Builds.idBuildCategory = BuildCategories.idBuildCategory\n'
530 ' AND Builds.tsExpire = \'infinity\'::TIMESTAMP\n'
531 'ORDER BY tsCreated DESC\n'
532 'LIMIT %s OFFSET %s\n'
533 , (cMaxRows, iStart,));
534 else:
535 self._oDb.execute('SELECT *\n'
536 'FROM Builds, BuildCategories\n'
537 'WHERE Builds.idBuildCategory = BuildCategories.idBuildCategory\n'
538 ' AND Builds.tsExpire > %s\n'
539 ' AND Builds.tsEffective <= %s\n'
540 'ORDER BY tsCreated DESC\n'
541 'LIMIT %s OFFSET %s\n'
542 , (tsNow, tsNow, cMaxRows, iStart,));
543
544 aoRows = [];
545 for _ in range(self._oDb.getRowCount()):
546 aoRows.append(BuildDataEx().initFromDbRow(self._oDb.fetchOne()));
547 return aoRows;
548
549 def addEntry(self, oBuildData, uidAuthor = None, fCommit = False):
550 """
551 Adds the build to the database, optionally adding the build category if
552 a BuildDataEx object used and it's necessary.
553
554 Returns updated data object. Raises exception on failure.
555 """
556
557 # Find/Add the build category if specified.
558 if isinstance(oBuildData, BuildDataEx) \
559 and oBuildData.idBuildCategory is None:
560 BuildCategoryLogic(self._oDb).addBuildCategory(oBuildData.oCat, fCommit = False);
561 oBuildData.idBuildCategory = oBuildData.oCat.idBuildCategory;
562
563 # Add the build.
564 self._oDb.execute('INSERT INTO Builds (uidAuthor,\n'
565 ' idBuildCategory,\n'
566 ' iRevision,\n'
567 ' sVersion,\n'
568 ' sLogUrl,\n'
569 ' sBinaries,\n'
570 ' fBinariesDeleted)\n'
571 'VALUES (%s, %s, %s, %s, %s, %s, %s)\n'
572 'RETURNING idBuild, tsCreated\n'
573 , ( uidAuthor,
574 oBuildData.idBuildCategory,
575 oBuildData.iRevision,
576 oBuildData.sVersion,
577 oBuildData.sLogUrl,
578 oBuildData.sBinaries,
579 oBuildData.fBinariesDeleted,
580 ));
581 aoRow = self._oDb.fetchOne();
582 oBuildData.idBuild = aoRow[0];
583 oBuildData.tsCreated = aoRow[1];
584
585 self._oDb.maybeCommit(fCommit);
586 return oBuildData;
587
588 def editEntry(self, oData, uidAuthor = None, fCommit = False):
589 """Modify database record"""
590
591 #
592 # Validate input and get current data.
593 #
594 dErrors = oData.validateAndConvert(self._oDb, oData.ksValidateFor_Edit);
595 if dErrors:
596 raise TMInvalidData('editEntry invalid input: %s' % (dErrors,));
597 oOldData = BuildData().initFromDbWithId(self._oDb, oData.idBuild);
598
599 #
600 # Do the work.
601 #
602 if not oData.isEqualEx(oOldData, [ 'tsEffective', 'tsExpire', 'uidAuthor' ]):
603 self._historizeBuild(oData.idBuild);
604 self._oDb.execute('INSERT INTO Builds (uidAuthor,\n'
605 ' idBuild,\n'
606 ' tsCreated,\n'
607 ' idBuildCategory,\n'
608 ' iRevision,\n'
609 ' sVersion,\n'
610 ' sLogUrl,\n'
611 ' sBinaries,\n'
612 ' fBinariesDeleted)\n'
613 'VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)\n'
614 'RETURNING idBuild, tsCreated\n'
615 , ( uidAuthor,
616 oData.idBuild,
617 oData.tsCreated,
618 oData.idBuildCategory,
619 oData.iRevision,
620 oData.sVersion,
621 oData.sLogUrl,
622 oData.sBinaries,
623 oData.fBinariesDeleted,
624 ));
625
626 self._oDb.maybeCommit(fCommit);
627 return True;
628
629 def removeEntry(self, uidAuthor, idBuild, fCascade = False, fCommit = False):
630 """
631 Historize record
632 """
633
634 #
635 # No non-historic refs here, so just go ahead and expire the build.
636 #
637 _ = fCascade;
638 _ = uidAuthor; ## @todo record deleter.
639
640 self._historizeBuild(idBuild, None);
641
642 self._oDb.maybeCommit(fCommit);
643 return True;
644
645 def cachedLookup(self, idBuild):
646 """
647 Looks up the most recent BuildDataEx object for idBuild
648 via an object cache.
649
650 Returns a shared BuildDataEx object. None if not found.
651 Raises exception on DB error.
652 """
653 if self.dCache is None:
654 self.dCache = self._oDb.getCache('BuildDataEx');
655 oEntry = self.dCache.get(idBuild, None);
656 if oEntry is None:
657 self._oDb.execute('SELECT Builds.*, BuildCategories.*\n'
658 'FROM Builds, BuildCategories\n'
659 'WHERE Builds.idBuild = %s\n'
660 ' AND Builds.idBuildCategory = BuildCategories.idBuildCategory\n'
661 ' AND tsExpire = \'infinity\'::TIMESTAMP\n'
662 , (idBuild, ));
663 if self._oDb.getRowCount() == 0:
664 # Maybe it was deleted, try get the last entry.
665 self._oDb.execute('SELECT Builds.*, BuildCategories.*\n'
666 'FROM Builds, BuildCategories\n'
667 'WHERE Builds.idBuild = %s\n'
668 ' AND Builds.idBuildCategory = BuildCategories.idBuildCategory\n'
669 'ORDER BY tsExpire DESC\n'
670 'LIMIT 1\n'
671 , (idBuild, ));
672 elif self._oDb.getRowCount() > 1:
673 raise self._oDb.integrityException('%s infinity rows for %s' % (self._oDb.getRowCount(), idBuild));
674
675 if self._oDb.getRowCount() == 1:
676 aaoRow = self._oDb.fetchOne();
677 oEntry = BuildDataEx();
678 oEntry.initFromDbRow(aaoRow);
679 self.dCache[idBuild] = oEntry;
680 return oEntry;
681
682
683 #
684 # Other methods.
685 #
686
687 def tryFindSameBuildForOsArch(self, oBuildEx, sOs, sCpuArch):
688 """
689 Attempts to find a matching build for the given OS.ARCH. May return
690 the input build if if matches.
691
692 Returns BuildDataEx instance if found, None if none. May raise
693 exception on database error.
694 """
695
696 if oBuildEx.oCat.matchesOsArch(sOs, sCpuArch):
697 return oBuildEx;
698
699 self._oDb.execute('SELECT Builds.*, BuildCategories.*\n'
700 'FROM Builds, BuildCategories\n'
701 'WHERE BuildCategories.sProduct = %s\n'
702 ' AND BuildCategories.sBranch = %s\n'
703 ' AND BuildCategories.sType = %s\n'
704 ' AND ( %s = ANY(BuildCategories.asOsArches)\n'
705 ' OR %s = ANY(BuildCategories.asOsArches)\n'
706 ' OR %s = ANY(BuildCategories.asOsArches))\n'
707 ' AND Builds.idBuildCategory = BuildCategories.idBuildCategory\n'
708 ' AND Builds.tsExpire = \'infinity\'::TIMESTAMP\n'
709 ' AND Builds.iRevision = %s\n'
710 ' AND Builds.sRelease = %s\n'
711 ' AND Builds.fBinariesDeleted IS FALSE\n'
712 'ORDER BY tsCreated DESC\n'
713 'LIMIT 4096\n' # stay sane.
714 , (oBuildEx.oCat.sProduct,
715 oBuildEx.oCat.sBranch,
716 oBuildEx.oCat.sType,
717 '%s.%s' % (sOs, sCpuArch),
718 '%s.noarch' % (sOs,),
719 'os-agnostic.%s' % (sCpuArch,),
720 'os-agnostic.noarch',
721 oBuildEx.iRevision,
722 oBuildEx.sRelease,
723 ) );
724 aaoRows = self._oDb.fetchAll();
725
726 for aoRow in aaoRows:
727 oBuildExRet = BuildDataEx().initFromDbRow(self, aoRow);
728 if not self.isBuildBlacklisted(oBuildExRet):
729 return oBuildExRet;
730
731 return None;
732
733 def isBuildBlacklisted(self, oBuildEx):
734 """
735 Checks if the given build is blacklisted
736 Returns True/False. May raise exception on database error.
737 """
738
739 asOsAgnosticArch = [];
740 asOsNoArch = [];
741 for i in range(len(oBuildEx.oCat.asOsArches)):
742 asParts = oBuildEx.oCat.asOsArches[i].split('.');
743 if len(asParts) != 2 or not asParts[0] or not asParts[1]:
744 raise self._oDb.integrityException('Bad build asOsArches value: %s (idBuild=%s idBuildCategory=%s)'
745 % (oBuildEx.asOsArches[i], oBuildEx.idBuild, oBuildEx.idBuildCategory));
746 asOsNoArch.append(asParts[0] + '.noarch');
747 asOsNoArch.append('os-agnostic.' + asParts[1]);
748
749 self._oDb.execute('SELECT COUNT(*)\n'
750 'FROM BuildBlacklist\n'
751 'WHERE BuildBlacklist.tsExpire > CURRENT_TIMESTAMP\n'
752 ' AND BuildBlacklist.tsEffective <= CURRENT_TIMESTAMP\n'
753 ' AND BuildBlacklist.sProduct = %s\n'
754 ' AND BuildBlacklist.sBranch = %s\n'
755 ' AND ( BuildBlacklist.asTypes is NULL\n'
756 ' OR %s = ANY(BuildBlacklist.asTypes))\n'
757 ' AND ( BuildBlacklist.asOsArches is NULL\n'
758 ' OR %s && BuildBlacklist.asOsArches\n' ## @todo check array rep! Need overload?
759 ' OR %s && BuildBlacklist.asOsArches\n'
760 ' OR %s && BuildBlacklist.asOsArches\n'
761 ' OR %s = ANY(BuildBlacklist.asOsArches))\n'
762 ' AND BuildBlacklist.iFirstRevision <= %s\n'
763 ' AND BuildBlacklist.iLastRevision >= %s\n'
764 , (oBuildEx.oCat.sProduct,
765 oBuildEx.oCat.sBranch,
766 oBuildEx.oCat.sType,
767 oBuildEx.oCat.asOsArches,
768 asOsAgnosticArch,
769 asOsNoArch,
770 'os-agnostic.noarch',
771 oBuildEx.iRevision,
772 oBuildEx.iRevision,
773 ) );
774 return self._oDb.fetchOne()[0] > 0;
775
776
777 def getById(self, idBuild):
778 """
779 Get build record by its id
780 """
781 self._oDb.execute('SELECT Builds.*, BuildCategories.*\n'
782 'FROM Builds, BuildCategories\n'
783 'WHERE Builds.idBuild=%s\n'
784 ' AND Builds.idBuildCategory=BuildCategories.idBuildCategory\n'
785 ' AND Builds.tsExpire = \'infinity\'::TIMESTAMP\n', (idBuild,))
786
787 aRows = self._oDb.fetchAll()
788 if len(aRows) not in (0, 1):
789 raise TMTooManyRows('Found more than one build with the same credentials. Database structure is corrupted.')
790 try:
791 return BuildDataEx().initFromDbRow(aRows[0])
792 except IndexError:
793 return None
794
795
796 def getAll(self, tsEffective = None):
797 """
798 Gets the list of all builds.
799 Returns an array of BuildDataEx instances.
800 """
801 if tsEffective is None:
802 self._oDb.execute('SELECT Builds.*, BuildCategories.*\n'
803 'FROM Builds, BuildCategories\n'
804 'WHERE Builds.tsExpire = \'infinity\'::TIMESTAMP\n'
805 ' AND Builds.idBuildCategory=BuildCategories.idBuildCategory')
806 else:
807 self._oDb.execute('SELECT Builds.*, BuildCategories.*\n'
808 'FROM Builds, BuildCategories\n'
809 'WHERE Builds.tsExpire > %s\n'
810 ' AND Builds.tsEffective <= %s'
811 ' AND Builds.idBuildCategory=BuildCategories.idBuildCategory'
812 , (tsEffective, tsEffective))
813 aoRet = []
814 for aoRow in self._oDb.fetchAll():
815 aoRet.append(BuildDataEx().initFromDbRow(aoRow))
816 return aoRet
817
818
819 def markDeletedByBinaries(self, sBinaries, fCommit = False):
820 """
821 Marks zero or more builds deleted given the build binaries.
822
823 Returns the number of affected builds.
824 """
825 # Fetch a list of affected build IDs (generally 1 build), and used the
826 # editEntry method to do the rest. This isn't 100% optimal, but it's
827 # short and simple, the main effort is anyway the first query.
828 self._oDb.execute('SELECT idBuild\n'
829 'FROM Builds\n'
830 'WHERE sBinaries = %s\n'
831 ' AND fBinariesDeleted = FALSE\n'
832 ' AND tsExpire = \'infinity\'::TIMESTAMP\n'
833 , (sBinaries,));
834 aaoRows = self._oDb.fetchAll();
835 for aoRow in aaoRows:
836 oData = BuildData().initFromDbWithId(self._oDb, aoRow[0]);
837 assert not oData.fBinariesDeleted;
838 oData.fBinariesDeleted = True;
839 self.editEntry(oData, fCommit = False);
840 self._oDb.maybeCommit(fCommit);
841 return len(aaoRows);
842
843
844
845 #
846 # Internal helpers.
847 #
848
849 def _historizeBuild(self, idBuild, tsExpire = None):
850 """ Historizes the current entry for the specified build. """
851 if tsExpire is None:
852 self._oDb.execute('UPDATE Builds\n'
853 'SET tsExpire = CURRENT_TIMESTAMP\n'
854 'WHERE idBuild = %s\n'
855 ' AND tsExpire = \'infinity\'::TIMESTAMP\n'
856 , (idBuild,));
857 else:
858 self._oDb.execute('UPDATE Builds\n'
859 'SET tsExpire = %s\n'
860 'WHERE idBuild = %s\n'
861 ' AND tsExpire = \'infinity\'::TIMESTAMP\n'
862 , (tsExpire, idBuild,));
863 return True;
864
865#
866# Unit testing.
867#
868
869# pylint: disable=C0111
870class BuildCategoryDataTestCase(ModelDataBaseTestCase):
871 def setUp(self):
872 self.aoSamples = [BuildCategoryData(),];
873
874class BuildDataTestCase(ModelDataBaseTestCase):
875 def setUp(self):
876 self.aoSamples = [BuildData(),];
877
878if __name__ == '__main__':
879 unittest.main();
880 # not reached.
881
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