Revision 912b2278

b/Makefile.am
465 465

  
466 466
rpc_PYTHON = \
467 467
	lib/rpc/__init__.py \
468
	lib/rpc/client.py \
468 469
	lib/rpc/errors.py \
469 470
	lib/rpc/node.py \
470 471
	lib/rpc/transport.py
......
1444 1445
	test/py/ganeti.rapi.rlib2_unittest.py \
1445 1446
	test/py/ganeti.rapi.testutils_unittest.py \
1446 1447
	test/py/ganeti.rpc_unittest.py \
1448
	test/py/ganeti.rpc.client_unittest.py \
1447 1449
	test/py/ganeti.runtime_unittest.py \
1448 1450
	test/py/ganeti.serializer_unittest.py \
1449 1451
	test/py/ganeti.server.rapi_unittest.py \
b/lib/luxi.py
29 29

  
30 30
"""
31 31

  
32
import logging
33

  
34
from ganeti import serializer
35 32
from ganeti import constants
36
from ganeti import errors
37 33
from ganeti import objects
38
from ganeti import pathutils
39
from ganeti.rpc import transport as t
34
import ganeti.rpc.client as cl
35
from ganeti.rpc.transport import Transport
40 36
from ganeti.rpc.errors import (ProtocolError, ConnectionClosedError,
41 37
                               TimeoutError, RequestError, NoMasterError,
42 38
                               PermissionError)
......
49 45
  "RequestError",
50 46
  "NoMasterError",
51 47
  "PermissionError",
52
  "ParseRequest",
53
  "ParseResponse",
54
  "FormatRequest",
55
  "FormatResponse",
56
  "CallLuxiMethod",
57 48
  # classes:
58 49
  "Client"
59 50
  ]
60 51

  
61

  
62
KEY_METHOD = constants.LUXI_KEY_METHOD
63
KEY_ARGS = constants.LUXI_KEY_ARGS
64
KEY_SUCCESS = constants.LUXI_KEY_SUCCESS
65
KEY_RESULT = constants.LUXI_KEY_RESULT
66
KEY_VERSION = constants.LUXI_KEY_VERSION
67

  
68 52
REQ_SUBMIT_JOB = constants.LUXI_REQ_SUBMIT_JOB
69 53
REQ_SUBMIT_JOB_TO_DRAINED_QUEUE = constants.LUXI_REQ_SUBMIT_JOB_TO_DRAINED_QUEUE
70 54
REQ_SUBMIT_MANY_JOBS = constants.LUXI_REQ_SUBMIT_MANY_JOBS
......
93 77
WFJC_TIMEOUT = constants.LUXI_WFJC_TIMEOUT
94 78

  
95 79

  
96
def ParseRequest(msg):
97
  """Parses a LUXI request message.
98

  
99
  """
100
  try:
101
    request = serializer.LoadJson(msg)
102
  except ValueError, err:
103
    raise ProtocolError("Invalid LUXI request (parsing error): %s" % err)
104

  
105
  logging.debug("LUXI request: %s", request)
106

  
107
  if not isinstance(request, dict):
108
    logging.error("LUXI request not a dict: %r", msg)
109
    raise ProtocolError("Invalid LUXI request (not a dict)")
110

  
111
  method = request.get(KEY_METHOD, None) # pylint: disable=E1103
112
  args = request.get(KEY_ARGS, None) # pylint: disable=E1103
113
  version = request.get(KEY_VERSION, None) # pylint: disable=E1103
114

  
115
  if method is None or args is None:
116
    logging.error("LUXI request missing method or arguments: %r", msg)
117
    raise ProtocolError(("Invalid LUXI request (no method or arguments"
118
                         " in request): %r") % msg)
119

  
120
  return (method, args, version)
121

  
122

  
123
def ParseResponse(msg):
124
  """Parses a LUXI response message.
125

  
126
  """
127
  # Parse the result
128
  try:
129
    data = serializer.LoadJson(msg)
130
  except KeyboardInterrupt:
131
    raise
132
  except Exception, err:
133
    raise ProtocolError("Error while deserializing response: %s" % str(err))
134

  
135
  # Validate response
136
  if not (isinstance(data, dict) and
137
          KEY_SUCCESS in data and
138
          KEY_RESULT in data):
139
    raise ProtocolError("Invalid response from server: %r" % data)
140

  
141
  return (data[KEY_SUCCESS], data[KEY_RESULT],
142
          data.get(KEY_VERSION, None)) # pylint: disable=E1103
143

  
144

  
145
def FormatResponse(success, result, version=None):
146
  """Formats a LUXI response message.
147

  
148
  """
149
  response = {
150
    KEY_SUCCESS: success,
151
    KEY_RESULT: result,
152
    }
153

  
154
  if version is not None:
155
    response[KEY_VERSION] = version
156

  
157
  logging.debug("LUXI response: %s", response)
158

  
159
  return serializer.DumpJson(response)
160

  
161

  
162
def FormatRequest(method, args, version=None):
163
  """Formats a LUXI request message.
164

  
165
  """
166
  # Build request
167
  request = {
168
    KEY_METHOD: method,
169
    KEY_ARGS: args,
170
    }
171

  
172
  if version is not None:
173
    request[KEY_VERSION] = version
174

  
175
  # Serialize the request
176
  return serializer.DumpJson(request)
177

  
178

  
179
def CallLuxiMethod(transport_cb, method, args, version=None):
180
  """Send a LUXI request via a transport and return the response.
181

  
182
  """
183
  assert callable(transport_cb)
184

  
185
  request_msg = FormatRequest(method, args, version=version)
186

  
187
  # Send request and wait for response
188
  response_msg = transport_cb(request_msg)
189

  
190
  (success, result, resp_version) = ParseResponse(response_msg)
191

  
192
  # Verify version if there was one in the response
193
  if resp_version is not None and resp_version != version:
194
    raise errors.LuxiError("LUXI version mismatch, client %s, response %s" %
195
                           (version, resp_version))
196

  
197
  if success:
198
    return result
199

  
200
  errors.MaybeRaise(result)
201
  raise RequestError(result)
202

  
203

  
204
class Client(object):
80
class Client(cl.AbstractClient):
205 81
  """High-level client implementation.
206 82

  
207 83
  This uses a backing Transport-like class on top of which it
208 84
  implements data serialization/deserialization.
209 85

  
210 86
  """
211
  def __init__(self, address=None, timeouts=None, transport=t.Transport):
87
  def __init__(self, address=None, timeouts=None, transport=Transport):
212 88
    """Constructor for the Client class.
213 89

  
214
    Arguments:
215
      - address: a valid address the the used transport class
216
      - timeout: a list of timeouts, to be used on connect and read/write
217
      - transport: a Transport-like class
218

  
219

  
220
    If timeout is not passed, the default timeouts of the transport
221
    class are used.
222

  
223
    """
224
    if address is None:
225
      address = pathutils.MASTER_SOCKET
226
    self.address = address
227
    self.timeouts = timeouts
228
    self.transport_class = transport
229
    self.transport = None
230
    self._InitTransport()
231

  
232
  def _InitTransport(self):
233
    """(Re)initialize the transport if needed.
234

  
235
    """
236
    if self.transport is None:
237
      self.transport = self.transport_class(self.address,
238
                                            timeouts=self.timeouts)
239

  
240
  def _CloseTransport(self):
241
    """Close the transport, ignoring errors.
242

  
243
    """
244
    if self.transport is None:
245
      return
246
    try:
247
      old_transp = self.transport
248
      self.transport = None
249
      old_transp.Close()
250
    except Exception: # pylint: disable=W0703
251
      pass
252

  
253
  def _SendMethodCall(self, data):
254
    # Send request and wait for response
255
    try:
256
      self._InitTransport()
257
      return self.transport.Call(data)
258
    except Exception:
259
      self._CloseTransport()
260
      raise
261

  
262
  def Close(self):
263
    """Close the underlying connection.
264

  
265
    """
266
    self._CloseTransport()
267

  
268
  def CallMethod(self, method, args):
269
    """Send a generic request and return the response.
90
    Arguments are the same as for L{AbstractClient}.
270 91

  
271 92
    """
272
    if not isinstance(args, (list, tuple)):
273
      raise errors.ProgrammerError("Invalid parameter passed to CallMethod:"
274
                                   " expected list, got %s" % type(args))
275
    return CallLuxiMethod(self._SendMethodCall, method, args,
276
                          version=constants.LUXI_VERSION)
93
    super(Client, self).__init__(address, timeouts, transport)
277 94

  
278 95
  def SetQueueDrainFlag(self, drain_flag):
279 96
    return self.CallMethod(REQ_SET_DRAIN_FLAG, (drain_flag, ))
b/lib/rapi/testutils.py
36 36
from ganeti import utils
37 37
from ganeti import compat
38 38
from ganeti import luxi
39
import ganeti.rpc.client as rpccl
39 40
from ganeti import rapi
40 41

  
41 42
import ganeti.http.server # pylint: disable=W0611
......
273 274
    raised. There is no return value.
274 275

  
275 276
    """
276
    (method, _, _) = luxi.ParseRequest(data)
277
    (method, _, _) = rpccl.ParseRequest(data)
277 278

  
278 279
    # Take a note of called method
279 280
    self._record_fn(method)
b/lib/rpc/client.py
1
#
2
#
3

  
4
# Copyright (C) 2013 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
"""Module for generic RPC clients.
23

  
24
"""
25

  
26
import logging
27

  
28
from ganeti import pathutils
29
import ganeti.rpc.transport as t
30

  
31
from ganeti import constants
32
from ganeti import errors
33
from ganeti.rpc.errors import (ProtocolError, RequestError, LuxiError)
34
from ganeti import serializer
35

  
36
KEY_METHOD = constants.LUXI_KEY_METHOD
37
KEY_ARGS = constants.LUXI_KEY_ARGS
38
KEY_SUCCESS = constants.LUXI_KEY_SUCCESS
39
KEY_RESULT = constants.LUXI_KEY_RESULT
40
KEY_VERSION = constants.LUXI_KEY_VERSION
41

  
42

  
43
def ParseRequest(msg):
44
  """Parses a request message.
45

  
46
  """
47
  try:
48
    request = serializer.LoadJson(msg)
49
  except ValueError, err:
50
    raise ProtocolError("Invalid LUXI request (parsing error): %s" % err)
51

  
52
  logging.debug("LUXI request: %s", request)
53

  
54
  if not isinstance(request, dict):
55
    logging.error("LUXI request not a dict: %r", msg)
56
    raise ProtocolError("Invalid LUXI request (not a dict)")
57

  
58
  method = request.get(KEY_METHOD, None) # pylint: disable=E1103
59
  args = request.get(KEY_ARGS, None) # pylint: disable=E1103
60
  version = request.get(KEY_VERSION, None) # pylint: disable=E1103
61

  
62
  if method is None or args is None:
63
    logging.error("LUXI request missing method or arguments: %r", msg)
64
    raise ProtocolError(("Invalid LUXI request (no method or arguments"
65
                         " in request): %r") % msg)
66

  
67
  return (method, args, version)
68

  
69

  
70
def ParseResponse(msg):
71
  """Parses a response message.
72

  
73
  """
74
  # Parse the result
75
  try:
76
    data = serializer.LoadJson(msg)
77
  except KeyboardInterrupt:
78
    raise
79
  except Exception, err:
80
    raise ProtocolError("Error while deserializing response: %s" % str(err))
81

  
82
  # Validate response
83
  if not (isinstance(data, dict) and
84
          KEY_SUCCESS in data and
85
          KEY_RESULT in data):
86
    raise ProtocolError("Invalid response from server: %r" % data)
87

  
88
  return (data[KEY_SUCCESS], data[KEY_RESULT],
89
          data.get(KEY_VERSION, None)) # pylint: disable=E1103
90

  
91

  
92
def FormatResponse(success, result, version=None):
93
  """Formats a response message.
94

  
95
  """
96
  response = {
97
    KEY_SUCCESS: success,
98
    KEY_RESULT: result,
99
    }
100

  
101
  if version is not None:
102
    response[KEY_VERSION] = version
103

  
104
  logging.debug("LUXI response: %s", response)
105

  
106
  return serializer.DumpJson(response)
107

  
108

  
109
def FormatRequest(method, args, version=None):
110
  """Formats a request message.
111

  
112
  """
113
  # Build request
114
  request = {
115
    KEY_METHOD: method,
116
    KEY_ARGS: args,
117
    }
118

  
119
  if version is not None:
120
    request[KEY_VERSION] = version
121

  
122
  # Serialize the request
123
  return serializer.DumpJson(request)
124

  
125

  
126
def CallLuxiMethod(transport_cb, method, args, version=None):
127
  """Send a LUXI request via a transport and return the response.
128

  
129
  """
130
  assert callable(transport_cb)
131

  
132
  request_msg = FormatRequest(method, args, version=version)
133

  
134
  # Send request and wait for response
135
  response_msg = transport_cb(request_msg)
136

  
137
  (success, result, resp_version) = ParseResponse(response_msg)
138

  
139
  # Verify version if there was one in the response
140
  if resp_version is not None and resp_version != version:
141
    raise LuxiError("LUXI version mismatch, client %s, response %s" %
142
                    (version, resp_version))
143

  
144
  if success:
145
    return result
146

  
147
  errors.MaybeRaise(result)
148
  raise RequestError(result)
149

  
150

  
151
class AbstractClient(object):
152
  """High-level client abstraction.
153

  
154
  This uses a backing Transport-like class on top of which it
155
  implements data serialization/deserialization.
156

  
157
  """
158

  
159
  def __init__(self, address=None, timeouts=None,
160
               transport=t.Transport):
161
    """Constructor for the Client class.
162

  
163
    Arguments:
164
      - address: a valid address the the used transport class
165
      - timeout: a list of timeouts, to be used on connect and read/write
166
      - transport: a Transport-like class
167

  
168

  
169
    If timeout is not passed, the default timeouts of the transport
170
    class are used.
171

  
172
    """
173
    if address is None:
174
      address = pathutils.MASTER_SOCKET
175
    self.address = address
176
    self.timeouts = timeouts
177
    self.transport_class = transport
178
    self.transport = None
179
    self._InitTransport()
180

  
181
  def _InitTransport(self):
182
    """(Re)initialize the transport if needed.
183

  
184
    """
185
    if self.transport is None:
186
      self.transport = self.transport_class(self.address,
187
                                            timeouts=self.timeouts)
188

  
189
  def _CloseTransport(self):
190
    """Close the transport, ignoring errors.
191

  
192
    """
193
    if self.transport is None:
194
      return
195
    try:
196
      old_transp = self.transport
197
      self.transport = None
198
      old_transp.Close()
199
    except Exception: # pylint: disable=W0703
200
      pass
201

  
202
  def _SendMethodCall(self, data):
203
    # Send request and wait for response
204
    try:
205
      self._InitTransport()
206
      return self.transport.Call(data)
207
    except Exception:
208
      self._CloseTransport()
209
      raise
210

  
211
  def Close(self):
212
    """Close the underlying connection.
213

  
214
    """
215
    self._CloseTransport()
216

  
217
  def close(self):
218
    """Same as L{Close}, to be used with contextlib.closing(...).
219

  
220
    """
221
    self.Close()
222

  
223
  def CallMethod(self, method, args):
224
    """Send a generic request and return the response.
225

  
226
    """
227
    if not isinstance(args, (list, tuple)):
228
      raise errors.ProgrammerError("Invalid parameter passed to CallMethod:"
229
                                   " expected list, got %s" % type(args))
230
    return CallLuxiMethod(self._SendMethodCall, method, args,
231
                          version=constants.LUXI_VERSION)
b/lib/server/masterd.py
53 53
from ganeti import ssconf
54 54
from ganeti import workerpool
55 55
import ganeti.rpc.node as rpc
56
import ganeti.rpc.client as rpccl
56 57
from ganeti import bootstrap
57 58
from ganeti import netutils
58 59
from ganeti import objects
......
92 93
    client_ops = ClientOps(server)
93 94

  
94 95
    try:
95
      (method, args, ver) = luxi.ParseRequest(message)
96
      (method, args, ver) = rpccl.ParseRequest(message)
96 97
    except luxi.ProtocolError, err:
97 98
      logging.error("Protocol Error: %s", err)
98 99
      client.close_log()
......
117 118
      result = "Caught exception: %s" % str(err[1])
118 119

  
119 120
    try:
120
      reply = luxi.FormatResponse(success, result)
121
      reply = rpccl.FormatResponse(success, result)
121 122
      client.send_message(reply)
122 123
      # awake the main thread so that it can write out the data.
123 124
      server.awaker.signal()
b/test/py/ganeti.luxi_unittest.py
1 1
#!/usr/bin/python
2 2
#
3 3

  
4
# Copyright (C) 2010 Google Inc.
4
# Copyright (C) 2010, 2013 Google Inc.
5 5
#
6 6
# This program is free software; you can redistribute it and/or modify
7 7
# it under the terms of the GNU General Public License as published by
......
19 19
# 02110-1301, USA.
20 20

  
21 21

  
22
"""Script for unittesting the luxi module"""
22
"""Script for unittesting the luxi module.
23

  
24
Currently empty (after all the tests moved to ganeti.rpc.client_unittest.py)."""
23 25

  
24 26

  
25 27
import unittest
......
30 32
from ganeti import serializer
31 33

  
32 34
import testutils
33

  
34

  
35
class TestLuxiParsing(testutils.GanetiTestCase):
36
  def testParseRequest(self):
37
    msg = serializer.DumpJson({
38
      luxi.KEY_METHOD: "foo",
39
      luxi.KEY_ARGS: ("bar", "baz", 123),
40
      })
41

  
42
    self.assertEqualValues(luxi.ParseRequest(msg),
43
                           ("foo", ["bar", "baz", 123], None))
44

  
45
    self.assertRaises(luxi.ProtocolError, luxi.ParseRequest,
46
                      "this\"is {invalid, ]json data")
47

  
48
    # No dict
49
    self.assertRaises(luxi.ProtocolError, luxi.ParseRequest,
50
                      serializer.DumpJson(123))
51

  
52
    # Empty dict
53
    self.assertRaises(luxi.ProtocolError, luxi.ParseRequest,
54
                      serializer.DumpJson({ }))
55

  
56
    # No arguments
57
    self.assertRaises(luxi.ProtocolError, luxi.ParseRequest,
58
                      serializer.DumpJson({ luxi.KEY_METHOD: "foo", }))
59

  
60
    # No method
61
    self.assertRaises(luxi.ProtocolError, luxi.ParseRequest,
62
                      serializer.DumpJson({ luxi.KEY_ARGS: [], }))
63

  
64
    # No method or arguments
65
    self.assertRaises(luxi.ProtocolError, luxi.ParseRequest,
66
                      serializer.DumpJson({ luxi.KEY_VERSION: 1, }))
67

  
68
  def testParseRequestWithVersion(self):
69
    msg = serializer.DumpJson({
70
      luxi.KEY_METHOD: "version",
71
      luxi.KEY_ARGS: (["some"], "args", 0, "here"),
72
      luxi.KEY_VERSION: 20100101,
73
      })
74

  
75
    self.assertEqualValues(luxi.ParseRequest(msg),
76
                           ("version", [["some"], "args", 0, "here"], 20100101))
77

  
78
  def testParseResponse(self):
79
    msg = serializer.DumpJson({
80
      luxi.KEY_SUCCESS: True,
81
      luxi.KEY_RESULT: None,
82
      })
83

  
84
    self.assertEqual(luxi.ParseResponse(msg), (True, None, None))
85

  
86
    self.assertRaises(luxi.ProtocolError, luxi.ParseResponse,
87
                      "this\"is {invalid, ]json data")
88

  
89
    # No dict
90
    self.assertRaises(luxi.ProtocolError, luxi.ParseResponse,
91
                      serializer.DumpJson(123))
92

  
93
    # Empty dict
94
    self.assertRaises(luxi.ProtocolError, luxi.ParseResponse,
95
                      serializer.DumpJson({ }))
96

  
97
    # No success
98
    self.assertRaises(luxi.ProtocolError, luxi.ParseResponse,
99
                      serializer.DumpJson({ luxi.KEY_RESULT: True, }))
100

  
101
    # No result
102
    self.assertRaises(luxi.ProtocolError, luxi.ParseResponse,
103
                      serializer.DumpJson({ luxi.KEY_SUCCESS: True, }))
104

  
105
    # No result or success
106
    self.assertRaises(luxi.ProtocolError, luxi.ParseResponse,
107
                      serializer.DumpJson({ luxi.KEY_VERSION: 123, }))
108

  
109
  def testParseResponseWithVersion(self):
110
    msg = serializer.DumpJson({
111
      luxi.KEY_SUCCESS: True,
112
      luxi.KEY_RESULT: "Hello World",
113
      luxi.KEY_VERSION: 19991234,
114
      })
115

  
116
    self.assertEqual(luxi.ParseResponse(msg), (True, "Hello World", 19991234))
117

  
118
  def testFormatResponse(self):
119
    for success, result in [(False, "error"), (True, "abc"),
120
                            (True, { "a": 123, "b": None, })]:
121
      msg = luxi.FormatResponse(success, result)
122
      msgdata = serializer.LoadJson(msg)
123
      self.assert_(luxi.KEY_SUCCESS in msgdata)
124
      self.assert_(luxi.KEY_RESULT in msgdata)
125
      self.assert_(luxi.KEY_VERSION not in msgdata)
126
      self.assertEqualValues(msgdata,
127
                             { luxi.KEY_SUCCESS: success,
128
                               luxi.KEY_RESULT: result,
129
                             })
130

  
131
  def testFormatResponseWithVersion(self):
132
    for success, result, version in [(False, "error", 123), (True, "abc", 999),
133
                                     (True, { "a": 123, "b": None, }, 2010)]:
134
      msg = luxi.FormatResponse(success, result, version=version)
135
      msgdata = serializer.LoadJson(msg)
136
      self.assert_(luxi.KEY_SUCCESS in msgdata)
137
      self.assert_(luxi.KEY_RESULT in msgdata)
138
      self.assert_(luxi.KEY_VERSION in msgdata)
139
      self.assertEqualValues(msgdata,
140
                             { luxi.KEY_SUCCESS: success,
141
                               luxi.KEY_RESULT: result,
142
                               luxi.KEY_VERSION: version,
143
                             })
144

  
145
  def testFormatRequest(self):
146
    for method, args in [("a", []), ("b", [1, 2, 3])]:
147
      msg = luxi.FormatRequest(method, args)
148
      msgdata = serializer.LoadJson(msg)
149
      self.assert_(luxi.KEY_METHOD in msgdata)
150
      self.assert_(luxi.KEY_ARGS in msgdata)
151
      self.assert_(luxi.KEY_VERSION not in msgdata)
152
      self.assertEqualValues(msgdata,
153
                             { luxi.KEY_METHOD: method,
154
                               luxi.KEY_ARGS: args,
155
                             })
156

  
157
  def testFormatRequestWithVersion(self):
158
    for method, args, version in [("fn1", [], 123), ("fn2", [1, 2, 3], 999)]:
159
      msg = luxi.FormatRequest(method, args, version=version)
160
      msgdata = serializer.LoadJson(msg)
161
      self.assert_(luxi.KEY_METHOD in msgdata)
162
      self.assert_(luxi.KEY_ARGS in msgdata)
163
      self.assert_(luxi.KEY_VERSION in msgdata)
164
      self.assertEqualValues(msgdata,
165
                             { luxi.KEY_METHOD: method,
166
                               luxi.KEY_ARGS: args,
167
                               luxi.KEY_VERSION: version,
168
                             })
169

  
170

  
171
class TestCallLuxiMethod(unittest.TestCase):
172
  MY_LUXI_VERSION = 1234
173
  assert constants.LUXI_VERSION != MY_LUXI_VERSION
174

  
175
  def testSuccessNoVersion(self):
176
    def _Cb(msg):
177
      (method, args, version) = luxi.ParseRequest(msg)
178
      self.assertEqual(method, "fn1")
179
      self.assertEqual(args, "Hello World")
180
      return luxi.FormatResponse(True, "x")
181

  
182
    result = luxi.CallLuxiMethod(_Cb, "fn1", "Hello World")
183

  
184
  def testServerVersionOnly(self):
185
    def _Cb(msg):
186
      (method, args, version) = luxi.ParseRequest(msg)
187
      self.assertEqual(method, "fn1")
188
      self.assertEqual(args, "Hello World")
189
      return luxi.FormatResponse(True, "x", version=self.MY_LUXI_VERSION)
190

  
191
    self.assertRaises(errors.LuxiError, luxi.CallLuxiMethod,
192
                      _Cb, "fn1", "Hello World")
193

  
194
  def testWithVersion(self):
195
    def _Cb(msg):
196
      (method, args, version) = luxi.ParseRequest(msg)
197
      self.assertEqual(method, "fn99")
198
      self.assertEqual(args, "xyz")
199
      return luxi.FormatResponse(True, "y", version=self.MY_LUXI_VERSION)
200

  
201
    self.assertEqual("y", luxi.CallLuxiMethod(_Cb, "fn99", "xyz",
202
                                              version=self.MY_LUXI_VERSION))
203

  
204
  def testVersionMismatch(self):
205
    def _Cb(msg):
206
      (method, args, version) = luxi.ParseRequest(msg)
207
      self.assertEqual(method, "fn5")
208
      self.assertEqual(args, "xyz")
209
      return luxi.FormatResponse(True, "F", version=self.MY_LUXI_VERSION * 2)
210

  
211
    self.assertRaises(errors.LuxiError, luxi.CallLuxiMethod,
212
                      _Cb, "fn5", "xyz", version=self.MY_LUXI_VERSION)
213

  
214
  def testError(self):
215
    def _Cb(msg):
216
      (method, args, version) = luxi.ParseRequest(msg)
217
      self.assertEqual(method, "fnErr")
218
      self.assertEqual(args, [])
219
      err = errors.OpPrereqError("Test")
220
      return luxi.FormatResponse(False, errors.EncodeException(err))
221

  
222
    self.assertRaises(errors.OpPrereqError, luxi.CallLuxiMethod,
223
                      _Cb, "fnErr", [])
224

  
225
  def testErrorWithVersionMismatch(self):
226
    def _Cb(msg):
227
      (method, args, version) = luxi.ParseRequest(msg)
228
      self.assertEqual(method, "fnErr")
229
      self.assertEqual(args, [])
230
      err = errors.OpPrereqError("TestVer")
231
      return luxi.FormatResponse(False, errors.EncodeException(err),
232
                                 version=self.MY_LUXI_VERSION * 2)
233

  
234
    self.assertRaises(errors.LuxiError, luxi.CallLuxiMethod,
235
                      _Cb, "fnErr", [],
236
                      version=self.MY_LUXI_VERSION)
237

  
238
  def testErrorWithVersion(self):
239
    def _Cb(msg):
240
      (method, args, version) = luxi.ParseRequest(msg)
241
      self.assertEqual(method, "fn9")
242
      self.assertEqual(args, [])
243
      err = errors.OpPrereqError("TestVer")
244
      return luxi.FormatResponse(False, errors.EncodeException(err),
245
                                 version=self.MY_LUXI_VERSION)
246

  
247
    self.assertRaises(errors.OpPrereqError, luxi.CallLuxiMethod,
248
                      _Cb, "fn9", [],
249
                      version=self.MY_LUXI_VERSION)
250

  
251

  
252
if __name__ == "__main__":
253
  testutils.GanetiTestProgram()
b/test/py/ganeti.rpc.client_unittest.py
1
#!/usr/bin/python
2
#
3

  
4
# Copyright (C) 2013 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 RPC client module"""
23

  
24

  
25
import unittest
26

  
27
from ganeti import constants
28
from ganeti import errors
29
import ganeti.rpc.client as luxi
30
from ganeti import serializer
31

  
32
import testutils
33

  
34

  
35
class TestLuxiParsing(testutils.GanetiTestCase):
36
  def testParseRequest(self):
37
    msg = serializer.DumpJson({
38
      luxi.KEY_METHOD: "foo",
39
      luxi.KEY_ARGS: ("bar", "baz", 123),
40
      })
41

  
42
    self.assertEqualValues(luxi.ParseRequest(msg),
43
                           ("foo", ["bar", "baz", 123], None))
44

  
45
    self.assertRaises(luxi.ProtocolError, luxi.ParseRequest,
46
                      "this\"is {invalid, ]json data")
47

  
48
    # No dict
49
    self.assertRaises(luxi.ProtocolError, luxi.ParseRequest,
50
                      serializer.DumpJson(123))
51

  
52
    # Empty dict
53
    self.assertRaises(luxi.ProtocolError, luxi.ParseRequest,
54
                      serializer.DumpJson({ }))
55

  
56
    # No arguments
57
    self.assertRaises(luxi.ProtocolError, luxi.ParseRequest,
58
                      serializer.DumpJson({ luxi.KEY_METHOD: "foo", }))
59

  
60
    # No method
61
    self.assertRaises(luxi.ProtocolError, luxi.ParseRequest,
62
                      serializer.DumpJson({ luxi.KEY_ARGS: [], }))
63

  
64
    # No method or arguments
65
    self.assertRaises(luxi.ProtocolError, luxi.ParseRequest,
66
                      serializer.DumpJson({ luxi.KEY_VERSION: 1, }))
67

  
68
  def testParseRequestWithVersion(self):
69
    msg = serializer.DumpJson({
70
      luxi.KEY_METHOD: "version",
71
      luxi.KEY_ARGS: (["some"], "args", 0, "here"),
72
      luxi.KEY_VERSION: 20100101,
73
      })
74

  
75
    self.assertEqualValues(luxi.ParseRequest(msg),
76
                           ("version", [["some"], "args", 0, "here"], 20100101))
77

  
78
  def testParseResponse(self):
79
    msg = serializer.DumpJson({
80
      luxi.KEY_SUCCESS: True,
81
      luxi.KEY_RESULT: None,
82
      })
83

  
84
    self.assertEqual(luxi.ParseResponse(msg), (True, None, None))
85

  
86
    self.assertRaises(luxi.ProtocolError, luxi.ParseResponse,
87
                      "this\"is {invalid, ]json data")
88

  
89
    # No dict
90
    self.assertRaises(luxi.ProtocolError, luxi.ParseResponse,
91
                      serializer.DumpJson(123))
92

  
93
    # Empty dict
94
    self.assertRaises(luxi.ProtocolError, luxi.ParseResponse,
95
                      serializer.DumpJson({ }))
96

  
97
    # No success
98
    self.assertRaises(luxi.ProtocolError, luxi.ParseResponse,
99
                      serializer.DumpJson({ luxi.KEY_RESULT: True, }))
100

  
101
    # No result
102
    self.assertRaises(luxi.ProtocolError, luxi.ParseResponse,
103
                      serializer.DumpJson({ luxi.KEY_SUCCESS: True, }))
104

  
105
    # No result or success
106
    self.assertRaises(luxi.ProtocolError, luxi.ParseResponse,
107
                      serializer.DumpJson({ luxi.KEY_VERSION: 123, }))
108

  
109
  def testParseResponseWithVersion(self):
110
    msg = serializer.DumpJson({
111
      luxi.KEY_SUCCESS: True,
112
      luxi.KEY_RESULT: "Hello World",
113
      luxi.KEY_VERSION: 19991234,
114
      })
115

  
116
    self.assertEqual(luxi.ParseResponse(msg), (True, "Hello World", 19991234))
117

  
118
  def testFormatResponse(self):
119
    for success, result in [(False, "error"), (True, "abc"),
120
                            (True, { "a": 123, "b": None, })]:
121
      msg = luxi.FormatResponse(success, result)
122
      msgdata = serializer.LoadJson(msg)
123
      self.assert_(luxi.KEY_SUCCESS in msgdata)
124
      self.assert_(luxi.KEY_RESULT in msgdata)
125
      self.assert_(luxi.KEY_VERSION not in msgdata)
126
      self.assertEqualValues(msgdata,
127
                             { luxi.KEY_SUCCESS: success,
128
                               luxi.KEY_RESULT: result,
129
                             })
130

  
131
  def testFormatResponseWithVersion(self):
132
    for success, result, version in [(False, "error", 123), (True, "abc", 999),
133
                                     (True, { "a": 123, "b": None, }, 2010)]:
134
      msg = luxi.FormatResponse(success, result, version=version)
135
      msgdata = serializer.LoadJson(msg)
136
      self.assert_(luxi.KEY_SUCCESS in msgdata)
137
      self.assert_(luxi.KEY_RESULT in msgdata)
138
      self.assert_(luxi.KEY_VERSION in msgdata)
139
      self.assertEqualValues(msgdata,
140
                             { luxi.KEY_SUCCESS: success,
141
                               luxi.KEY_RESULT: result,
142
                               luxi.KEY_VERSION: version,
143
                             })
144

  
145
  def testFormatRequest(self):
146
    for method, args in [("a", []), ("b", [1, 2, 3])]:
147
      msg = luxi.FormatRequest(method, args)
148
      msgdata = serializer.LoadJson(msg)
149
      self.assert_(luxi.KEY_METHOD in msgdata)
150
      self.assert_(luxi.KEY_ARGS in msgdata)
151
      self.assert_(luxi.KEY_VERSION not in msgdata)
152
      self.assertEqualValues(msgdata,
153
                             { luxi.KEY_METHOD: method,
154
                               luxi.KEY_ARGS: args,
155
                             })
156

  
157
  def testFormatRequestWithVersion(self):
158
    for method, args, version in [("fn1", [], 123), ("fn2", [1, 2, 3], 999)]:
159
      msg = luxi.FormatRequest(method, args, version=version)
160
      msgdata = serializer.LoadJson(msg)
161
      self.assert_(luxi.KEY_METHOD in msgdata)
162
      self.assert_(luxi.KEY_ARGS in msgdata)
163
      self.assert_(luxi.KEY_VERSION in msgdata)
164
      self.assertEqualValues(msgdata,
165
                             { luxi.KEY_METHOD: method,
166
                               luxi.KEY_ARGS: args,
167
                               luxi.KEY_VERSION: version,
168
                             })
169

  
170

  
171
class TestCallLuxiMethod(unittest.TestCase):
172
  MY_LUXI_VERSION = 1234
173
  assert constants.LUXI_VERSION != MY_LUXI_VERSION
174

  
175
  def testSuccessNoVersion(self):
176
    def _Cb(msg):
177
      (method, args, version) = luxi.ParseRequest(msg)
178
      self.assertEqual(method, "fn1")
179
      self.assertEqual(args, "Hello World")
180
      return luxi.FormatResponse(True, "x")
181

  
182
    result = luxi.CallLuxiMethod(_Cb, "fn1", "Hello World")
183

  
184
  def testServerVersionOnly(self):
185
    def _Cb(msg):
186
      (method, args, version) = luxi.ParseRequest(msg)
187
      self.assertEqual(method, "fn1")
188
      self.assertEqual(args, "Hello World")
189
      return luxi.FormatResponse(True, "x", version=self.MY_LUXI_VERSION)
190

  
191
    self.assertRaises(errors.LuxiError, luxi.CallLuxiMethod,
192
                      _Cb, "fn1", "Hello World")
193

  
194
  def testWithVersion(self):
195
    def _Cb(msg):
196
      (method, args, version) = luxi.ParseRequest(msg)
197
      self.assertEqual(method, "fn99")
198
      self.assertEqual(args, "xyz")
199
      return luxi.FormatResponse(True, "y", version=self.MY_LUXI_VERSION)
200

  
201
    self.assertEqual("y", luxi.CallLuxiMethod(_Cb, "fn99", "xyz",
202
                                              version=self.MY_LUXI_VERSION))
203

  
204
  def testVersionMismatch(self):
205
    def _Cb(msg):
206
      (method, args, version) = luxi.ParseRequest(msg)
207
      self.assertEqual(method, "fn5")
208
      self.assertEqual(args, "xyz")
209
      return luxi.FormatResponse(True, "F", version=self.MY_LUXI_VERSION * 2)
210

  
211
    self.assertRaises(errors.LuxiError, luxi.CallLuxiMethod,
212
                      _Cb, "fn5", "xyz", version=self.MY_LUXI_VERSION)
213

  
214
  def testError(self):
215
    def _Cb(msg):
216
      (method, args, version) = luxi.ParseRequest(msg)
217
      self.assertEqual(method, "fnErr")
218
      self.assertEqual(args, [])
219
      err = errors.OpPrereqError("Test")
220
      return luxi.FormatResponse(False, errors.EncodeException(err))
221

  
222
    self.assertRaises(errors.OpPrereqError, luxi.CallLuxiMethod,
223
                      _Cb, "fnErr", [])
224

  
225
  def testErrorWithVersionMismatch(self):
226
    def _Cb(msg):
227
      (method, args, version) = luxi.ParseRequest(msg)
228
      self.assertEqual(method, "fnErr")
229
      self.assertEqual(args, [])
230
      err = errors.OpPrereqError("TestVer")
231
      return luxi.FormatResponse(False, errors.EncodeException(err),
232
                                 version=self.MY_LUXI_VERSION * 2)
233

  
234
    self.assertRaises(errors.LuxiError, luxi.CallLuxiMethod,
235
                      _Cb, "fnErr", [],
236
                      version=self.MY_LUXI_VERSION)
237

  
238
  def testErrorWithVersion(self):
239
    def _Cb(msg):
240
      (method, args, version) = luxi.ParseRequest(msg)
241
      self.assertEqual(method, "fn9")
242
      self.assertEqual(args, [])
243
      err = errors.OpPrereqError("TestVer")
244
      return luxi.FormatResponse(False, errors.EncodeException(err),
245
                                 version=self.MY_LUXI_VERSION)
246

  
247
    self.assertRaises(errors.OpPrereqError, luxi.CallLuxiMethod,
248
                      _Cb, "fn9", [],
249
                      version=self.MY_LUXI_VERSION)
250

  
251

  
252
if __name__ == "__main__":
253
  testutils.GanetiTestProgram()

Also available in: Unified diff