VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/testboxscript/testboxconnection.py@ 64226

Last change on this file since 64226 was 62484, checked in by vboxsync, 8 years ago

(C) 2016

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 11.1 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: testboxconnection.py 62484 2016-07-22 18:35:33Z vboxsync $
3
4"""
5TestBox Script - HTTP Connection Handling.
6"""
7
8__copyright__ = \
9"""
10Copyright (C) 2012-2016 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: 62484 $"
30
31
32# Standard python imports.
33import httplib
34import urllib
35import urlparse
36import sys
37
38# Validation Kit imports.
39from common import constants
40from common import utils
41import testboxcommons
42
43
44
45class TestBoxResponse(object):
46 """
47 Response object return by TestBoxConnection.request().
48 """
49 def __init__(self, oResponse):
50 """
51 Convert the HTTPResponse to a dictionary, raising TestBoxException on
52 malformed response.
53 """
54 if oResponse is not None:
55 # Read the whole response (so we can log it).
56 sBody = oResponse.read();
57
58 # Check the content type.
59 sContentType = oResponse.getheader('Content-Type');
60 if sContentType is None or sContentType != 'application/x-www-form-urlencoded; charset=utf-8':
61 testboxcommons.log('SERVER RESPONSE: Content-Type: %s' % (sContentType,));
62 testboxcommons.log('SERVER RESPONSE: %s' % (sBody.rstrip(),))
63 raise testboxcommons.TestBoxException('Invalid server response type: "%s"' % (sContentType,));
64
65 # Parse the body (this should be the exact reverse of what
66 # TestBoxConnection.postRequestRaw).
67 ##testboxcommons.log2('SERVER RESPONSE: "%s"' % (sBody,))
68 self._dResponse = urlparse.parse_qs(sBody, strict_parsing=True);
69
70 # Convert the dictionary from 'field:values' to 'field:value'. Fail
71 # if a field has more than one value (i.e. given more than once).
72 for sField in self._dResponse:
73 if len(self._dResponse[sField]) != 1:
74 raise testboxcommons.TestBoxException('The field "%s" appears more than once in the server response' \
75 % (sField,));
76 self._dResponse[sField] = self._dResponse[sField][0]
77 else:
78 # Special case, dummy response object.
79 self._dResponse = dict();
80 # Done.
81
82 def getStringChecked(self, sField):
83 """
84 Check if specified field is present in server response and returns it as string.
85 If not present, a fitting exception will be raised.
86 """
87 if not sField in self._dResponse:
88 raise testboxcommons.TestBoxException('Required data (' + str(sField) + ') was not found in server response');
89 return str(self._dResponse[sField]).strip();
90
91 def getIntChecked(self, sField, iMin = None, iMax = None):
92 """
93 Check if specified field is present in server response and returns it as integer.
94 If not present, a fitting exception will be raised.
95
96 The iMin and iMax values are inclusive.
97 """
98 if not sField in self._dResponse:
99 raise testboxcommons.TestBoxException('Required data (' + str(sField) + ') was not found in server response')
100 try:
101 iValue = int(self._dResponse[sField]);
102 except:
103 raise testboxcommons.TestBoxException('Malformed integer field %s: "%s"' % (sField, self._dResponse[sField]));
104
105 if (iMin is not None and iValue < iMin) \
106 or (iMax is not None and iValue > iMax):
107 raise testboxcommons.TestBoxException('Value (%d) of field %s is out of range [%s..%s]' \
108 % (iValue, sField, iMin, iMax));
109 return iValue;
110
111 def checkParameterCount(self, cExpected):
112 """
113 Checks the parameter count, raise TestBoxException if it doesn't meet
114 the expectations.
115 """
116 if len(self._dResponse) != cExpected:
117 raise testboxcommons.TestBoxException('Expected %d parameters, server sent %d' % (cExpected, len(self._dResponse)));
118 return True;
119
120 def toString(self):
121 """
122 Convers the response to a string (for debugging purposes).
123 """
124 return str(self._dResponse);
125
126
127class TestBoxConnection(object):
128 """
129 Wrapper around HTTPConnection.
130 """
131
132 def __init__(self, sTestManagerUrl, sTestBoxId, sTestBoxUuid, fLongTimeout = False):
133 """
134 Constructor.
135 """
136 self._oConn = None;
137 self._oParsedUrl = urlparse.urlparse(sTestManagerUrl);
138 self._sTestBoxId = sTestBoxId;
139 self._sTestBoxUuid = sTestBoxUuid;
140
141 #
142 # Connect to it - may raise exception on failure.
143 # When connecting we're using a 15 second timeout, we increase it later.
144 #
145 if self._oParsedUrl.scheme == 'https': # pylint: disable=E1101
146 fnCtor = httplib.HTTPSConnection;
147 else:
148 fnCtor = httplib.HTTPConnection;
149 if sys.version_info[0] >= 3 \
150 or (sys.version_info[0] == 2 and sys.version_info[1] >= 6):
151
152 self._oConn = fnCtor(self._oParsedUrl.hostname, timeout=15);
153 else:
154 self._oConn = fnCtor(self._oParsedUrl.hostname);
155
156 if self._oConn.sock is None:
157 self._oConn.connect();
158
159 #
160 # Increase the timeout for the non-connect operations.
161 #
162 try:
163 self._oConn.sock.settimeout(5*60 if fLongTimeout else 1 * 60);
164 except:
165 pass;
166
167 ##testboxcommons.log2('hostname=%s timeout=%u' % (self._oParsedUrl.hostname, self._oConn.sock.gettimeout()));
168
169 def __del__(self):
170 """ Makes sure the connection is really closed on destruction """
171 self.close()
172
173 def close(self):
174 """ Closes the connection """
175 if self._oConn is not None:
176 self._oConn.close();
177 self._oConn = None;
178
179 def postRequestRaw(self, sAction, dParams):
180 """
181 Posts a request to the test manager and gets the response. The dParams
182 argument is a dictionary of unencoded key-value pairs (will be
183 modified).
184 Raises exception on failure.
185 """
186 dHeader = \
187 {
188 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
189 'User-Agent': 'TestBoxScript/%s.0 (%s, %s)' % (__version__, utils.getHostOs(), utils.getHostArch()),
190 'Accept': 'text/plain,application/x-www-form-urlencoded',
191 'Accept-Encoding': 'identity',
192 'Cache-Control': 'max-age=0',
193 'Connection': 'keep-alive',
194 };
195 sServerPath = '/%s/testboxdisp.py' % (self._oParsedUrl.path.strip('/'),); # pylint: disable=E1101
196 dParams[constants.tbreq.ALL_PARAM_ACTION] = sAction;
197 sBody = urllib.urlencode(dParams);
198 ##testboxcommons.log2('sServerPath=%s' % (sServerPath,));
199 try:
200 self._oConn.request('POST', sServerPath, sBody, dHeader);
201 oResponse = self._oConn.getresponse();
202 oResponse2 = TestBoxResponse(oResponse);
203 except:
204 testboxcommons.log2Xcpt();
205 raise
206 return oResponse2;
207
208 def postRequest(self, sAction, dParams = None):
209 """
210 Posts a request to the test manager, prepending the testbox ID and
211 UUID to the arguments, and gets the response. The dParams argument is a
212 is a dictionary of unencoded key-value pairs (will be modified).
213 Raises exception on failure.
214 """
215 if dParams is None:
216 dParams = dict();
217 dParams[constants.tbreq.ALL_PARAM_TESTBOX_ID] = self._sTestBoxId;
218 dParams[constants.tbreq.ALL_PARAM_TESTBOX_UUID] = self._sTestBoxUuid;
219 return self.postRequestRaw(sAction, dParams);
220
221 def sendReply(self, sReplyAction, sCmdName):
222 """
223 Sends a reply to a test manager command.
224 Raises exception on failure.
225 """
226 return self.postRequest(sReplyAction, { constants.tbreq.COMMAND_ACK_PARAM_CMD_NAME: sCmdName });
227
228 def sendReplyAndClose(self, sReplyAction, sCmdName):
229 """
230 Sends a reply to a test manager command and closes the connection.
231 Raises exception on failure.
232 """
233 self.sendReply(sReplyAction, sCmdName);
234 self.close();
235 return True;
236
237 def sendAckAndClose(self, sCmdName):
238 """
239 Acks a command and closes the connection to the test manager.
240 Raises exception on failure.
241 """
242 return self.sendReplyAndClose(constants.tbreq.COMMAND_ACK, sCmdName);
243
244 def sendAck(self, sCmdName):
245 """
246 Acks a command.
247 Raises exception on failure.
248 """
249 return self.sendReply(constants.tbreq.COMMAND_ACK, sCmdName);
250
251 @staticmethod
252 def sendSignOn(sTestManagerUrl, dParams):
253 """
254 Sends a sign-on request to the server, returns the response (TestBoxResponse).
255 No exceptions will be raised.
256 """
257 oConnection = None;
258 try:
259 oConnection = TestBoxConnection(sTestManagerUrl, None, None);
260 return oConnection.postRequestRaw(constants.tbreq.SIGNON, dParams);
261 except:
262 testboxcommons.log2Xcpt();
263 if oConnection is not None: # Be kind to apache.
264 try: oConnection.close();
265 except: pass;
266
267 return TestBoxResponse(None);
268
269 @staticmethod
270 def requestCommandWithConnection(sTestManagerUrl, sTestBoxId, sTestBoxUuid, fBusy):
271 """
272 Queries the test manager for a command and returns its respons + an open
273 connection for acking/nack the command (and maybe more).
274
275 No exceptions will be raised. On failure (None, None) will be returned.
276 """
277 oConnection = None;
278 try:
279 oConnection = TestBoxConnection(sTestManagerUrl, sTestBoxId, sTestBoxUuid, fLongTimeout = not fBusy);
280 if fBusy:
281 oResponse = oConnection.postRequest(constants.tbreq.REQUEST_COMMAND_BUSY);
282 else:
283 oResponse = oConnection.postRequest(constants.tbreq.REQUEST_COMMAND_IDLE);
284 return (oResponse, oConnection);
285 except:
286 testboxcommons.log2Xcpt();
287 if oConnection is not None: # Be kind to apache.
288 try: oConnection.close();
289 except: pass;
290 return (None, None);
291
292 def isConnected(self):
293 """
294 Checks if we are still connected.
295 """
296 return self._oConn is not None;
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