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 import ganeti.rpc.client as rpccl
49 from ganeti import rapi
50
51 import ganeti.http.server
52 import ganeti.server.rapi
53 from ganeti.rapi.auth import users_file
54 import ganeti.rapi.client
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 """
67
70 """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)
77
88
89 return wrapper
90
115
119 """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))
134
137 """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 None
145
156
159 """Fake cURL object.
160
161 """
163 """Initialize this class
164
165 @param handler: Request handler instance
166
167 """
168 self._handler = handler
169 self._opts = {}
170 self._info = {}
171
172 - def setopt(self, opt, value):
173 self._opts[opt] = value
174
176 return self._opts.get(opt)
177
179 self._opts.pop(opt, None)
180
182 return self._info[info]
183
217
220 """Mocking out the RAPI server parts.
221
222 """
223 - def __init__(self, user_fn, luxi_client, reqauth=False):
224 """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)
233
235 """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)
260
263 """Mocked LUXI transport.
264
265 Raises L{errors.RapiTestResult} for all method calls, no matter the
266 arguments.
267
268 """
269 - def __init__(self, record_fn, address, timeouts=None,
270 allow_non_master=None):
271 """Initializes this class.
272
273 """
274 self._record_fn = record_fn
275
278
279 - def Call(self, data):
280 """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
290 self._record_fn(method)
291
292
293 raise errors.RapiTestResult
294
297 """Records all called LUXI client methods.
298
299 """
301 """Initializes this class.
302
303 """
304 self._called = set()
305
307 """Records a called function name.
308
309 """
310 self._called.add(name)
311
313 """Returns a list of called LUXI methods.
314
315 """
316 return self._called
317
328
331 """Wrapper for ignoring L{errors.RapiTestResult}.
332
333 """
334 try:
335 return fn(*args, **kwargs)
336 except errors.RapiTestResult:
337
338 return NotImplemented
339
373
374 self._lcr = _LuxiCallRecorder()
375
376
377 handler = _RapiMock(SimpleAuthenticator(), self._lcr)
378
379 self._client = \
380 rapi.client.GanetiRapiClient("master.example.com",
381 username=username, password=password,
382 curl_factory=lambda: FakeCurl(handler))
383
389
399