Statistics
| Branch: | Tag: | Revision:

root / lib / rpc / client.py @ f3aebf6f

History | View | Annotate | Download (6.9 kB)

1 912b2278 Petr Pudlak
#
2 912b2278 Petr Pudlak
#
3 912b2278 Petr Pudlak
4 912b2278 Petr Pudlak
# Copyright (C) 2013 Google Inc.
5 912b2278 Petr Pudlak
#
6 912b2278 Petr Pudlak
# This program is free software; you can redistribute it and/or modify
7 912b2278 Petr Pudlak
# it under the terms of the GNU General Public License as published by
8 912b2278 Petr Pudlak
# the Free Software Foundation; either version 2 of the License, or
9 912b2278 Petr Pudlak
# (at your option) any later version.
10 912b2278 Petr Pudlak
#
11 912b2278 Petr Pudlak
# This program is distributed in the hope that it will be useful, but
12 912b2278 Petr Pudlak
# WITHOUT ANY WARRANTY; without even the implied warranty of
13 912b2278 Petr Pudlak
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 912b2278 Petr Pudlak
# General Public License for more details.
15 912b2278 Petr Pudlak
#
16 912b2278 Petr Pudlak
# You should have received a copy of the GNU General Public License
17 912b2278 Petr Pudlak
# along with this program; if not, write to the Free Software
18 912b2278 Petr Pudlak
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 912b2278 Petr Pudlak
# 02110-1301, USA.
20 912b2278 Petr Pudlak
21 912b2278 Petr Pudlak
22 912b2278 Petr Pudlak
"""Module for generic RPC clients.
23 912b2278 Petr Pudlak

24 912b2278 Petr Pudlak
"""
25 912b2278 Petr Pudlak
26 912b2278 Petr Pudlak
import logging
27 912b2278 Petr Pudlak
28 912b2278 Petr Pudlak
import ganeti.rpc.transport as t
29 912b2278 Petr Pudlak
30 912b2278 Petr Pudlak
from ganeti import constants
31 912b2278 Petr Pudlak
from ganeti import errors
32 912b2278 Petr Pudlak
from ganeti.rpc.errors import (ProtocolError, RequestError, LuxiError)
33 912b2278 Petr Pudlak
from ganeti import serializer
34 912b2278 Petr Pudlak
35 912b2278 Petr Pudlak
KEY_METHOD = constants.LUXI_KEY_METHOD
36 912b2278 Petr Pudlak
KEY_ARGS = constants.LUXI_KEY_ARGS
37 912b2278 Petr Pudlak
KEY_SUCCESS = constants.LUXI_KEY_SUCCESS
38 912b2278 Petr Pudlak
KEY_RESULT = constants.LUXI_KEY_RESULT
39 912b2278 Petr Pudlak
KEY_VERSION = constants.LUXI_KEY_VERSION
40 912b2278 Petr Pudlak
41 912b2278 Petr Pudlak
42 912b2278 Petr Pudlak
def ParseRequest(msg):
43 912b2278 Petr Pudlak
  """Parses a request message.
44 912b2278 Petr Pudlak

45 912b2278 Petr Pudlak
  """
46 912b2278 Petr Pudlak
  try:
47 912b2278 Petr Pudlak
    request = serializer.LoadJson(msg)
48 912b2278 Petr Pudlak
  except ValueError, err:
49 24c09d5e Petr Pudlak
    raise ProtocolError("Invalid RPC request (parsing error): %s" % err)
50 912b2278 Petr Pudlak
51 24c09d5e Petr Pudlak
  logging.debug("RPC request: %s", request)
52 912b2278 Petr Pudlak
53 912b2278 Petr Pudlak
  if not isinstance(request, dict):
54 24c09d5e Petr Pudlak
    logging.error("RPC request not a dict: %r", msg)
55 24c09d5e Petr Pudlak
    raise ProtocolError("Invalid RPC request (not a dict)")
56 912b2278 Petr Pudlak
57 912b2278 Petr Pudlak
  method = request.get(KEY_METHOD, None) # pylint: disable=E1103
58 912b2278 Petr Pudlak
  args = request.get(KEY_ARGS, None) # pylint: disable=E1103
59 912b2278 Petr Pudlak
  version = request.get(KEY_VERSION, None) # pylint: disable=E1103
60 912b2278 Petr Pudlak
61 912b2278 Petr Pudlak
  if method is None or args is None:
62 24c09d5e Petr Pudlak
    logging.error("RPC request missing method or arguments: %r", msg)
63 24c09d5e Petr Pudlak
    raise ProtocolError(("Invalid RPC request (no method or arguments"
64 912b2278 Petr Pudlak
                         " in request): %r") % msg)
65 912b2278 Petr Pudlak
66 912b2278 Petr Pudlak
  return (method, args, version)
67 912b2278 Petr Pudlak
68 912b2278 Petr Pudlak
69 912b2278 Petr Pudlak
def ParseResponse(msg):
70 912b2278 Petr Pudlak
  """Parses a response message.
71 912b2278 Petr Pudlak

72 912b2278 Petr Pudlak
  """
73 912b2278 Petr Pudlak
  # Parse the result
74 912b2278 Petr Pudlak
  try:
75 912b2278 Petr Pudlak
    data = serializer.LoadJson(msg)
76 912b2278 Petr Pudlak
  except KeyboardInterrupt:
77 912b2278 Petr Pudlak
    raise
78 912b2278 Petr Pudlak
  except Exception, err:
79 912b2278 Petr Pudlak
    raise ProtocolError("Error while deserializing response: %s" % str(err))
80 912b2278 Petr Pudlak
81 912b2278 Petr Pudlak
  # Validate response
82 912b2278 Petr Pudlak
  if not (isinstance(data, dict) and
83 912b2278 Petr Pudlak
          KEY_SUCCESS in data and
84 912b2278 Petr Pudlak
          KEY_RESULT in data):
85 912b2278 Petr Pudlak
    raise ProtocolError("Invalid response from server: %r" % data)
86 912b2278 Petr Pudlak
87 912b2278 Petr Pudlak
  return (data[KEY_SUCCESS], data[KEY_RESULT],
88 912b2278 Petr Pudlak
          data.get(KEY_VERSION, None)) # pylint: disable=E1103
89 912b2278 Petr Pudlak
90 912b2278 Petr Pudlak
91 912b2278 Petr Pudlak
def FormatResponse(success, result, version=None):
92 912b2278 Petr Pudlak
  """Formats a response message.
93 912b2278 Petr Pudlak

94 912b2278 Petr Pudlak
  """
95 912b2278 Petr Pudlak
  response = {
96 912b2278 Petr Pudlak
    KEY_SUCCESS: success,
97 912b2278 Petr Pudlak
    KEY_RESULT: result,
98 912b2278 Petr Pudlak
    }
99 912b2278 Petr Pudlak
100 912b2278 Petr Pudlak
  if version is not None:
101 912b2278 Petr Pudlak
    response[KEY_VERSION] = version
102 912b2278 Petr Pudlak
103 24c09d5e Petr Pudlak
  logging.debug("RPC response: %s", response)
104 912b2278 Petr Pudlak
105 912b2278 Petr Pudlak
  return serializer.DumpJson(response)
106 912b2278 Petr Pudlak
107 912b2278 Petr Pudlak
108 912b2278 Petr Pudlak
def FormatRequest(method, args, version=None):
109 912b2278 Petr Pudlak
  """Formats a request message.
110 912b2278 Petr Pudlak

111 912b2278 Petr Pudlak
  """
112 912b2278 Petr Pudlak
  # Build request
113 912b2278 Petr Pudlak
  request = {
114 912b2278 Petr Pudlak
    KEY_METHOD: method,
115 912b2278 Petr Pudlak
    KEY_ARGS: args,
116 912b2278 Petr Pudlak
    }
117 912b2278 Petr Pudlak
118 912b2278 Petr Pudlak
  if version is not None:
119 912b2278 Petr Pudlak
    request[KEY_VERSION] = version
120 912b2278 Petr Pudlak
121 912b2278 Petr Pudlak
  # Serialize the request
122 560ef132 Santi Raffa
  return serializer.DumpJson(request,
123 560ef132 Santi Raffa
                             private_encoder=serializer.EncodeWithPrivateFields)
124 912b2278 Petr Pudlak
125 912b2278 Petr Pudlak
126 24c09d5e Petr Pudlak
def CallRPCMethod(transport_cb, method, args, version=None):
127 24c09d5e Petr Pudlak
  """Send a RPC request via a transport and return the response.
128 912b2278 Petr Pudlak

129 912b2278 Petr Pudlak
  """
130 912b2278 Petr Pudlak
  assert callable(transport_cb)
131 912b2278 Petr Pudlak
132 912b2278 Petr Pudlak
  request_msg = FormatRequest(method, args, version=version)
133 912b2278 Petr Pudlak
134 912b2278 Petr Pudlak
  # Send request and wait for response
135 912b2278 Petr Pudlak
  response_msg = transport_cb(request_msg)
136 912b2278 Petr Pudlak
137 912b2278 Petr Pudlak
  (success, result, resp_version) = ParseResponse(response_msg)
138 912b2278 Petr Pudlak
139 912b2278 Petr Pudlak
  # Verify version if there was one in the response
140 912b2278 Petr Pudlak
  if resp_version is not None and resp_version != version:
141 24c09d5e Petr Pudlak
    raise LuxiError("RPC version mismatch, client %s, response %s" %
142 912b2278 Petr Pudlak
                    (version, resp_version))
143 912b2278 Petr Pudlak
144 912b2278 Petr Pudlak
  if success:
145 912b2278 Petr Pudlak
    return result
146 912b2278 Petr Pudlak
147 912b2278 Petr Pudlak
  errors.MaybeRaise(result)
148 912b2278 Petr Pudlak
  raise RequestError(result)
149 912b2278 Petr Pudlak
150 912b2278 Petr Pudlak
151 912b2278 Petr Pudlak
class AbstractClient(object):
152 912b2278 Petr Pudlak
  """High-level client abstraction.
153 912b2278 Petr Pudlak

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

157 912b2278 Petr Pudlak
  """
158 912b2278 Petr Pudlak
159 d36e433d Petr Pudlak
  def __init__(self, timeouts=None, transport=t.Transport):
160 912b2278 Petr Pudlak
    """Constructor for the Client class.
161 912b2278 Petr Pudlak

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

167 912b2278 Petr Pudlak

168 912b2278 Petr Pudlak
    If timeout is not passed, the default timeouts of the transport
169 912b2278 Petr Pudlak
    class are used.
170 912b2278 Petr Pudlak

171 912b2278 Petr Pudlak
    """
172 912b2278 Petr Pudlak
    self.timeouts = timeouts
173 912b2278 Petr Pudlak
    self.transport_class = transport
174 912b2278 Petr Pudlak
    self.transport = None
175 cda215a9 Petr Pudlak
    # The version used in RPC communication, by default unused:
176 cda215a9 Petr Pudlak
    self.version = None
177 912b2278 Petr Pudlak
178 d36e433d Petr Pudlak
  def _GetAddress(self):
179 d36e433d Petr Pudlak
    """Returns the socket address
180 d36e433d Petr Pudlak

181 d36e433d Petr Pudlak
    """
182 d36e433d Petr Pudlak
    raise NotImplementedError
183 d36e433d Petr Pudlak
184 912b2278 Petr Pudlak
  def _InitTransport(self):
185 912b2278 Petr Pudlak
    """(Re)initialize the transport if needed.
186 912b2278 Petr Pudlak

187 912b2278 Petr Pudlak
    """
188 912b2278 Petr Pudlak
    if self.transport is None:
189 d36e433d Petr Pudlak
      self.transport = self.transport_class(self._GetAddress(),
190 912b2278 Petr Pudlak
                                            timeouts=self.timeouts)
191 912b2278 Petr Pudlak
192 912b2278 Petr Pudlak
  def _CloseTransport(self):
193 912b2278 Petr Pudlak
    """Close the transport, ignoring errors.
194 912b2278 Petr Pudlak

195 912b2278 Petr Pudlak
    """
196 912b2278 Petr Pudlak
    if self.transport is None:
197 912b2278 Petr Pudlak
      return
198 912b2278 Petr Pudlak
    try:
199 912b2278 Petr Pudlak
      old_transp = self.transport
200 912b2278 Petr Pudlak
      self.transport = None
201 912b2278 Petr Pudlak
      old_transp.Close()
202 912b2278 Petr Pudlak
    except Exception: # pylint: disable=W0703
203 912b2278 Petr Pudlak
      pass
204 912b2278 Petr Pudlak
205 912b2278 Petr Pudlak
  def _SendMethodCall(self, data):
206 912b2278 Petr Pudlak
    # Send request and wait for response
207 f3aebf6f Petr Pudlak
    def send(try_no):
208 f3aebf6f Petr Pudlak
      if try_no:
209 f3aebf6f Petr Pudlak
        logging.debug("RPC peer disconnected, retrying")
210 912b2278 Petr Pudlak
      self._InitTransport()
211 912b2278 Petr Pudlak
      return self.transport.Call(data)
212 f3aebf6f Petr Pudlak
    return t.Transport.RetryOnBrokenPipe(send, lambda _: self._CloseTransport())
213 912b2278 Petr Pudlak
214 912b2278 Petr Pudlak
  def Close(self):
215 912b2278 Petr Pudlak
    """Close the underlying connection.
216 912b2278 Petr Pudlak

217 912b2278 Petr Pudlak
    """
218 912b2278 Petr Pudlak
    self._CloseTransport()
219 912b2278 Petr Pudlak
220 912b2278 Petr Pudlak
  def close(self):
221 912b2278 Petr Pudlak
    """Same as L{Close}, to be used with contextlib.closing(...).
222 912b2278 Petr Pudlak

223 912b2278 Petr Pudlak
    """
224 912b2278 Petr Pudlak
    self.Close()
225 912b2278 Petr Pudlak
226 912b2278 Petr Pudlak
  def CallMethod(self, method, args):
227 912b2278 Petr Pudlak
    """Send a generic request and return the response.
228 912b2278 Petr Pudlak

229 912b2278 Petr Pudlak
    """
230 912b2278 Petr Pudlak
    if not isinstance(args, (list, tuple)):
231 912b2278 Petr Pudlak
      raise errors.ProgrammerError("Invalid parameter passed to CallMethod:"
232 912b2278 Petr Pudlak
                                   " expected list, got %s" % type(args))
233 24c09d5e Petr Pudlak
    return CallRPCMethod(self._SendMethodCall, method, args,
234 cda215a9 Petr Pudlak
                         version=self.version)
235 c4071978 Petr Pudlak
236 c4071978 Petr Pudlak
237 c4071978 Petr Pudlak
class AbstractStubClient(AbstractClient):
238 c4071978 Petr Pudlak
  """An abstract Client that connects a generated stub client to a L{Transport}.
239 c4071978 Petr Pudlak

240 c4071978 Petr Pudlak
  Subclasses should inherit from this class (first) as well and a designated
241 c4071978 Petr Pudlak
  stub (second).
242 c4071978 Petr Pudlak
  """
243 c4071978 Petr Pudlak
244 c4071978 Petr Pudlak
  def __init__(self, timeouts=None, transport=t.Transport):
245 c4071978 Petr Pudlak
    """Constructor for the class.
246 c4071978 Petr Pudlak

247 c4071978 Petr Pudlak
    Arguments are the same as for L{AbstractClient}. Checks that SOCKET_PATH
248 c4071978 Petr Pudlak
    attribute is defined (in the stub class).
249 c4071978 Petr Pudlak
    """
250 c4071978 Petr Pudlak
251 c4071978 Petr Pudlak
    super(AbstractStubClient, self).__init__(timeouts, transport)
252 c4071978 Petr Pudlak
253 c4071978 Petr Pudlak
  def _GenericInvoke(self, method, *args):
254 c4071978 Petr Pudlak
    return self.CallMethod(method, args)
255 c4071978 Petr Pudlak
256 c4071978 Petr Pudlak
  def _GetAddress(self):
257 c4071978 Petr Pudlak
    return self._GetSocketPath() # pylint: disable=E1101