VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/testmanager/core/restdispatcher.py@ 93115

Last change on this file since 93115 was 93115, checked in by vboxsync, 3 years ago

scm --update-copyright-year

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 16.4 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: restdispatcher.py 93115 2022-01-01 11:31:46Z vboxsync $
3
4"""
5Test Manager Core - REST cgi handler.
6"""
7
8__copyright__ = \
9"""
10Copyright (C) 2012-2022 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: 93115 $"
30
31
32# Standard python imports.
33import os;
34import sys;
35
36# Validation Kit imports.
37#from common import constants;
38from common import utils;
39from testmanager import config;
40#from testmanager.core import coreconsts;
41from testmanager.core.db import TMDatabaseConnection;
42from testmanager.core.base import TMExceptionBase, ModelDataBase;
43
44# Python 3 hacks:
45if sys.version_info[0] >= 3:
46 long = int; # pylint: disable=redefined-builtin,invalid-name
47
48
49#
50# Exceptions
51#
52
53class RestDispException(TMExceptionBase):
54 """
55 Exception class for the REST dispatcher.
56 """
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). """
69 pass; # pylint: disable=unnecessary-pass
70
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
92 """
93 REST main dispatcher class.
94 """
95
96 ksParam_sPath = 'sPath';
97
98
99 def __init__(self, oSrvGlue):
100 self._oSrvGlue = oSrvGlue;
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.
107 self._dParams = None; # _getStandardParams / dispatchRequest sets this later on.
108 self._asCheckedParams = [];
109 self._dGetTree = {
110 'vcs': {
111 'changelog': self._handleVcsChangelog_Get,
112 'bugreferences': self._handleVcsBugReferences_Get,
113 },
114 };
115 self._dMethodTrees = {
116 'GET': self._dGetTree,
117 }
118
119 #
120 # Helpers.
121 #
122
123 def _getStringParam(self, sName, asValidValues = None, fStrip = False, sDefValue = None):
124 """
125 Gets a string parameter (stripped).
126
127 Raises exception if not found and no default is provided, or if the
128 value isn't found in asValidValues.
129 """
130 if sName not in self._dParams:
131 if sDefValue is None:
132 raise RestMissingParameter('%s parameter %s is missing' % (self._sPath, sName));
133 return sDefValue;
134 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));
141 if fStrip:
142 sValue = sValue.strip();
143
144 if sName not in self._asCheckedParams:
145 self._asCheckedParams.append(sName);
146
147 if asValidValues is not None and sValue not in asValidValues:
148 raise RestBadParameter('%s parameter %s value "%s" not in %s '
149 % (self._sPath, sName, sValue, asValidValues));
150 return sValue;
151
152 def _getBoolParam(self, sName, fDefValue = None):
153 """
154 Gets a boolean parameter.
155
156 Raises exception if not found and no default is provided, or if not a
157 valid boolean.
158 """
159 sValue = self._getStringParam(sName, [ 'True', 'true', '1', 'False', 'false', '0'], sDefValue = str(fDefValue));
160 return sValue in ('True', 'true', '1',);
161
162 def _getIntParam(self, sName, iMin = None, iMax = None):
163 """
164 Gets a string parameter.
165 Raises exception if not found, not a valid integer, or if the value
166 isn't in the range defined by iMin and iMax.
167 """
168 sValue = self._getStringParam(sName);
169 try:
170 iValue = int(sValue, 0);
171 except:
172 raise RestBadParameter('%s parameter %s value "%s" cannot be convert to an integer'
173 % (self._sPath, sName, sValue));
174
175 if (iMin is not None and iValue < iMin) \
176 or (iMax is not None and iValue > iMax):
177 raise RestBadParameter('%s parameter %s value %d is out of range [%s..%s]'
178 % (self._sPath, sName, iValue, iMin, iMax));
179 return iValue;
180
181 def _getLongParam(self, sName, lMin = None, lMax = None, lDefValue = None):
182 """
183 Gets a string parameter.
184 Raises exception if not found, not a valid long integer, or if the value
185 isn't in the range defined by lMin and lMax.
186 """
187 sValue = self._getStringParam(sName, sDefValue = (str(lDefValue) if lDefValue is not None else None));
188 try:
189 lValue = long(sValue, 0);
190 except Exception as oXcpt:
191 raise RestBadParameter('%s parameter %s value "%s" cannot be convert to an integer (%s)'
192 % (self._sPath, sName, sValue, oXcpt));
193
194 if (lMin is not None and lValue < lMin) \
195 or (lMax is not None and lValue > lMax):
196 raise RestBadParameter('%s parameter %s value %d is out of range [%s..%s]'
197 % (self._sPath, sName, lValue, lMin, lMax));
198 return lValue;
199
200 def _checkForUnknownParameters(self):
201 """
202 Check if we've handled all parameters, raises exception if anything
203 unknown was found.
204 """
205
206 if len(self._asCheckedParams) != len(self._dParams):
207 sUnknownParams = '';
208 for sKey in self._dParams:
209 if sKey not in self._asCheckedParams:
210 sUnknownParams += ' ' + sKey + '=' + self._dParams[sKey];
211 raise RestUnknownParameters('Unknown parameters: ' + sUnknownParams);
212
213 return True;
214
215 def writeToMainLog(self, oTestSet, sText, fIgnoreSizeCheck = False):
216 """ Writes the text to the main log file. """
217
218 # Calc the file name and open the file.
219 sFile = os.path.join(config.g_ksFileAreaRootDir, oTestSet.sBaseFilename + '-main.log');
220 if not os.path.exists(os.path.dirname(sFile)):
221 os.makedirs(os.path.dirname(sFile), 0o755);
222 oFile = open(sFile, 'ab');
223
224 # Check the size.
225 fSizeOk = True;
226 if not fIgnoreSizeCheck:
227 oStat = os.fstat(oFile.fileno());
228 fSizeOk = oStat.st_size / (1024 * 1024) < config.g_kcMbMaxMainLog;
229
230 # Write the text.
231 if fSizeOk:
232 if sys.version_info[0] >= 3:
233 oFile.write(bytes(sText, 'utf-8'));
234 else:
235 oFile.write(sText);
236
237 # Done
238 oFile.close();
239 return fSizeOk;
240
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();
306 self._checkForUnknownParameters();
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();
344 self._checkForUnknownParameters();
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();
359 self._checkForUnknownParameters();
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.
388 #
389 try:
390 sMethod = self._oSrvGlue.getMethod();
391 except Exception as oXcpt:
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.
431 #
432 try:
433 dParams = self._oSrvGlue.getParameters();
434 except Exception as oXcpt:
435 raise RestDispException('Error retriving parameters: %s' % (oXcpt,), 500);
436 self._dParams = dParams;
437
438 #
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
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