Revision ab221ddf
b/NEWS | ||
---|---|---|
1 | 1 |
News |
2 | 2 |
==== |
3 | 3 |
|
4 |
Version 2.2.0 |
|
5 |
------------- |
|
6 |
|
|
7 |
- RAPI now requires a Content-Type header for requests with a body (e.g. |
|
8 |
``PUT`` or ``POST``) which must be set to ``application/json`` (see |
|
9 |
RFC2616 (HTTP/1.1), section 7.2.1) |
|
10 |
|
|
4 | 11 |
|
5 | 12 |
Version 2.1.0 |
6 | 13 |
------------- |
b/daemons/ganeti-noded | ||
---|---|---|
45 | 45 |
from ganeti import http |
46 | 46 |
from ganeti import utils |
47 | 47 |
from ganeti import storage |
48 |
from ganeti import serializer |
|
48 | 49 |
|
49 | 50 |
import ganeti.http.server # pylint: disable-msg=W0611 |
50 | 51 |
|
... | ... | |
99 | 100 |
raise http.HttpNotFound() |
100 | 101 |
|
101 | 102 |
try: |
102 |
rvalue = method(req.request_body) |
|
103 |
return True, rvalue |
|
103 |
result = (True, method(serializer.LoadJson(req.request_body))) |
|
104 | 104 |
|
105 | 105 |
except backend.RPCFail, err: |
106 | 106 |
# our custom failure exception; str(err) works fine if the |
107 | 107 |
# exception was constructed with a single argument, and in |
108 | 108 |
# this case, err.message == err.args[0] == str(err) |
109 |
return (False, str(err))
|
|
109 |
result = (False, str(err))
|
|
110 | 110 |
except errors.QuitGanetiException, err: |
111 | 111 |
# Tell parent to quit |
112 | 112 |
logging.info("Shutting down the node daemon, arguments: %s", |
... | ... | |
114 | 114 |
os.kill(self.noded_pid, signal.SIGTERM) |
115 | 115 |
# And return the error's arguments, which must be already in |
116 | 116 |
# correct tuple format |
117 |
return err.args
|
|
117 |
result = err.args
|
|
118 | 118 |
except Exception, err: |
119 | 119 |
logging.exception("Error in RPC call") |
120 |
return False, "Error while executing backend function: %s" % str(err) |
|
120 |
result = (False, "Error while executing backend function: %s" % str(err)) |
|
121 |
|
|
122 |
return serializer.DumpJson(result, indent=False) |
|
121 | 123 |
|
122 | 124 |
# the new block devices -------------------------- |
123 | 125 |
|
b/daemons/ganeti-rapi | ||
---|---|---|
52 | 52 |
self.handler = None |
53 | 53 |
self.handler_fn = None |
54 | 54 |
self.handler_access = None |
55 |
self.body_data = None |
|
55 | 56 |
|
56 | 57 |
|
57 | 58 |
class JsonErrorRequestExecutor(http.server.HttpServerRequestExecutor): |
58 | 59 |
"""Custom Request Executor class that formats HTTP errors in JSON. |
59 | 60 |
|
60 | 61 |
""" |
61 |
error_content_type = "application/json"
|
|
62 |
error_content_type = http.HttpJsonConverter.CONTENT_TYPE
|
|
62 | 63 |
|
63 | 64 |
def _FormatErrorMessage(self, values): |
64 | 65 |
"""Formats the body of an error message. |
... | ... | |
116 | 117 |
if ctx.handler_access is None: |
117 | 118 |
raise AssertionError("Permissions definition missing") |
118 | 119 |
|
120 |
# This is only made available in HandleRequest |
|
121 |
ctx.body_data = None |
|
122 |
|
|
119 | 123 |
req.private = ctx |
120 | 124 |
|
121 | 125 |
return req.private |
... | ... | |
162 | 166 |
""" |
163 | 167 |
ctx = self._GetRequestContext(req) |
164 | 168 |
|
169 |
# Deserialize request parameters |
|
170 |
if req.request_body: |
|
171 |
# RFC2616, 7.2.1: Any HTTP/1.1 message containing an entity-body SHOULD |
|
172 |
# include a Content-Type header field defining the media type of that |
|
173 |
# body. [...] If the media type remains unknown, the recipient SHOULD |
|
174 |
# treat it as type "application/octet-stream". |
|
175 |
req_content_type = req.request_headers.get(http.HTTP_CONTENT_TYPE, |
|
176 |
http.HTTP_APP_OCTET_STREAM) |
|
177 |
if (req_content_type.lower() != |
|
178 |
http.HttpJsonConverter.CONTENT_TYPE.lower()): |
|
179 |
raise http.HttpUnsupportedMediaType() |
|
180 |
|
|
181 |
try: |
|
182 |
ctx.body_data = serializer.LoadJson(req.request_body) |
|
183 |
except Exception: |
|
184 |
raise http.HttpBadRequest(message="Unable to parse JSON data") |
|
185 |
else: |
|
186 |
ctx.body_data = None |
|
187 |
|
|
165 | 188 |
try: |
166 | 189 |
result = ctx.handler_fn() |
167 | 190 |
except luxi.TimeoutError: |
... | ... | |
173 | 196 |
logging.exception("Error while handling the %s request", method) |
174 | 197 |
raise |
175 | 198 |
|
176 |
return result |
|
199 |
req.resp_headers[http.HTTP_CONTENT_TYPE] = \ |
|
200 |
http.HttpJsonConverter.CONTENT_TYPE |
|
201 |
|
|
202 |
return serializer.DumpJson(result) |
|
177 | 203 |
|
178 | 204 |
|
179 | 205 |
def CheckRapi(options, args): |
b/lib/http/__init__.py | ||
---|---|---|
680 | 680 |
self.start_line = None |
681 | 681 |
self.headers = None |
682 | 682 |
self.body = None |
683 |
self.decoded_body = None |
|
684 | 683 |
|
685 | 684 |
|
686 | 685 |
class HttpClientToServerStartLine(object): |
... | ... | |
851 | 850 |
assert self.parser_status == self.PS_COMPLETE |
852 | 851 |
assert not buf, "Parser didn't read full response" |
853 | 852 |
|
853 |
# Body is complete |
|
854 | 854 |
msg.body = self.body_buffer.getvalue() |
855 | 855 |
|
856 |
# TODO: Content-type, error handling |
|
857 |
if msg.body: |
|
858 |
msg.decoded_body = HttpJsonConverter().Decode(msg.body) |
|
859 |
else: |
|
860 |
msg.decoded_body = None |
|
861 |
|
|
862 |
if msg.decoded_body: |
|
863 |
logging.debug("Message body: %s", msg.decoded_body) |
|
864 |
|
|
865 | 856 |
def _ContinueParsing(self, buf, eof): |
866 | 857 |
"""Main function for HTTP message state machine. |
867 | 858 |
|
b/lib/http/server.py | ||
---|---|---|
78 | 78 |
self.request_method = request_msg.start_line.method |
79 | 79 |
self.request_path = request_msg.start_line.path |
80 | 80 |
self.request_headers = request_msg.headers |
81 |
self.request_body = request_msg.decoded_body
|
|
81 |
self.request_body = request_msg.body |
|
82 | 82 |
|
83 | 83 |
# Response attributes |
84 | 84 |
self.resp_headers = {} |
... | ... | |
333 | 333 |
logging.exception("Unknown exception") |
334 | 334 |
raise http.HttpInternalServerError(message="Unknown error") |
335 | 335 |
|
336 |
# TODO: Content-type |
|
337 |
encoder = http.HttpJsonConverter() |
|
336 |
if not isinstance(result, basestring): |
|
337 |
raise http.HttpError("Handler function didn't return string type") |
|
338 |
|
|
338 | 339 |
self.response_msg.start_line.code = http.HTTP_OK |
339 |
self.response_msg.body = encoder.Encode(result) |
|
340 | 340 |
self.response_msg.headers = handler_context.resp_headers |
341 |
self.response_msg.headers[http.HTTP_CONTENT_TYPE] = encoder.CONTENT_TYPE
|
|
341 |
self.response_msg.body = result
|
|
342 | 342 |
finally: |
343 | 343 |
# No reason to keep this any longer, even for exceptions |
344 | 344 |
handler_context.private = None |
b/lib/rapi/baserlib.py | ||
---|---|---|
272 | 272 |
@param name: the required parameter |
273 | 273 |
|
274 | 274 |
""" |
275 |
if name in self.req.request_body:
|
|
276 |
return self.req.request_body[name]
|
|
277 |
elif args:
|
|
278 |
return args[0]
|
|
279 |
else:
|
|
280 |
raise http.HttpBadRequest("Required parameter '%s' is missing" % |
|
281 |
name)
|
|
275 |
try:
|
|
276 |
return self.req.private.body_data[name]
|
|
277 |
except KeyError:
|
|
278 |
if args:
|
|
279 |
return args[0]
|
|
280 |
|
|
281 |
raise http.HttpBadRequest("Required parameter '%s' is missing" % name)
|
|
282 | 282 |
|
283 | 283 |
def useLocking(self): |
284 | 284 |
"""Check if the request specifies locking. |
b/lib/rapi/rlib2.py | ||
---|---|---|
256 | 256 |
@return: a job id |
257 | 257 |
|
258 | 258 |
""" |
259 |
if not isinstance(self.req.request_body, basestring):
|
|
259 |
if not isinstance(self.req.private.body_data, basestring):
|
|
260 | 260 |
raise http.HttpBadRequest("Invalid body contents, not a string") |
261 | 261 |
|
262 | 262 |
node_name = self.items[0] |
263 |
role = self.req.request_body
|
|
263 |
role = self.req.private.body_data
|
|
264 | 264 |
|
265 | 265 |
if role == _NR_REGULAR: |
266 | 266 |
candidate = False |
... | ... | |
431 | 431 |
@return: a job id |
432 | 432 |
|
433 | 433 |
""" |
434 |
if not isinstance(self.req.request_body, dict):
|
|
434 |
if not isinstance(self.req.private.body_data, dict):
|
|
435 | 435 |
raise http.HttpBadRequest("Invalid body contents, not a dictionary") |
436 | 436 |
|
437 |
beparams = baserlib.MakeParamsDict(self.req.request_body,
|
|
437 |
beparams = baserlib.MakeParamsDict(self.req.private.body_data,
|
|
438 | 438 |
constants.BES_PARAMETERS) |
439 |
hvparams = baserlib.MakeParamsDict(self.req.request_body,
|
|
439 |
hvparams = baserlib.MakeParamsDict(self.req.private.body_data,
|
|
440 | 440 |
constants.HVS_PARAMETERS) |
441 | 441 |
fn = self.getBodyParameter |
442 | 442 |
|
Also available in: Unified diff