Revision 231db3a5
b/Makefile.am | ||
---|---|---|
319 | 319 |
test/ganeti.hooks_unittest.py \ |
320 | 320 |
test/ganeti.http_unittest.py \ |
321 | 321 |
test/ganeti.locking_unittest.py \ |
322 |
test/ganeti.luxi_unittest.py \ |
|
322 | 323 |
test/ganeti.mcpu_unittest.py \ |
323 | 324 |
test/ganeti.objects_unittest.py \ |
324 | 325 |
test/ganeti.rapi.resources_unittest.py \ |
b/daemons/ganeti-masterd | ||
---|---|---|
157 | 157 |
logging.debug("client closed connection") |
158 | 158 |
break |
159 | 159 |
|
160 |
request = serializer.LoadJson(msg) |
|
161 |
logging.debug("request: %s", request) |
|
162 |
if not isinstance(request, dict): |
|
163 |
logging.error("wrong request received: %s", msg) |
|
164 |
break |
|
165 |
|
|
166 |
method = request.get(luxi.KEY_METHOD, None) |
|
167 |
args = request.get(luxi.KEY_ARGS, None) |
|
168 |
if method is None or args is None: |
|
169 |
logging.error("no method or args in request") |
|
170 |
break |
|
160 |
(method, args) = luxi.ParseRequest(msg) |
|
171 | 161 |
|
172 | 162 |
success = False |
173 | 163 |
try: |
174 | 164 |
result = self._ops.handle_request(method, args) |
175 | 165 |
success = True |
176 | 166 |
except errors.GenericError, err: |
177 |
success = False |
|
178 | 167 |
result = errors.EncodeException(err) |
179 | 168 |
except: |
180 | 169 |
logging.error("Unexpected exception", exc_info=True) |
181 |
err = sys.exc_info() |
|
182 |
result = "Caught exception: %s" % str(err[1]) |
|
170 |
result = "Caught exception: %s" % str(sys.exc_info()[1]) |
|
183 | 171 |
|
184 |
response = { |
|
185 |
luxi.KEY_SUCCESS: success, |
|
186 |
luxi.KEY_RESULT: result, |
|
187 |
} |
|
188 |
logging.debug("response: %s", response) |
|
189 |
self.send_message(serializer.DumpJson(response)) |
|
172 |
self.send_message(luxi.FormatResponse(success, result)) |
|
190 | 173 |
|
191 | 174 |
def read_message(self): |
192 | 175 |
while not self._msgs: |
... | ... | |
199 | 182 |
return self._msgs.popleft() |
200 | 183 |
|
201 | 184 |
def send_message(self, msg): |
202 |
#print "sending", msg |
|
203 | 185 |
# TODO: sendall is not guaranteed to send everything |
204 | 186 |
self.request.sendall(msg + self.EOM) |
205 | 187 |
|
b/lib/http/__init__.py | ||
---|---|---|
815 | 815 |
buf = self._ContinueParsing(buf, eof) |
816 | 816 |
|
817 | 817 |
# Must be done only after the buffer has been evaluated |
818 |
# TODO: Connection-length < len(data read) and connection closed
|
|
818 |
# TODO: Content-Length < len(data read) and connection closed
|
|
819 | 819 |
if (eof and |
820 | 820 |
self.parser_status in (self.PS_START_LINE, |
821 | 821 |
self.PS_HEADERS)): |
b/lib/luxi.py | ||
---|---|---|
33 | 33 |
import collections |
34 | 34 |
import time |
35 | 35 |
import errno |
36 |
import logging |
|
36 | 37 |
|
37 | 38 |
from ganeti import serializer |
38 | 39 |
from ganeti import constants |
39 | 40 |
from ganeti import errors |
40 | 41 |
|
41 | 42 |
|
42 |
KEY_METHOD = 'method'
|
|
43 |
KEY_ARGS = 'args'
|
|
43 |
KEY_METHOD = "method"
|
|
44 |
KEY_ARGS = "args"
|
|
44 | 45 |
KEY_SUCCESS = "success" |
45 | 46 |
KEY_RESULT = "result" |
46 | 47 |
|
... | ... | |
233 | 234 |
self.socket = None |
234 | 235 |
|
235 | 236 |
|
237 |
def ParseRequest(msg): |
|
238 |
"""Parses a LUXI request message. |
|
239 |
|
|
240 |
""" |
|
241 |
try: |
|
242 |
request = serializer.LoadJson(msg) |
|
243 |
except ValueError, err: |
|
244 |
raise ProtocolError("Invalid LUXI request (parsing error): %s" % err) |
|
245 |
|
|
246 |
logging.debug("LUXI request: %s", request) |
|
247 |
|
|
248 |
if not isinstance(request, dict): |
|
249 |
logging.error("LUXI request not a dict: %r", msg) |
|
250 |
raise ProtocolError("Invalid LUXI request (not a dict)") |
|
251 |
|
|
252 |
method = request.get(KEY_METHOD, None) |
|
253 |
args = request.get(KEY_ARGS, None) |
|
254 |
if method is None or args is None: |
|
255 |
logging.error("LUXI request missing method or arguments: %r", msg) |
|
256 |
raise ProtocolError(("Invalid LUXI request (no method or arguments" |
|
257 |
" in request): %r") % msg) |
|
258 |
|
|
259 |
return (method, args) |
|
260 |
|
|
261 |
|
|
262 |
def ParseResponse(msg): |
|
263 |
"""Parses a LUXI response message. |
|
264 |
|
|
265 |
""" |
|
266 |
# Parse the result |
|
267 |
try: |
|
268 |
data = serializer.LoadJson(msg) |
|
269 |
except Exception, err: |
|
270 |
raise ProtocolError("Error while deserializing response: %s" % str(err)) |
|
271 |
|
|
272 |
# Validate response |
|
273 |
if not (isinstance(data, dict) and |
|
274 |
KEY_SUCCESS in data and |
|
275 |
KEY_RESULT in data): |
|
276 |
raise ProtocolError("Invalid response from server: %r" % data) |
|
277 |
|
|
278 |
return (data[KEY_SUCCESS], data[KEY_RESULT]) |
|
279 |
|
|
280 |
|
|
281 |
def FormatResponse(success, result): |
|
282 |
"""Formats a LUXI response message. |
|
283 |
|
|
284 |
""" |
|
285 |
response = { |
|
286 |
KEY_SUCCESS: success, |
|
287 |
KEY_RESULT: result, |
|
288 |
} |
|
289 |
|
|
290 |
logging.debug("LUXI response: %s", response) |
|
291 |
|
|
292 |
return serializer.DumpJson(response) |
|
293 |
|
|
294 |
|
|
295 |
def FormatRequest(method, args): |
|
296 |
"""Formats a LUXI request message. |
|
297 |
|
|
298 |
""" |
|
299 |
# Build request |
|
300 |
request = { |
|
301 |
KEY_METHOD: method, |
|
302 |
KEY_ARGS: args, |
|
303 |
} |
|
304 |
|
|
305 |
# Serialize the request |
|
306 |
return serializer.DumpJson(request, indent=False) |
|
307 |
|
|
308 |
|
|
309 |
def CallLuxiMethod(transport_cb, method, args): |
|
310 |
"""Send a LUXI request via a transport and return the response. |
|
311 |
|
|
312 |
""" |
|
313 |
assert callable(transport_cb) |
|
314 |
|
|
315 |
request_msg = FormatRequest(method, args) |
|
316 |
|
|
317 |
# Send request and wait for response |
|
318 |
response_msg = transport_cb(request_msg) |
|
319 |
|
|
320 |
(success, result) = ParseResponse(response_msg) |
|
321 |
|
|
322 |
if success: |
|
323 |
return result |
|
324 |
|
|
325 |
errors.MaybeRaise(result) |
|
326 |
raise RequestError(result) |
|
327 |
|
|
328 |
|
|
236 | 329 |
class Client(object): |
237 | 330 |
"""High-level client implementation. |
238 | 331 |
|
... | ... | |
282 | 375 |
except Exception: # pylint: disable-msg=W0703 |
283 | 376 |
pass |
284 | 377 |
|
285 |
def CallMethod(self, method, args): |
|
286 |
"""Send a generic request and return the response. |
|
287 |
|
|
288 |
""" |
|
289 |
# Build request |
|
290 |
request = { |
|
291 |
KEY_METHOD: method, |
|
292 |
KEY_ARGS: args, |
|
293 |
} |
|
294 |
|
|
295 |
# Serialize the request |
|
296 |
send_data = serializer.DumpJson(request, indent=False) |
|
297 |
|
|
378 |
def _SendMethodCall(self, data): |
|
298 | 379 |
# Send request and wait for response |
299 | 380 |
try: |
300 | 381 |
self._InitTransport() |
301 |
result = self.transport.Call(send_data)
|
|
382 |
return self.transport.Call(data)
|
|
302 | 383 |
except Exception: |
303 | 384 |
self._CloseTransport() |
304 | 385 |
raise |
305 | 386 |
|
306 |
# Parse the result |
|
307 |
try: |
|
308 |
data = serializer.LoadJson(result) |
|
309 |
except Exception, err: |
|
310 |
raise ProtocolError("Error while deserializing response: %s" % str(err)) |
|
311 |
|
|
312 |
# Validate response |
|
313 |
if (not isinstance(data, dict) or |
|
314 |
KEY_SUCCESS not in data or |
|
315 |
KEY_RESULT not in data): |
|
316 |
raise ProtocolError("Invalid response from server: %s" % str(data)) |
|
317 |
|
|
318 |
result = data[KEY_RESULT] |
|
319 |
|
|
320 |
if not data[KEY_SUCCESS]: |
|
321 |
errors.MaybeRaise(result) |
|
322 |
raise RequestError(result) |
|
387 |
def CallMethod(self, method, args): |
|
388 |
"""Send a generic request and return the response. |
|
323 | 389 |
|
324 |
return result |
|
390 |
""" |
|
391 |
return CallLuxiMethod(self._SendMethodCall, method, args) |
|
325 | 392 |
|
326 | 393 |
def SetQueueDrainFlag(self, drain_flag): |
327 | 394 |
return self.CallMethod(REQ_QUEUE_SET_DRAIN_FLAG, drain_flag) |
b/test/ganeti.luxi_unittest.py | ||
---|---|---|
1 |
#!/usr/bin/python |
|
2 |
# |
|
3 |
|
|
4 |
# Copyright (C) 2010 Google Inc. |
|
5 |
# |
|
6 |
# This program is free software; you can redistribute it and/or modify |
|
7 |
# it under the terms of the GNU General Public License as published by |
|
8 |
# the Free Software Foundation; either version 2 of the License, or |
|
9 |
# (at your option) any later version. |
|
10 |
# |
|
11 |
# This program is distributed in the hope that it will be useful, but |
|
12 |
# WITHOUT ANY WARRANTY; without even the implied warranty of |
|
13 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
14 |
# General Public License for more details. |
|
15 |
# |
|
16 |
# You should have received a copy of the GNU General Public License |
|
17 |
# along with this program; if not, write to the Free Software |
|
18 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
|
19 |
# 02110-1301, USA. |
|
20 |
|
|
21 |
|
|
22 |
"""Script for unittesting the luxi module""" |
|
23 |
|
|
24 |
|
|
25 |
import unittest |
|
26 |
|
|
27 |
from ganeti import luxi |
|
28 |
from ganeti import serializer |
|
29 |
|
|
30 |
import testutils |
|
31 |
|
|
32 |
|
|
33 |
class TestLuxiParsing(testutils.GanetiTestCase): |
|
34 |
def testParseRequest(self): |
|
35 |
msg = serializer.DumpJson({ |
|
36 |
luxi.KEY_METHOD: "foo", |
|
37 |
luxi.KEY_ARGS: ("bar", "baz", 123), |
|
38 |
}) |
|
39 |
|
|
40 |
self.assertEqualValues(luxi.ParseRequest(msg), |
|
41 |
("foo", ["bar", "baz", 123])) |
|
42 |
|
|
43 |
self.assertRaises(luxi.ProtocolError, luxi.ParseRequest, |
|
44 |
"this\"is {invalid, ]json data") |
|
45 |
|
|
46 |
# No dict |
|
47 |
self.assertRaises(luxi.ProtocolError, luxi.ParseRequest, |
|
48 |
serializer.DumpJson(123)) |
|
49 |
|
|
50 |
# Empty dict |
|
51 |
self.assertRaises(luxi.ProtocolError, luxi.ParseRequest, |
|
52 |
serializer.DumpJson({ })) |
|
53 |
|
|
54 |
# No arguments |
|
55 |
self.assertRaises(luxi.ProtocolError, luxi.ParseRequest, |
|
56 |
serializer.DumpJson({ luxi.KEY_METHOD: "foo", })) |
|
57 |
|
|
58 |
# No method |
|
59 |
self.assertRaises(luxi.ProtocolError, luxi.ParseRequest, |
|
60 |
serializer.DumpJson({ luxi.KEY_ARGS: [], })) |
|
61 |
|
|
62 |
def testParseResponse(self): |
|
63 |
msg = serializer.DumpJson({ |
|
64 |
luxi.KEY_SUCCESS: True, |
|
65 |
luxi.KEY_RESULT: None, |
|
66 |
}) |
|
67 |
|
|
68 |
self.assertEqual(luxi.ParseResponse(msg), (True, None)) |
|
69 |
|
|
70 |
self.assertRaises(luxi.ProtocolError, luxi.ParseResponse, |
|
71 |
"this\"is {invalid, ]json data") |
|
72 |
|
|
73 |
# No dict |
|
74 |
self.assertRaises(luxi.ProtocolError, luxi.ParseResponse, |
|
75 |
serializer.DumpJson(123)) |
|
76 |
|
|
77 |
# Empty dict |
|
78 |
self.assertRaises(luxi.ProtocolError, luxi.ParseResponse, |
|
79 |
serializer.DumpJson({ })) |
|
80 |
|
|
81 |
# No success |
|
82 |
self.assertRaises(luxi.ProtocolError, luxi.ParseResponse, |
|
83 |
serializer.DumpJson({ luxi.KEY_RESULT: True, })) |
|
84 |
|
|
85 |
# No result |
|
86 |
self.assertRaises(luxi.ProtocolError, luxi.ParseResponse, |
|
87 |
serializer.DumpJson({ luxi.KEY_SUCCESS: True, })) |
|
88 |
|
|
89 |
def testFormatResponse(self): |
|
90 |
for success, result in [(False, "error"), (True, "abc"), |
|
91 |
(True, { "a": 123, "b": None, })]: |
|
92 |
msg = luxi.FormatResponse(success, result) |
|
93 |
msgdata = serializer.LoadJson(msg) |
|
94 |
self.assert_(luxi.KEY_SUCCESS in msgdata) |
|
95 |
self.assert_(luxi.KEY_RESULT in msgdata) |
|
96 |
self.assertEqualValues(msgdata, |
|
97 |
{ luxi.KEY_SUCCESS: success, |
|
98 |
luxi.KEY_RESULT: result, |
|
99 |
}) |
|
100 |
|
|
101 |
def testFormatRequest(self): |
|
102 |
for method, args in [("a", []), ("b", [1, 2, 3])]: |
|
103 |
msg = luxi.FormatRequest(method, args) |
|
104 |
msgdata = serializer.LoadJson(msg) |
|
105 |
self.assert_(luxi.KEY_METHOD in msgdata) |
|
106 |
self.assert_(luxi.KEY_ARGS in msgdata) |
|
107 |
self.assertEqualValues(msgdata, |
|
108 |
{ luxi.KEY_METHOD: method, |
|
109 |
luxi.KEY_ARGS: args, |
|
110 |
}) |
|
111 |
|
|
112 |
|
|
113 |
if __name__ == "__main__": |
|
114 |
testutils.GanetiTestProgram() |
Also available in: Unified diff