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

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 = \ ...@@ -319,6 +319,7 @@ dist_TESTS = \
test/ganeti.hooks_unittest.py \ test/ganeti.hooks_unittest.py \
test/ganeti.http_unittest.py \ test/ganeti.http_unittest.py \
test/ganeti.locking_unittest.py \ test/ganeti.locking_unittest.py \
test/ganeti.luxi_unittest.py \
test/ganeti.mcpu_unittest.py \ test/ganeti.mcpu_unittest.py \
test/ganeti.objects_unittest.py \ test/ganeti.objects_unittest.py \
test/ganeti.rapi.resources_unittest.py \ test/ganeti.rapi.resources_unittest.py \
......
...@@ -157,36 +157,19 @@ class ClientRqHandler(SocketServer.BaseRequestHandler): ...@@ -157,36 +157,19 @@ class ClientRqHandler(SocketServer.BaseRequestHandler):
logging.debug("client closed connection") logging.debug("client closed connection")
break break
request = serializer.LoadJson(msg) (method, args) = luxi.ParseRequest(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
success = False success = False
try: try:
result = self._ops.handle_request(method, args) result = self._ops.handle_request(method, args)
success = True success = True
except errors.GenericError, err: except errors.GenericError, err:
success = False
result = errors.EncodeException(err) result = errors.EncodeException(err)
except: except:
logging.error("Unexpected exception", exc_info=True) logging.error("Unexpected exception", exc_info=True)
err = sys.exc_info() result = "Caught exception: %s" % str(sys.exc_info()[1])
result = "Caught exception: %s" % str(err[1])
response = { self.send_message(luxi.FormatResponse(success, result))
luxi.KEY_SUCCESS: success,
luxi.KEY_RESULT: result,
}
logging.debug("response: %s", response)
self.send_message(serializer.DumpJson(response))
def read_message(self): def read_message(self):
while not self._msgs: while not self._msgs:
...@@ -199,7 +182,6 @@ class ClientRqHandler(SocketServer.BaseRequestHandler): ...@@ -199,7 +182,6 @@ class ClientRqHandler(SocketServer.BaseRequestHandler):
return self._msgs.popleft() return self._msgs.popleft()
def send_message(self, msg): def send_message(self, msg):
#print "sending", msg
# TODO: sendall is not guaranteed to send everything # TODO: sendall is not guaranteed to send everything
self.request.sendall(msg + self.EOM) self.request.sendall(msg + self.EOM)
......
...@@ -815,7 +815,7 @@ class HttpMessageReader(object): ...@@ -815,7 +815,7 @@ class HttpMessageReader(object):
buf = self._ContinueParsing(buf, eof) buf = self._ContinueParsing(buf, eof)
# Must be done only after the buffer has been evaluated # 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 if (eof and
self.parser_status in (self.PS_START_LINE, self.parser_status in (self.PS_START_LINE,
self.PS_HEADERS)): self.PS_HEADERS)):
......
...@@ -33,14 +33,15 @@ import socket ...@@ -33,14 +33,15 @@ import socket
import collections import collections
import time import time
import errno import errno
import logging
from ganeti import serializer from ganeti import serializer
from ganeti import constants from ganeti import constants
from ganeti import errors from ganeti import errors
KEY_METHOD = 'method' KEY_METHOD = "method"
KEY_ARGS = 'args' KEY_ARGS = "args"
KEY_SUCCESS = "success" KEY_SUCCESS = "success"
KEY_RESULT = "result" KEY_RESULT = "result"
...@@ -233,6 +234,98 @@ class Transport: ...@@ -233,6 +234,98 @@ class Transport:
self.socket = None 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): class Client(object):
"""High-level client implementation. """High-level client implementation.
...@@ -282,46 +375,20 @@ class Client(object): ...@@ -282,46 +375,20 @@ class Client(object):
except Exception: # pylint: disable-msg=W0703 except Exception: # pylint: disable-msg=W0703
pass pass
def CallMethod(self, method, args): def _SendMethodCall(self, data):
"""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)
# Send request and wait for response # Send request and wait for response
try: try:
self._InitTransport() self._InitTransport()
result = self.transport.Call(send_data) return self.transport.Call(data)
except Exception: except Exception:
self._CloseTransport() self._CloseTransport()
raise raise
# Parse the result def CallMethod(self, method, args):
try: """Send a generic request and return the response.
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)
return result """
return CallLuxiMethod(self._SendMethodCall, method, args)
def SetQueueDrainFlag(self, drain_flag): def SetQueueDrainFlag(self, drain_flag):
return self.CallMethod(REQ_QUEUE_SET_DRAIN_FLAG, 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