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