Trees | Indices | Help |
|
---|
|
1 # 2 # 3 4 # Copyright (C) 2012 Google Inc. 5 # All rights reserved. 6 # 7 # Redistribution and use in source and binary forms, with or without 8 # modification, are permitted provided that the following conditions are 9 # met: 10 # 11 # 1. Redistributions of source code must retain the above copyright notice, 12 # this list of conditions and the following disclaimer. 13 # 14 # 2. Redistributions in binary form must reproduce the above copyright 15 # notice, this list of conditions and the following disclaimer in the 16 # documentation and/or other materials provided with the distribution. 17 # 18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 19 # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 20 # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 21 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 22 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 23 # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 24 # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 25 # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 26 # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 27 # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 30 31 """Remote API test utilities. 32 33 """ 34 35 import logging 36 import re 37 import base64 38 import pycurl 39 from cStringIO import StringIO 40 41 from ganeti import errors 42 from ganeti import opcodes 43 from ganeti import http 44 from ganeti import server 45 from ganeti import utils 46 from ganeti import compat 47 from ganeti import luxi 48 import ganeti.rpc.client as rpccl 49 from ganeti import rapi 50 51 import ganeti.http.server # pylint: disable=W0611 52 import ganeti.server.rapi 53 import ganeti.rapi.client 54 55 56 _URI_RE = re.compile(r"https://(?P<host>.*):(?P<port>\d+)(?P<path>/.*)")60 """Dedicated error class for test utilities. 61 62 This class is used to hide all of Ganeti's internal exception, so that 63 external users of these utilities don't have to integrate Ganeti's exception 64 hierarchy. 65 66 """6770 """Tries to get an opcode class based on its C{OP_ID}. 71 72 """ 73 try: 74 return opcodes.OP_MAPPING[op_id] 75 except KeyError: 76 raise VerificationError("Unknown opcode ID '%s'" % op_id)7780 """Hides Ganeti-internal exceptions, see L{VerificationError}. 81 82 """ 83 def wrapper(*args, **kwargs): 84 try: 85 return fn(*args, **kwargs) 86 except (errors.GenericError, rapi.client.GanetiApiError), err: 87 raise VerificationError("Unhandled Ganeti error: %s" % err)88 89 return wrapper 9094 """Verifies opcode parameters according to their definition. 95 96 @type op_id: string 97 @param op_id: Opcode ID (C{OP_ID} attribute), e.g. C{OP_CLUSTER_VERIFY} 98 @type data: dict 99 @param data: Opcode parameter values 100 @raise VerificationError: Parameter verification failed 101 102 """ 103 op_cls = _GetOpById(op_id) 104 105 try: 106 op = op_cls(**data) # pylint: disable=W0142 107 except TypeError, err: 108 raise VerificationError("Unable to create opcode instance: %s" % err) 109 110 try: 111 op.Validate(False) 112 except errors.OpPrereqError, err: 113 raise VerificationError("Parameter validation for opcode '%s' failed: %s" % 114 (op_id, err))115119 """Verifies opcode results used in tests (e.g. in a mock). 120 121 @type op_id: string 122 @param op_id: Opcode ID (C{OP_ID} attribute), e.g. C{OP_CLUSTER_VERIFY} 123 @param result: Mocked opcode result 124 @raise VerificationError: Return value verification failed 125 126 """ 127 resultcheck_fn = _GetOpById(op_id).OP_RESULT 128 129 if not resultcheck_fn: 130 logging.warning("Opcode '%s' has no result type definition", op_id) 131 elif not resultcheck_fn(result): 132 raise VerificationError("Given result does not match result description" 133 " for opcode '%s': %s" % (op_id, resultcheck_fn))134137 """Gets the path and query from a URI. 138 139 """ 140 match = _URI_RE.match(uri) 141 if match: 142 return match.groupdict()["path"] 143 else: 144 return None145148 """Formats HTTP headers. 149 150 @type headers: sequence of strings 151 @rtype: string 152 153 """ 154 assert compat.all(": " in header for header in headers) 155 return "\n".join(headers)156159 """Fake cURL object. 160 161 """217163 """Initialize this class 164 165 @param handler: Request handler instance 166 167 """ 168 self._handler = handler 169 self._opts = {} 170 self._info = {}171 174176 return self._opts.get(opt)177 180 183185 method = self._opts[pycurl.CUSTOMREQUEST] 186 url = self._opts[pycurl.URL] 187 request_body = self._opts[pycurl.POSTFIELDS] 188 writefn = self._opts[pycurl.WRITEFUNCTION] 189 190 if pycurl.HTTPHEADER in self._opts: 191 baseheaders = _FormatHeaders(self._opts[pycurl.HTTPHEADER]) 192 else: 193 baseheaders = "" 194 195 headers = http.ParseHeaders(StringIO(baseheaders)) 196 197 if request_body: 198 headers[http.HTTP_CONTENT_LENGTH] = str(len(request_body)) 199 200 if self._opts.get(pycurl.HTTPAUTH, 0) & pycurl.HTTPAUTH_BASIC: 201 try: 202 userpwd = self._opts[pycurl.USERPWD] 203 except KeyError: 204 raise errors.ProgrammerError("Basic authentication requires username" 205 " and password") 206 207 headers[http.HTTP_AUTHORIZATION] = \ 208 "%s %s" % (http.auth.HTTP_BASIC_AUTH, base64.b64encode(userpwd)) 209 210 path = _GetPathFromUri(url) 211 (code, _, resp_body) = \ 212 self._handler.FetchResponse(path, method, headers, request_body) 213 214 self._info[pycurl.RESPONSE_CODE] = code 215 if resp_body is not None: 216 writefn(resp_body)220 """Mocking out the RAPI server parts. 221 222 """260224 """Initialize this class. 225 226 @type user_fn: callable 227 @param user_fn: Function to authentication username 228 @param luxi_client: A LUXI client implementation 229 230 """ 231 self.handler = \ 232 server.rapi.RemoteApiHandler(user_fn, reqauth, _client_cls=luxi_client)233235 """This is a callback method used to fetch a response. 236 237 This method is called by the FakeCurl.perform method 238 239 @type path: string 240 @param path: Requested path 241 @type method: string 242 @param method: HTTP method 243 @type request_body: string 244 @param request_body: Request body 245 @type headers: mimetools.Message 246 @param headers: Request headers 247 @return: Tuple containing status code, response headers and response body 248 249 """ 250 req_msg = http.HttpMessage() 251 req_msg.start_line = \ 252 http.HttpClientToServerStartLine(method, path, http.HTTP_1_0) 253 req_msg.headers = headers 254 req_msg.body = request_body 255 256 (_, _, _, resp_msg) = \ 257 http.server.HttpResponder(self.handler)(lambda: (req_msg, None)) 258 259 return (resp_msg.start_line.code, resp_msg.headers, resp_msg.body)263 """Mocked LUXI transport. 264 265 Raises L{errors.RapiTestResult} for all method calls, no matter the 266 arguments. 267 268 """275 278269 - def __init__(self, record_fn, address, timeouts=None, # pylint: disable=W0613 270 allow_non_master=None): # pylint: disable=W0613271 """Initializes this class. 272 273 """ 274 self._record_fn = record_fn280 """Calls LUXI method. 281 282 In this test class the method is not actually called, but added to a list 283 of called methods and then an exception (L{errors.RapiTestResult}) is 284 raised. There is no return value. 285 286 """ 287 (method, _, _) = rpccl.ParseRequest(data) 288 289 # Take a note of called method 290 self._record_fn(method) 291 292 # Everything went fine until here, so let's abort the test 293 raise errors.RapiTestResult294 328331 """Wrapper for ignoring L{errors.RapiTestResult}. 332 333 """ 334 try: 335 return fn(*args, **kwargs) 336 except errors.RapiTestResult: 337 # Everything was fine up to the point of sending a LUXI request 338 return NotImplemented339342 """Test version of RAPI client. 343 344 Instances of this class can be used to test input arguments for RAPI client 345 calls. See L{rapi.client.GanetiRapiClient} for available methods and their 346 arguments. Functions can return C{NotImplemented} if all arguments are 347 acceptable, but a LUXI request would be necessary to provide an actual return 348 value. In case of an error, L{VerificationError} is raised. 349 350 @see: An example on how to use this class can be found in 351 C{doc/examples/rapi_testutils.py} 352 353 """378355 """Initializes this class. 356 357 """ 358 username = utils.GenerateSecret() 359 password = utils.GenerateSecret() 360 361 def user_fn(wanted): 362 """Called to verify user credentials given in HTTP request. 363 364 """ 365 assert username == wanted 366 return http.auth.PasswordFileUser(username, password, 367 [rapi.RAPI_ACCESS_WRITE])368 369 self._lcr = _LuxiCallRecorder() 370 371 # Create a mock RAPI server 372 handler = _RapiMock(user_fn, self._lcr) 373 374 self._client = \ 375 rapi.client.GanetiRapiClient("master.example.com", 376 username=username, password=password, 377 curl_factory=lambda: FakeCurl(handler))380 """Returns the names of all called LUXI client functions. 381 382 """ 383 return self._lcr.CalledNames()384386 """Finds method by name. 387 388 The method is wrapped using L{_TestWrapper} to produce the actual test 389 result. 390 391 """ 392 return _HideInternalErrors(compat.partial(_TestWrapper, 393 getattr(self._client, name)))394
Trees | Indices | Help |
|
---|
Generated by Epydoc 3.0.1 on Tue Apr 12 13:39:00 2016 | http://epydoc.sourceforge.net |