VirtualBox

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

Last change on this file since 70560 was 70560, checked in by vboxsync, 7 years ago

testboxscript: Fix of py3 adjustments.

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