root / test / py / ganeti.server.rapi_unittest.py @ 22b7f6f8
History | View | Annotate | Download (9.1 kB)
1 | 105f0d47 | Michael Hanselmann | #!/usr/bin/python
|
---|---|---|---|
2 | 105f0d47 | Michael Hanselmann | #
|
3 | 105f0d47 | Michael Hanselmann | |
4 | 105f0d47 | Michael Hanselmann | # Copyright (C) 2012 Google Inc.
|
5 | 105f0d47 | Michael Hanselmann | #
|
6 | 105f0d47 | Michael Hanselmann | # This program is free software; you can redistribute it and/or modify
|
7 | 105f0d47 | Michael Hanselmann | # it under the terms of the GNU General Public License as published by
|
8 | 105f0d47 | Michael Hanselmann | # the Free Software Foundation; either version 2 of the License, or
|
9 | 105f0d47 | Michael Hanselmann | # (at your option) any later version.
|
10 | 105f0d47 | Michael Hanselmann | #
|
11 | 105f0d47 | Michael Hanselmann | # This program is distributed in the hope that it will be useful, but
|
12 | 105f0d47 | Michael Hanselmann | # WITHOUT ANY WARRANTY; without even the implied warranty of
|
13 | 105f0d47 | Michael Hanselmann | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
14 | 105f0d47 | Michael Hanselmann | # General Public License for more details.
|
15 | 105f0d47 | Michael Hanselmann | #
|
16 | 105f0d47 | Michael Hanselmann | # You should have received a copy of the GNU General Public License
|
17 | 105f0d47 | Michael Hanselmann | # along with this program; if not, write to the Free Software
|
18 | 105f0d47 | Michael Hanselmann | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
19 | 105f0d47 | Michael Hanselmann | # 02110-1301, USA.
|
20 | 105f0d47 | Michael Hanselmann | |
21 | 105f0d47 | Michael Hanselmann | |
22 | 105f0d47 | Michael Hanselmann | """Script for testing ganeti.server.rapi"""
|
23 | 105f0d47 | Michael Hanselmann | |
24 | 105f0d47 | Michael Hanselmann | import re |
25 | 105f0d47 | Michael Hanselmann | import unittest |
26 | 105f0d47 | Michael Hanselmann | import random |
27 | 105f0d47 | Michael Hanselmann | import mimetools |
28 | 105f0d47 | Michael Hanselmann | import base64 |
29 | 105f0d47 | Michael Hanselmann | from cStringIO import StringIO |
30 | 105f0d47 | Michael Hanselmann | |
31 | 105f0d47 | Michael Hanselmann | from ganeti import constants |
32 | 105f0d47 | Michael Hanselmann | from ganeti import utils |
33 | 105f0d47 | Michael Hanselmann | from ganeti import compat |
34 | 105f0d47 | Michael Hanselmann | from ganeti import errors |
35 | 105f0d47 | Michael Hanselmann | from ganeti import serializer |
36 | 105f0d47 | Michael Hanselmann | from ganeti import rapi |
37 | 105f0d47 | Michael Hanselmann | from ganeti import http |
38 | 105f0d47 | Michael Hanselmann | from ganeti import objects |
39 | 105f0d47 | Michael Hanselmann | |
40 | 105f0d47 | Michael Hanselmann | import ganeti.rapi.baserlib |
41 | 105f0d47 | Michael Hanselmann | import ganeti.rapi.testutils |
42 | 105f0d47 | Michael Hanselmann | import ganeti.rapi.rlib2 |
43 | 105f0d47 | Michael Hanselmann | import ganeti.http.auth |
44 | 105f0d47 | Michael Hanselmann | |
45 | 105f0d47 | Michael Hanselmann | import testutils |
46 | 105f0d47 | Michael Hanselmann | |
47 | 105f0d47 | Michael Hanselmann | |
48 | 105f0d47 | Michael Hanselmann | class TestRemoteApiHandler(unittest.TestCase): |
49 | 105f0d47 | Michael Hanselmann | @staticmethod
|
50 | 105f0d47 | Michael Hanselmann | def _LookupWrongUser(_): |
51 | 105f0d47 | Michael Hanselmann | return None |
52 | 105f0d47 | Michael Hanselmann | |
53 | 105f0d47 | Michael Hanselmann | def _Test(self, method, path, headers, reqbody, |
54 | 27a8a190 | Michael Hanselmann | user_fn=NotImplemented, luxi_client=NotImplemented, |
55 | 27a8a190 | Michael Hanselmann | reqauth=False):
|
56 | 27a8a190 | Michael Hanselmann | rm = rapi.testutils._RapiMock(user_fn, luxi_client, reqauth=reqauth) |
57 | 105f0d47 | Michael Hanselmann | |
58 | 105f0d47 | Michael Hanselmann | (resp_code, resp_headers, resp_body) = \ |
59 | 105f0d47 | Michael Hanselmann | rm.FetchResponse(path, method, http.ParseHeaders(StringIO(headers)), |
60 | 105f0d47 | Michael Hanselmann | reqbody) |
61 | 105f0d47 | Michael Hanselmann | |
62 | 105f0d47 | Michael Hanselmann | self.assertTrue(resp_headers[http.HTTP_DATE])
|
63 | 105f0d47 | Michael Hanselmann | self.assertEqual(resp_headers[http.HTTP_CONNECTION], "close") |
64 | 105f0d47 | Michael Hanselmann | self.assertEqual(resp_headers[http.HTTP_CONTENT_TYPE], http.HTTP_APP_JSON)
|
65 | 105f0d47 | Michael Hanselmann | self.assertEqual(resp_headers[http.HTTP_SERVER], http.HTTP_GANETI_VERSION)
|
66 | 105f0d47 | Michael Hanselmann | |
67 | 105f0d47 | Michael Hanselmann | return (resp_code, resp_headers, serializer.LoadJson(resp_body))
|
68 | 105f0d47 | Michael Hanselmann | |
69 | 105f0d47 | Michael Hanselmann | def testRoot(self): |
70 | 105f0d47 | Michael Hanselmann | (code, _, data) = self._Test(http.HTTP_GET, "/", "", None) |
71 | 105f0d47 | Michael Hanselmann | self.assertEqual(code, http.HTTP_OK)
|
72 | 105f0d47 | Michael Hanselmann | self.assertTrue(data is None) |
73 | 105f0d47 | Michael Hanselmann | |
74 | 27a8a190 | Michael Hanselmann | def testRootReqAuth(self): |
75 | 27a8a190 | Michael Hanselmann | (code, _, _) = self._Test(http.HTTP_GET, "/", "", None, reqauth=True) |
76 | 27a8a190 | Michael Hanselmann | self.assertEqual(code, http.HttpUnauthorized.code)
|
77 | 27a8a190 | Michael Hanselmann | |
78 | 105f0d47 | Michael Hanselmann | def testVersion(self): |
79 | 105f0d47 | Michael Hanselmann | (code, _, data) = self._Test(http.HTTP_GET, "/version", "", None) |
80 | 105f0d47 | Michael Hanselmann | self.assertEqual(code, http.HTTP_OK)
|
81 | 105f0d47 | Michael Hanselmann | self.assertEqual(data, constants.RAPI_VERSION)
|
82 | 105f0d47 | Michael Hanselmann | |
83 | 105f0d47 | Michael Hanselmann | def testSlashTwo(self): |
84 | 105f0d47 | Michael Hanselmann | (code, _, data) = self._Test(http.HTTP_GET, "/2", "", None) |
85 | 105f0d47 | Michael Hanselmann | self.assertEqual(code, http.HTTP_OK)
|
86 | 105f0d47 | Michael Hanselmann | self.assertTrue(data is None) |
87 | 105f0d47 | Michael Hanselmann | |
88 | 105f0d47 | Michael Hanselmann | def testFeatures(self): |
89 | 105f0d47 | Michael Hanselmann | (code, _, data) = self._Test(http.HTTP_GET, "/2/features", "", None) |
90 | 105f0d47 | Michael Hanselmann | self.assertEqual(code, http.HTTP_OK)
|
91 | 105f0d47 | Michael Hanselmann | self.assertEqual(set(data), set(rapi.rlib2.ALL_FEATURES)) |
92 | 105f0d47 | Michael Hanselmann | |
93 | 105f0d47 | Michael Hanselmann | def testPutInstances(self): |
94 | 105f0d47 | Michael Hanselmann | (code, _, data) = self._Test(http.HTTP_PUT, "/2/instances", "", None) |
95 | 105f0d47 | Michael Hanselmann | self.assertEqual(code, http.HttpNotImplemented.code)
|
96 | 105f0d47 | Michael Hanselmann | self.assertTrue(data["message"].startswith("Method PUT is unsupported")) |
97 | 105f0d47 | Michael Hanselmann | |
98 | 105f0d47 | Michael Hanselmann | def testPostInstancesNoAuth(self): |
99 | 105f0d47 | Michael Hanselmann | (code, _, _) = self._Test(http.HTTP_POST, "/2/instances", "", None) |
100 | 105f0d47 | Michael Hanselmann | self.assertEqual(code, http.HttpUnauthorized.code)
|
101 | 105f0d47 | Michael Hanselmann | |
102 | 105f0d47 | Michael Hanselmann | def testRequestWithUnsupportedMediaType(self): |
103 | 105f0d47 | Michael Hanselmann | for fn in [lambda s: s, lambda s: s.upper(), lambda s: s.title()]: |
104 | 105f0d47 | Michael Hanselmann | headers = rapi.testutils._FormatHeaders([ |
105 | 105f0d47 | Michael Hanselmann | "%s: %s" % (http.HTTP_CONTENT_TYPE, fn("un/supported/media/type")), |
106 | 105f0d47 | Michael Hanselmann | ]) |
107 | 105f0d47 | Michael Hanselmann | (code, _, data) = self._Test(http.HTTP_GET, "/", headers, "body") |
108 | 105f0d47 | Michael Hanselmann | self.assertEqual(code, http.HttpUnsupportedMediaType.code)
|
109 | 105f0d47 | Michael Hanselmann | self.assertEqual(data["message"], "Unsupported Media Type") |
110 | 105f0d47 | Michael Hanselmann | |
111 | 105f0d47 | Michael Hanselmann | def testRequestWithInvalidJsonData(self): |
112 | 105f0d47 | Michael Hanselmann | body = "_this/is/no'valid.json"
|
113 | 105f0d47 | Michael Hanselmann | self.assertRaises(Exception, serializer.LoadJson, body) |
114 | 105f0d47 | Michael Hanselmann | |
115 | 105f0d47 | Michael Hanselmann | headers = rapi.testutils._FormatHeaders([ |
116 | 105f0d47 | Michael Hanselmann | "%s: %s" % (http.HTTP_CONTENT_TYPE, http.HTTP_APP_JSON),
|
117 | 105f0d47 | Michael Hanselmann | ]) |
118 | 105f0d47 | Michael Hanselmann | |
119 | 105f0d47 | Michael Hanselmann | (code, _, data) = self._Test(http.HTTP_GET, "/", headers, body) |
120 | 105f0d47 | Michael Hanselmann | self.assertEqual(code, http.HttpBadRequest.code)
|
121 | 105f0d47 | Michael Hanselmann | self.assertEqual(data["message"], "Unable to parse JSON data") |
122 | 105f0d47 | Michael Hanselmann | |
123 | 105f0d47 | Michael Hanselmann | def testUnsupportedAuthScheme(self): |
124 | 105f0d47 | Michael Hanselmann | headers = rapi.testutils._FormatHeaders([ |
125 | 105f0d47 | Michael Hanselmann | "%s: %s" % (http.HTTP_AUTHORIZATION, "Unsupported scheme"), |
126 | 105f0d47 | Michael Hanselmann | ]) |
127 | 105f0d47 | Michael Hanselmann | |
128 | 105f0d47 | Michael Hanselmann | (code, _, _) = self._Test(http.HTTP_POST, "/2/instances", headers, "") |
129 | 105f0d47 | Michael Hanselmann | self.assertEqual(code, http.HttpUnauthorized.code)
|
130 | 105f0d47 | Michael Hanselmann | |
131 | 105f0d47 | Michael Hanselmann | def testIncompleteBasicAuth(self): |
132 | 105f0d47 | Michael Hanselmann | headers = rapi.testutils._FormatHeaders([ |
133 | 105f0d47 | Michael Hanselmann | "%s: Basic" % http.HTTP_AUTHORIZATION,
|
134 | 105f0d47 | Michael Hanselmann | ]) |
135 | 105f0d47 | Michael Hanselmann | |
136 | 105f0d47 | Michael Hanselmann | (code, _, data) = self._Test(http.HTTP_POST, "/2/instances", headers, "") |
137 | 105f0d47 | Michael Hanselmann | self.assertEqual(code, http.HttpBadRequest.code)
|
138 | 105f0d47 | Michael Hanselmann | self.assertEqual(data["message"], |
139 | 105f0d47 | Michael Hanselmann | "Basic authentication requires credentials")
|
140 | 105f0d47 | Michael Hanselmann | |
141 | 105f0d47 | Michael Hanselmann | def testInvalidBasicAuth(self): |
142 | 105f0d47 | Michael Hanselmann | for auth in ["!invalid=base!64.", base64.b64encode(" "), |
143 | 105f0d47 | Michael Hanselmann | base64.b64encode("missingcolonchar")]:
|
144 | 105f0d47 | Michael Hanselmann | headers = rapi.testutils._FormatHeaders([ |
145 | 105f0d47 | Michael Hanselmann | "%s: Basic %s" % (http.HTTP_AUTHORIZATION, auth),
|
146 | 105f0d47 | Michael Hanselmann | ]) |
147 | 105f0d47 | Michael Hanselmann | |
148 | 105f0d47 | Michael Hanselmann | (code, _, data) = self._Test(http.HTTP_POST, "/2/instances", headers, "") |
149 | 105f0d47 | Michael Hanselmann | self.assertEqual(code, http.HttpUnauthorized.code)
|
150 | 105f0d47 | Michael Hanselmann | |
151 | 105f0d47 | Michael Hanselmann | @staticmethod
|
152 | 105f0d47 | Michael Hanselmann | def _MakeAuthHeaders(username, password, correct_password): |
153 | 105f0d47 | Michael Hanselmann | if correct_password:
|
154 | 105f0d47 | Michael Hanselmann | pw = password |
155 | 105f0d47 | Michael Hanselmann | else:
|
156 | 105f0d47 | Michael Hanselmann | pw = "wrongpass"
|
157 | 105f0d47 | Michael Hanselmann | |
158 | 105f0d47 | Michael Hanselmann | return rapi.testutils._FormatHeaders([
|
159 | 105f0d47 | Michael Hanselmann | "%s: Basic %s" % (http.HTTP_AUTHORIZATION,
|
160 | 105f0d47 | Michael Hanselmann | base64.b64encode("%s:%s" % (username, pw))),
|
161 | 105f0d47 | Michael Hanselmann | "%s: %s" % (http.HTTP_CONTENT_TYPE, http.HTTP_APP_JSON),
|
162 | 105f0d47 | Michael Hanselmann | ]) |
163 | 105f0d47 | Michael Hanselmann | |
164 | 105f0d47 | Michael Hanselmann | def testQueryAuth(self): |
165 | 105f0d47 | Michael Hanselmann | username = "admin"
|
166 | 105f0d47 | Michael Hanselmann | password = "2046920054"
|
167 | 105f0d47 | Michael Hanselmann | |
168 | 105f0d47 | Michael Hanselmann | header_fn = compat.partial(self._MakeAuthHeaders, username, password)
|
169 | 105f0d47 | Michael Hanselmann | |
170 | 105f0d47 | Michael Hanselmann | def _LookupUserNoWrite(name): |
171 | 105f0d47 | Michael Hanselmann | if name == username:
|
172 | 105f0d47 | Michael Hanselmann | return http.auth.PasswordFileUser(name, password, [])
|
173 | 105f0d47 | Michael Hanselmann | else:
|
174 | 105f0d47 | Michael Hanselmann | return None |
175 | 105f0d47 | Michael Hanselmann | |
176 | 5e12acfe | Michael Hanselmann | for access in [rapi.RAPI_ACCESS_WRITE, rapi.RAPI_ACCESS_READ]: |
177 | 5e12acfe | Michael Hanselmann | def _LookupUserWithWrite(name): |
178 | 5e12acfe | Michael Hanselmann | if name == username:
|
179 | 5e12acfe | Michael Hanselmann | return http.auth.PasswordFileUser(name, password, [
|
180 | 5e12acfe | Michael Hanselmann | access, |
181 | 5e12acfe | Michael Hanselmann | ]) |
182 | 105f0d47 | Michael Hanselmann | else:
|
183 | 5e12acfe | Michael Hanselmann | return None |
184 | 5e12acfe | Michael Hanselmann | |
185 | 5e12acfe | Michael Hanselmann | for qr in constants.QR_VIA_RAPI: |
186 | 5e12acfe | Michael Hanselmann | # The /2/query resource has somewhat special rules for authentication as
|
187 | 5e12acfe | Michael Hanselmann | # it can be used to retrieve critical information
|
188 | 5e12acfe | Michael Hanselmann | path = "/2/query/%s" % qr
|
189 | 5e12acfe | Michael Hanselmann | |
190 | 5e12acfe | Michael Hanselmann | for method in rapi.baserlib._SUPPORTED_METHODS: |
191 | 5e12acfe | Michael Hanselmann | # No authorization
|
192 | 5e12acfe | Michael Hanselmann | (code, _, _) = self._Test(method, path, "", "") |
193 | 5e12acfe | Michael Hanselmann | |
194 | 5e12acfe | Michael Hanselmann | if method in (http.HTTP_DELETE, http.HTTP_POST): |
195 | 5e12acfe | Michael Hanselmann | self.assertEqual(code, http.HttpNotImplemented.code)
|
196 | 5e12acfe | Michael Hanselmann | continue
|
197 | 5e12acfe | Michael Hanselmann | |
198 | 5e12acfe | Michael Hanselmann | self.assertEqual(code, http.HttpUnauthorized.code)
|
199 | 5e12acfe | Michael Hanselmann | |
200 | 5e12acfe | Michael Hanselmann | # Incorrect user
|
201 | 5e12acfe | Michael Hanselmann | (code, _, _) = self._Test(method, path, header_fn(True), "", |
202 | 5e12acfe | Michael Hanselmann | user_fn=self._LookupWrongUser)
|
203 | 5e12acfe | Michael Hanselmann | self.assertEqual(code, http.HttpUnauthorized.code)
|
204 | 5e12acfe | Michael Hanselmann | |
205 | 5e12acfe | Michael Hanselmann | # User has no write access, but the password is correct
|
206 | 5e12acfe | Michael Hanselmann | (code, _, _) = self._Test(method, path, header_fn(True), "", |
207 | 5e12acfe | Michael Hanselmann | user_fn=_LookupUserNoWrite) |
208 | 5e12acfe | Michael Hanselmann | self.assertEqual(code, http.HttpForbidden.code)
|
209 | 5e12acfe | Michael Hanselmann | |
210 | 5e12acfe | Michael Hanselmann | # Wrong password and no write access
|
211 | 5e12acfe | Michael Hanselmann | (code, _, _) = self._Test(method, path, header_fn(False), "", |
212 | 5e12acfe | Michael Hanselmann | user_fn=_LookupUserNoWrite) |
213 | 5e12acfe | Michael Hanselmann | self.assertEqual(code, http.HttpUnauthorized.code)
|
214 | 5e12acfe | Michael Hanselmann | |
215 | 5e12acfe | Michael Hanselmann | # Wrong password with write access
|
216 | 5e12acfe | Michael Hanselmann | (code, _, _) = self._Test(method, path, header_fn(False), "", |
217 | 5e12acfe | Michael Hanselmann | user_fn=_LookupUserWithWrite) |
218 | 5e12acfe | Michael Hanselmann | self.assertEqual(code, http.HttpUnauthorized.code)
|
219 | 5e12acfe | Michael Hanselmann | |
220 | 5e12acfe | Michael Hanselmann | # Prepare request information
|
221 | 5e12acfe | Michael Hanselmann | if method == http.HTTP_PUT:
|
222 | 5e12acfe | Michael Hanselmann | reqpath = path |
223 | 5e12acfe | Michael Hanselmann | body = serializer.DumpJson({ |
224 | 5e12acfe | Michael Hanselmann | "fields": ["name"], |
225 | 5e12acfe | Michael Hanselmann | }) |
226 | 5e12acfe | Michael Hanselmann | elif method == http.HTTP_GET:
|
227 | 5e12acfe | Michael Hanselmann | reqpath = "%s?fields=name" % path
|
228 | 5e12acfe | Michael Hanselmann | body = ""
|
229 | 5e12acfe | Michael Hanselmann | else:
|
230 | 5e12acfe | Michael Hanselmann | self.fail("Unknown method '%s'" % method) |
231 | 5e12acfe | Michael Hanselmann | |
232 | 5e12acfe | Michael Hanselmann | # User has write access, password is correct
|
233 | 5e12acfe | Michael Hanselmann | (code, _, data) = self._Test(method, reqpath, header_fn(True), body, |
234 | 5e12acfe | Michael Hanselmann | user_fn=_LookupUserWithWrite, |
235 | 5e12acfe | Michael Hanselmann | luxi_client=_FakeLuxiClientForQuery) |
236 | 5e12acfe | Michael Hanselmann | self.assertEqual(code, http.HTTP_OK)
|
237 | 5e12acfe | Michael Hanselmann | self.assertTrue(objects.QueryResponse.FromDict(data))
|
238 | 5e12acfe | Michael Hanselmann | |
239 | 5e12acfe | Michael Hanselmann | def testConsole(self): |
240 | 5e12acfe | Michael Hanselmann | path = "/2/instances/inst1.example.com/console"
|
241 | 5e12acfe | Michael Hanselmann | |
242 | 5e12acfe | Michael Hanselmann | for method in rapi.baserlib._SUPPORTED_METHODS: |
243 | 27a8a190 | Michael Hanselmann | for reqauth in [False, True]: |
244 | 27a8a190 | Michael Hanselmann | # No authorization
|
245 | 27a8a190 | Michael Hanselmann | (code, _, _) = self._Test(method, path, "", "", reqauth=reqauth) |
246 | 5e12acfe | Michael Hanselmann | |
247 | 27a8a190 | Michael Hanselmann | if method == http.HTTP_GET or reqauth: |
248 | 27a8a190 | Michael Hanselmann | self.assertEqual(code, http.HttpUnauthorized.code)
|
249 | 27a8a190 | Michael Hanselmann | else:
|
250 | 27a8a190 | Michael Hanselmann | self.assertEqual(code, http.HttpNotImplemented.code)
|
251 | 105f0d47 | Michael Hanselmann | |
252 | 105f0d47 | Michael Hanselmann | |
253 | 105f0d47 | Michael Hanselmann | class _FakeLuxiClientForQuery: |
254 | 105f0d47 | Michael Hanselmann | def __init__(self, *args, **kwargs): |
255 | 105f0d47 | Michael Hanselmann | pass
|
256 | 105f0d47 | Michael Hanselmann | |
257 | 105f0d47 | Michael Hanselmann | def Query(self, *args): |
258 | 105f0d47 | Michael Hanselmann | return objects.QueryResponse(fields=[])
|
259 | 105f0d47 | Michael Hanselmann | |
260 | 105f0d47 | Michael Hanselmann | |
261 | 105f0d47 | Michael Hanselmann | if __name__ == "__main__": |
262 | 105f0d47 | Michael Hanselmann | testutils.GanetiTestProgram() |