VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/testmanager/core/buildsource.py@ 60308

Last change on this file since 60308 was 56295, checked in by vboxsync, 10 years ago

ValidationKit: Updated (C) year.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 19.6 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: buildsource.py 56295 2015-06-09 14:29:55Z vboxsync $
3
4"""
5Test Manager - Build Sources.
6"""
7
8__copyright__ = \
9"""
10Copyright (C) 2012-2015 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: 56295 $"
30
31
32# Standard python imports.
33import unittest;
34
35# Validation Kit imports.
36from common import utils;
37from testmanager.core.base import ModelDataBase, ModelDataBaseTestCase, ModelLogicBase, TMExceptionBase;
38from testmanager.core import coreconsts;
39
40
41class BuildSourceData(ModelDataBase):
42 """
43 A build source.
44 """
45
46 ksIdAttr = 'idBuildSrc';
47
48 ksParam_idBuildSrc = 'BuildSource_idBuildSrc';
49 ksParam_tsEffective = 'BuildSource_tsEffective';
50 ksParam_tsExpire = 'BuildSource_tsExpire';
51 ksParam_uidAuthor = 'BuildSource_uidAuthor';
52 ksParam_sName = 'BuildSource_sName';
53 ksParam_sDescription = 'BuildSource_sDescription';
54 ksParam_sProduct = 'BuildSource_sProduct';
55 ksParam_sBranch = 'BuildSource_sBranch';
56 ksParam_asTypes = 'BuildSource_asTypes';
57 ksParam_asOsArches = 'BuildSource_asOsArches';
58 ksParam_iFirstRevision = 'BuildSource_iFirstRevision';
59 ksParam_iLastRevision = 'BuildSource_iLastRevision';
60 ksParam_cSecMaxAge = 'BuildSource_cSecMaxAge';
61
62 kasAllowNullAttributes = [ 'idBuildSrc', 'tsEffective', 'tsExpire', 'uidAuthor', 'sDescription', 'asTypes',
63 'asOsArches', 'iFirstRevision', 'iLastRevision', 'cSecMaxAge' ];
64
65 def __init__(self):
66 ModelDataBase.__init__(self);
67
68 #
69 # Initialize with defaults.
70 # See the database for explanations of each of these fields.
71 #
72 self.idBuildSrc = None;
73 self.tsEffective = None;
74 self.tsExpire = None;
75 self.uidAuthor = None;
76 self.sName = None;
77 self.sDescription = None;
78 self.sProduct = None;
79 self.sBranch = None;
80 self.asTypes = None;
81 self.asOsArches = None;
82 self.iFirstRevision = None;
83 self.iLastRevision = None;
84 self.cSecMaxAge = None;
85
86 def initFromDbRow(self, aoRow):
87 """
88 Re-initializes the object from a SELECT * FROM BuildSources row.
89 Returns self. Raises exception if aoRow is None.
90 """
91 if aoRow is None:
92 raise TMExceptionBase('Build source not found.');
93
94 self.idBuildSrc = aoRow[0];
95 self.tsEffective = aoRow[1];
96 self.tsExpire = aoRow[2];
97 self.uidAuthor = aoRow[3];
98 self.sName = aoRow[4];
99 self.sDescription = aoRow[5];
100 self.sProduct = aoRow[6];
101 self.sBranch = aoRow[7];
102 self.asTypes = aoRow[8];
103 self.asOsArches = aoRow[9];
104 self.iFirstRevision = aoRow[10];
105 self.iLastRevision = aoRow[11];
106 self.cSecMaxAge = aoRow[12];
107 return self;
108
109 def initFromDbWithId(self, oDb, idBuildSrc, tsNow = None, sPeriodBack = None):
110 """
111 Initialize from the database, given the ID of a row.
112 """
113 oDb.execute(self.formatSimpleNowAndPeriodQuery(oDb,
114 'SELECT *\n'
115 'FROM BuildSources\n'
116 'WHERE idBuildSrc = %s\n'
117 , ( idBuildSrc,), tsNow, sPeriodBack));
118 aoRow = oDb.fetchOne()
119 if aoRow is None:
120 raise TMExceptionBase('idBuildSrc=%s not found (tsNow=%s sPeriodBack=%s)' % (idBuildSrc, tsNow, sPeriodBack,));
121 return self.initFromDbRow(aoRow);
122
123 def _validateAndConvertAttribute(self, sAttr, sParam, oValue, aoNilValues, fAllowNull, oDb):
124 # Handle asType 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:
129 if len(self.asTypes) <= 0:
130 oNewValue = None;
131 else:
132 for sType in oNewValue:
133 if len(sType) < 2 or sType.lower() != sType:
134 if sError is None: sError = '';
135 else: sError += ', ';
136 sError += 'invalid value "%s"' % (sType,);
137
138 elif sAttr == 'asOsArches':
139 (oNewValue, sError) = self.validateListOfStr(oValue, aoNilValues = aoNilValues, fAllowNull = fAllowNull,
140 asValidValues = coreconsts.g_kasOsDotCpusAll);
141 if sError is not None and oNewValue is not None:
142 oNewValue = sorted(oNewValue); # Must be sorted!
143
144 elif sAttr == 'cSecMaxAge' and oValue not in aoNilValues: # Allow human readable interval formats.
145 (oNewValue, sError) = utils.parseIntervalSeconds(oValue);
146 else:
147 return ModelDataBase._validateAndConvertAttribute(self, sAttr, sParam, oValue, aoNilValues, fAllowNull, oDb);
148
149 return (oNewValue, sError);
150
151class BuildSourceLogic(ModelLogicBase): # pylint: disable=R0903
152 """
153 Build source database logic.
154 """
155
156 #
157 # Standard methods.
158 #
159
160 def fetchForListing(self, iStart, cMaxRows, tsNow):
161 """
162 Fetches build sources.
163
164 Returns an array (list) of BuildSourceData items, empty list if none.
165 Raises exception on error.
166 """
167 if tsNow is None:
168 self._oDb.execute('SELECT *\n'
169 'FROM BuildSources\n'
170 'WHERE tsExpire = \'infinity\'::TIMESTAMP\n'
171 'ORDER BY idBuildSrc DESC\n'
172 'LIMIT %s OFFSET %s\n'
173 , (cMaxRows, iStart,));
174 else:
175 self._oDb.execute('SELECT *\n'
176 'FROM BuildSources\n'
177 'WHERE tsExpire > %s\n'
178 ' AND tsEffective <= %s\n'
179 'ORDER BY idBuildSrc DESC\n'
180 'LIMIT %s OFFSET %s\n'
181 , (tsNow, tsNow, cMaxRows, iStart,));
182
183 aoRows = []
184 for aoRow in self._oDb.fetchAll():
185 aoRows.append(BuildSourceData().initFromDbRow(aoRow))
186 return aoRows
187
188 def fetchForCombo(self):
189 """Fetch data which is aimed to be passed to HTML form"""
190 self._oDb.execute('SELECT idBuildSrc, sName, sProduct\n'
191 'FROM BuildSources\n'
192 'WHERE tsExpire = \'infinity\'::TIMESTAMP\n'
193 'ORDER BY idBuildSrc DESC\n')
194 asRet = self._oDb.fetchAll();
195 asRet.insert(0, (-1, 'None', 'None'));
196 return asRet;
197
198
199 def addEntry(self, oData, uidAuthor, fCommit = False):
200 """
201 Add a new build source to the database.
202 """
203
204 #
205 # Validate the input.
206 #
207 dErrors = oData.validateAndConvert(self._oDb);
208 if len(dErrors) > 0:
209 raise TMExceptionBase('addEntry invalid input: %s' % (dErrors,));
210 self._assertUnique(oData, None);
211
212 #
213 # Add it.
214 #
215 self._oDb.execute('INSERT INTO BuildSources (\n'
216 ' uidAuthor,\n'
217 ' sName,\n'
218 ' sDescription,\n'
219 ' sProduct,\n'
220 ' sBranch,\n'
221 ' asTypes,\n'
222 ' asOsArches,\n'
223 ' iFirstRevision,\n'
224 ' iLastRevision,\n'
225 ' cSecMaxAge)\n'
226 'VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)\n'
227 , ( uidAuthor,
228 oData.sName,
229 oData.sDescription,
230 oData.sProduct,
231 oData.sBranch,
232 oData.asTypes,
233 oData.asOsArches,
234 oData.iFirstRevision,
235 oData.iLastRevision,
236 oData.cSecMaxAge, ));
237
238 self._oDb.maybeCommit(fCommit);
239 return True;
240
241 def editEntry(self, oData, uidAuthor, fCommit = False):
242 """
243 Modifies a build source.
244 """
245
246 #
247 # Validate the input and read the old entry.
248 #
249 dErrors = oData.validateAndConvert(self._oDb);
250 if len(dErrors) > 0:
251 raise TMExceptionBase('addEntry invalid input: %s' % (dErrors,));
252 self._assertUnique(oData, oData.idBuildSrc);
253 oOldData = BuildSourceData().initFromDbWithId(self._oDb, oData.idBuildSrc);
254
255 #
256 # Make the changes (if something actually changed).
257 #
258 if not oData.isEqualEx(oOldData, [ 'tsEffective', 'tsExpire', 'uidAuthor', ]):
259 self._historizeBuildSource(oData.idBuildSrc);
260 self._oDb.execute('INSERT INTO BuildSources (\n'
261 ' uidAuthor,\n'
262 ' idBuildSrc,\n'
263 ' sName,\n'
264 ' sDescription,\n'
265 ' sProduct,\n'
266 ' sBranch,\n'
267 ' asTypes,\n'
268 ' asOsArches,\n'
269 ' iFirstRevision,\n'
270 ' iLastRevision,\n'
271 ' cSecMaxAge)\n'
272 'VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)\n'
273 , ( uidAuthor,
274 oData.idBuildSrc,
275 oData.sName,
276 oData.sDescription,
277 oData.sProduct,
278 oData.sBranch,
279 oData.asTypes,
280 oData.asOsArches,
281 oData.iFirstRevision,
282 oData.iLastRevision,
283 oData.cSecMaxAge, ));
284 self._oDb.maybeCommit(fCommit);
285 return True;
286
287 def removeEntry(self, uidAuthor, idBuildSrc, fCascade = False, fCommit = False):
288 """
289 Deletes a build sources.
290 """
291
292 #
293 # Check cascading.
294 #
295 if fCascade is not True:
296 self._oDb.execute('SELECT idSchedGroup, sName\n'
297 'FROM SchedGroups\n'
298 'WHERE idBuildSrc = %s\n'
299 ' OR idBuildSrcTestSuite = %s\n'
300 , (idBuildSrc, idBuildSrc,));
301 if self._oDb.getRowCount() > 0:
302 asGroups = [];
303 for aoRow in self._oDb.fetchAll():
304 asGroups.append('%s (#%d)' % (aoRow[1], aoRow[0]));
305 raise TMExceptionBase('Build source #%d is used by one or more scheduling groups: %s'
306 % (idBuildSrc, ', '.join(asGroups),));
307 else:
308 self._oDb.execute('UPDATE SchedGroups\n'
309 'SET idBuildSrc = NULL\n'
310 'WHERE idBuildSrc = %s'
311 , ( idBuildSrc,));
312 self._oDb.execute('UPDATE SchedGroups\n'
313 'SET idBuildSrcTestSuite = NULL\n'
314 'WHERE idBuildSrcTestSuite = %s'
315 , ( idBuildSrc,));
316
317 #
318 # Do the job.
319 #
320 self._historizeBuildSource(idBuildSrc, None);
321 _ = uidAuthor; ## @todo record deleter.
322
323 self._oDb.maybeCommit(fCommit);
324 return True;
325
326
327 #
328 # Other methods.
329 #
330
331 def openBuildCursor(self, oBuildSource, sOs, sCpuArch, tsNow):
332 """
333 Opens a cursor (SELECT) using the criteria found in the build source
334 and the given OS.CPUARCH.
335
336 Returns database cursor. May raise exception on bad input or logic error.
337
338 Used by SchedulerBase.
339 """
340
341 oCursor = self._oDb.openCursor();
342
343 #
344 # Construct the extra conditionals.
345 #
346 sExtraConditions = '';
347
348 # Types
349 if oBuildSource.asTypes is not None and len(oBuildSource.asTypes) > 0:
350 if len(oBuildSource.asTypes) == 1:
351 sExtraConditions += oCursor.formatBindArgs(' AND BuildCategories.sType = %s', (oBuildSource.asTypes[0],));
352 else:
353 sExtraConditions += oCursor.formatBindArgs(' AND BuildCategories.sType IN (%s', (oBuildSource.asTypes[0],))
354 for i in range(1, len(oBuildSource.asTypes) - 1):
355 sExtraConditions += oCursor.formatBindArgs(', %s', (oBuildSource.asTypes[i],));
356 sExtraConditions += oCursor.formatBindArgs(', %s)\n', (oBuildSource.asTypes[-1],));
357
358 # BuildSource OSes.ARCHes. (Paranoia: use a dictionary to avoid duplicate values.)
359 if oBuildSource.asOsArches is not None and len(oBuildSource.asOsArches) > 0:
360 sExtraConditions += oCursor.formatBindArgs(' AND BuildCategories.asOsArches && %s', (oBuildSource.asOsArches,));
361
362 # TestBox OSes.ARCHes. (Paranoia: use a dictionary to avoid duplicate values.)
363 dOsDotArches = {};
364 dOsDotArches[sOs + '.' + sCpuArch] = 1;
365 dOsDotArches[sOs + '.' + coreconsts.g_ksCpuArchAgnostic] = 1;
366 dOsDotArches[coreconsts.g_ksOsAgnostic + '.' + sCpuArch] = 1;
367 dOsDotArches[coreconsts.g_ksOsDotArchAgnostic] = 1;
368 sExtraConditions += oCursor.formatBindArgs(' AND BuildCategories.asOsArches && %s', (list(dOsDotArches.keys()),));
369
370 # Revision range.
371 if oBuildSource.iFirstRevision is not None:
372 sExtraConditions += oCursor.formatBindArgs(' AND Builds.iRevision >= %s\n', (oBuildSource.iFirstRevision,));
373 if oBuildSource.iLastRevision is not None:
374 sExtraConditions += oCursor.formatBindArgs(' AND Builds.iRevision <= %s\n', (oBuildSource.iLastRevision,));
375
376 # Max age.
377 if oBuildSource.cSecMaxAge is not None:
378 sExtraConditions += oCursor.formatBindArgs(' AND Builds.tsCreated >= (%s - \'%s seconds\'::INTERVAL)\n',
379 (tsNow, oBuildSource.cSecMaxAge,));
380
381 #
382 # Execute the query.
383 #
384 oCursor.execute('SELECT Builds.*, BuildCategories.*,\n'
385 ' EXISTS( SELECT tsExpire\n'
386 ' FROM BuildBlacklist\n'
387 ' WHERE BuildBlacklist.tsExpire = \'infinity\'::TIMESTAMP\n'
388 ' AND BuildBlacklist.sProduct = %s\n'
389 ' AND BuildBlacklist.sBranch = %s\n'
390 ' AND BuildBlacklist.iFirstRevision <= Builds.iRevision\n'
391 ' AND BuildBlacklist.iLastRevision >= Builds.iRevision ) AS fMaybeBlacklisted\n'
392 'FROM Builds, BuildCategories\n'
393 'WHERE Builds.idBuildCategory = BuildCategories.idBuildCategory\n'
394 ' AND Builds.tsExpire = \'infinity\'::TIMESTAMP\n'
395 ' AND Builds.tsEffective <= %s\n'
396 ' AND Builds.fBinariesDeleted is FALSE\n'
397 ' AND BuildCategories.sProduct = %s\n'
398 ' AND BuildCategories.sBranch = %s\n'
399 + sExtraConditions +
400 'ORDER BY Builds.idBuild DESC\n'
401 'LIMIT 256\n'
402 , ( oBuildSource.sProduct, oBuildSource.sBranch,
403 tsNow, oBuildSource.sProduct, oBuildSource.sBranch,));
404
405 return oCursor;
406
407
408 def getById(self, idBuildSrc):
409 """Get Build Source data by idBuildSrc"""
410
411 self._oDb.execute('SELECT *\n'
412 'FROM BuildSources\n'
413 'WHERE tsExpire = \'infinity\'::timestamp\n'
414 ' AND idBuildSrc = %s;', (idBuildSrc,))
415 aRows = self._oDb.fetchAll()
416 if len(aRows) not in (0, 1):
417 raise self._oDb.integrityException(
418 'Found more than one build sources with the same credentials. Database structure is corrupted.')
419 try:
420 return BuildSourceData().initFromDbRow(aRows[0])
421 except IndexError:
422 return None
423
424 #
425 # Internal helpers.
426 #
427
428 def _assertUnique(self, oData, idBuildSrcIgnore):
429 """ Checks that the build source name is unique, raises exception if it isn't. """
430 self._oDb.execute('SELECT idBuildSrc\n'
431 'FROM BuildSources\n'
432 'WHERE sName = %s\n'
433 ' AND tsExpire = \'infinity\'::TIMESTAMP\n'
434 + ('' if idBuildSrcIgnore is None else ' AND idBuildSrc <> %d\n' % (idBuildSrcIgnore,))
435 , ( oData.sName, ))
436 if self._oDb.getRowCount() > 0:
437 raise TMExceptionBase('A build source with name "%s" already exist.' % (oData.sName,));
438 return True;
439
440
441 def _historizeBuildSource(self, idBuildSrc, tsExpire = None):
442 """ Historizes the current build source entry. """
443 if tsExpire is None:
444 self._oDb.execute('UPDATE BuildSources\n'
445 'SET tsExpire = CURRENT_TIMESTAMP\n'
446 'WHERE idBuildSrc = %s\n'
447 ' AND tsExpire = \'infinity\'::TIMESTAMP\n'
448 , ( idBuildSrc, ));
449 else:
450 self._oDb.execute('UPDATE BuildSources\n'
451 'SET tsExpire = %s\n'
452 'WHERE idBuildSrc = %s\n'
453 ' AND tsExpire = \'infinity\'::TIMESTAMP\n'
454 , ( tsExpire, idBuildSrc, ));
455 return True;
456
457
458
459
460
461#
462# Unit testing.
463#
464
465# pylint: disable=C0111
466class BuildSourceDataTestCase(ModelDataBaseTestCase):
467 def setUp(self):
468 self.aoSamples = [BuildSourceData(),];
469
470if __name__ == '__main__':
471 unittest.main();
472 # not reached.
473
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