Revision 2a7c3583 test/ganeti.rapi.client_unittest.py

b/test/ganeti.rapi.client_unittest.py
25 25
import re
26 26
import unittest
27 27
import warnings
28
import pycurl
28 29

  
30
from ganeti import constants
29 31
from ganeti import http
30 32
from ganeti import serializer
31 33

  
......
50 52
    return None
51 53

  
52 54

  
53
class HttpResponseMock:
54
  """Dumb mock of httplib.HTTPResponse.
55

  
56
  """
57

  
58
  def __init__(self, code, data):
59
    self.code = code
60
    self._data = data
55
class FakeCurl:
56
  def __init__(self, rapi):
57
    self._rapi = rapi
58
    self._opts = {}
59
    self._info = {}
61 60

  
62
  def read(self):
63
    return self._data
61
  def setopt(self, opt, value):
62
    self._opts[opt] = value
64 63

  
64
  def getopt(self, opt):
65
    return self._opts.get(opt)
65 66

  
66
class OpenerDirectorMock:
67
  """Mock for urllib.OpenerDirector.
67
  def unsetopt(self, opt):
68
    self._opts.pop(opt, None)
68 69

  
69
  """
70
  def getinfo(self, info):
71
    return self._info[info]
70 72

  
71
  def __init__(self, rapi):
72
    self._rapi = rapi
73
    self.last_request = None
73
  def perform(self):
74
    method = self._opts[pycurl.CUSTOMREQUEST]
75
    url = self._opts[pycurl.URL]
76
    request_body = self._opts[pycurl.POSTFIELDS]
77
    writefn = self._opts[pycurl.WRITEFUNCTION]
74 78

  
75
  def open(self, req):
76
    self.last_request = req
79
    path = _GetPathFromUri(url)
80
    (code, resp_body) = self._rapi.FetchResponse(path, method, request_body)
77 81

  
78
    path = _GetPathFromUri(req.get_full_url())
79
    code, resp_body = self._rapi.FetchResponse(path, req.get_method())
80
    return HttpResponseMock(code, resp_body)
82
    self._info[pycurl.RESPONSE_CODE] = code
83
    if resp_body is not None:
84
      writefn(resp_body)
81 85

  
82 86

  
83 87
class RapiMock(object):
......
85 89
    self._mapper = connector.Mapper()
86 90
    self._responses = []
87 91
    self._last_handler = None
92
    self._last_req_data = None
88 93

  
89 94
  def AddResponse(self, response, code=200):
90 95
    self._responses.insert(0, (code, response))
......
92 97
  def GetLastHandler(self):
93 98
    return self._last_handler
94 99

  
95
  def FetchResponse(self, path, method):
100
  def GetLastRequestData(self):
101
    return self._last_req_data
102

  
103
  def FetchResponse(self, path, method, request_body):
104
    self._last_req_data = request_body
105

  
96 106
    try:
97 107
      HandlerClass, items, args = self._mapper.getController(path)
98 108
      self._last_handler = HandlerClass(items, args, None)
......
111 121
    return code, response
112 122

  
113 123

  
124
class TestConstants(unittest.TestCase):
125
  def test(self):
126
    self.assertEqual(client.GANETI_RAPI_PORT, constants.DEFAULT_RAPI_PORT)
127
    self.assertEqual(client.GANETI_RAPI_VERSION, constants.RAPI_VERSION)
128
    self.assertEqual(client.HTTP_APP_JSON, http.HTTP_APP_JSON)
129
    self.assertEqual(client._REQ_DATA_VERSION_FIELD, rlib2._REQ_DATA_VERSION)
130
    self.assertEqual(client._INST_CREATE_REQV1, rlib2._INST_CREATE_REQV1)
131

  
132

  
114 133
class RapiMockTest(unittest.TestCase):
115 134
  def test(self):
116 135
    rapi = RapiMock()
117 136
    path = "/version"
118
    self.assertEqual((404, None), rapi.FetchResponse("/foo", "GET"))
137
    self.assertEqual((404, None), rapi.FetchResponse("/foo", "GET", None))
119 138
    self.assertEqual((501, "Method not implemented"),
120
                     rapi.FetchResponse("/version", "POST"))
139
                     rapi.FetchResponse("/version", "POST", None))
121 140
    rapi.AddResponse("2")
122
    code, response = rapi.FetchResponse("/version", "GET")
141
    code, response = rapi.FetchResponse("/version", "GET", None)
123 142
    self.assertEqual(200, code)
124 143
    self.assertEqual("2", response)
125 144
    self.failUnless(isinstance(rapi.GetLastHandler(), rlib2.R_version))
126 145

  
127 146

  
147
def _FakeNoSslPycurlVersion():
148
  # Note: incomplete version tuple
149
  return (3, "7.16.0", 462848, "mysystem", 1581, None, 0)
150

  
151

  
152
def _FakeFancySslPycurlVersion():
153
  # Note: incomplete version tuple
154
  return (3, "7.16.0", 462848, "mysystem", 1581, "FancySSL/1.2.3", 0)
155

  
156

  
157
def _FakeOpenSslPycurlVersion():
158
  # Note: incomplete version tuple
159
  return (2, "7.15.5", 462597, "othersystem", 668, "OpenSSL/0.9.8c", 0)
160

  
161

  
162
def _FakeGnuTlsPycurlVersion():
163
  # Note: incomplete version tuple
164
  return (3, "7.18.0", 463360, "somesystem", 1581, "GnuTLS/2.0.4", 0)
165

  
166

  
167
class TestExtendedConfig(unittest.TestCase):
168
  def testAuth(self):
169
    curl = FakeCurl(RapiMock())
170
    cl = client.GanetiRapiClient("master.example.com",
171
                                 username="user", password="pw",
172
                                 curl=curl)
173

  
174
    self.assertEqual(curl.getopt(pycurl.HTTPAUTH), pycurl.HTTPAUTH_BASIC)
175
    self.assertEqual(curl.getopt(pycurl.USERPWD), "user:pw")
176

  
177
  def testInvalidAuth(self):
178
    # No username
179
    self.assertRaises(client.Error, client.GanetiRapiClient,
180
                      "master-a.example.com", password="pw")
181
    # No password
182
    self.assertRaises(client.Error, client.GanetiRapiClient,
183
                      "master-b.example.com", username="user")
184

  
185
  def testCertVerifyInvalidCombinations(self):
186
    self.assertRaises(client.Error, client.GenericCurlConfig,
187
                      use_curl_cabundle=True, cafile="cert1.pem")
188
    self.assertRaises(client.Error, client.GenericCurlConfig,
189
                      use_curl_cabundle=True, capath="certs/")
190
    self.assertRaises(client.Error, client.GenericCurlConfig,
191
                      use_curl_cabundle=True,
192
                      cafile="cert1.pem", capath="certs/")
193

  
194
  def testProxySignalVerifyHostname(self):
195
    for use_gnutls in [False, True]:
196
      if use_gnutls:
197
        pcverfn = _FakeGnuTlsPycurlVersion
198
      else:
199
        pcverfn = _FakeOpenSslPycurlVersion
200

  
201
      for proxy in ["", "http://127.0.0.1:1234"]:
202
        for use_signal in [False, True]:
203
          for verify_hostname in [False, True]:
204
            cfgfn = client.GenericCurlConfig(proxy=proxy, use_signal=use_signal,
205
                                             verify_hostname=verify_hostname,
206
                                             _pycurl_version_fn=pcverfn)
207

  
208
            curl = FakeCurl(RapiMock())
209
            cl = client.GanetiRapiClient("master.example.com",
210
                                         curl_config_fn=cfgfn, curl=curl)
211

  
212
            self.assertEqual(curl.getopt(pycurl.PROXY), proxy)
213
            self.assertEqual(curl.getopt(pycurl.NOSIGNAL), not use_signal)
214

  
215
            if verify_hostname:
216
              self.assertEqual(curl.getopt(pycurl.SSL_VERIFYHOST), 2)
217
            else:
218
              self.assertEqual(curl.getopt(pycurl.SSL_VERIFYHOST), 0)
219

  
220
  def testNoCertVerify(self):
221
    cfgfn = client.GenericCurlConfig()
222

  
223
    curl = FakeCurl(RapiMock())
224
    cl = client.GanetiRapiClient("master.example.com", curl_config_fn=cfgfn,
225
                                 curl=curl)
226

  
227
    self.assertFalse(curl.getopt(pycurl.SSL_VERIFYPEER))
228
    self.assertFalse(curl.getopt(pycurl.CAINFO))
229
    self.assertFalse(curl.getopt(pycurl.CAPATH))
230

  
231
  def testCertVerifyCurlBundle(self):
232
    cfgfn = client.GenericCurlConfig(use_curl_cabundle=True)
233

  
234
    curl = FakeCurl(RapiMock())
235
    cl = client.GanetiRapiClient("master.example.com", curl_config_fn=cfgfn,
236
                                 curl=curl)
237

  
238
    self.assert_(curl.getopt(pycurl.SSL_VERIFYPEER))
239
    self.assertFalse(curl.getopt(pycurl.CAINFO))
240
    self.assertFalse(curl.getopt(pycurl.CAPATH))
241

  
242
  def testCertVerifyCafile(self):
243
    mycert = "/tmp/some/UNUSED/cert/file.pem"
244
    cfgfn = client.GenericCurlConfig(cafile=mycert)
245

  
246
    curl = FakeCurl(RapiMock())
247
    cl = client.GanetiRapiClient("master.example.com", curl_config_fn=cfgfn,
248
                                 curl=curl)
249

  
250
    self.assert_(curl.getopt(pycurl.SSL_VERIFYPEER))
251
    self.assertEqual(curl.getopt(pycurl.CAINFO), mycert)
252
    self.assertFalse(curl.getopt(pycurl.CAPATH))
253

  
254
  def testCertVerifyCapath(self):
255
    certdir = "/tmp/some/UNUSED/cert/directory"
256
    pcverfn = _FakeOpenSslPycurlVersion
257
    cfgfn = client.GenericCurlConfig(capath=certdir,
258
                                     _pycurl_version_fn=pcverfn)
259

  
260
    curl = FakeCurl(RapiMock())
261
    cl = client.GanetiRapiClient("master.example.com", curl_config_fn=cfgfn,
262
                                 curl=curl)
263

  
264
    self.assert_(curl.getopt(pycurl.SSL_VERIFYPEER))
265
    self.assertEqual(curl.getopt(pycurl.CAPATH), certdir)
266
    self.assertFalse(curl.getopt(pycurl.CAINFO))
267

  
268
  def testCertVerifyCapathGnuTls(self):
269
    certdir = "/tmp/some/UNUSED/cert/directory"
270
    pcverfn = _FakeGnuTlsPycurlVersion
271
    cfgfn = client.GenericCurlConfig(capath=certdir,
272
                                     _pycurl_version_fn=pcverfn)
273

  
274
    curl = FakeCurl(RapiMock())
275
    self.assertRaises(client.Error, client.GanetiRapiClient,
276
                      "master.example.com", curl_config_fn=cfgfn, curl=curl)
277

  
278
  def testCertVerifyNoSsl(self):
279
    certdir = "/tmp/some/UNUSED/cert/directory"
280
    pcverfn = _FakeNoSslPycurlVersion
281
    cfgfn = client.GenericCurlConfig(capath=certdir,
282
                                     _pycurl_version_fn=pcverfn)
283

  
284
    curl = FakeCurl(RapiMock())
285
    self.assertRaises(client.Error, client.GanetiRapiClient,
286
                      "master.example.com", curl_config_fn=cfgfn, curl=curl)
287

  
288
  def testCertVerifyFancySsl(self):
289
    certdir = "/tmp/some/UNUSED/cert/directory"
290
    pcverfn = _FakeFancySslPycurlVersion
291
    cfgfn = client.GenericCurlConfig(capath=certdir,
292
                                     _pycurl_version_fn=pcverfn)
293

  
294
    curl = FakeCurl(RapiMock())
295
    self.assertRaises(NotImplementedError, client.GanetiRapiClient,
296
                      "master.example.com", curl_config_fn=cfgfn, curl=curl)
297

  
298
  def testCertVerifyCapath(self):
299
    for connect_timeout in [None, 1, 5, 10, 30, 60, 300]:
300
      for timeout in [None, 1, 30, 60, 3600, 24 * 3600]:
301
        cfgfn = client.GenericCurlConfig(connect_timeout=connect_timeout,
302
                                         timeout=timeout)
303

  
304
        curl = FakeCurl(RapiMock())
305
        cl = client.GanetiRapiClient("master.example.com", curl_config_fn=cfgfn,
306
                                     curl=curl)
307

  
308
        self.assertEqual(curl.getopt(pycurl.CONNECTTIMEOUT), connect_timeout)
309
        self.assertEqual(curl.getopt(pycurl.TIMEOUT), timeout)
310

  
311

  
128 312
class GanetiRapiClientTests(testutils.GanetiTestCase):
129 313
  def setUp(self):
130 314
    testutils.GanetiTestCase.setUp(self)
131 315

  
132 316
    self.rapi = RapiMock()
133
    self.http = OpenerDirectorMock(self.rapi)
134
    self.client = client.GanetiRapiClient('master.foo.com')
135
    self.client._http = self.http
136
    # Hard-code the version for easier testing.
137
    self.client._version = 2
317
    self.curl = FakeCurl(self.rapi)
318
    self.client = client.GanetiRapiClient("master.example.com",
319
                                          curl=self.curl)
320

  
321
    # Signals should be disabled by default
322
    self.assert_(self.curl.getopt(pycurl.NOSIGNAL))
323

  
324
    # No auth and no proxy
325
    self.assertFalse(self.curl.getopt(pycurl.USERPWD))
326
    self.assert_(self.curl.getopt(pycurl.PROXY) is None)
327

  
328
    # Content-type is required for requests
329
    headers = self.curl.getopt(pycurl.HTTPHEADER)
330
    self.assert_("Content-type: application/json" in headers)
138 331

  
139 332
  def assertHandler(self, handler_cls):
140 333
    self.failUnless(isinstance(self.rapi.GetLastHandler(), handler_cls))
......
273 466
    self.assertHandler(rlib2.R_2_instances)
274 467
    self.assertDryRun()
275 468

  
276
    data = serializer.LoadJson(self.http.last_request.data)
469
    data = serializer.LoadJson(self.rapi.GetLastRequestData())
277 470

  
278 471
    for field in ["dry_run", "beparams", "hvparams", "start"]:
279 472
      self.assertFalse(field in data)
......
293 486
    self.assertEqual(job_id, 24740)
294 487
    self.assertHandler(rlib2.R_2_instances)
295 488

  
296
    data = serializer.LoadJson(self.http.last_request.data)
489
    data = serializer.LoadJson(self.rapi.GetLastRequestData())
297 490
    self.assertEqual(data[rlib2._REQ_DATA_VERSION], 1)
298 491
    self.assertEqual(data["name"], "inst2.example.com")
299 492
    self.assertEqual(data["disk_template"], "drbd8")
......
411 604
    self.assertHandler(rlib2.R_2_instances_name_export)
412 605
    self.assertItems(["inst2"])
413 606

  
414
    data = serializer.LoadJson(self.http.last_request.data)
607
    data = serializer.LoadJson(self.rapi.GetLastRequestData())
415 608
    self.assertEqual(data["mode"], "local")
416 609
    self.assertEqual(data["destination"], "nodeX")
417 610
    self.assertEqual(data["shutdown"], True)
......
509 702
    self.assertHandler(rlib2.R_2_nodes_name_role)
510 703
    self.assertItems(["node-foo"])
511 704
    self.assertQuery("force", ["1"])
512
    self.assertEqual("\"master-candidate\"", self.http.last_request.data)
705
    self.assertEqual("\"master-candidate\"", self.rapi.GetLastRequestData())
513 706

  
514 707
  def testGetNodeStorageUnits(self):
515 708
    self.rapi.AddResponse("42")
......
576 769

  
577 770

  
578 771
if __name__ == '__main__':
579
  testutils.GanetiTestProgram()
772
  client.UsesRapiClient(testutils.GanetiTestProgram)()

Also available in: Unified diff