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) |