Statistics
| Branch: | Tag: | Revision:

root / lib / rpc / client.py @ 912b2278

History | View | Annotate | Download (6 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
from ganeti import pathutils
29 912b2278 Petr Pudlak
import ganeti.rpc.transport as t
30 912b2278 Petr Pudlak
31 912b2278 Petr Pudlak
from ganeti import constants
32 912b2278 Petr Pudlak
from ganeti import errors
33 912b2278 Petr Pudlak
from ganeti.rpc.errors import (ProtocolError, RequestError, LuxiError)
34 912b2278 Petr Pudlak
from ganeti import serializer
35 912b2278 Petr Pudlak
36 912b2278 Petr Pudlak
KEY_METHOD = constants.LUXI_KEY_METHOD
37 912b2278 Petr Pudlak
KEY_ARGS = constants.LUXI_KEY_ARGS
38 912b2278 Petr Pudlak
KEY_SUCCESS = constants.LUXI_KEY_SUCCESS
39 912b2278 Petr Pudlak
KEY_RESULT = constants.LUXI_KEY_RESULT
40 912b2278 Petr Pudlak
KEY_VERSION = constants.LUXI_KEY_VERSION
41 912b2278 Petr Pudlak
42 912b2278 Petr Pudlak
43 912b2278 Petr Pudlak
def ParseRequest(msg):
44 912b2278 Petr Pudlak
  """Parses a request message.
45 912b2278 Petr Pudlak

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

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

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

112 912b2278 Petr Pudlak
  """
113 912b2278 Petr Pudlak
  # Build request
114 912b2278 Petr Pudlak
  request = {
115 912b2278 Petr Pudlak
    KEY_METHOD: method,
116 912b2278 Petr Pudlak
    KEY_ARGS: args,
117 912b2278 Petr Pudlak
    }
118 912b2278 Petr Pudlak
119 912b2278 Petr Pudlak
  if version is not None:
120 912b2278 Petr Pudlak
    request[KEY_VERSION] = version
121 912b2278 Petr Pudlak
122 912b2278 Petr Pudlak
  # Serialize the request
123 912b2278 Petr Pudlak
  return serializer.DumpJson(request)
124 912b2278 Petr Pudlak
125 912b2278 Petr Pudlak
126 912b2278 Petr Pudlak
def CallLuxiMethod(transport_cb, method, args, version=None):
127 912b2278 Petr Pudlak
  """Send a LUXI 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 912b2278 Petr Pudlak
    raise LuxiError("LUXI 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 912b2278 Petr Pudlak
  def __init__(self, address=None, timeouts=None,
160 912b2278 Petr Pudlak
               transport=t.Transport):
161 912b2278 Petr Pudlak
    """Constructor for the Client class.
162 912b2278 Petr Pudlak

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

168 912b2278 Petr Pudlak

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

172 912b2278 Petr Pudlak
    """
173 912b2278 Petr Pudlak
    if address is None:
174 912b2278 Petr Pudlak
      address = pathutils.MASTER_SOCKET
175 912b2278 Petr Pudlak
    self.address = address
176 912b2278 Petr Pudlak
    self.timeouts = timeouts
177 912b2278 Petr Pudlak
    self.transport_class = transport
178 912b2278 Petr Pudlak
    self.transport = None
179 912b2278 Petr Pudlak
    self._InitTransport()
180 912b2278 Petr Pudlak
181 912b2278 Petr Pudlak
  def _InitTransport(self):
182 912b2278 Petr Pudlak
    """(Re)initialize the transport if needed.
183 912b2278 Petr Pudlak

184 912b2278 Petr Pudlak
    """
185 912b2278 Petr Pudlak
    if self.transport is None:
186 912b2278 Petr Pudlak
      self.transport = self.transport_class(self.address,
187 912b2278 Petr Pudlak
                                            timeouts=self.timeouts)
188 912b2278 Petr Pudlak
189 912b2278 Petr Pudlak
  def _CloseTransport(self):
190 912b2278 Petr Pudlak
    """Close the transport, ignoring errors.
191 912b2278 Petr Pudlak

192 912b2278 Petr Pudlak
    """
193 912b2278 Petr Pudlak
    if self.transport is None:
194 912b2278 Petr Pudlak
      return
195 912b2278 Petr Pudlak
    try:
196 912b2278 Petr Pudlak
      old_transp = self.transport
197 912b2278 Petr Pudlak
      self.transport = None
198 912b2278 Petr Pudlak
      old_transp.Close()
199 912b2278 Petr Pudlak
    except Exception: # pylint: disable=W0703
200 912b2278 Petr Pudlak
      pass
201 912b2278 Petr Pudlak
202 912b2278 Petr Pudlak
  def _SendMethodCall(self, data):
203 912b2278 Petr Pudlak
    # Send request and wait for response
204 912b2278 Petr Pudlak
    try:
205 912b2278 Petr Pudlak
      self._InitTransport()
206 912b2278 Petr Pudlak
      return self.transport.Call(data)
207 912b2278 Petr Pudlak
    except Exception:
208 912b2278 Petr Pudlak
      self._CloseTransport()
209 912b2278 Petr Pudlak
      raise
210 912b2278 Petr Pudlak
211 912b2278 Petr Pudlak
  def Close(self):
212 912b2278 Petr Pudlak
    """Close the underlying connection.
213 912b2278 Petr Pudlak

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

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

226 912b2278 Petr Pudlak
    """
227 912b2278 Petr Pudlak
    if not isinstance(args, (list, tuple)):
228 912b2278 Petr Pudlak
      raise errors.ProgrammerError("Invalid parameter passed to CallMethod:"
229 912b2278 Petr Pudlak
                                   " expected list, got %s" % type(args))
230 912b2278 Petr Pudlak
    return CallLuxiMethod(self._SendMethodCall, method, args,
231 912b2278 Petr Pudlak
                          version=constants.LUXI_VERSION)