Commit 231db3a5 authored by Michael Hanselmann's avatar Michael Hanselmann

Factorize LUXI parsing and handling code

Also fix a typo in http/__init__.py and add unittests
for the LUXI parsing and formatting functions.
Signed-off-by: default avatarMichael Hanselmann <hansmi@google.com>
Reviewed-by: default avatarIustin Pop <iustin@google.com>
parent 797506fc
......@@ -319,6 +319,7 @@ dist_TESTS = \
test/ganeti.hooks_unittest.py \
test/ganeti.http_unittest.py \
test/ganeti.locking_unittest.py \
test/ganeti.luxi_unittest.py \
test/ganeti.mcpu_unittest.py \
test/ganeti.objects_unittest.py \
test/ganeti.rapi.resources_unittest.py \
......
......@@ -157,36 +157,19 @@ class ClientRqHandler(SocketServer.BaseRequestHandler):
logging.debug("client closed connection")
break
request = serializer.LoadJson(msg)
logging.debug("request: %s", request)
if not isinstance(request, dict):
logging.error("wrong request received: %s", msg)
break
method = request.get(luxi.KEY_METHOD, None)
args = request.get(luxi.KEY_ARGS, None)
if method is None or args is None:
logging.error("no method or args in request")
break
(method, args) = luxi.ParseRequest(msg)
success = False
try:
result = self._ops.handle_request(method, args)
success = True
except errors.GenericError, err:
success = False
result = errors.EncodeException(err)
except:
logging.error("Unexpected exception", exc_info=True)
err = sys.exc_info()
result = "Caught exception: %s" % str(err[1])
result = "Caught exception: %s" % str(sys.exc_info()[1])
response = {
luxi.KEY_SUCCESS: success,
luxi.KEY_RESULT: result,
}
logging.debug("response: %s", response)
self.send_message(serializer.DumpJson(response))
self.send_message(luxi.FormatResponse(success, result))
def read_message(self):
while not self._msgs:
......@@ -199,7 +182,6 @@ class ClientRqHandler(SocketServer.BaseRequestHandler):
return self._msgs.popleft()
def send_message(self, msg):
#print "sending", msg
# TODO: sendall is not guaranteed to send everything
self.request.sendall(msg + self.EOM)
......
......@@ -815,7 +815,7 @@ class HttpMessageReader(object):
buf = self._ContinueParsing(buf, eof)
# Must be done only after the buffer has been evaluated
# TODO: Connection-length < len(data read) and connection closed
# TODO: Content-Length < len(data read) and connection closed
if (eof and
self.parser_status in (self.PS_START_LINE,
self.PS_HEADERS)):
......
......@@ -33,14 +33,15 @@ import socket
import collections
import time
import errno
import logging
from ganeti import serializer
from ganeti import constants
from ganeti import errors
KEY_METHOD = 'method'
KEY_ARGS = 'args'
KEY_METHOD = "method"
KEY_ARGS = "args"
KEY_SUCCESS = "success"
KEY_RESULT = "result"
......@@ -233,6 +234,98 @@ class Transport:
self.socket = None
def ParseRequest(msg):
"""Parses a LUXI request message.
"""
try:
request = serializer.LoadJson(msg)
except ValueError, err:
raise ProtocolError("Invalid LUXI request (parsing error): %s" % err)
logging.debug("LUXI request: %s", request)
if not isinstance(request, dict):
logging.error("LUXI request not a dict: %r", msg)
raise ProtocolError("Invalid LUXI request (not a dict)")
method = request.get(KEY_METHOD, None)
args = request.get(KEY_ARGS, None)
if method is None or args is None:
logging.error("LUXI request missing method or arguments: %r", msg)
raise ProtocolError(("Invalid LUXI request (no method or arguments"
" in request): %r") % msg)
return (method, args)
def ParseResponse(msg):
"""Parses a LUXI response message.
"""
# Parse the result
try:
data = serializer.LoadJson(msg)
except Exception, err:
raise ProtocolError("Error while deserializing response: %s" % str(err))
# Validate response
if not (isinstance(data, dict) and
KEY_SUCCESS in data and
KEY_RESULT in data):
raise ProtocolError("Invalid response from server: %r" % data)
return (data[KEY_SUCCESS], data[KEY_RESULT])
def FormatResponse(success, result):
"""Formats a LUXI response message.
"""
response = {
KEY_SUCCESS: success,
KEY_RESULT: result,
}
logging.debug("LUXI response: %s", response)
return serializer.DumpJson(response)
def FormatRequest(method, args):
"""Formats a LUXI request message.
"""
# Build request
request = {
KEY_METHOD: method,
KEY_ARGS: args,
}
# Serialize the request
return serializer.DumpJson(request, indent=False)
def CallLuxiMethod(transport_cb, method, args):
"""Send a LUXI request via a transport and return the response.
"""
assert callable(transport_cb)
request_msg = FormatRequest(method, args)
# Send request and wait for response
response_msg = transport_cb(request_msg)
(success, result) = ParseResponse(response_msg)
if success:
return result
errors.MaybeRaise(result)
raise RequestError(result)
class Client(object):
"""High-level client implementation.
......@@ -282,46 +375,20 @@ class Client(object):
except Exception: # pylint: disable-msg=W0703
pass
def CallMethod(self, method, args):
"""Send a generic request and return the response.
"""
# Build request
request = {
KEY_METHOD: method,
KEY_ARGS: args,
}
# Serialize the request
send_data = serializer.DumpJson(request, indent=False)
def _SendMethodCall(self, data):
# Send request and wait for response
try:
self._InitTransport()
result = self.transport.Call(send_data)
return self.transport.Call(data)
except Exception:
self._CloseTransport()
raise
# Parse the result
try:
data = serializer.LoadJson(result)
except Exception, err:
raise ProtocolError("Error while deserializing response: %s" % str(err))
# Validate response
if (not isinstance(data, dict) or
KEY_SUCCESS not in data or
KEY_RESULT not in data):
raise ProtocolError("Invalid response from server: %s" % str(data))
result = data[KEY_RESULT]
if not data[KEY_SUCCESS]:
errors.MaybeRaise(result)
raise RequestError(result)
def CallMethod(self, method, args):
"""Send a generic request and return the response.
return result
"""
return CallLuxiMethod(self._SendMethodCall, method, args)
def SetQueueDrainFlag(self, drain_flag):
return self.CallMethod(REQ_QUEUE_SET_DRAIN_FLAG, drain_flag)
......
#!/usr/bin/python
#
# Copyright (C) 2010 Google Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.
"""Script for unittesting the luxi module"""
import unittest
from ganeti import luxi
from ganeti import serializer
import testutils
class TestLuxiParsing(testutils.GanetiTestCase):
def testParseRequest(self):
msg = serializer.DumpJson({
luxi.KEY_METHOD: "foo",
luxi.KEY_ARGS: ("bar", "baz", 123),
})
self.assertEqualValues(luxi.ParseRequest(msg),
("foo", ["bar", "baz", 123]))
self.assertRaises(luxi.ProtocolError, luxi.ParseRequest,
"this\"is {invalid, ]json data")
# No dict
self.assertRaises(luxi.ProtocolError, luxi.ParseRequest,
serializer.DumpJson(123))
# Empty dict
self.assertRaises(luxi.ProtocolError, luxi.ParseRequest,
serializer.DumpJson({ }))
# No arguments
self.assertRaises(luxi.ProtocolError, luxi.ParseRequest,
serializer.DumpJson({ luxi.KEY_METHOD: "foo", }))
# No method
self.assertRaises(luxi.ProtocolError, luxi.ParseRequest,
serializer.DumpJson({ luxi.KEY_ARGS: [], }))
def testParseResponse(self):
msg = serializer.DumpJson({
luxi.KEY_SUCCESS: True,
luxi.KEY_RESULT: None,
})
self.assertEqual(luxi.ParseResponse(msg), (True, None))
self.assertRaises(luxi.ProtocolError, luxi.ParseResponse,
"this\"is {invalid, ]json data")
# No dict
self.assertRaises(luxi.ProtocolError, luxi.ParseResponse,
serializer.DumpJson(123))
# Empty dict
self.assertRaises(luxi.ProtocolError, luxi.ParseResponse,
serializer.DumpJson({ }))
# No success
self.assertRaises(luxi.ProtocolError, luxi.ParseResponse,
serializer.DumpJson({ luxi.KEY_RESULT: True, }))
# No result
self.assertRaises(luxi.ProtocolError, luxi.ParseResponse,
serializer.DumpJson({ luxi.KEY_SUCCESS: True, }))
def testFormatResponse(self):
for success, result in [(False, "error"), (True, "abc"),
(True, { "a": 123, "b": None, })]:
msg = luxi.FormatResponse(success, result)
msgdata = serializer.LoadJson(msg)
self.assert_(luxi.KEY_SUCCESS in msgdata)
self.assert_(luxi.KEY_RESULT in msgdata)
self.assertEqualValues(msgdata,
{ luxi.KEY_SUCCESS: success,
luxi.KEY_RESULT: result,
})
def testFormatRequest(self):
for method, args in [("a", []), ("b", [1, 2, 3])]:
msg = luxi.FormatRequest(method, args)
msgdata = serializer.LoadJson(msg)
self.assert_(luxi.KEY_METHOD in msgdata)
self.assert_(luxi.KEY_ARGS in msgdata)
self.assertEqualValues(msgdata,
{ luxi.KEY_METHOD: method,
luxi.KEY_ARGS: args,
})
if __name__ == "__main__":
testutils.GanetiTestProgram()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment