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