VirtualBox

Ignore:
Timestamp:
May 29, 2020 1:12:32 AM (5 years ago)
Author:
vboxsync
svn:sync-xref-src-repo-rev:
138332
Message:

TestManager: Reworking the changelog tooltip and adding a way to get commits for bugs for the bug trackers. Work in progress

Location:
trunk/src/VBox/ValidationKit/testmanager/core
Files:
3 edited
1 copied

Legend:

Unmodified
Added
Removed
  • trunk/src/VBox/ValidationKit/testmanager/core/base.py

    r84498 r84599  
    3333# Standard python imports.
    3434import copy;
     35import datetime;
     36import json;
    3537import re;
    3638import socket;
     
    10621064             + ModelDataBase.formatSimpleNowAndPeriod(oDb, tsNow, sPeriodBack, sTablePrefix, sExpCol, sEffCol);
    10631065
     1066
     1067    #
     1068    # JSON
     1069    #
     1070
     1071    @staticmethod
     1072    def stringToJson(sString):
     1073        """ Converts a string to a JSON value string. """
     1074        if not utils.isString(sString):
     1075            sString = utils.toUnicode(sString);
     1076            if not utils.isString(sString):
     1077                sString = str(sString);
     1078        return json.dumps(sString);
     1079
     1080    @staticmethod
     1081    def dictToJson(dDict, dOptions = None):
     1082        """ Converts a dictionary to a JSON string. """
     1083        sJson = u'{ ';
     1084        for i, oKey in enumerate(dDict):
     1085            if i > 0:
     1086                sJson += ', ';
     1087            sJson += '%s: %s' % (ModelDataBase.stringToJson(oKey),
     1088                                 ModelDataBase.genericToJson(dDict[oKey], dOptions));
     1089        return sJson + ' }';
     1090
     1091    @staticmethod
     1092    def listToJson(aoList, dOptions = None):
     1093        """ Converts list of something to a JSON string. """
     1094        sJson = u'[ ';
     1095        for i, oValue in enumerate(aoList):
     1096            if i > 0:
     1097                sJson += u', ';
     1098            sJson += ModelDataBase.genericToJson(oValue, dOptions);
     1099        return sJson + u' ]';
     1100
     1101    @staticmethod
     1102    def datetimeToJson(oDateTime, dOptions = None):
     1103        """ Converts a datetime instance to a JSON string. """
     1104        return '"%s"' % (oDateTime,);
     1105
     1106
     1107    @staticmethod
     1108    def genericToJson(oValue, dOptions = None):
     1109        """ Converts a generic object to a JSON string. """
     1110        if isinstance(oValue, ModelDataBase):
     1111            return oValue.toJson();
     1112        if isinstance(oValue, dict):
     1113            return ModelDataBase.dictToJson(oValue, dOptions);
     1114        if isinstance(oValue, (list, tuple, set, frozenset)):
     1115            return ModelDataBase.listToJson(oValue, dOptions);
     1116        if isinstance(oValue, datetime.datetime):
     1117            return ModelDataBase.datetimeToJson(oValue, dOptions)
     1118        return json.dumps(oValue);
     1119
     1120    def attribValueToJson(self, sAttr, oValue, dOptions = None):
     1121        """
     1122        Converts the attribute value to JSON.
     1123        Returns JSON (string).
     1124        """
     1125        _ = sAttr;
     1126        return self.genericToJson(oValue, dOptions);
     1127
     1128    def toJson(self, dOptions = None):
     1129        """
     1130        Converts the object to JSON.
     1131        Returns JSON (string).
     1132        """
     1133        sJson = u'{ ';
     1134        for iAttr, sAttr in enumerate(self.getDataAttributes()):
     1135            oValue = getattr(self, sAttr);
     1136            if iAttr > 0:
     1137                sJson += ', ';
     1138            sJson += u'"%s": ' % (sAttr,);
     1139            sJson += self.attribValueToJson(sAttr, oValue, dOptions);
     1140        return sJson + u' }';
     1141
     1142
    10641143    #
    10651144    # Sub-classes.
  • trunk/src/VBox/ValidationKit/testmanager/core/restdispatcher.py

    r84555 r84599  
    33
    44"""
    5 Test Manager Core - Web Server Abstraction Base Class.
     5Test Manager Core - REST cgi handler.
    66"""
    77
     
    3131
    3232# Standard python imports.
    33 import re;
    3433import os;
    35 import string;                          # pylint: disable=deprecated-module
    3634import sys;
    37 import uuid;
    3835
    3936# Validation Kit imports.
    40 from common                             import constants;
     37#from common                             import constants;
     38from common                             import utils;
    4139from testmanager                        import config;
    42 from testmanager.core                   import coreconsts;
     40#from testmanager.core                   import coreconsts;
    4341from testmanager.core.db                import TMDatabaseConnection;
    44 from testmanager.core.base              import TMExceptionBase;
    45 from testmanager.core.globalresource    import GlobalResourceLogic;
    46 from testmanager.core.testboxstatus     import TestBoxStatusData, TestBoxStatusLogic;
    47 from testmanager.core.testbox           import TestBoxData, TestBoxLogic;
    48 from testmanager.core.testresults       import TestResultLogic, TestResultFileData;
    49 from testmanager.core.testset           import TestSetData, TestSetLogic;
    50 from testmanager.core.systemlog         import SystemLogData, SystemLogLogic;
    51 from testmanager.core.schedulerbase     import SchedulerBase;
     42from testmanager.core.base              import TMExceptionBase, ModelDataBase;
    5243
    5344# Python 3 hacks:
     
    5647
    5748
    58 class TestBoxControllerException(TMExceptionBase):
     49#
     50# Exceptions
     51#
     52
     53class RestDispException(TMExceptionBase):
    5954    """
    60     Exception class for TestBoxController.
     55    Exception class for the REST dispatcher.
    6156    """
     57    def __init__(self, sMsg, iStatus):
     58        TMExceptionBase.__init__(self, sMsg);
     59        self.iStatus = iStatus;
     60
     61# 400
     62class RestDispException400(RestDispException):
     63    """ A 400 error """
     64    def __init__(self, sMsg):
     65        RestDispException.__init__(self, sMsg, 400);
     66
     67class RestUnknownParameters(RestDispException400):
     68    """ Unknown parameter(s). """
    6269    pass;                               # pylint: disable=unnecessary-pass
    6370
    64 
    65 class TestBoxController(object): # pylint: disable=too-few-public-methods
     71# 404
     72class RestDispException404(RestDispException):
     73    """ A 404 error """
     74    def __init__(self, sMsg):
     75        RestDispException.__init__(self, sMsg, 404);
     76
     77class RestBadPathException(RestDispException404):
     78    """ We've got a bad path. """
     79    pass;                               # pylint: disable=unnecessary-pass
     80
     81class RestBadParameter(RestDispException404):
     82    """ Bad parameter. """
     83    pass;                               # pylint: disable=unnecessary-pass
     84
     85class RestMissingParameter(RestDispException404):
     86    """ Missing parameter. """
     87    pass;                               # pylint: disable=unnecessary-pass
     88
     89
     90
     91class RestMain(object): # pylint: disable=too-few-public-methods
    6692    """
    67     TestBox Controller class.
     93    REST main dispatcher class.
    6894    """
    6995
    70     ## Applicable testbox commands to an idle TestBox.
    71     kasIdleCmds = [TestBoxData.ksTestBoxCmd_Reboot,
    72                    TestBoxData.ksTestBoxCmd_Upgrade,
    73                    TestBoxData.ksTestBoxCmd_UpgradeAndReboot,
    74                    TestBoxData.ksTestBoxCmd_Special];
    75     ## Applicable testbox commands to a busy TestBox.
    76     kasBusyCmds = [TestBoxData.ksTestBoxCmd_Abort, TestBoxData.ksTestBoxCmd_Reboot];
    77     ## Commands that can be ACK'ed.
    78     kasAckableCmds  = [constants.tbresp.CMD_EXEC, constants.tbresp.CMD_ABORT, constants.tbresp.CMD_REBOOT,
    79                        constants.tbresp.CMD_UPGRADE, constants.tbresp.CMD_UPGRADE_AND_REBOOT, constants.tbresp.CMD_SPECIAL];
    80     ## Commands that can be NACK'ed or NOTSUP'ed.
    81     kasNackableCmds = kasAckableCmds + [kasAckableCmds, constants.tbresp.CMD_IDLE, constants.tbresp.CMD_WAIT];
    82 
    83     ## Mapping from TestBoxCmd_T to TestBoxState_T
    84     kdCmdToState = \
    85     { \
    86         TestBoxData.ksTestBoxCmd_Abort:             None,
    87         TestBoxData.ksTestBoxCmd_Reboot:            TestBoxStatusData.ksTestBoxState_Rebooting,
    88         TestBoxData.ksTestBoxCmd_Upgrade:           TestBoxStatusData.ksTestBoxState_Upgrading,
    89         TestBoxData.ksTestBoxCmd_UpgradeAndReboot:  TestBoxStatusData.ksTestBoxState_UpgradingAndRebooting,
    90         TestBoxData.ksTestBoxCmd_Special:           TestBoxStatusData.ksTestBoxState_DoingSpecialCmd,
    91     };
    92 
    93     ## Mapping from TestBoxCmd_T to TestBox responses commands.
    94     kdCmdToTbRespCmd = \
    95     {
    96         TestBoxData.ksTestBoxCmd_Abort:             constants.tbresp.CMD_ABORT,
    97         TestBoxData.ksTestBoxCmd_Reboot:            constants.tbresp.CMD_REBOOT,
    98         TestBoxData.ksTestBoxCmd_Upgrade:           constants.tbresp.CMD_UPGRADE,
    99         TestBoxData.ksTestBoxCmd_UpgradeAndReboot:  constants.tbresp.CMD_UPGRADE_AND_REBOOT,
    100         TestBoxData.ksTestBoxCmd_Special:           constants.tbresp.CMD_SPECIAL,
    101     };
    102 
    103     ## Mapping from TestBox responses to TestBoxCmd_T commands.
    104     kdTbRespCmdToCmd = \
    105     {
    106         constants.tbresp.CMD_IDLE:               None,
    107         constants.tbresp.CMD_WAIT:               None,
    108         constants.tbresp.CMD_EXEC:               None,
    109         constants.tbresp.CMD_ABORT:              TestBoxData.ksTestBoxCmd_Abort,
    110         constants.tbresp.CMD_REBOOT:             TestBoxData.ksTestBoxCmd_Reboot,
    111         constants.tbresp.CMD_UPGRADE:            TestBoxData.ksTestBoxCmd_Upgrade,
    112         constants.tbresp.CMD_UPGRADE_AND_REBOOT: TestBoxData.ksTestBoxCmd_UpgradeAndReboot,
    113         constants.tbresp.CMD_SPECIAL:            TestBoxData.ksTestBoxCmd_Special,
    114     };
    115 
    116 
    117     ## The path to the upgrade zip, relative WebServerGlueBase.getBaseUrl().
    118     ksUpgradeZip = 'htdocs/upgrade/VBoxTestBoxScript.zip';
    119 
    120     ## Valid TestBox result values.
    121     kasValidResults = list(constants.result.g_kasValidResults);
    122     ## Mapping TestBox result values to TestStatus_T values.
    123     kadTbResultToStatus = \
    124     {
    125         constants.result.PASSED:        TestSetData.ksTestStatus_Success,
    126         constants.result.SKIPPED:       TestSetData.ksTestStatus_Skipped,
    127         constants.result.ABORTED:       TestSetData.ksTestStatus_Aborted,
    128         constants.result.BAD_TESTBOX:   TestSetData.ksTestStatus_BadTestBox,
    129         constants.result.FAILED:        TestSetData.ksTestStatus_Failure,
    130         constants.result.TIMED_OUT:     TestSetData.ksTestStatus_TimedOut,
    131         constants.result.REBOOTED:      TestSetData.ksTestStatus_Rebooted,
    132     };
     96    ksParam_sPath = 'sPath';
    13397
    13498
    13599    def __init__(self, oSrvGlue):
    136         """
    137         Won't raise exceptions.
    138         """
    139100        self._oSrvGlue          = oSrvGlue;
    140         self._sAction           = None; # _getStandardParams / dispatchRequest sets this later on.
    141         self._idTestBox         = None; # _getStandardParams / dispatchRequest sets this later on.
    142         self._sTestBoxUuid      = None; # _getStandardParams / dispatchRequest sets this later on.
    143         self._sTestBoxAddr      = None; # _getStandardParams / dispatchRequest sets this later on.
    144         self._idTestSet         = None; # _getStandardParams / dispatchRequest sets this later on.
     101        self._oDb               = TMDatabaseConnection(oSrvGlue.dprint);
     102        self._iFirstHandlerPath = 0;
     103        self._iNextHandlerPath  = 0;
     104        self._sPath             = None; # _getStandardParams / dispatchRequest sets this later on.
     105        self._asPath            = None; # _getStandardParams / dispatchRequest sets this later on.
     106        self._sMethod           = None; # _getStandardParams / dispatchRequest sets this later on.
    145107        self._dParams           = None; # _getStandardParams / dispatchRequest sets this later on.
    146108        self._asCheckedParams   = [];
    147         self._dActions          = \
    148         { \
    149             constants.tbreq.SIGNON              : self._actionSignOn,
    150             constants.tbreq.REQUEST_COMMAND_BUSY: self._actionRequestCommandBusy,
    151             constants.tbreq.REQUEST_COMMAND_IDLE: self._actionRequestCommandIdle,
    152             constants.tbreq.COMMAND_ACK         : self._actionCommandAck,
    153             constants.tbreq.COMMAND_NACK        : self._actionCommandNack,
    154             constants.tbreq.COMMAND_NOTSUP      : self._actionCommandNotSup,
    155             constants.tbreq.LOG_MAIN            : self._actionLogMain,
    156             constants.tbreq.UPLOAD              : self._actionUpload,
    157             constants.tbreq.XML_RESULTS         : self._actionXmlResults,
    158             constants.tbreq.EXEC_COMPLETED      : self._actionExecCompleted,
     109        self._dGetTree          = {
     110            'vcs': {
     111                'changelog': self._handleVcsChangelog_Get,
     112                'bugreferences': self._handleVcsBugReferences_Get,
     113            },
    159114        };
     115        self._dMethodTrees      = {
     116            'GET': self._dGetTree,
     117        }
     118
     119    #
     120    # Helpers.
     121    #
    160122
    161123    def _getStringParam(self, sName, asValidValues = None, fStrip = False, sDefValue = None):
     
    168130        if sName not in self._dParams:
    169131            if sDefValue is None:
    170                 raise TestBoxControllerException('%s parameter %s is missing' % (self._sAction, sName));
     132                raise RestMissingParameter('%s parameter %s is missing' % (self._sPath, sName));
    171133            return sDefValue;
    172134        sValue = self._dParams[sName];
     135        if isinstance(sValue, list):
     136            if len(sValue) == 1:
     137                sValue = sValue[0];
     138            else:
     139                raise RestBadParameter('%s parameter %s value is not a string but list: %s'
     140                                       % (self._sPath, sName, sValue));
    173141        if fStrip:
    174142            sValue = sValue.strip();
     
    178146
    179147        if asValidValues is not None and sValue not in asValidValues:
    180             raise TestBoxControllerException('%s parameter %s value "%s" not in %s ' \
    181                                              % (self._sAction, sName, sValue, asValidValues));
     148            raise RestBadParameter('%s parameter %s value "%s" not in %s '
     149                                   % (self._sPath, sName, sValue, asValidValues));
    182150        return sValue;
    183151
     
    202170            iValue = int(sValue, 0);
    203171        except:
    204             raise TestBoxControllerException('%s parameter %s value "%s" cannot be convert to an integer' \
    205                                              % (self._sAction, sName, sValue));
     172            raise RestBadParameter('%s parameter %s value "%s" cannot be convert to an integer'
     173                                   % (self._sPath, sName, sValue));
    206174
    207175        if   (iMin is not None and iValue < iMin) \
    208176          or (iMax is not None and iValue > iMax):
    209             raise TestBoxControllerException('%s parameter %s value %d is out of range [%s..%s]' \
    210                                              % (self._sAction, sName, iValue, iMin, iMax));
     177            raise RestBadParameter('%s parameter %s value %d is out of range [%s..%s]'
     178                                   % (self._sPath, sName, iValue, iMin, iMax));
    211179        return iValue;
    212180
     
    221189            lValue = long(sValue, 0);
    222190        except Exception as oXcpt:
    223             raise TestBoxControllerException('%s parameter %s value "%s" cannot be convert to an integer (%s)' \
    224                                              % (self._sAction, sName, sValue, oXcpt));
     191            raise RestBadParameter('%s parameter %s value "%s" cannot be convert to an integer (%s)'
     192                                   % (self._sPath, sName, sValue, oXcpt));
    225193
    226194        if   (lMin is not None and lValue < lMin) \
    227195          or (lMax is not None and lValue > lMax):
    228             raise TestBoxControllerException('%s parameter %s value %d is out of range [%s..%s]' \
    229                                              % (self._sAction, sName, lValue, lMin, lMax));
     196            raise RestBadParameter('%s parameter %s value %d is out of range [%s..%s]'
     197                                   % (self._sPath, sName, lValue, lMin, lMax));
    230198        return lValue;
    231199
     
    241209                if sKey not in self._asCheckedParams:
    242210                    sUnknownParams += ' ' + sKey + '=' + self._dParams[sKey];
    243             raise TestBoxControllerException('Unknown parameters: ' + sUnknownParams);
     211            raise RestUnknownParameters('Unknown parameters: ' + sUnknownParams);
    244212
    245213        return True;
    246 
    247     def _writeResponse(self, dParams):
    248         """
    249         Makes a reply to the testbox script.
    250         Will raise exception on failure.
    251         """
    252         self._oSrvGlue.writeParams(dParams);
    253         self._oSrvGlue.flush();
    254         return True;
    255 
    256     def _resultResponse(self, sResultValue):
    257         """
    258         Makes a simple reply to the testbox script.
    259         Will raise exception on failure.
    260         """
    261         return self._writeResponse({constants.tbresp.ALL_PARAM_RESULT: sResultValue});
    262 
    263 
    264     def _idleResponse(self):
    265         """
    266         Makes an IDLE reply to the testbox script.
    267         Will raise exception on failure.
    268         """
    269         return self._writeResponse({ constants.tbresp.ALL_PARAM_RESULT: constants.tbresp.CMD_IDLE });
    270 
    271     def _cleanupOldTest(self, oDb, oStatusData):
    272         """
    273         Cleans up any old test set that may be left behind and changes the
    274         state to 'idle'.  See scenario #9:
    275         file://../../docs/AutomaticTestingRevamp.html#cleaning-up-abandoned-testcase
    276 
    277         Note. oStatusData.enmState is set to idle, but tsUpdated is not changed.
    278         """
    279 
    280         # Cleanup any abandoned test.
    281         if oStatusData.idTestSet is not None:
    282             SystemLogLogic(oDb).addEntry(SystemLogData.ksEvent_TestSetAbandoned,
    283                                          "idTestSet=%u idTestBox=%u enmState=%s %s"
    284                                          % (oStatusData.idTestSet, oStatusData.idTestBox,
    285                                             oStatusData.enmState, self._sAction),
    286                                          fCommit = False);
    287             TestSetLogic(oDb).completeAsAbandoned(oStatusData.idTestSet, fCommit = False);
    288             GlobalResourceLogic(oDb).freeGlobalResourcesByTestBox(self._idTestBox, fCommit = False);
    289 
    290         # Change to idle status
    291         if oStatusData.enmState != TestBoxStatusData.ksTestBoxState_Idle:
    292             TestBoxStatusLogic(oDb).updateState(self._idTestBox, TestBoxStatusData.ksTestBoxState_Idle, fCommit = False);
    293             oStatusData.tsUpdated = oDb.getCurrentTimestamp();
    294             oStatusData.enmState  = TestBoxStatusData.ksTestBoxState_Idle;
    295 
    296         # Commit.
    297         oDb.commit();
    298 
    299         return True;
    300 
    301     def _connectToDbAndValidateTb(self, asValidStates = None):
    302         """
    303         Connects to the database and validates the testbox.
    304 
    305         Returns (TMDatabaseConnection, TestBoxStatusData, TestBoxData) on success.
    306         Returns (None, None, None) on failure after sending the box an appropriate response.
    307         May raise exception on DB error.
    308         """
    309         oDb    = TMDatabaseConnection(self._oSrvGlue.dprint);
    310         oLogic = TestBoxStatusLogic(oDb);
    311         (oStatusData, oTestBoxData) = oLogic.tryFetchStatusAndConfig(self._idTestBox, self._sTestBoxUuid, self._sTestBoxAddr);
    312         if oStatusData is None:
    313             self._resultResponse(constants.tbresp.STATUS_DEAD);
    314         elif asValidStates is not None and oStatusData.enmState not in asValidStates:
    315             self._resultResponse(constants.tbresp.STATUS_NACK);
    316         elif self._idTestSet is not None and self._idTestSet != oStatusData.idTestSet:
    317             self._resultResponse(constants.tbresp.STATUS_NACK);
    318         else:
    319             return (oDb, oStatusData, oTestBoxData);
    320         return (None, None, None);
    321214
    322215    def writeToMainLog(self, oTestSet, sText, fIgnoreSizeCheck = False):
     
    346239        return fSizeOk;
    347240
    348     def _actionSignOn(self):        # pylint: disable=too-many-locals
    349         """ Implement sign-on """
    350 
    351         #
    352         # Validate parameters (raises exception on failure).
    353         #
    354         sOs                 = self._getStringParam(constants.tbreq.SIGNON_PARAM_OS, coreconsts.g_kasOses);
    355         sOsVersion          = self._getStringParam(constants.tbreq.SIGNON_PARAM_OS_VERSION);
    356         sCpuVendor          = self._getStringParam(constants.tbreq.SIGNON_PARAM_CPU_VENDOR);
    357         sCpuArch            = self._getStringParam(constants.tbreq.SIGNON_PARAM_CPU_ARCH, coreconsts.g_kasCpuArches);
    358         sCpuName            = self._getStringParam(constants.tbreq.SIGNON_PARAM_CPU_NAME, fStrip = True, sDefValue = ''); # new
    359         lCpuRevision        = self._getLongParam(  constants.tbreq.SIGNON_PARAM_CPU_REVISION, lMin = 0, lDefValue = 0);   # new
    360         cCpus               = self._getIntParam(   constants.tbreq.SIGNON_PARAM_CPU_COUNT, 1, 16384);
    361         fCpuHwVirt          = self._getBoolParam(  constants.tbreq.SIGNON_PARAM_HAS_HW_VIRT);
    362         fCpuNestedPaging    = self._getBoolParam(  constants.tbreq.SIGNON_PARAM_HAS_NESTED_PAGING);
    363         fCpu64BitGuest      = self._getBoolParam(  constants.tbreq.SIGNON_PARAM_HAS_64_BIT_GUEST, fDefValue = True);
    364         fChipsetIoMmu       = self._getBoolParam(  constants.tbreq.SIGNON_PARAM_HAS_IOMMU);
    365         fRawMode            = self._getBoolParam(  constants.tbreq.SIGNON_PARAM_WITH_RAW_MODE, fDefValue = None);
    366         cMbMemory           = self._getLongParam(  constants.tbreq.SIGNON_PARAM_MEM_SIZE,     8, 1073741823); # 8MB..1PB
    367         cMbScratch          = self._getLongParam(  constants.tbreq.SIGNON_PARAM_SCRATCH_SIZE, 0, 1073741823); # 0..1PB
    368         sReport             = self._getStringParam(constants.tbreq.SIGNON_PARAM_REPORT, fStrip = True, sDefValue = '');   # new
    369         iTestBoxScriptRev   = self._getIntParam(   constants.tbreq.SIGNON_PARAM_SCRIPT_REV, 1, 100000000);
    370         iPythonHexVersion   = self._getIntParam(   constants.tbreq.SIGNON_PARAM_PYTHON_VERSION, 0x020300f0, 0x030f00f0);
     241    def _getNextPathElementString(self, sName, oDefault = None):
     242        """
     243        Gets the next handler specific path element.
     244        Returns unprocessed string.
     245        Throws exception
     246        """
     247        i = self._iNextHandlerPath;
     248        if i < len(self._asPath):
     249            self._iNextHandlerPath = i + 1;
     250            return self._asPath[i];
     251        if oDefault is None:
     252            raise RestBadPathException('Requires a "%s" element after "%s"' % (sName, self._sPath,));
     253        return oDefault;
     254
     255    def _getNextPathElementInt(self, sName, iDefault = None, iMin = None, iMax = None):
     256        """
     257        Gets the next handle specific path element as an integer.
     258        Returns integer value.
     259        Throws exception if not found or not a valid integer.
     260        """
     261        sValue = self._getNextPathElementString(sName, oDefault = iDefault);
     262        try:
     263            iValue = int(sValue);
     264        except:
     265            raise RestBadPathException('Not an integer "%s" (%s)' % (sValue, sName,));
     266        if iMin is not None and iValue < iMin:
     267            raise RestBadPathException('Integer "%s" value (%s) is too small, min %s' % (sValue, sName, iMin));
     268        if iMax is not None and iValue > iMax:
     269            raise RestBadPathException('Integer "%s" value (%s) is too large, max %s' % (sValue, sName, iMax));
     270        return iValue;
     271
     272    def _getNextPathElementLong(self, sName, iDefault = None, iMin = None, iMax = None):
     273        """
     274        Gets the next handle specific path element as a long integer.
     275        Returns integer value.
     276        Throws exception if not found or not a valid integer.
     277        """
     278        sValue = self._getNextPathElementString(sName, oDefault = iDefault);
     279        try:
     280            iValue = long(sValue);
     281        except:
     282            raise RestBadPathException('Not an integer "%s" (%s)' % (sValue, sName,));
     283        if iMin is not None and iValue < iMin:
     284            raise RestBadPathException('Integer "%s" value (%s) is too small, min %s' % (sValue, sName, iMin));
     285        if iMax is not None and iValue > iMax:
     286            raise RestBadPathException('Integer "%s" value (%s) is too large, max %s' % (sValue, sName, iMax));
     287        return iValue;
     288
     289    def _checkNoMorePathElements(self):
     290        """
     291        Checks that there are no more path elements.
     292        Throws exception if there are.
     293        """
     294        i = self._iNextHandlerPath;
     295        if i < len(self._asPath):
     296            raise RestBadPathException('Unknown subpath "%s" below "%s"' %
     297                                       ('/'.join(self._asPath[i:]), '/'.join(self._asPath[:i]),));
     298        return True;
     299
     300    def _doneParsingArguments(self):
     301        """
     302        Checks that there are no more path elements or unhandled parameters.
     303        Throws exception if there are.
     304        """
     305        self._checkNoMorePathElements();
    371306        self._checkForUnknownParameters();
    372 
    373         # Null conversions for new parameters.
    374         if not sReport:
    375             sReport = None;
    376         if not sCpuName:
    377             sCpuName = None;
    378         if lCpuRevision <= 0:
    379             lCpuRevision = None;
    380 
    381         #
    382         # Connect to the database and validate the testbox.
    383         #
    384         oDb = TMDatabaseConnection(self._oSrvGlue.dprint);
    385         oTestBoxLogic = TestBoxLogic(oDb);
    386         oTestBox      = oTestBoxLogic.tryFetchTestBoxByUuid(self._sTestBoxUuid);
    387         if oTestBox is None:
    388             oSystemLogLogic = SystemLogLogic(oDb);
    389             oSystemLogLogic.addEntry(SystemLogData.ksEvent_TestBoxUnknown,
    390                                      'addr=%s  uuid=%s  os=%s  %d cpus' \
    391                                      % (self._sTestBoxAddr, self._sTestBoxUuid, sOs, cCpus),
    392                                      24, fCommit = True);
    393             return self._resultResponse(constants.tbresp.STATUS_NACK);
    394 
    395         #
    396         # Update the row in TestBoxes if something changed.
    397         #
    398         if oTestBox.cMbScratch is not None and oTestBox.cMbScratch != 0:
    399             cPctScratchDiff = (cMbScratch - oTestBox.cMbScratch) * 100 / oTestBox.cMbScratch;
    400         else:
    401             cPctScratchDiff = 100;
    402 
    403         # pylint: disable=too-many-boolean-expressions
    404         if   self._sTestBoxAddr != oTestBox.ip \
    405           or sOs                != oTestBox.sOs \
    406           or sOsVersion         != oTestBox.sOsVersion \
    407           or sCpuVendor         != oTestBox.sCpuVendor \
    408           or sCpuArch           != oTestBox.sCpuArch \
    409           or sCpuName           != oTestBox.sCpuName \
    410           or lCpuRevision       != oTestBox.lCpuRevision \
    411           or cCpus              != oTestBox.cCpus \
    412           or fCpuHwVirt         != oTestBox.fCpuHwVirt \
    413           or fCpuNestedPaging   != oTestBox.fCpuNestedPaging \
    414           or fCpu64BitGuest     != oTestBox.fCpu64BitGuest \
    415           or fChipsetIoMmu      != oTestBox.fChipsetIoMmu \
    416           or fRawMode           != oTestBox.fRawMode \
    417           or cMbMemory          != oTestBox.cMbMemory \
    418           or abs(cPctScratchDiff) >= min(4 + cMbScratch / 10240, 12) \
    419           or sReport            != oTestBox.sReport \
    420           or iTestBoxScriptRev  != oTestBox.iTestBoxScriptRev \
    421           or iPythonHexVersion  != oTestBox.iPythonHexVersion:
    422             oTestBoxLogic.updateOnSignOn(oTestBox.idTestBox,
    423                                          oTestBox.idGenTestBox,
    424                                          sTestBoxAddr      = self._sTestBoxAddr,
    425                                          sOs               = sOs,
    426                                          sOsVersion        = sOsVersion,
    427                                          sCpuVendor        = sCpuVendor,
    428                                          sCpuArch          = sCpuArch,
    429                                          sCpuName          = sCpuName,
    430                                          lCpuRevision      = lCpuRevision,
    431                                          cCpus             = cCpus,
    432                                          fCpuHwVirt        = fCpuHwVirt,
    433                                          fCpuNestedPaging  = fCpuNestedPaging,
    434                                          fCpu64BitGuest    = fCpu64BitGuest,
    435                                          fChipsetIoMmu     = fChipsetIoMmu,
    436                                          fRawMode          = fRawMode,
    437                                          cMbMemory         = cMbMemory,
    438                                          cMbScratch        = cMbScratch,
    439                                          sReport           = sReport,
    440                                          iTestBoxScriptRev = iTestBoxScriptRev,
    441                                          iPythonHexVersion = iPythonHexVersion);
    442 
    443         #
    444         # Update the testbox status, making sure there is a status.
    445         #
    446         oStatusLogic = TestBoxStatusLogic(oDb);
    447         oStatusData  = oStatusLogic.tryFetchStatus(oTestBox.idTestBox);
    448         if oStatusData is not None:
    449             self._cleanupOldTest(oDb, oStatusData);
    450         else:
    451             oStatusLogic.insertIdleStatus(oTestBox.idTestBox, oTestBox.idGenTestBox, fCommit = True);
    452 
    453         #
    454         # ACK the request.
    455         #
    456         dResponse = \
    457         {
    458             constants.tbresp.ALL_PARAM_RESULT:  constants.tbresp.STATUS_ACK,
    459             constants.tbresp.SIGNON_PARAM_ID:   oTestBox.idTestBox,
    460             constants.tbresp.SIGNON_PARAM_NAME: oTestBox.sName,
    461         }
    462         return self._writeResponse(dResponse);
    463 
    464     def _doGangCleanup(self, oDb, oStatusData):
    465         """
    466         _doRequestCommand worker for handling a box in gang-cleanup.
    467         This will check if all testboxes has completed their run, pretending to
    468         be busy until that happens.  Once all are completed, resources will be
    469         freed and the testbox returns to idle state (we update oStatusData).
    470         """
    471         oStatusLogic = TestBoxStatusLogic(oDb)
    472         oTestSet = TestSetData().initFromDbWithId(oDb, oStatusData.idTestSet);
    473         if oStatusLogic.isWholeGangDoneTesting(oTestSet.idTestSetGangLeader):
    474             oDb.begin();
    475 
    476             GlobalResourceLogic(oDb).freeGlobalResourcesByTestBox(self._idTestBox, fCommit = False);
    477             TestBoxStatusLogic(oDb).updateState(self._idTestBox, TestBoxStatusData.ksTestBoxState_Idle, fCommit = False);
    478 
    479             oStatusData.tsUpdated = oDb.getCurrentTimestamp();
    480             oStatusData.enmState = TestBoxStatusData.ksTestBoxState_Idle;
    481 
    482             oDb.commit();
    483         return None;
    484 
    485     def _doGangGatheringTimedOut(self, oDb, oStatusData):
    486         """
    487         _doRequestCommand worker for handling a box in gang-gathering-timed-out state.
    488         This will do clean-ups similar to _cleanupOldTest and update the state likewise.
    489         """
    490         oDb.begin();
    491 
    492         TestSetLogic(oDb).completeAsGangGatheringTimeout(oStatusData.idTestSet, fCommit = False);
    493         GlobalResourceLogic(oDb).freeGlobalResourcesByTestBox(self._idTestBox, fCommit = False);
    494         TestBoxStatusLogic(oDb).updateState(self._idTestBox, TestBoxStatusData.ksTestBoxState_Idle, fCommit = False);
    495 
    496         oStatusData.tsUpdated = oDb.getCurrentTimestamp();
    497         oStatusData.enmState  = TestBoxStatusData.ksTestBoxState_Idle;
    498 
    499         oDb.commit();
    500         return None;
    501 
    502     def _doGangGathering(self, oDb, oStatusData):
    503         """
    504         _doRequestCommand worker for handling a box in gang-gathering state.
    505         This only checks for timeout.  It will update the oStatusData if a
    506         timeout is detected, so that the box will be idle upon return.
    507         """
    508         oStatusLogic = TestBoxStatusLogic(oDb);
    509         if     oStatusLogic.timeSinceLastChangeInSecs(oStatusData) > config.g_kcSecGangGathering \
    510            and SchedulerBase.tryCancelGangGathering(oDb, oStatusData): # <-- Updates oStatusData.
    511             self._doGangGatheringTimedOut(oDb, oStatusData);
    512         return None;
    513 
    514     def _doRequestCommand(self, fIdle):
    515         """
    516         Common code for handling command request.
    517         """
    518 
    519         (oDb, oStatusData, oTestBoxData) = self._connectToDbAndValidateTb();
    520         if oDb is None:
    521             return False;
    522 
    523         #
    524         # Status clean up.
    525         #
    526         # Only when BUSY will the TestBox Script request and execute commands
    527         # concurrently.  So, it must be idle when sending REQUEST_COMMAND_IDLE.
    528         #
    529         if fIdle:
    530             if oStatusData.enmState == TestBoxStatusData.ksTestBoxState_GangGathering:
    531                 self._doGangGathering(oDb, oStatusData);
    532             elif oStatusData.enmState == TestBoxStatusData.ksTestBoxState_GangGatheringTimedOut:
    533                 self._doGangGatheringTimedOut(oDb, oStatusData);
    534             elif oStatusData.enmState == TestBoxStatusData.ksTestBoxState_GangTesting:
    535                 dResponse = SchedulerBase.composeExecResponse(oDb, oTestBoxData.idTestBox, self._oSrvGlue.getBaseUrl());
    536                 if dResponse is not None:
    537                     return dResponse;
    538             elif oStatusData.enmState == TestBoxStatusData.ksTestBoxState_GangCleanup:
    539                 self._doGangCleanup(oDb, oStatusData);
    540             elif oStatusData.enmState != TestBoxStatusData.ksTestBoxState_Idle: # (includes ksTestBoxState_GangGatheringTimedOut)
    541                 self._cleanupOldTest(oDb, oStatusData);
    542 
    543         #
    544         # Check for pending command.
    545         #
    546         if oTestBoxData.enmPendingCmd != TestBoxData.ksTestBoxCmd_None:
    547             asValidCmds = TestBoxController.kasIdleCmds if fIdle else TestBoxController.kasBusyCmds;
    548             if oTestBoxData.enmPendingCmd in asValidCmds:
    549                 dResponse = { constants.tbresp.ALL_PARAM_RESULT: TestBoxController.kdCmdToTbRespCmd[oTestBoxData.enmPendingCmd] };
    550                 if oTestBoxData.enmPendingCmd in [TestBoxData.ksTestBoxCmd_Upgrade, TestBoxData.ksTestBoxCmd_UpgradeAndReboot]:
    551                     dResponse[constants.tbresp.UPGRADE_PARAM_URL] = self._oSrvGlue.getBaseUrl() + TestBoxController.ksUpgradeZip;
    552                 return self._writeResponse(dResponse);
    553 
    554             if oTestBoxData.enmPendingCmd == TestBoxData.ksTestBoxCmd_Abort and fIdle:
    555                 TestBoxLogic(oDb).setCommand(self._idTestBox, sOldCommand = oTestBoxData.enmPendingCmd,
    556                                              sNewCommand = TestBoxData.ksTestBoxCmd_None, fCommit = True);
    557 
    558         #
    559         # If doing gang stuff, return 'CMD_WAIT'.
    560         #
    561         ## @todo r=bird: Why is GangTesting included here? Figure out when testing gang testing.
    562         if oStatusData.enmState in [TestBoxStatusData.ksTestBoxState_GangGathering,
    563                                     TestBoxStatusData.ksTestBoxState_GangTesting,
    564                                     TestBoxStatusData.ksTestBoxState_GangCleanup]:
    565             return self._resultResponse(constants.tbresp.CMD_WAIT);
    566 
    567         #
    568         # If idling and enabled try schedule a new task.
    569         #
    570         if    fIdle \
    571           and oTestBoxData.fEnabled \
    572           and not TestSetLogic(oDb).isTestBoxExecutingToRapidly(oTestBoxData.idTestBox) \
    573           and oStatusData.enmState == TestBoxStatusData.ksTestBoxState_Idle: # (paranoia)
    574             dResponse = SchedulerBase.scheduleNewTask(oDb, oTestBoxData, oStatusData.iWorkItem, self._oSrvGlue.getBaseUrl());
    575             if dResponse is not None:
    576                 return self._writeResponse(dResponse);
    577 
    578         #
    579         # Touch the status row every couple of mins so we can tell that the box is alive.
    580         #
    581         oStatusLogic = TestBoxStatusLogic(oDb);
    582         if    oStatusData.enmState != TestBoxStatusData.ksTestBoxState_GangGathering \
    583           and oStatusLogic.timeSinceLastChangeInSecs(oStatusData) >= TestBoxStatusLogic.kcSecIdleTouchStatus:
    584             oStatusLogic.touchStatus(oTestBoxData.idTestBox, fCommit = True);
    585 
    586         return self._idleResponse();
    587 
    588     def _actionRequestCommandBusy(self):
    589         """ Implement request for command. """
     307        return True;
     308
     309    def _dataArrayToJsonReply(self, aoData, sName = 'aoData', dExtraFields = None, iStatus = 200):
     310        """
     311        Converts aoData into an array objects
     312        return True.
     313        """
     314        self._oSrvGlue.setContentType('application/json');
     315        self._oSrvGlue.setStatus(iStatus);
     316        self._oSrvGlue.write(u'{\n');
     317        if dExtraFields:
     318            for sKey in dExtraFields:
     319                self._oSrvGlue.write(u'  "%s": %s,\n' % (sKey, ModelDataBase.genericToJson(dExtraFields[sKey]),));
     320        self._oSrvGlue.write(u'  "c%s": %u,\n' % (sName[2:],len(aoData),));
     321        self._oSrvGlue.write(u'  "%s": [\n' % (sName,));
     322        for i, oData in enumerate(aoData):
     323            if i > 0:
     324                self._oSrvGlue.write(u',\n');
     325            self._oSrvGlue.write(ModelDataBase.genericToJson(oData));
     326        self._oSrvGlue.write(u'  ]\n');
     327        ## @todo if config.g_kfWebUiSqlDebug:
     328        self._oSrvGlue.write(u'}\n');
     329        self._oSrvGlue.flush();
     330        return True;
     331
     332
     333    #
     334    # Handlers.
     335    #
     336
     337    def _handleVcsChangelog_Get(self):
     338        """ GET /vcs/changelog/{sRepository}/{iStartRev}[/{cEntriesBack}] """
     339        # Parse arguments
     340        sRepository  = self._getNextPathElementString('sRepository');
     341        iStartRev    = self._getNextPathElementInt('iStartRev', iMin = 0);
     342        cEntriesBack = self._getNextPathElementInt('cEntriesBack', iDefault = 32, iMin = 0, iMax = 8192);
     343        self._checkNoMorePathElements();
    590344        self._checkForUnknownParameters();
    591         return self._doRequestCommand(False);
    592 
    593     def _actionRequestCommandIdle(self):
    594         """ Implement request for command. """
     345
     346        # Execute it.
     347        from testmanager.core.vcsrevisions import VcsRevisionLogic;
     348        oLogic = VcsRevisionLogic(self._oDb);
     349        return self._dataArrayToJsonReply(oLogic.fetchTimeline(sRepository, iStartRev, cEntriesBack), 'aoCommits',
     350                                          { 'sTracChangesetUrlFmt':
     351                                            config.g_ksTracChangsetUrlFmt.replace('%(sRepository)s', sRepository), } );
     352
     353    def _handleVcsBugReferences_Get(self):
     354        """ GET /vcs/bugreferences/{sTrackerId}/{lBugId} """
     355        # Parse arguments
     356        sTrackerId   = self._getNextPathElementString('sTrackerId');
     357        lBugId       = self._getNextPathElementLong('lBugId', iMin = 0);
     358        self._checkNoMorePathElements();
    595359        self._checkForUnknownParameters();
    596         return self._doRequestCommand(True);
    597 
    598     def _doCommandAckNck(self, sCmd):
    599         """ Implements ACK, NACK and NACK(ENOTSUP). """
    600 
    601         (oDb, _, _) = self._connectToDbAndValidateTb();
    602         if oDb is None:
    603             return False;
    604 
    605         #
    606         # If the command maps to a TestBoxCmd_T value, it means we have to
    607         # check and update TestBoxes.  If it's an ACK, the testbox status will
    608         # need updating as well.
    609         #
    610         sPendingCmd = TestBoxController.kdTbRespCmdToCmd[sCmd];
    611         if sPendingCmd is not None:
    612             oTestBoxLogic = TestBoxLogic(oDb)
    613             oTestBoxLogic.setCommand(self._idTestBox, sOldCommand = sPendingCmd,
    614                                      sNewCommand = TestBoxData.ksTestBoxCmd_None, fCommit = False);
    615 
    616             if    self._sAction == constants.tbreq.COMMAND_ACK \
    617               and TestBoxController.kdCmdToState[sPendingCmd] is not None:
    618                 oStatusLogic = TestBoxStatusLogic(oDb);
    619                 oStatusLogic.updateState(self._idTestBox, TestBoxController.kdCmdToState[sPendingCmd], fCommit = False);
    620 
    621             # Commit the two updates.
    622             oDb.commit();
    623 
    624         #
    625         # Log NACKs.
    626         #
    627         if self._sAction != constants.tbreq.COMMAND_ACK:
    628             oSysLogLogic = SystemLogLogic(oDb);
    629             oSysLogLogic.addEntry(SystemLogData.ksEvent_CmdNacked,
    630                                   'idTestBox=%s sCmd=%s' % (self._idTestBox, sPendingCmd),
    631                                   24, fCommit = True);
    632 
    633         return self._resultResponse(constants.tbresp.STATUS_ACK);
    634 
    635     def _actionCommandAck(self):
    636         """ Implement command ACK'ing """
    637         sCmd = self._getStringParam(constants.tbreq.COMMAND_ACK_PARAM_CMD_NAME, TestBoxController.kasAckableCmds);
    638         self._checkForUnknownParameters();
    639         return self._doCommandAckNck(sCmd);
    640 
    641     def _actionCommandNack(self):
    642         """ Implement command NACK'ing """
    643         sCmd = self._getStringParam(constants.tbreq.COMMAND_ACK_PARAM_CMD_NAME, TestBoxController.kasNackableCmds);
    644         self._checkForUnknownParameters();
    645         return self._doCommandAckNck(sCmd);
    646 
    647     def _actionCommandNotSup(self):
    648         """ Implement command NACK(ENOTSUP)'ing """
    649         sCmd = self._getStringParam(constants.tbreq.COMMAND_ACK_PARAM_CMD_NAME, TestBoxController.kasNackableCmds);
    650         self._checkForUnknownParameters();
    651         return self._doCommandAckNck(sCmd);
    652 
    653     def _actionLogMain(self):
    654         """ Implement submitting log entries to the main log file. """
    655         #
    656         # Parameter validation.
    657         #
    658         sBody = self._getStringParam(constants.tbreq.LOG_PARAM_BODY, fStrip = False);
    659         if not sBody:
    660             return self._resultResponse(constants.tbresp.STATUS_NACK);
    661         self._checkForUnknownParameters();
    662 
    663         (oDb, oStatusData, _) = self._connectToDbAndValidateTb([TestBoxStatusData.ksTestBoxState_Testing,
    664                                                                 TestBoxStatusData.ksTestBoxState_GangTesting]);
    665         if oStatusData is None:
    666             return False;
    667 
    668         #
    669         # Write the text to the log file.
    670         #
    671         oTestSet = TestSetData().initFromDbWithId(oDb, oStatusData.idTestSet);
    672         self.writeToMainLog(oTestSet, sBody);
    673         ## @todo Overflow is a hanging offence, need to note it and fail whatever is going on...
    674 
    675         # Done.
    676         return self._resultResponse(constants.tbresp.STATUS_ACK);
    677 
    678     def _actionUpload(self):
    679         """ Implement uploading of files. """
    680         #
    681         # Parameter validation.
    682         #
    683         sName = self._getStringParam(constants.tbreq.UPLOAD_PARAM_NAME);
    684         sMime = self._getStringParam(constants.tbreq.UPLOAD_PARAM_MIME);
    685         sKind = self._getStringParam(constants.tbreq.UPLOAD_PARAM_KIND);
    686         sDesc = self._getStringParam(constants.tbreq.UPLOAD_PARAM_DESC);
    687         self._checkForUnknownParameters();
    688 
    689         (oDb, oStatusData, _) = self._connectToDbAndValidateTb([TestBoxStatusData.ksTestBoxState_Testing,
    690                                                                 TestBoxStatusData.ksTestBoxState_GangTesting]);
    691         if oStatusData is None:
    692             return False;
    693 
    694         if len(sName) > 128 or len(sName) < 3:
    695             raise TestBoxControllerException('Invalid file name "%s"' % (sName,));
    696         if re.match(r'^[a-zA-Z0-9_\-(){}#@+,.=]*$', sName) is None:
    697             raise TestBoxControllerException('Invalid file name "%s"' % (sName,));
    698 
    699         if sMime not in [ 'text/plain', #'text/html', 'text/xml',
    700                           'application/octet-stream',
    701                           'image/png', #'image/gif', 'image/jpeg',
    702                           #'video/webm', 'video/mpeg', 'video/mpeg4-generic',
    703                           ]:
    704             raise TestBoxControllerException('Invalid MIME type "%s"' % (sMime,));
    705 
    706         if sKind not in TestResultFileData.kasKinds:
    707             raise TestBoxControllerException('Invalid kind "%s"' % (sKind,));
    708 
    709         if len(sDesc) > 256:
    710             raise TestBoxControllerException('Invalid description "%s"' % (sDesc,));
    711         if not set(sDesc).issubset(set(string.printable)):
    712             raise TestBoxControllerException('Invalid description "%s"' % (sDesc,));
    713 
    714         if ('application/octet-stream', {}) != self._oSrvGlue.getContentType():
    715             raise TestBoxControllerException('Unexpected content type: %s; %s' % self._oSrvGlue.getContentType());
    716 
    717         cbFile = self._oSrvGlue.getContentLength();
    718         if cbFile <= 0:
    719             raise TestBoxControllerException('File "%s" is empty or negative in size (%s)' % (sName, cbFile));
    720         if (cbFile + 1048575) / 1048576 > config.g_kcMbMaxUploadSingle:
    721             raise TestBoxControllerException('File "%s" is too big %u bytes (max %u MiB)'
    722                                              % (sName, cbFile, config.g_kcMbMaxUploadSingle,));
    723 
    724         #
    725         # Write the text to the log file.
    726         #
    727         oTestSet = TestSetData().initFromDbWithId(oDb, oStatusData.idTestSet);
    728         oDstFile = TestSetLogic(oDb).createFile(oTestSet, sName = sName, sMime = sMime, sKind = sKind, sDesc = sDesc,
    729                                                 cbFile = cbFile, fCommit = True);
    730 
    731         offFile  = 0;
    732         oSrcFile = self._oSrvGlue.getBodyIoStream();
    733         while offFile < cbFile:
    734             cbToRead = cbFile - offFile;
    735             if cbToRead > 256*1024:
    736                 cbToRead = 256*1024;
    737             offFile += cbToRead;
    738 
    739             abBuf = oSrcFile.read(cbToRead);
    740             oDstFile.write(abBuf); # pylint: disable=maybe-no-member
    741             del abBuf;
    742 
    743         oDstFile.close(); # pylint: disable=maybe-no-member
    744 
    745         # Done.
    746         return self._resultResponse(constants.tbresp.STATUS_ACK);
    747 
    748     def _actionXmlResults(self):
    749         """ Implement submitting "XML" like test result stream. """
    750         #
    751         # Parameter validation.
    752         #
    753         sXml = self._getStringParam(constants.tbreq.XML_RESULT_PARAM_BODY, fStrip = False);
    754         self._checkForUnknownParameters();
    755         if not sXml: # Used for link check by vboxinstaller.py on Windows.
    756             return self._resultResponse(constants.tbresp.STATUS_ACK);
    757 
    758         (oDb, oStatusData, _) = self._connectToDbAndValidateTb([TestBoxStatusData.ksTestBoxState_Testing,
    759                                                                 TestBoxStatusData.ksTestBoxState_GangTesting]);
    760         if oStatusData is None:
    761             return False;
    762 
    763         #
    764         # Process the XML.
    765         #
    766         (sError, fUnforgivable) = TestResultLogic(oDb).processXmlStream(sXml, self._idTestSet);
    767         if sError is not None:
    768             oTestSet = TestSetData().initFromDbWithId(oDb, oStatusData.idTestSet);
    769             self.writeToMainLog(oTestSet, '\n!!XML error: %s\n%s\n\n' % (sError, sXml,));
    770             if fUnforgivable:
    771                 return self._resultResponse(constants.tbresp.STATUS_NACK);
    772         return self._resultResponse(constants.tbresp.STATUS_ACK);
    773 
    774 
    775     def _actionExecCompleted(self):
    776         """
    777         Implement EXEC completion.
    778 
    779         Because the action is request by the worker thread of the testbox
    780         script we cannot pass pending commands back to it like originally
    781         planned.  So, we just complete the test set and update the status.
    782         """
    783         #
    784         # Parameter validation.
    785         #
    786         sStatus = self._getStringParam(constants.tbreq.EXEC_COMPLETED_PARAM_RESULT, TestBoxController.kasValidResults);
    787         self._checkForUnknownParameters();
    788 
    789         (oDb, oStatusData, _) = self._connectToDbAndValidateTb([TestBoxStatusData.ksTestBoxState_Testing,
    790                                                                 TestBoxStatusData.ksTestBoxState_GangTesting]);
    791         if oStatusData is None:
    792             return False;
    793 
    794         #
    795         # Complete the status.
    796         #
    797         oDb.rollback();
    798         oDb.begin();
    799         oTestSetLogic = TestSetLogic(oDb);
    800         idTestSetGangLeader = oTestSetLogic.complete(oStatusData.idTestSet, self.kadTbResultToStatus[sStatus], fCommit = False);
    801 
    802         oStatusLogic = TestBoxStatusLogic(oDb);
    803         if oStatusData.enmState == TestBoxStatusData.ksTestBoxState_Testing:
    804             assert idTestSetGangLeader is None;
    805             GlobalResourceLogic(oDb).freeGlobalResourcesByTestBox(self._idTestBox);
    806             oStatusLogic.updateState(self._idTestBox, TestBoxStatusData.ksTestBoxState_Idle, fCommit = False);
    807         else:
    808             assert idTestSetGangLeader is not None;
    809             oStatusLogic.updateState(self._idTestBox, TestBoxStatusData.ksTestBoxState_GangCleanup, oStatusData.idTestSet,
    810                                      fCommit = False);
    811             if oStatusLogic.isWholeGangDoneTesting(idTestSetGangLeader):
    812                 GlobalResourceLogic(oDb).freeGlobalResourcesByTestBox(self._idTestBox);
    813                 oStatusLogic.updateState(self._idTestBox, TestBoxStatusData.ksTestBoxState_Idle, fCommit = False);
    814 
    815         oDb.commit();
    816         return self._resultResponse(constants.tbresp.STATUS_ACK);
    817 
    818 
    819 
    820     def _getStandardParams(self, dParams):
    821         """
    822         Gets the standard parameters and validates them.
    823 
    824         The parameters are returned as a tuple: sAction, idTestBox, sTestBoxUuid.
    825         Note! the sTextBoxId can be None if it's a SIGNON request.
    826 
    827         Raises TestBoxControllerException on invalid input.
    828         """
    829         #
    830         # Get the action parameter and validate it.
    831         #
    832         if constants.tbreq.ALL_PARAM_ACTION not in dParams:
    833             raise TestBoxControllerException('No "%s" parameter in request (params: %s)' \
    834                                              % (constants.tbreq.ALL_PARAM_ACTION, dParams,));
    835         sAction = dParams[constants.tbreq.ALL_PARAM_ACTION];
    836 
    837         if sAction not in self._dActions:
    838             raise TestBoxControllerException('Unknown action "%s" in request (params: %s; action: %s)' \
    839                                              % (sAction, dParams, self._dActions));
    840 
    841         #
    842         # TestBox UUID.
    843         #
    844         if constants.tbreq.ALL_PARAM_TESTBOX_UUID not in dParams:
    845             raise TestBoxControllerException('No "%s" parameter in request (params: %s)' \
    846                                              % (constants.tbreq.ALL_PARAM_TESTBOX_UUID, dParams,));
    847         sTestBoxUuid = dParams[constants.tbreq.ALL_PARAM_TESTBOX_UUID];
    848         try:
    849             sTestBoxUuid = str(uuid.UUID(sTestBoxUuid));
    850         except Exception as oXcpt:
    851             raise TestBoxControllerException('Invalid %s parameter value "%s": %s ' \
    852                                              % (constants.tbreq.ALL_PARAM_TESTBOX_UUID, sTestBoxUuid, oXcpt));
    853         if sTestBoxUuid == '00000000-0000-0000-0000-000000000000':
    854             raise TestBoxControllerException('Invalid %s parameter value "%s": NULL UUID not allowed.' \
    855                                              % (constants.tbreq.ALL_PARAM_TESTBOX_UUID, sTestBoxUuid));
    856 
    857         #
    858         # TestBox ID.
    859         #
    860         if constants.tbreq.ALL_PARAM_TESTBOX_ID in dParams:
    861             sTestBoxId = dParams[constants.tbreq.ALL_PARAM_TESTBOX_ID];
    862             try:
    863                 idTestBox = int(sTestBoxId);
    864                 if idTestBox <= 0 or idTestBox >= 0x7fffffff:
    865                     raise Exception;
    866             except:
    867                 raise TestBoxControllerException('Bad value for "%s": "%s"' \
    868                                                  % (constants.tbreq.ALL_PARAM_TESTBOX_ID, sTestBoxId));
    869         elif sAction == constants.tbreq.SIGNON:
    870             idTestBox = None;
    871         else:
    872             raise TestBoxControllerException('No "%s" parameter in request (params: %s)' \
    873                                              % (constants.tbreq.ALL_PARAM_TESTBOX_ID, dParams,));
    874 
    875         #
    876         # Test Set ID.
    877         #
    878         if constants.tbreq.RESULT_PARAM_TEST_SET_ID in dParams:
    879             sTestSetId = dParams[constants.tbreq.RESULT_PARAM_TEST_SET_ID];
    880             try:
    881                 idTestSet = int(sTestSetId);
    882                 if idTestSet <= 0 or idTestSet >= 0x7fffffff:
    883                     raise Exception;
    884             except:
    885                 raise TestBoxControllerException('Bad value for "%s": "%s"' \
    886                                                  % (constants.tbreq.RESULT_PARAM_TEST_SET_ID, sTestSetId));
    887         elif sAction not in [ constants.tbreq.XML_RESULTS, ]: ## More later.
    888             idTestSet = None;
    889         else:
    890             raise TestBoxControllerException('No "%s" parameter in request (params: %s)' \
    891                                              % (constants.tbreq.RESULT_PARAM_TEST_SET_ID, dParams,));
    892 
    893         #
    894         # The testbox address.
    895         #
    896         sTestBoxAddr = self._oSrvGlue.getClientAddr();
    897         if sTestBoxAddr is None or sTestBoxAddr.strip() == '':
    898             raise TestBoxControllerException('Invalid client address "%s"' % (sTestBoxAddr,));
    899 
    900         #
    901         # Update the list of checked parameters.
    902         #
    903         self._asCheckedParams.extend([constants.tbreq.ALL_PARAM_TESTBOX_UUID, constants.tbreq.ALL_PARAM_ACTION]);
    904         if idTestBox is not None:
    905             self._asCheckedParams.append(constants.tbreq.ALL_PARAM_TESTBOX_ID);
    906         if idTestSet is not None:
    907             self._asCheckedParams.append(constants.tbreq.RESULT_PARAM_TEST_SET_ID);
    908 
    909         return (sAction, idTestBox, sTestBoxUuid, sTestBoxAddr, idTestSet);
    910 
    911     def dispatchRequest(self):
    912         """
    913         Dispatches the incoming request.
    914 
    915         Will raise TestBoxControllerException on failure.
    916         """
    917 
    918         #
    919         # Must be a POST request.
     360
     361        # Execute it.
     362        from testmanager.core.vcsbugreference import VcsBugReferenceLogic;
     363        oLogic = VcsBugReferenceLogic(self._oDb);
     364        oLogic.fetchForBug(sTrackerId, lBugId)
     365        return self._dataArrayToJsonReply(oLogic.fetchForBug(sTrackerId, lBugId), 'aoCommits',
     366                                          { 'sTracChangesetUrlFmt': config.g_ksTracChangsetUrlFmt, } );
     367
     368
     369    #
     370    # Dispatching.
     371    #
     372
     373    def _dispatchRequestCommon(self):
     374        """
     375        Dispatches the incoming request after have gotten the path and parameters.
     376
     377        Will raise RestDispException on failure.
     378        """
     379
     380        #
     381        # Split up the path.
     382        #
     383        asPath = self._sPath.split('/');
     384        self._asPath = asPath;
     385
     386        #
     387        # Get the method and the corresponding handler tree.
    920388        #
    921389        try:
    922390            sMethod = self._oSrvGlue.getMethod();
    923391        except Exception as oXcpt:
    924             raise TestBoxControllerException('Error retriving request method: %s' % (oXcpt,));
    925         if sMethod != 'POST':
    926             raise TestBoxControllerException('Error expected POST request not "%s"' % (sMethod,));
    927 
    928         #
    929         # Get the parameters and checks for duplicates.
     392            raise RestDispException('Error retriving request method: %s' % (oXcpt,), 400);
     393        self._sMethod = sMethod;
     394
     395        try:
     396            dTree = self._dMethodTrees[sMethod];
     397        except KeyError:
     398            raise RestDispException('Unsupported method %s' % (sMethod,), 405);
     399
     400        #
     401        # Walk the path till we find a handler for it.
     402        #
     403        iPath = 0;
     404        while iPath < len(asPath):
     405            try:
     406                oTreeOrHandler = dTree[asPath[iPath]];
     407            except KeyError:
     408                raise RestBadPathException('Path element #%u "%s" not found (path="%s")' % (iPath, asPath[iPath], self._sPath));
     409            iPath += 1;
     410            if isinstance(oTreeOrHandler, dict):
     411                dTree = oTreeOrHandler;
     412            else:
     413                #
     414                # Call the handler.
     415                #
     416                self._iFirstHandlerPath = iPath;
     417                self._iNextHandlerPath  = iPath;
     418                return oTreeOrHandler();
     419
     420        raise RestBadPathException('Empty path (%s)' % (self._sPath,));
     421
     422    def dispatchRequest(self):
     423        """
     424        Dispatches the incoming request where the path is given as an argument.
     425
     426        Will raise RestDispException on failure.
     427        """
     428
     429        #
     430        # Get the parameters.
    930431        #
    931432        try:
    932433            dParams = self._oSrvGlue.getParameters();
    933434        except Exception as oXcpt:
    934             raise TestBoxControllerException('Error retriving parameters: %s' % (oXcpt,));
    935         for sKey in dParams.keys():
    936             if len(dParams[sKey]) > 1:
    937                 raise TestBoxControllerException('Parameter "%s" is given multiple times: %s' % (sKey, dParams[sKey]));
    938             dParams[sKey] = dParams[sKey][0];
     435            raise RestDispException('Error retriving parameters: %s' % (oXcpt,), 500);
    939436        self._dParams = dParams;
    940437
    941438        #
    942         # Get+validate the standard action parameters and dispatch the request.
    943         #
    944         (self._sAction, self._idTestBox, self._sTestBoxUuid, self._sTestBoxAddr, self._idTestSet) = \
    945             self._getStandardParams(dParams);
    946         return self._dActions[self._sAction]();
    947 
     439        # Get the path parameter.
     440        #
     441        if self.ksParam_sPath not in dParams:
     442            raise RestDispException('No "%s" parameter in request (params: %s)' % (self.ksParam_sPath, dParams,), 500);
     443        self._sPath = self._getStringParam(self.ksParam_sPath);
     444        assert utils.isString(self._sPath);
     445
     446        return self._dispatchRequestCommon();
     447
     448
  • trunk/src/VBox/ValidationKit/testmanager/core/vcsbugreference.py

    r84550 r84599  
    8686        self.sBugTracker        = sBugTracker;
    8787        self.lBugNo             = lBugNo;
     88        return self;
     89
     90
     91class VcsBugReferenceDataEx(VcsBugReferenceData):
     92    """
     93    Extended version of VcsBugReferenceData that includes the commit details.
     94    """
     95    def __init__(self):
     96        VcsBugReferenceData.__init__(self);
     97        self.tsCreated          = None;
     98        self.sAuthor            = None;
     99        self.sMessage           = None;
     100
     101    def initFromDbRow(self, aoRow):
     102        VcsBugReferenceData.initFromDbRow(self, aoRow);
     103        self.tsCreated          = aoRow[4];
     104        self.sAuthor            = aoRow[5];
     105        self.sMessage           = aoRow[6];
    88106        return self;
    89107
     
    183201        return self._oDb.fetchOne()[0];
    184202
     203    def fetchForBug(self, sBugTracker, lBugNo):
     204        """
     205        Fetches VCS revisions for a bug.
     206
     207        Returns an array (list) of VcsBugReferenceDataEx items, empty list if none.
     208        Raises exception on error.
     209        """
     210        self._oDb.execute('''
     211SELECT  VcsBugReferences.*,
     212        VcsRevisions.tsCreated,
     213        VcsRevisions.sAuthor,
     214        VcsRevisions.sMessage
     215FROM    VcsBugReferences
     216LEFT OUTER JOIN VcsRevisions ON (    VcsRevisions.sRepository = VcsBugReferences.sRepository
     217                                 AND VcsRevisions.iRevision   = VcsBugReferences.iRevision )
     218WHERE   sBugTracker = %s
     219    AND lBugNo      = %s
     220ORDER BY VcsRevisions.tsCreated, VcsBugReferences.sRepository, VcsBugReferences.iRevision
     221''', (sBugTracker, lBugNo,));
     222
     223        aoRows = [];
     224        for _ in range(self._oDb.getRowCount()):
     225            aoRows.append(VcsBugReferenceDataEx().initFromDbRow(self._oDb.fetchOne()));
     226        return aoRows;
     227
    185228
    186229#
  • trunk/src/VBox/ValidationKit/testmanager/core/webservergluebase.py

    r83381 r84599  
    6262    ksUnknownUser = 'Unknown User';
    6363
     64    ## HTTP status codes and their messages.
     65    kdStatusMsgs = {
     66        100: 'Continue',
     67        101: 'Switching Protocols',
     68        102: 'Processing',
     69        103: 'Early Hints',
     70        200: 'OK',
     71        201: 'Created',
     72        202: 'Accepted',
     73        203: 'Non-Authoritative Information',
     74        204: 'No Content',
     75        205: 'Reset Content',
     76        206: 'Partial Content',
     77        207: 'Multi-Status',
     78        208: 'Already Reported',
     79        226: 'IM Used',
     80        300: 'Multiple Choices',
     81        301: 'Moved Permantently',
     82        302: 'Found',
     83        303: 'See Other',
     84        304: 'Not Modified',
     85        305: 'Use Proxy',
     86        306: 'Switch Proxy',
     87        307: 'Temporary Redirect',
     88        308: 'Permanent Redirect',
     89        400: 'Bad Request',
     90        401: 'Unauthorized',
     91        402: 'Payment Required',
     92        403: 'Forbidden',
     93        404: 'Not Found',
     94        405: 'Method Not Allowed',
     95        406: 'Not Acceptable',
     96        407: 'Proxy Authentication Required',
     97        408: 'Request Timeout',
     98        409: 'Conflict',
     99        410: 'Gone',
     100        411: 'Length Required',
     101        412: 'Precondition Failed',
     102        413: 'Payload Too Large',
     103        414: 'URI Too Long',
     104        415: 'Unsupported Media Type',
     105        416: 'Range Not Satisfiable',
     106        417: 'Expectation Failed',
     107        418: 'I\'m a teapot',
     108        421: 'Misdirection Request',
     109        422: 'Unprocessable Entity',
     110        423: 'Locked',
     111        424: 'Failed Dependency',
     112        425: 'Too Early',
     113        426: 'Upgrade Required',
     114        428: 'Precondition Required',
     115        429: 'Too Many Requests',
     116        431: 'Request Header Fields Too Large',
     117        451: 'Unavailable For Legal Reasons',
     118        500: 'Internal Server Error',
     119        501: 'Not Implemented',
     120        502: 'Bad Gateway',
     121        503: 'Service Unavailable',
     122        504: 'Gateway Timeout',
     123        505: 'HTTP Version Not Supported',
     124        506: 'Variant Also Negotiates',
     125        507: 'Insufficient Storage',
     126        508: 'Loop Detected',
     127        510: 'Not Extended',
     128        511: 'Network Authentication Required',
     129    };
     130
    64131
    65132    def __init__(self, sValidationKitDir, fHtmlDebugOutput = True):
     
    70137        self._fHtmlDebugOutput = fHtmlDebugOutput; # For trace
    71138        self._oDbgFile         = sys.stderr;
    72         if config.g_ksSrcGlueDebugLogDst is not None and config.g_kfSrvGlueDebug is True:
    73             self._oDbgFile = open(config.g_ksSrcGlueDebugLogDst, 'a');
     139        if config.g_ksSrvGlueDebugLogDst is not None and config.g_kfSrvGlueDebug is True:
     140            self._oDbgFile = open(config.g_ksSrvGlueDebugLogDst, 'a');
    74141            if config.g_kfSrvGlueCgiDumpArgs:
    75142                self._oDbgFile.write('Arguments: %s\nEnvironment:\n' % (sys.argv,));
     
    221288        Worker function which child classes can override.
    222289        """
     290        sys.stderr.write('_writeHeader: cch=%s "%s..."\n' % (len(sHeaderLine), sHeaderLine[0:10],))
    223291        self.oOutputText.write(sHeaderLine);
    224292        return True;
     
    257325        return True;
    258326
     327    def setStatus(self, iStatus, sMsg = None):
     328        """ Sets the status code. """
     329        if not sMsg:
     330            sMsg = self.kdStatusMsgs[iStatus];
     331        return self.setHeaderField('Status', '%u %s' % (iStatus, sMsg));
     332
     333    def setContentType(self, sType):
     334        """ Sets the content type header field. """
     335        return self.setHeaderField('Content-Type', sType);
     336
    259337    def _writeWorker(self, sChunkOfHtml):
    260338        """
    261339        Worker function which child classes can override.
    262340        """
     341        sys.stderr.write('_writeWorker: cch=%s "%s..."\n' % (len(sChunkOfHtml), sChunkOfHtml[0:10],))
    263342        self.oOutputText.write(sChunkOfHtml);
    264343        return True;
     
    286365        """
    287366        if self._sBodyType is None:
    288             self._sBodyType = 'html';
    289         elif self._sBodyType != 'html':
    290             raise WebServerGlueException('Cannot use writeParameter when body type is "%s"' % (self._sBodyType, ));
     367            self._sBodyType = 'raw';
     368        elif self._sBodyType != 'raw':
     369            raise WebServerGlueException('Cannot use writeRaw when body type is "%s"' % (self._sBodyType, ));
    291370
    292371        self.flushHeader();
     
    294373            self.flush();
    295374
     375        sys.stderr.write('writeRaw: cb=%s\n' % (len(writeRaw),))
    296376        self.oOutputRaw.write(abChunk);
    297377        return True;
Note: See TracChangeset for help on using the changeset viewer.

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