1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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 from ganeti import rapi
49
50 import ganeti.http.server
51 import ganeti.server.rapi
52 import ganeti.rapi.client
53
54
55 _URI_RE = re.compile(r"https://(?P<host>.*):(?P<port>\d+)(?P<path>/.*)")
59 """Dedicated error class for test utilities.
60
61 This class is used to hide all of Ganeti's internal exception, so that
62 external users of these utilities don't have to integrate Ganeti's exception
63 hierarchy.
64
65 """
66
69 """Tries to get an opcode class based on its C{OP_ID}.
70
71 """
72 try:
73 return opcodes.OP_MAPPING[op_id]
74 except KeyError:
75 raise VerificationError("Unknown opcode ID '%s'" % op_id)
76
87
88 return wrapper
89
114
118 """Verifies opcode results used in tests (e.g. in a mock).
119
120 @type op_id: string
121 @param op_id: Opcode ID (C{OP_ID} attribute), e.g. C{OP_CLUSTER_VERIFY}
122 @param result: Mocked opcode result
123 @raise VerificationError: Return value verification failed
124
125 """
126 resultcheck_fn = _GetOpById(op_id).OP_RESULT
127
128 if not resultcheck_fn:
129 logging.warning("Opcode '%s' has no result type definition", op_id)
130 elif not resultcheck_fn(result):
131 raise VerificationError("Given result does not match result description"
132 " for opcode '%s': %s" % (op_id, resultcheck_fn))
133
136 """Gets the path and query from a URI.
137
138 """
139 match = _URI_RE.match(uri)
140 if match:
141 return match.groupdict()["path"]
142 else:
143 return None
144
155
158 """Fake cURL object.
159
160 """
162 """Initialize this class
163
164 @param handler: Request handler instance
165
166 """
167 self._handler = handler
168 self._opts = {}
169 self._info = {}
170
171 - def setopt(self, opt, value):
172 self._opts[opt] = value
173
175 return self._opts.get(opt)
176
178 self._opts.pop(opt, None)
179
181 return self._info[info]
182
216
219 """Mocking out the RAPI server parts.
220
221 """
222 - def __init__(self, user_fn, luxi_client, reqauth=False):
223 """Initialize this class.
224
225 @type user_fn: callable
226 @param user_fn: Function to authentication username
227 @param luxi_client: A LUXI client implementation
228
229 """
230 self.handler = \
231 server.rapi.RemoteApiHandler(user_fn, reqauth, _client_cls=luxi_client)
232
234 """This is a callback method used to fetch a response.
235
236 This method is called by the FakeCurl.perform method
237
238 @type path: string
239 @param path: Requested path
240 @type method: string
241 @param method: HTTP method
242 @type request_body: string
243 @param request_body: Request body
244 @type headers: mimetools.Message
245 @param headers: Request headers
246 @return: Tuple containing status code, response headers and response body
247
248 """
249 req_msg = http.HttpMessage()
250 req_msg.start_line = \
251 http.HttpClientToServerStartLine(method, path, http.HTTP_1_0)
252 req_msg.headers = headers
253 req_msg.body = request_body
254
255 (_, _, _, resp_msg) = \
256 http.server.HttpResponder(self.handler)(lambda: (req_msg, None))
257
258 return (resp_msg.start_line.code, resp_msg.headers, resp_msg.body)
259
262 """Mocked LUXI transport.
263
264 Raises L{errors.RapiTestResult} for all method calls, no matter the
265 arguments.
266
267 """
268 - def __init__(self, record_fn, address, timeouts=None):
269 """Initializes this class.
270
271 """
272 self._record_fn = record_fn
273
276
277 - def Call(self, data):
278 """Calls LUXI method.
279
280 In this test class the method is not actually called, but added to a list
281 of called methods and then an exception (L{errors.RapiTestResult}) is
282 raised. There is no return value.
283
284 """
285 (method, _, _) = luxi.ParseRequest(data)
286
287
288 self._record_fn(method)
289
290
291 raise errors.RapiTestResult
292
295 """Records all called LUXI client methods.
296
297 """
299 """Initializes this class.
300
301 """
302 self._called = set()
303
305 """Records a called function name.
306
307 """
308 self._called.add(name)
309
311 """Returns a list of called LUXI methods.
312
313 """
314 return self._called
315
317 """Creates an instrumented LUXI client.
318
319 The LUXI client will record all method calls (use L{CalledNames} to
320 retrieve them).
321
322 """
323 return luxi.Client(transport=compat.partial(_TestLuxiTransport,
324 self.Record),
325 address=address)
326
329 """Wrapper for ignoring L{errors.RapiTestResult}.
330
331 """
332 try:
333 return fn(*args, **kwargs)
334 except errors.RapiTestResult:
335
336 return NotImplemented
337
376
382
392