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 |