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