1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 """Remote API test utilities.
23
24 """
25
26 import logging
27 import re
28 import base64
29 import pycurl
30 from cStringIO import StringIO
31
32 from ganeti import errors
33 from ganeti import opcodes
34 from ganeti import http
35 from ganeti import server
36 from ganeti import utils
37 from ganeti import compat
38 from ganeti import luxi
39 from ganeti import rapi
40
41 import ganeti.http.server
42 import ganeti.server.rapi
43 import ganeti.rapi.client
44
45
46 _URI_RE = re.compile(r"https://(?P<host>.*):(?P<port>\d+)(?P<path>/.*)")
50 """Dedicated error class for test utilities.
51
52 This class is used to hide all of Ganeti's internal exception, so that
53 external users of these utilities don't have to integrate Ganeti's exception
54 hierarchy.
55
56 """
57
60 """Tries to get an opcode class based on its C{OP_ID}.
61
62 """
63 try:
64 return opcodes.OP_MAPPING[op_id]
65 except KeyError:
66 raise VerificationError("Unknown opcode ID '%s'" % op_id)
67
78
79 return wrapper
80
105
109 """Verifies opcode results used in tests (e.g. in a mock).
110
111 @type op_id: string
112 @param op_id: Opcode ID (C{OP_ID} attribute), e.g. C{OP_CLUSTER_VERIFY}
113 @param result: Mocked opcode result
114 @raise VerificationError: Return value verification failed
115
116 """
117 resultcheck_fn = _GetOpById(op_id).OP_RESULT
118
119 if not resultcheck_fn:
120 logging.warning("Opcode '%s' has no result type definition", op_id)
121 elif not resultcheck_fn(result):
122 raise VerificationError("Given result does not match result description"
123 " for opcode '%s': %s" % (op_id, resultcheck_fn))
124
127 """Gets the path and query from a URI.
128
129 """
130 match = _URI_RE.match(uri)
131 if match:
132 return match.groupdict()["path"]
133 else:
134 return None
135
146
149 """Fake cURL object.
150
151 """
153 """Initialize this class
154
155 @param handler: Request handler instance
156
157 """
158 self._handler = handler
159 self._opts = {}
160 self._info = {}
161
162 - def setopt(self, opt, value):
163 self._opts[opt] = value
164
166 return self._opts.get(opt)
167
169 self._opts.pop(opt, None)
170
172 return self._info[info]
173
207
210 """Mocking out the RAPI server parts.
211
212 """
213 - def __init__(self, user_fn, luxi_client, reqauth=False):
214 """Initialize this class.
215
216 @type user_fn: callable
217 @param user_fn: Function to authentication username
218 @param luxi_client: A LUXI client implementation
219
220 """
221 self.handler = \
222 server.rapi.RemoteApiHandler(user_fn, reqauth, _client_cls=luxi_client)
223
225 """This is a callback method used to fetch a response.
226
227 This method is called by the FakeCurl.perform method
228
229 @type path: string
230 @param path: Requested path
231 @type method: string
232 @param method: HTTP method
233 @type request_body: string
234 @param request_body: Request body
235 @type headers: mimetools.Message
236 @param headers: Request headers
237 @return: Tuple containing status code, response headers and response body
238
239 """
240 req_msg = http.HttpMessage()
241 req_msg.start_line = \
242 http.HttpClientToServerStartLine(method, path, http.HTTP_1_0)
243 req_msg.headers = headers
244 req_msg.body = request_body
245
246 (_, _, _, resp_msg) = \
247 http.server.HttpResponder(self.handler)(lambda: (req_msg, None))
248
249 return (resp_msg.start_line.code, resp_msg.headers, resp_msg.body)
250
253 """Mocked LUXI transport.
254
255 Raises L{errors.RapiTestResult} for all method calls, no matter the
256 arguments.
257
258 """
259 - def __init__(self, record_fn, address, timeouts=None):
260 """Initializes this class.
261
262 """
263 self._record_fn = record_fn
264
267
268 - def Call(self, data):
269 """Calls LUXI method.
270
271 In this test class the method is not actually called, but added to a list
272 of called methods and then an exception (L{errors.RapiTestResult}) is
273 raised. There is no return value.
274
275 """
276 (method, _, _) = luxi.ParseRequest(data)
277
278
279 self._record_fn(method)
280
281
282 raise errors.RapiTestResult
283
286 """Records all called LUXI client methods.
287
288 """
290 """Initializes this class.
291
292 """
293 self._called = set()
294
296 """Records a called function name.
297
298 """
299 self._called.add(name)
300
302 """Returns a list of called LUXI methods.
303
304 """
305 return self._called
306
308 """Creates an instrumented LUXI client.
309
310 The LUXI client will record all method calls (use L{CalledNames} to
311 retrieve them).
312
313 """
314 return luxi.Client(transport=compat.partial(_TestLuxiTransport,
315 self.Record),
316 address=address)
317
320 """Wrapper for ignoring L{errors.RapiTestResult}.
321
322 """
323 try:
324 return fn(*args, **kwargs)
325 except errors.RapiTestResult:
326
327 return NotImplemented
328
367
373
383