build.py revision cf22150eaeeb72431bf1cf65c309a431454fb22b
# -*- coding: utf-8 -*-
# $Id$
"""
Test Manager - Builds.
"""
__copyright__ = \
"""
Copyright (C) 2012-2014 Oracle Corporation
This file is part of VirtualBox Open Source Edition (OSE), as
available from http://www.virtualbox.org. This file is free software;
General Public License (GPL) as published by the Free Software
Foundation, in version 2 as it comes in the "COPYING" file of the
VirtualBox OSE distribution. VirtualBox OSE is distributed in the
hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
The contents of this file may alternatively be used under the terms
of the Common Development and Distribution License Version 1.0
(CDDL) only, as it comes in the "COPYING.CDDL" file of the
VirtualBox OSE distribution, in which case the provisions of the
CDDL are applicable instead of those of the GPL.
You may elect to license modified versions of this file under the
terms and conditions of either the GPL or the CDDL or both.
"""
__version__ = "$Revision$"
# Standard python imports.
import os;
import unittest;
# Validation Kit imports.
from testmanager import config;
from testmanager.core.base import ModelDataBase, ModelDataBaseTestCase, ModelLogicBase, TMExceptionBase;
class BuildCategoryData(ModelDataBase):
"""
A build category.
"""
ksIdAttr = 'idBuildCategory';
ksParam_idBuildCategory = 'BuildCategory_idBuildCategory';
ksParam_sProduct = 'BuildCategory_sProduct';
ksParam_sRepository = 'BuildCategory_sRepository';
ksParam_sBranch = 'BuildCategory_sBranch';
ksParam_sType = 'BuildCategory_sType';
ksParam_asOsArches = 'BuildCategory_asOsArches';
kasAllowNullAttributes = ['idBuildCategory', ];
#
# Initialize with defaults.
# See the database for explanations of each of these fields.
#
self.idBuildCategory = None;
self.sRepository = None;
self.asOsArches = None;
"""
Re-initializes the object from a SELECT * FROM BuildCategories row.
Returns self. Raises exception if aoRow is None.
"""
if aoRow is None:
raise TMExceptionBase('BuildCategory not found.');
return self;
"""
Initialize from the database, given the ID of a row.
"""
if aoRow is None:
def initFromValues(self, sProduct, sRepository, sBranch, sType, asOsArches, idBuildCategory = None):
"""
Reinitializes form a set of values.
return self.
"""
return self;
# Handle sType and asOsArches specially.
if sAttr == 'sType':
sError = 'Invalid build type value';
elif sAttr == 'asOsArches':
(oNewValue, sError) = self.validateListOfStr(oValue, aoNilValues = aoNilValues, fAllowNull = fAllowNull,
else:
return ModelDataBase._validateAndConvertAttribute(self, sAttr, sParam, oValue, aoNilValues, fAllowNull, oDb);
""" Checks if the build matches the given OS and architecture. """
return True;
return True;
return True;
return True;
return False;
"""
Build categories database logic.
"""
"""
Fetches testboxes for listing.
Returns an array (list) of UserAccountData items, empty list if none.
Raises exception on error.
"""
_ = tsNow;
'FROM BuildCategories\n'
'ORDER BY sProduct, sRepository, sBranch, sType, idBuildCategory\n'
'LIMIT %s OFFSET %s\n'
aoRows = [];
return aoRows;
def fetchForCombo(self):
"""
Gets the list of Build Categories for a combo box.
Returns an array of (value [idBuildCategory], drop-down-name [info],
hover-text [info]) tuples.
"""
'FROM BuildCategories\n'
'ORDER BY sProduct, sBranch, sType, asOsArches')
aoRet = []
sInfo = '%s / %s / %s / %s' % \
# Make short info string if necessary
return aoRet
"""
Standard method for adding a build category.
"""
# Lazy bird warning! Reuse the soft addBuildCategory method.
_ = uidAuthor;
return True;
"""
Tries to delete the build category.
Note! Does not implement cascading. This is intentional!
"""
#
# Check that the build category isn't used by anyone.
#
'FROM Builds\n'
'WHERE idBuildCategory = %s\n'
, (idBuildCategory,));
if cBuilds > 0:
raise TMExceptionBase('Build category #%d is used by %d builds and can therefore not be deleted.'
% (idBuildCategory, cBuilds,));
#
# Ok, it's not used, so just delete it.
# (No history on this table. This code is for typos.)
#
'WHERE idBuildCategory = %s\n'
, (idBuildCategory,));
return True;
#
# Other methods.
#
"""
Try fetch the build category with the given ID.
Returns BuildCategoryData instance if found, None if not found.
May raise exception on database error.
"""
'FROM BuildCategories\n'
'WHERE idBuildCategory = %s\n'
, (idBuildCategory,))
return None;
"""
Tries to find the matching build category from the sProduct, sBranch,
sType and asOsArches members of oData.
Returns a valid build category ID and an updated oData object if found.
Returns None and unmodified oData object if not found.
May raise exception on database error.
"""
'FROM BuildCategories\n'
'WHERE sProduct = %s\n'
' AND sRepository = %s\n'
' AND sBranch = %s\n'
' AND sType = %s\n'
' AND asOsArches = %s\n'
));
return None;
return oData.idBuildCategory;
"""
Add Build Category record into the database if needed, returning updated oData.
Raises exception on input and database errors.
"""
# Check BuildCategoryData before do anything
# Does it already exist?
# No, We'll have to add it.
self._oDb.execute('INSERT INTO BuildCategories (sProduct, sRepository, sBranch, sType, asOsArches)\n'
'VALUES (%s, %s, %s, %s, %s)\n'
'RETURNING idBuildCategory'
));
return oData;
class BuildData(ModelDataBase):
"""
A build.
"""
ksIdAttr = 'idBuild';
ksParam_idBuild = 'Build_idBuild';
ksParam_tsCreated = 'Build_tsCreated';
ksParam_tsEffective = 'Build_tsEffective';
ksParam_tsExpire = 'Build_tsExpire';
ksParam_uidAuthor = 'Build_uidAuthor';
ksParam_idBuildCategory = 'Build_idBuildCategory';
ksParam_iRevision = 'Build_iRevision';
ksParam_sVersion = 'Build_sVersion';
ksParam_sLogUrl = 'Build_sLogUrl';
ksParam_sBinaries = 'Build_sBinaries';
ksParam_fBinariesDeleted = 'Build_fBinariesDeleted';
kasAllowNullAttributes = ['idBuild', 'tsCreated', 'tsEffective', 'tsExpire', 'uidAuthor', 'tsCreated', 'sLogUrl'];
#
# Initialize with defaults.
# See the database for explanations of each of these fields.
#
self.tsEffective = None;
self.idBuildCategory = None;
"""
Re-initializes the object from a SELECT * FROM Builds row.
Returns self. Raises exception if aoRow is None.
"""
if aoRow is None:
raise TMExceptionBase('Build not found.');
return self;
"""
Initialize from the database, given the ID of a row.
"""
'SELECT *\n'
'FROM Builds\n'
'WHERE idBuild = %s\n'
if aoRow is None:
raise TMExceptionBase('idBuild=%s not found (tsNow=%s sPeriodBack=%s)' % (idBuild, tsNow, sPeriodBack,));
def areFilesStillThere(self):
"""
Try check if the build files are still there.
Returns True if they are, None if we cannot tell, and False if one or
more are missing.
"""
if self.fBinariesDeleted:
return False;
continue;
# Same URL tests as in webutils.downloadFile().
# URL - don't bother trying to verify that (we don't use it atm).
fRc = None;
else:
# File.
if config.g_ksBuildBinRootDir is not None:
if not fRc \
fRc = None; # Root file missing, so the share might not be mounted correctly.
else:
fRc = None;
return fRc;
return True;
class BuildDataEx(BuildData):
"""
Complete data set.
"""
"""
Reinitialize from a SELECT Builds.*, BuildCategories.* FROM Builds, BuildCategories query.
Returns self. Raises exception if aoRow is None.
"""
if aoRow is None:
raise TMExceptionBase('Build not found.');
return self;
"""
Reinitialize from database given a row ID.
Returns self. Raises exception on database error or if the ID is invalid.
"""
'SELECT Builds.*, BuildCategories.*\n'
'FROM Builds, BuildCategories\n'
'WHERE idBuild = %s\n'
' AND Builds.idBuildCategory = BuildCategories.idBuildCategory\n'
if aoRow is None:
raise TMExceptionBase('idBuild=%s not found (tsNow=%s sPeriodBack=%s)' % (idBuild, tsNow, sPeriodBack,));
def convertFromParamNull(self):
raise TMExceptionBase('Not implemented');
raise TMExceptionBase('Not implemented');
"""
Build database logic (covers build categories as well as builds).
"""
#
# Standard methods.
#
"""
Fetches builds for listing.
Returns an array (list) of BuildDataEx items, empty list if none.
Raises exception on error.
"""
if tsNow is None:
'FROM Builds, BuildCategories\n'
'WHERE Builds.idBuildCategory = BuildCategories.idBuildCategory\n'
' AND Builds.tsExpire = \'infinity\'::TIMESTAMP\n'
'ORDER BY tsCreated DESC\n'
'LIMIT %s OFFSET %s\n'
else:
'FROM Builds, BuildCategories\n'
'WHERE Builds.idBuildCategory = BuildCategories.idBuildCategory\n'
' AND Builds.tsExpire > %s\n'
' AND Builds.tsEffective <= %s\n'
'ORDER BY tsCreated DESC\n'
'LIMIT %s OFFSET %s\n'
aoRows = [];
return aoRows;
"""
Adds the build to the database, optionally adding the build category if
a BuildDataEx object used and it's necessary.
Returns updated data object. Raises exception on failure.
"""
and oBuildData.idBuildCategory is None:
# Add the build.
' idBuildCategory,\n'
' iRevision,\n'
' sVersion,\n'
' sLogUrl,\n'
' sBinaries,\n'
' fBinariesDeleted)\n'
'VALUES (%s, %s, %s, %s, %s, %s, %s)\n'
'RETURNING idBuild, tsCreated\n'
, ( uidAuthor,
));
return oBuildData;
"""Modify database record"""
#
# Validate input and get current data.
#
#
# Do the work.
#
' idBuild,\n'
' tsCreated,\n'
' idBuildCategory,\n'
' iRevision,\n'
' sVersion,\n'
' sLogUrl,\n'
' sBinaries,\n'
' fBinariesDeleted)\n'
'VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)\n'
'RETURNING idBuild, tsCreated\n'
, ( uidAuthor,
));
return True;
"""
Historize record
"""
#
# No non-historic refs here, so just go ahead and expire the build.
#
_ = fCascade;
_ = uidAuthor; ## @todo record deleter.
return True;
#
# Other methods.
#
"""
Attempts to find a matching build for the given OS.ARCH. May return
the input build if if matches.
Returns BuildDataEx instance if found, None if none. May raise
exception on database error.
"""
return oBuildEx;
'FROM Builds, BuildCategories\n'
'WHERE BuildCategories.sProduct = %s\n'
' AND BuildCategories.sBranch = %s\n'
' AND BuildCategories.sType = %s\n'
' AND ( %s = ANY(BuildCategories.asOsArches)\n'
' OR %s = ANY(BuildCategories.asOsArches)\n'
' OR %s = ANY(BuildCategories.asOsArches))\n'
' AND Builds.idBuildCategory = BuildCategories.idBuildCategory\n'
' AND Builds.tsExpire = \'infinity\'::TIMESTAMP\n'
' AND Builds.iRevision = %s\n'
' AND Builds.sRelease = %s\n'
' AND Builds.fBinariesDeleted IS FALSE\n'
'ORDER BY tsCreated DESC\n'
'LIMIT 4096\n' # stay sane.
'%s.noarch' % (sOs,),
'os-agnostic.%s' % (sCpuArch,),
'os-agnostic.noarch',
) );
return oBuildExRet;
return None;
"""
Checks if the given build is blacklisted
"""
asOsAgnosticArch = [];
asOsNoArch = [];
'FROM BuildBlacklist\n'
'WHERE BuildBlacklist.tsExpire > CURRENT_TIMESTAMP\n'
' AND BuildBlacklist.tsEffective <= CURRENT_TIMESTAMP\n'
' AND BuildBlacklist.sProduct = %s\n'
' AND BuildBlacklist.sBranch = %s\n'
' AND ( BuildBlacklist.asTypes is NULL\n'
' OR %s = ANY(BuildBlacklist.asTypes))\n'
' AND ( BuildBlacklist.asOsArches is NULL\n'
' OR %s && BuildBlacklist.asOsArches\n' ## @todo check array rep! Need overload?
' OR %s && BuildBlacklist.asOsArches\n'
' OR %s && BuildBlacklist.asOsArches\n'
' OR %s = ANY(BuildBlacklist.asOsArches))\n'
' AND BuildBlacklist.iFirstRevision <= %s\n'
' AND BuildBlacklist.iLastRevision >= %s\n'
'os-agnostic.noarch',
) );
"""
Get build record by its id
"""
'FROM Builds, BuildCategories\n'
'WHERE Builds.idBuild=%s\n'
' AND Builds.idBuildCategory=BuildCategories.idBuildCategory\n'
' AND Builds.tsExpire = \'infinity\'::TIMESTAMP\n', (idBuild,))
raise TMExceptionBase('Found more than one build with the same credentials. Database structure is corrupted.')
try:
except IndexError:
return None
"""
Gets the list of all builds.
Returns an array of BuildDataEx instances.
"""
if tsEffective is None:
'FROM Builds, BuildCategories\n'
'WHERE Builds.tsExpire = \'infinity\'::TIMESTAMP\n'
' AND Builds.idBuildCategory=BuildCategories.idBuildCategory')
else:
'FROM Builds, BuildCategories\n'
'WHERE Builds.tsExpire > %s\n'
' AND Builds.tsEffective <= %s'
' AND Builds.idBuildCategory=BuildCategories.idBuildCategory'
, (tsEffective, tsEffective))
aoRet = []
return aoRet
"""
Marks zero or more builds deleted given the build binaries.
Returns the number of affected builds.
"""
# Fetch a list of affected build IDs (generally 1 build), and used the
# editEntry method to do the rest. This isn't 100% optimal, but it's
# short and simple, the main effort is anyway the first query.
'FROM Builds\n'
'WHERE sBinaries = %s\n'
' AND fBinariesDeleted = FALSE\n'
' AND tsExpire = \'infinity\'::TIMESTAMP\n'
, (sBinaries,));
assert not oData.fBinariesDeleted;
#
# Internal helpers.
#
""" Historizes the current entry for the specified build. """
if tsExpire is None:
'SET tsExpire = CURRENT_TIMESTAMP\n'
'WHERE idBuild = %s\n'
' AND tsExpire = \'infinity\'::TIMESTAMP\n'
, (idBuild,));
else:
'SET tsExpire = %s\n'
'WHERE idBuild = %s\n'
' AND tsExpire = \'infinity\'::TIMESTAMP\n'
return True;
#
# Unit testing.
#
# pylint: disable=C0111
if __name__ == '__main__':
# not reached.