VirtualBox

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

Last change on this file since 98103 was 98103, checked in by vboxsync, 2 years ago

Copyright year updates by scm.

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