Statistics
| Branch: | Tag: | Revision:

root / lib / rapi / testutils.py @ 1fa6fcba

History | View | Annotate | Download (9.9 kB)

1 a85f23fa Michael Hanselmann
#
2 a85f23fa Michael Hanselmann
#
3 a85f23fa Michael Hanselmann
4 a85f23fa Michael Hanselmann
# Copyright (C) 2012 Google Inc.
5 a85f23fa Michael Hanselmann
#
6 a85f23fa Michael Hanselmann
# This program is free software; you can redistribute it and/or modify
7 a85f23fa Michael Hanselmann
# it under the terms of the GNU General Public License as published by
8 a85f23fa Michael Hanselmann
# the Free Software Foundation; either version 2 of the License, or
9 a85f23fa Michael Hanselmann
# (at your option) any later version.
10 a85f23fa Michael Hanselmann
#
11 a85f23fa Michael Hanselmann
# This program is distributed in the hope that it will be useful, but
12 a85f23fa Michael Hanselmann
# WITHOUT ANY WARRANTY; without even the implied warranty of
13 a85f23fa Michael Hanselmann
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 a85f23fa Michael Hanselmann
# General Public License for more details.
15 a85f23fa Michael Hanselmann
#
16 a85f23fa Michael Hanselmann
# You should have received a copy of the GNU General Public License
17 a85f23fa Michael Hanselmann
# along with this program; if not, write to the Free Software
18 a85f23fa Michael Hanselmann
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 a85f23fa Michael Hanselmann
# 02110-1301, USA.
20 a85f23fa Michael Hanselmann
21 a85f23fa Michael Hanselmann
22 a85f23fa Michael Hanselmann
"""Remote API test utilities.
23 a85f23fa Michael Hanselmann

24 a85f23fa Michael Hanselmann
"""
25 a85f23fa Michael Hanselmann
26 a85f23fa Michael Hanselmann
import logging
27 f90a1ab5 René Nussbaumer
import re
28 d9492490 Michael Hanselmann
import base64
29 f90a1ab5 René Nussbaumer
import pycurl
30 d9492490 Michael Hanselmann
from cStringIO import StringIO
31 a85f23fa Michael Hanselmann
32 a85f23fa Michael Hanselmann
from ganeti import errors
33 a85f23fa Michael Hanselmann
from ganeti import opcodes
34 d9492490 Michael Hanselmann
from ganeti import http
35 1afa108c Michael Hanselmann
from ganeti import server
36 1afa108c Michael Hanselmann
from ganeti import utils
37 1afa108c Michael Hanselmann
from ganeti import compat
38 1afa108c Michael Hanselmann
from ganeti import luxi
39 1afa108c Michael Hanselmann
from ganeti import rapi
40 1afa108c Michael Hanselmann
41 1afa108c Michael Hanselmann
import ganeti.http.server # pylint: disable=W0611
42 1afa108c Michael Hanselmann
import ganeti.server.rapi
43 1afa108c Michael Hanselmann
import ganeti.rapi.client
44 a85f23fa Michael Hanselmann
45 a85f23fa Michael Hanselmann
46 f90a1ab5 René Nussbaumer
_URI_RE = re.compile(r"https://(?P<host>.*):(?P<port>\d+)(?P<path>/.*)")
47 f90a1ab5 René Nussbaumer
48 f90a1ab5 René Nussbaumer
49 a85f23fa Michael Hanselmann
class VerificationError(Exception):
50 a85f23fa Michael Hanselmann
  """Dedicated error class for test utilities.
51 a85f23fa Michael Hanselmann

52 a85f23fa Michael Hanselmann
  This class is used to hide all of Ganeti's internal exception, so that
53 a85f23fa Michael Hanselmann
  external users of these utilities don't have to integrate Ganeti's exception
54 a85f23fa Michael Hanselmann
  hierarchy.
55 a85f23fa Michael Hanselmann

56 a85f23fa Michael Hanselmann
  """
57 a85f23fa Michael Hanselmann
58 a85f23fa Michael Hanselmann
59 a85f23fa Michael Hanselmann
def _GetOpById(op_id):
60 a85f23fa Michael Hanselmann
  """Tries to get an opcode class based on its C{OP_ID}.
61 a85f23fa Michael Hanselmann

62 a85f23fa Michael Hanselmann
  """
63 a85f23fa Michael Hanselmann
  try:
64 a85f23fa Michael Hanselmann
    return opcodes.OP_MAPPING[op_id]
65 a85f23fa Michael Hanselmann
  except KeyError:
66 a85f23fa Michael Hanselmann
    raise VerificationError("Unknown opcode ID '%s'" % op_id)
67 a85f23fa Michael Hanselmann
68 a85f23fa Michael Hanselmann
69 a85f23fa Michael Hanselmann
def _HideInternalErrors(fn):
70 a85f23fa Michael Hanselmann
  """Hides Ganeti-internal exceptions, see L{VerificationError}.
71 a85f23fa Michael Hanselmann

72 a85f23fa Michael Hanselmann
  """
73 a85f23fa Michael Hanselmann
  def wrapper(*args, **kwargs):
74 a85f23fa Michael Hanselmann
    try:
75 a85f23fa Michael Hanselmann
      return fn(*args, **kwargs)
76 1afa108c Michael Hanselmann
    except (errors.GenericError, rapi.client.GanetiApiError), err:
77 a85f23fa Michael Hanselmann
      raise VerificationError("Unhandled Ganeti error: %s" % err)
78 a85f23fa Michael Hanselmann
79 a85f23fa Michael Hanselmann
  return wrapper
80 a85f23fa Michael Hanselmann
81 a85f23fa Michael Hanselmann
82 a85f23fa Michael Hanselmann
@_HideInternalErrors
83 a85f23fa Michael Hanselmann
def VerifyOpInput(op_id, data):
84 a85f23fa Michael Hanselmann
  """Verifies opcode parameters according to their definition.
85 a85f23fa Michael Hanselmann

86 a85f23fa Michael Hanselmann
  @type op_id: string
87 a85f23fa Michael Hanselmann
  @param op_id: Opcode ID (C{OP_ID} attribute), e.g. C{OP_CLUSTER_VERIFY}
88 a85f23fa Michael Hanselmann
  @type data: dict
89 a85f23fa Michael Hanselmann
  @param data: Opcode parameter values
90 a85f23fa Michael Hanselmann
  @raise VerificationError: Parameter verification failed
91 a85f23fa Michael Hanselmann

92 a85f23fa Michael Hanselmann
  """
93 a85f23fa Michael Hanselmann
  op_cls = _GetOpById(op_id)
94 a85f23fa Michael Hanselmann
95 a85f23fa Michael Hanselmann
  try:
96 a85f23fa Michael Hanselmann
    op = op_cls(**data) # pylint: disable=W0142
97 a85f23fa Michael Hanselmann
  except TypeError, err:
98 a85f23fa Michael Hanselmann
    raise VerificationError("Unable to create opcode instance: %s" % err)
99 a85f23fa Michael Hanselmann
100 a85f23fa Michael Hanselmann
  try:
101 a85f23fa Michael Hanselmann
    op.Validate(False)
102 a85f23fa Michael Hanselmann
  except errors.OpPrereqError, err:
103 a85f23fa Michael Hanselmann
    raise VerificationError("Parameter validation for opcode '%s' failed: %s" %
104 a85f23fa Michael Hanselmann
                            (op_id, err))
105 a85f23fa Michael Hanselmann
106 a85f23fa Michael Hanselmann
107 a85f23fa Michael Hanselmann
@_HideInternalErrors
108 a85f23fa Michael Hanselmann
def VerifyOpResult(op_id, result):
109 a85f23fa Michael Hanselmann
  """Verifies opcode results used in tests (e.g. in a mock).
110 a85f23fa Michael Hanselmann

111 a85f23fa Michael Hanselmann
  @type op_id: string
112 a85f23fa Michael Hanselmann
  @param op_id: Opcode ID (C{OP_ID} attribute), e.g. C{OP_CLUSTER_VERIFY}
113 a85f23fa Michael Hanselmann
  @param result: Mocked opcode result
114 a85f23fa Michael Hanselmann
  @raise VerificationError: Return value verification failed
115 a85f23fa Michael Hanselmann

116 a85f23fa Michael Hanselmann
  """
117 a85f23fa Michael Hanselmann
  resultcheck_fn = _GetOpById(op_id).OP_RESULT
118 a85f23fa Michael Hanselmann
119 a85f23fa Michael Hanselmann
  if not resultcheck_fn:
120 a85f23fa Michael Hanselmann
    logging.warning("Opcode '%s' has no result type definition", op_id)
121 a85f23fa Michael Hanselmann
  elif not resultcheck_fn(result):
122 a85f23fa Michael Hanselmann
    raise VerificationError("Given result does not match result description"
123 a85f23fa Michael Hanselmann
                            " for opcode '%s': %s" % (op_id, resultcheck_fn))
124 f90a1ab5 René Nussbaumer
125 f90a1ab5 René Nussbaumer
126 f90a1ab5 René Nussbaumer
def _GetPathFromUri(uri):
127 f90a1ab5 René Nussbaumer
  """Gets the path and query from a URI.
128 f90a1ab5 René Nussbaumer

129 f90a1ab5 René Nussbaumer
  """
130 f90a1ab5 René Nussbaumer
  match = _URI_RE.match(uri)
131 f90a1ab5 René Nussbaumer
  if match:
132 f90a1ab5 René Nussbaumer
    return match.groupdict()["path"]
133 f90a1ab5 René Nussbaumer
  else:
134 f90a1ab5 René Nussbaumer
    return None
135 f90a1ab5 René Nussbaumer
136 f90a1ab5 René Nussbaumer
137 1b8e72f3 Michael Hanselmann
def _FormatHeaders(headers):
138 1b8e72f3 Michael Hanselmann
  """Formats HTTP headers.
139 1b8e72f3 Michael Hanselmann

140 1b8e72f3 Michael Hanselmann
  @type headers: sequence of strings
141 1b8e72f3 Michael Hanselmann
  @rtype: string
142 1b8e72f3 Michael Hanselmann

143 1b8e72f3 Michael Hanselmann
  """
144 1b8e72f3 Michael Hanselmann
  assert compat.all(": " in header for header in headers)
145 1b8e72f3 Michael Hanselmann
  return "\n".join(headers)
146 1b8e72f3 Michael Hanselmann
147 1b8e72f3 Michael Hanselmann
148 f90a1ab5 René Nussbaumer
class FakeCurl:
149 f90a1ab5 René Nussbaumer
  """Fake cURL object.
150 f90a1ab5 René Nussbaumer

151 f90a1ab5 René Nussbaumer
  """
152 f90a1ab5 René Nussbaumer
  def __init__(self, handler):
153 f90a1ab5 René Nussbaumer
    """Initialize this class
154 f90a1ab5 René Nussbaumer

155 f90a1ab5 René Nussbaumer
    @param handler: Request handler instance
156 f90a1ab5 René Nussbaumer

157 f90a1ab5 René Nussbaumer
    """
158 f90a1ab5 René Nussbaumer
    self._handler = handler
159 f90a1ab5 René Nussbaumer
    self._opts = {}
160 f90a1ab5 René Nussbaumer
    self._info = {}
161 f90a1ab5 René Nussbaumer
162 f90a1ab5 René Nussbaumer
  def setopt(self, opt, value):
163 f90a1ab5 René Nussbaumer
    self._opts[opt] = value
164 f90a1ab5 René Nussbaumer
165 f90a1ab5 René Nussbaumer
  def getopt(self, opt):
166 f90a1ab5 René Nussbaumer
    return self._opts.get(opt)
167 f90a1ab5 René Nussbaumer
168 f90a1ab5 René Nussbaumer
  def unsetopt(self, opt):
169 f90a1ab5 René Nussbaumer
    self._opts.pop(opt, None)
170 f90a1ab5 René Nussbaumer
171 f90a1ab5 René Nussbaumer
  def getinfo(self, info):
172 f90a1ab5 René Nussbaumer
    return self._info[info]
173 f90a1ab5 René Nussbaumer
174 f90a1ab5 René Nussbaumer
  def perform(self):
175 f90a1ab5 René Nussbaumer
    method = self._opts[pycurl.CUSTOMREQUEST]
176 f90a1ab5 René Nussbaumer
    url = self._opts[pycurl.URL]
177 f90a1ab5 René Nussbaumer
    request_body = self._opts[pycurl.POSTFIELDS]
178 f90a1ab5 René Nussbaumer
    writefn = self._opts[pycurl.WRITEFUNCTION]
179 f90a1ab5 René Nussbaumer
180 d9492490 Michael Hanselmann
    if pycurl.HTTPHEADER in self._opts:
181 1b8e72f3 Michael Hanselmann
      baseheaders = _FormatHeaders(self._opts[pycurl.HTTPHEADER])
182 d9492490 Michael Hanselmann
    else:
183 d9492490 Michael Hanselmann
      baseheaders = ""
184 d9492490 Michael Hanselmann
185 0e632cbd Michael Hanselmann
    headers = http.ParseHeaders(StringIO(baseheaders))
186 d9492490 Michael Hanselmann
187 d9492490 Michael Hanselmann
    if request_body:
188 d9492490 Michael Hanselmann
      headers[http.HTTP_CONTENT_LENGTH] = str(len(request_body))
189 d9492490 Michael Hanselmann
190 d9492490 Michael Hanselmann
    if self._opts.get(pycurl.HTTPAUTH, 0) & pycurl.HTTPAUTH_BASIC:
191 d9492490 Michael Hanselmann
      try:
192 d9492490 Michael Hanselmann
        userpwd = self._opts[pycurl.USERPWD]
193 d9492490 Michael Hanselmann
      except KeyError:
194 d9492490 Michael Hanselmann
        raise errors.ProgrammerError("Basic authentication requires username"
195 d9492490 Michael Hanselmann
                                     " and password")
196 d9492490 Michael Hanselmann
197 d9492490 Michael Hanselmann
      headers[http.HTTP_AUTHORIZATION] = \
198 d9492490 Michael Hanselmann
        "%s %s" % (http.auth.HTTP_BASIC_AUTH, base64.b64encode(userpwd))
199 d9492490 Michael Hanselmann
200 f90a1ab5 René Nussbaumer
    path = _GetPathFromUri(url)
201 0351944b Michael Hanselmann
    (code, _, resp_body) = \
202 d9492490 Michael Hanselmann
      self._handler.FetchResponse(path, method, headers, request_body)
203 f90a1ab5 René Nussbaumer
204 f90a1ab5 René Nussbaumer
    self._info[pycurl.RESPONSE_CODE] = code
205 f90a1ab5 René Nussbaumer
    if resp_body is not None:
206 f90a1ab5 René Nussbaumer
      writefn(resp_body)
207 1afa108c Michael Hanselmann
208 1afa108c Michael Hanselmann
209 1afa108c Michael Hanselmann
class _RapiMock:
210 1afa108c Michael Hanselmann
  """Mocking out the RAPI server parts.
211 1afa108c Michael Hanselmann

212 1afa108c Michael Hanselmann
  """
213 27a8a190 Michael Hanselmann
  def __init__(self, user_fn, luxi_client, reqauth=False):
214 1afa108c Michael Hanselmann
    """Initialize this class.
215 1afa108c Michael Hanselmann

216 1afa108c Michael Hanselmann
    @type user_fn: callable
217 1afa108c Michael Hanselmann
    @param user_fn: Function to authentication username
218 1afa108c Michael Hanselmann
    @param luxi_client: A LUXI client implementation
219 1afa108c Michael Hanselmann

220 1afa108c Michael Hanselmann
    """
221 1afa108c Michael Hanselmann
    self.handler = \
222 27a8a190 Michael Hanselmann
      server.rapi.RemoteApiHandler(user_fn, reqauth, _client_cls=luxi_client)
223 1afa108c Michael Hanselmann
224 1afa108c Michael Hanselmann
  def FetchResponse(self, path, method, headers, request_body):
225 1afa108c Michael Hanselmann
    """This is a callback method used to fetch a response.
226 1afa108c Michael Hanselmann

227 1afa108c Michael Hanselmann
    This method is called by the FakeCurl.perform method
228 1afa108c Michael Hanselmann

229 1afa108c Michael Hanselmann
    @type path: string
230 1afa108c Michael Hanselmann
    @param path: Requested path
231 1afa108c Michael Hanselmann
    @type method: string
232 1afa108c Michael Hanselmann
    @param method: HTTP method
233 1afa108c Michael Hanselmann
    @type request_body: string
234 1afa108c Michael Hanselmann
    @param request_body: Request body
235 1afa108c Michael Hanselmann
    @type headers: mimetools.Message
236 1afa108c Michael Hanselmann
    @param headers: Request headers
237 0351944b Michael Hanselmann
    @return: Tuple containing status code, response headers and response body
238 1afa108c Michael Hanselmann

239 1afa108c Michael Hanselmann
    """
240 1afa108c Michael Hanselmann
    req_msg = http.HttpMessage()
241 1afa108c Michael Hanselmann
    req_msg.start_line = \
242 1afa108c Michael Hanselmann
      http.HttpClientToServerStartLine(method, path, http.HTTP_1_0)
243 1afa108c Michael Hanselmann
    req_msg.headers = headers
244 1afa108c Michael Hanselmann
    req_msg.body = request_body
245 1afa108c Michael Hanselmann
246 1afa108c Michael Hanselmann
    (_, _, _, resp_msg) = \
247 1afa108c Michael Hanselmann
      http.server.HttpResponder(self.handler)(lambda: (req_msg, None))
248 1afa108c Michael Hanselmann
249 0351944b Michael Hanselmann
    return (resp_msg.start_line.code, resp_msg.headers, resp_msg.body)
250 1afa108c Michael Hanselmann
251 1afa108c Michael Hanselmann
252 1afa108c Michael Hanselmann
class _TestLuxiTransport:
253 1afa108c Michael Hanselmann
  """Mocked LUXI transport.
254 1afa108c Michael Hanselmann

255 1afa108c Michael Hanselmann
  Raises L{errors.RapiTestResult} for all method calls, no matter the
256 1afa108c Michael Hanselmann
  arguments.
257 1afa108c Michael Hanselmann

258 1afa108c Michael Hanselmann
  """
259 1afa108c Michael Hanselmann
  def __init__(self, record_fn, address, timeouts=None): # pylint: disable=W0613
260 1afa108c Michael Hanselmann
    """Initializes this class.
261 1afa108c Michael Hanselmann

262 1afa108c Michael Hanselmann
    """
263 1afa108c Michael Hanselmann
    self._record_fn = record_fn
264 1afa108c Michael Hanselmann
265 1afa108c Michael Hanselmann
  def Close(self):
266 1afa108c Michael Hanselmann
    pass
267 1afa108c Michael Hanselmann
268 1afa108c Michael Hanselmann
  def Call(self, data):
269 1afa108c Michael Hanselmann
    """Calls LUXI method.
270 1afa108c Michael Hanselmann

271 1afa108c Michael Hanselmann
    In this test class the method is not actually called, but added to a list
272 1afa108c Michael Hanselmann
    of called methods and then an exception (L{errors.RapiTestResult}) is
273 1afa108c Michael Hanselmann
    raised. There is no return value.
274 1afa108c Michael Hanselmann

275 1afa108c Michael Hanselmann
    """
276 1afa108c Michael Hanselmann
    (method, _, _) = luxi.ParseRequest(data)
277 1afa108c Michael Hanselmann
278 1afa108c Michael Hanselmann
    # Take a note of called method
279 1afa108c Michael Hanselmann
    self._record_fn(method)
280 1afa108c Michael Hanselmann
281 1afa108c Michael Hanselmann
    # Everything went fine until here, so let's abort the test
282 1afa108c Michael Hanselmann
    raise errors.RapiTestResult
283 1afa108c Michael Hanselmann
284 1afa108c Michael Hanselmann
285 1afa108c Michael Hanselmann
class _LuxiCallRecorder:
286 1afa108c Michael Hanselmann
  """Records all called LUXI client methods.
287 1afa108c Michael Hanselmann

288 1afa108c Michael Hanselmann
  """
289 1afa108c Michael Hanselmann
  def __init__(self):
290 1afa108c Michael Hanselmann
    """Initializes this class.
291 1afa108c Michael Hanselmann

292 1afa108c Michael Hanselmann
    """
293 1afa108c Michael Hanselmann
    self._called = set()
294 1afa108c Michael Hanselmann
295 1afa108c Michael Hanselmann
  def Record(self, name):
296 1afa108c Michael Hanselmann
    """Records a called function name.
297 1afa108c Michael Hanselmann

298 1afa108c Michael Hanselmann
    """
299 1afa108c Michael Hanselmann
    self._called.add(name)
300 1afa108c Michael Hanselmann
301 1afa108c Michael Hanselmann
  def CalledNames(self):
302 1afa108c Michael Hanselmann
    """Returns a list of called LUXI methods.
303 1afa108c Michael Hanselmann

304 1afa108c Michael Hanselmann
    """
305 1afa108c Michael Hanselmann
    return self._called
306 1afa108c Michael Hanselmann
307 303bc802 Iustin Pop
  def __call__(self, address=None):
308 1afa108c Michael Hanselmann
    """Creates an instrumented LUXI client.
309 1afa108c Michael Hanselmann

310 1afa108c Michael Hanselmann
    The LUXI client will record all method calls (use L{CalledNames} to
311 1afa108c Michael Hanselmann
    retrieve them).
312 1afa108c Michael Hanselmann

313 1afa108c Michael Hanselmann
    """
314 1afa108c Michael Hanselmann
    return luxi.Client(transport=compat.partial(_TestLuxiTransport,
315 303bc802 Iustin Pop
                                                self.Record),
316 303bc802 Iustin Pop
                       address=address)
317 1afa108c Michael Hanselmann
318 1afa108c Michael Hanselmann
319 1afa108c Michael Hanselmann
def _TestWrapper(fn, *args, **kwargs):
320 1afa108c Michael Hanselmann
  """Wrapper for ignoring L{errors.RapiTestResult}.
321 1afa108c Michael Hanselmann

322 1afa108c Michael Hanselmann
  """
323 1afa108c Michael Hanselmann
  try:
324 1afa108c Michael Hanselmann
    return fn(*args, **kwargs)
325 1afa108c Michael Hanselmann
  except errors.RapiTestResult:
326 1afa108c Michael Hanselmann
    # Everything was fine up to the point of sending a LUXI request
327 1afa108c Michael Hanselmann
    return NotImplemented
328 1afa108c Michael Hanselmann
329 1afa108c Michael Hanselmann
330 1afa108c Michael Hanselmann
class InputTestClient:
331 1afa108c Michael Hanselmann
  """Test version of RAPI client.
332 1afa108c Michael Hanselmann

333 1afa108c Michael Hanselmann
  Instances of this class can be used to test input arguments for RAPI client
334 1afa108c Michael Hanselmann
  calls. See L{rapi.client.GanetiRapiClient} for available methods and their
335 1afa108c Michael Hanselmann
  arguments. Functions can return C{NotImplemented} if all arguments are
336 1afa108c Michael Hanselmann
  acceptable, but a LUXI request would be necessary to provide an actual return
337 1afa108c Michael Hanselmann
  value. In case of an error, L{VerificationError} is raised.
338 1afa108c Michael Hanselmann

339 1afa108c Michael Hanselmann
  @see: An example on how to use this class can be found in
340 1afa108c Michael Hanselmann
    C{doc/examples/rapi_testutils.py}
341 1afa108c Michael Hanselmann

342 1afa108c Michael Hanselmann
  """
343 1afa108c Michael Hanselmann
  def __init__(self):
344 1afa108c Michael Hanselmann
    """Initializes this class.
345 1afa108c Michael Hanselmann

346 1afa108c Michael Hanselmann
    """
347 1afa108c Michael Hanselmann
    username = utils.GenerateSecret()
348 1afa108c Michael Hanselmann
    password = utils.GenerateSecret()
349 1afa108c Michael Hanselmann
350 1afa108c Michael Hanselmann
    def user_fn(wanted):
351 1afa108c Michael Hanselmann
      """Called to verify user credentials given in HTTP request.
352 1afa108c Michael Hanselmann

353 1afa108c Michael Hanselmann
      """
354 1afa108c Michael Hanselmann
      assert username == wanted
355 1afa108c Michael Hanselmann
      return http.auth.PasswordFileUser(username, password,
356 1afa108c Michael Hanselmann
                                        [rapi.RAPI_ACCESS_WRITE])
357 1afa108c Michael Hanselmann
358 1afa108c Michael Hanselmann
    self._lcr = _LuxiCallRecorder()
359 1afa108c Michael Hanselmann
360 1afa108c Michael Hanselmann
    # Create a mock RAPI server
361 1afa108c Michael Hanselmann
    handler = _RapiMock(user_fn, self._lcr)
362 1afa108c Michael Hanselmann
363 1afa108c Michael Hanselmann
    self._client = \
364 1afa108c Michael Hanselmann
      rapi.client.GanetiRapiClient("master.example.com",
365 1afa108c Michael Hanselmann
                                   username=username, password=password,
366 1afa108c Michael Hanselmann
                                   curl_factory=lambda: FakeCurl(handler))
367 1afa108c Michael Hanselmann
368 1afa108c Michael Hanselmann
  def _GetLuxiCalls(self):
369 1afa108c Michael Hanselmann
    """Returns the names of all called LUXI client functions.
370 1afa108c Michael Hanselmann

371 1afa108c Michael Hanselmann
    """
372 1afa108c Michael Hanselmann
    return self._lcr.CalledNames()
373 1afa108c Michael Hanselmann
374 1afa108c Michael Hanselmann
  def __getattr__(self, name):
375 1afa108c Michael Hanselmann
    """Finds method by name.
376 1afa108c Michael Hanselmann

377 1afa108c Michael Hanselmann
    The method is wrapped using L{_TestWrapper} to produce the actual test
378 1afa108c Michael Hanselmann
    result.
379 1afa108c Michael Hanselmann

380 1afa108c Michael Hanselmann
    """
381 1afa108c Michael Hanselmann
    return _HideInternalErrors(compat.partial(_TestWrapper,
382 1afa108c Michael Hanselmann
                                              getattr(self._client, name)))