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 """261224 """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 req_reader = type('TestReader', (object, ), {'sock': None})() 256 257 (_, _, _, resp_msg) = \ 258 http.server.HttpResponder(self.handler)(lambda: (req_msg, req_reader)) 259 260 return (resp_msg.start_line.code, resp_msg.headers, resp_msg.body)264 """Mocked LUXI transport. 265 266 Raises L{errors.RapiTestResult} for all method calls, no matter the 267 arguments. 268 269 """276 279270 - def __init__(self, record_fn, address, timeouts=None, # pylint: disable=W0613 271 allow_non_master=None): # pylint: disable=W0613272 """Initializes this class. 273 274 """ 275 self._record_fn = record_fn281 """Calls LUXI method. 282 283 In this test class the method is not actually called, but added to a list 284 of called methods and then an exception (L{errors.RapiTestResult}) is 285 raised. There is no return value. 286 287 """ 288 (method, _, _) = rpccl.ParseRequest(data) 289 290 # Take a note of called method 291 self._record_fn(method) 292 293 # Everything went fine until here, so let's abort the test 294 raise errors.RapiTestResult295 329332 """Wrapper for ignoring L{errors.RapiTestResult}. 333 334 """ 335 try: 336 return fn(*args, **kwargs) 337 except errors.RapiTestResult: 338 # Everything was fine up to the point of sending a LUXI request 339 return NotImplemented340343 """Test version of RAPI client. 344 345 Instances of this class can be used to test input arguments for RAPI client 346 calls. See L{rapi.client.GanetiRapiClient} for available methods and their 347 arguments. Functions can return C{NotImplemented} if all arguments are 348 acceptable, but a LUXI request would be necessary to provide an actual return 349 value. In case of an error, L{VerificationError} is raised. 350 351 @see: An example on how to use this class can be found in 352 C{doc/examples/rapi_testutils.py} 353 354 """379356 """Initializes this class. 357 358 """ 359 username = utils.GenerateSecret() 360 password = utils.GenerateSecret() 361 362 def user_fn(wanted): 363 """Called to verify user credentials given in HTTP request. 364 365 """ 366 assert username == wanted 367 return http.auth.PasswordFileUser(username, password, 368 [rapi.RAPI_ACCESS_WRITE])369 370 self._lcr = _LuxiCallRecorder() 371 372 # Create a mock RAPI server 373 handler = _RapiMock(user_fn, self._lcr) 374 375 self._client = \ 376 rapi.client.GanetiRapiClient("master.example.com", 377 username=username, password=password, 378 curl_factory=lambda: FakeCurl(handler))381 """Returns the names of all called LUXI client functions. 382 383 """ 384 return self._lcr.CalledNames()385387 """Finds method by name. 388 389 The method is wrapped using L{_TestWrapper} to produce the actual test 390 result. 391 392 """ 393 return _HideInternalErrors(compat.partial(_TestWrapper, 394 getattr(self._client, name)))395
Trees | Indices | Help |
|
---|
Generated by Epydoc 3.0.1 on Mon Aug 22 11:13:44 2016 | http://epydoc.sourceforge.net |