Statistics
| Branch: | Tag: | Revision:

root / lib / luxi.py @ 1ef0324f

History | View | Annotate | Download (16.6 kB)

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

24 8d5b316c Iustin Pop
This module implements the local unix socket protocol. You only need
25 c2a03789 Iustin Pop
this module and the opcodes module in the client program in order to
26 c2a03789 Iustin Pop
communicate with the master.
27 c2a03789 Iustin Pop

28 7577196d Guido Trotter
The module is also used by the master daemon.
29 c2a03789 Iustin Pop

30 c2a03789 Iustin Pop
"""
31 c2a03789 Iustin Pop
32 c2a03789 Iustin Pop
import socket
33 c2a03789 Iustin Pop
import collections
34 c2a03789 Iustin Pop
import time
35 03a8dbdc Iustin Pop
import errno
36 231db3a5 Michael Hanselmann
import logging
37 c2a03789 Iustin Pop
38 fad50141 Michael Hanselmann
from ganeti import serializer
39 ceab32dd Iustin Pop
from ganeti import constants
40 6797ec29 Iustin Pop
from ganeti import errors
41 cb462b06 Michael Hanselmann
from ganeti import utils
42 28b71a76 Michael Hanselmann
from ganeti import objects
43 b87ee98f Michael Hanselmann
from ganeti import pathutils
44 c2a03789 Iustin Pop
45 c2a03789 Iustin Pop
46 fbb05686 Jose A. Lopes
KEY_METHOD = constants.LUXI_KEY_METHOD
47 fbb05686 Jose A. Lopes
KEY_ARGS = constants.LUXI_KEY_ARGS
48 fbb05686 Jose A. Lopes
KEY_SUCCESS = constants.LUXI_KEY_SUCCESS
49 fbb05686 Jose A. Lopes
KEY_RESULT = constants.LUXI_KEY_RESULT
50 fbb05686 Jose A. Lopes
KEY_VERSION = constants.LUXI_KEY_VERSION
51 fbb05686 Jose A. Lopes
52 fbb05686 Jose A. Lopes
REQ_SUBMIT_JOB = constants.LUXI_REQ_SUBMIT_JOB
53 fbb05686 Jose A. Lopes
REQ_SUBMIT_JOB_TO_DRAINED_QUEUE = constants.LUXI_REQ_SUBMIT_JOB_TO_DRAINED_QUEUE
54 fbb05686 Jose A. Lopes
REQ_SUBMIT_MANY_JOBS = constants.LUXI_REQ_SUBMIT_MANY_JOBS
55 fbb05686 Jose A. Lopes
REQ_WAIT_FOR_JOB_CHANGE = constants.LUXI_REQ_WAIT_FOR_JOB_CHANGE
56 fbb05686 Jose A. Lopes
REQ_CANCEL_JOB = constants.LUXI_REQ_CANCEL_JOB
57 fbb05686 Jose A. Lopes
REQ_ARCHIVE_JOB = constants.LUXI_REQ_ARCHIVE_JOB
58 fbb05686 Jose A. Lopes
REQ_CHANGE_JOB_PRIORITY = constants.LUXI_REQ_CHANGE_JOB_PRIORITY
59 fbb05686 Jose A. Lopes
REQ_AUTO_ARCHIVE_JOBS = constants.LUXI_REQ_AUTO_ARCHIVE_JOBS
60 fbb05686 Jose A. Lopes
REQ_QUERY = constants.LUXI_REQ_QUERY
61 fbb05686 Jose A. Lopes
REQ_QUERY_FIELDS = constants.LUXI_REQ_QUERY_FIELDS
62 fbb05686 Jose A. Lopes
REQ_QUERY_JOBS = constants.LUXI_REQ_QUERY_JOBS
63 fbb05686 Jose A. Lopes
REQ_QUERY_INSTANCES = constants.LUXI_REQ_QUERY_INSTANCES
64 fbb05686 Jose A. Lopes
REQ_QUERY_NODES = constants.LUXI_REQ_QUERY_NODES
65 fbb05686 Jose A. Lopes
REQ_QUERY_GROUPS = constants.LUXI_REQ_QUERY_GROUPS
66 fbb05686 Jose A. Lopes
REQ_QUERY_NETWORKS = constants.LUXI_REQ_QUERY_NETWORKS
67 fbb05686 Jose A. Lopes
REQ_QUERY_EXPORTS = constants.LUXI_REQ_QUERY_EXPORTS
68 fbb05686 Jose A. Lopes
REQ_QUERY_CONFIG_VALUES = constants.LUXI_REQ_QUERY_CONFIG_VALUES
69 fbb05686 Jose A. Lopes
REQ_QUERY_CLUSTER_INFO = constants.LUXI_REQ_QUERY_CLUSTER_INFO
70 fbb05686 Jose A. Lopes
REQ_QUERY_TAGS = constants.LUXI_REQ_QUERY_TAGS
71 fbb05686 Jose A. Lopes
REQ_SET_DRAIN_FLAG = constants.LUXI_REQ_SET_DRAIN_FLAG
72 fbb05686 Jose A. Lopes
REQ_SET_WATCHER_PAUSE = constants.LUXI_REQ_SET_WATCHER_PAUSE
73 fbb05686 Jose A. Lopes
REQ_ALL = constants.LUXI_REQ_ALL
74 fbb05686 Jose A. Lopes
75 fbb05686 Jose A. Lopes
DEF_CTMO = constants.LUXI_DEF_CTMO
76 fbb05686 Jose A. Lopes
DEF_RWTO = constants.LUXI_DEF_RWTO
77 fbb05686 Jose A. Lopes
WFJC_TIMEOUT = constants.LUXI_WFJC_TIMEOUT
78 793a8f7c Michael Hanselmann
79 c2a03789 Iustin Pop
80 7a8bda3f Michael Hanselmann
class ProtocolError(errors.LuxiError):
81 5a1c22fe Iustin Pop
  """Denotes an error in the LUXI protocol."""
82 c2a03789 Iustin Pop
83 c2a03789 Iustin Pop
84 c2a03789 Iustin Pop
class ConnectionClosedError(ProtocolError):
85 5a1c22fe Iustin Pop
  """Connection closed error."""
86 c2a03789 Iustin Pop
87 c2a03789 Iustin Pop
88 c2a03789 Iustin Pop
class TimeoutError(ProtocolError):
89 5a1c22fe Iustin Pop
  """Operation timeout error."""
90 c2a03789 Iustin Pop
91 c2a03789 Iustin Pop
92 b77acb3e Iustin Pop
class RequestError(ProtocolError):
93 5a1c22fe Iustin Pop
  """Error on request.
94 b77acb3e Iustin Pop

95 b77acb3e Iustin Pop
  This signifies an error in the request format or request handling,
96 b77acb3e Iustin Pop
  but not (e.g.) an error in starting up an instance.
97 b77acb3e Iustin Pop

98 b77acb3e Iustin Pop
  Some common conditions that can trigger this exception:
99 b77acb3e Iustin Pop
    - job submission failed because the job data was wrong
100 b77acb3e Iustin Pop
    - query failed because required fields were missing
101 b77acb3e Iustin Pop

102 b77acb3e Iustin Pop
  """
103 b77acb3e Iustin Pop
104 3d8548c4 Michael Hanselmann
105 03a8dbdc Iustin Pop
class NoMasterError(ProtocolError):
106 5a1c22fe Iustin Pop
  """The master cannot be reached.
107 03a8dbdc Iustin Pop

108 03a8dbdc Iustin Pop
  This means that the master daemon is not running or the socket has
109 03a8dbdc Iustin Pop
  been removed.
110 03a8dbdc Iustin Pop

111 03a8dbdc Iustin Pop
  """
112 03a8dbdc Iustin Pop
113 b77acb3e Iustin Pop
114 5a1c22fe Iustin Pop
class PermissionError(ProtocolError):
115 5a1c22fe Iustin Pop
  """Permission denied while connecting to the master socket.
116 5a1c22fe Iustin Pop

117 5a1c22fe Iustin Pop
  This means the user doesn't have the proper rights.
118 5a1c22fe Iustin Pop

119 5a1c22fe Iustin Pop
  """
120 5a1c22fe Iustin Pop
121 5a1c22fe Iustin Pop
122 c2a03789 Iustin Pop
class Transport:
123 c2a03789 Iustin Pop
  """Low-level transport class.
124 c2a03789 Iustin Pop

125 c2a03789 Iustin Pop
  This is used on the client side.
126 c2a03789 Iustin Pop

127 c2a03789 Iustin Pop
  This could be replace by any other class that provides the same
128 c2a03789 Iustin Pop
  semantics to the Client. This means:
129 c2a03789 Iustin Pop
    - can send messages and receive messages
130 c2a03789 Iustin Pop
    - safe for multithreading
131 c2a03789 Iustin Pop

132 c2a03789 Iustin Pop
  """
133 c2a03789 Iustin Pop
134 25942a6c Guido Trotter
  def __init__(self, address, timeouts=None):
135 c2a03789 Iustin Pop
    """Constructor for the Client class.
136 c2a03789 Iustin Pop

137 c2a03789 Iustin Pop
    Arguments:
138 c2a03789 Iustin Pop
      - address: a valid address the the used transport class
139 c2a03789 Iustin Pop
      - timeout: a list of timeouts, to be used on connect and read/write
140 c2a03789 Iustin Pop

141 c2a03789 Iustin Pop
    There are two timeouts used since we might want to wait for a long
142 c2a03789 Iustin Pop
    time for a response, but the connect timeout should be lower.
143 c2a03789 Iustin Pop

144 c2a03789 Iustin Pop
    If not passed, we use a default of 10 and respectively 60 seconds.
145 c2a03789 Iustin Pop

146 c2a03789 Iustin Pop
    Note that on reading data, since the timeout applies to an
147 c2a03789 Iustin Pop
    invidual receive, it might be that the total duration is longer
148 c2a03789 Iustin Pop
    than timeout value passed (we make a hard limit at twice the read
149 c2a03789 Iustin Pop
    timeout).
150 c2a03789 Iustin Pop

151 c2a03789 Iustin Pop
    """
152 c2a03789 Iustin Pop
    self.address = address
153 c2a03789 Iustin Pop
    if timeouts is None:
154 c2a03789 Iustin Pop
      self._ctimeout, self._rwtimeout = DEF_CTMO, DEF_RWTO
155 c2a03789 Iustin Pop
    else:
156 c2a03789 Iustin Pop
      self._ctimeout, self._rwtimeout = timeouts
157 c2a03789 Iustin Pop
158 c2a03789 Iustin Pop
    self.socket = None
159 c2a03789 Iustin Pop
    self._buffer = ""
160 c2a03789 Iustin Pop
    self._msgs = collections.deque()
161 c2a03789 Iustin Pop
162 c2a03789 Iustin Pop
    try:
163 c2a03789 Iustin Pop
      self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
164 cb462b06 Michael Hanselmann
165 cb462b06 Michael Hanselmann
      # Try to connect
166 c2a03789 Iustin Pop
      try:
167 cb462b06 Michael Hanselmann
        utils.Retry(self._Connect, 1.0, self._ctimeout,
168 cb462b06 Michael Hanselmann
                    args=(self.socket, address, self._ctimeout))
169 cb462b06 Michael Hanselmann
      except utils.RetryTimeout:
170 cb462b06 Michael Hanselmann
        raise TimeoutError("Connect timed out")
171 cb462b06 Michael Hanselmann
172 c2a03789 Iustin Pop
      self.socket.settimeout(self._rwtimeout)
173 03a8dbdc Iustin Pop
    except (socket.error, NoMasterError):
174 c2a03789 Iustin Pop
      if self.socket is not None:
175 c2a03789 Iustin Pop
        self.socket.close()
176 c2a03789 Iustin Pop
      self.socket = None
177 c2a03789 Iustin Pop
      raise
178 c2a03789 Iustin Pop
179 cb462b06 Michael Hanselmann
  @staticmethod
180 cb462b06 Michael Hanselmann
  def _Connect(sock, address, timeout):
181 cb462b06 Michael Hanselmann
    sock.settimeout(timeout)
182 cb462b06 Michael Hanselmann
    try:
183 cb462b06 Michael Hanselmann
      sock.connect(address)
184 cb462b06 Michael Hanselmann
    except socket.timeout, err:
185 cb462b06 Michael Hanselmann
      raise TimeoutError("Connect timed out: %s" % str(err))
186 cb462b06 Michael Hanselmann
    except socket.error, err:
187 5a1c22fe Iustin Pop
      error_code = err.args[0]
188 5a1c22fe Iustin Pop
      if error_code in (errno.ENOENT, errno.ECONNREFUSED):
189 cb462b06 Michael Hanselmann
        raise NoMasterError(address)
190 5a1c22fe Iustin Pop
      elif error_code in (errno.EPERM, errno.EACCES):
191 5a1c22fe Iustin Pop
        raise PermissionError(address)
192 5a1c22fe Iustin Pop
      elif error_code == errno.EAGAIN:
193 cb462b06 Michael Hanselmann
        # Server's socket backlog is full at the moment
194 cb462b06 Michael Hanselmann
        raise utils.RetryAgain()
195 cb462b06 Michael Hanselmann
      raise
196 cb462b06 Michael Hanselmann
197 c2a03789 Iustin Pop
  def _CheckSocket(self):
198 c2a03789 Iustin Pop
    """Make sure we are connected.
199 c2a03789 Iustin Pop

200 c2a03789 Iustin Pop
    """
201 c2a03789 Iustin Pop
    if self.socket is None:
202 c2a03789 Iustin Pop
      raise ProtocolError("Connection is closed")
203 c2a03789 Iustin Pop
204 c2a03789 Iustin Pop
  def Send(self, msg):
205 c2a03789 Iustin Pop
    """Send a message.
206 c2a03789 Iustin Pop

207 c2a03789 Iustin Pop
    This just sends a message and doesn't wait for the response.
208 c2a03789 Iustin Pop

209 c2a03789 Iustin Pop
    """
210 25942a6c Guido Trotter
    if constants.LUXI_EOM in msg:
211 797506fc Michael Hanselmann
      raise ProtocolError("Message terminator found in payload")
212 797506fc Michael Hanselmann
213 c2a03789 Iustin Pop
    self._CheckSocket()
214 c2a03789 Iustin Pop
    try:
215 6096ee13 Michael Hanselmann
      # TODO: sendall is not guaranteed to send everything
216 25942a6c Guido Trotter
      self.socket.sendall(msg + constants.LUXI_EOM)
217 c2a03789 Iustin Pop
    except socket.timeout, err:
218 c2a03789 Iustin Pop
      raise TimeoutError("Sending timeout: %s" % str(err))
219 c2a03789 Iustin Pop
220 c2a03789 Iustin Pop
  def Recv(self):
221 5bbd3f7f Michael Hanselmann
    """Try to receive a message from the socket.
222 c2a03789 Iustin Pop

223 c2a03789 Iustin Pop
    In case we already have messages queued, we just return from the
224 c2a03789 Iustin Pop
    queue. Otherwise, we try to read data with a _rwtimeout network
225 c2a03789 Iustin Pop
    timeout, and making sure we don't go over 2x_rwtimeout as a global
226 c2a03789 Iustin Pop
    limit.
227 c2a03789 Iustin Pop

228 c2a03789 Iustin Pop
    """
229 c2a03789 Iustin Pop
    self._CheckSocket()
230 c2a03789 Iustin Pop
    etime = time.time() + self._rwtimeout
231 c2a03789 Iustin Pop
    while not self._msgs:
232 c2a03789 Iustin Pop
      if time.time() > etime:
233 c2a03789 Iustin Pop
        raise TimeoutError("Extended receive timeout")
234 6096ee13 Michael Hanselmann
      while True:
235 6096ee13 Michael Hanselmann
        try:
236 6096ee13 Michael Hanselmann
          data = self.socket.recv(4096)
237 28e3e216 Michael Hanselmann
        except socket.timeout, err:
238 28e3e216 Michael Hanselmann
          raise TimeoutError("Receive timeout: %s" % str(err))
239 6096ee13 Michael Hanselmann
        except socket.error, err:
240 6096ee13 Michael Hanselmann
          if err.args and err.args[0] == errno.EAGAIN:
241 6096ee13 Michael Hanselmann
            continue
242 6096ee13 Michael Hanselmann
          raise
243 6096ee13 Michael Hanselmann
        break
244 c2a03789 Iustin Pop
      if not data:
245 c2a03789 Iustin Pop
        raise ConnectionClosedError("Connection closed while reading")
246 25942a6c Guido Trotter
      new_msgs = (self._buffer + data).split(constants.LUXI_EOM)
247 c2a03789 Iustin Pop
      self._buffer = new_msgs.pop()
248 c2a03789 Iustin Pop
      self._msgs.extend(new_msgs)
249 c2a03789 Iustin Pop
    return self._msgs.popleft()
250 c2a03789 Iustin Pop
251 c2a03789 Iustin Pop
  def Call(self, msg):
252 c2a03789 Iustin Pop
    """Send a message and wait for the response.
253 c2a03789 Iustin Pop

254 c2a03789 Iustin Pop
    This is just a wrapper over Send and Recv.
255 c2a03789 Iustin Pop

256 c2a03789 Iustin Pop
    """
257 c2a03789 Iustin Pop
    self.Send(msg)
258 c2a03789 Iustin Pop
    return self.Recv()
259 c2a03789 Iustin Pop
260 c2a03789 Iustin Pop
  def Close(self):
261 c2a03789 Iustin Pop
    """Close the socket"""
262 c2a03789 Iustin Pop
    if self.socket is not None:
263 c2a03789 Iustin Pop
      self.socket.close()
264 c2a03789 Iustin Pop
      self.socket = None
265 c2a03789 Iustin Pop
266 c2a03789 Iustin Pop
267 231db3a5 Michael Hanselmann
def ParseRequest(msg):
268 231db3a5 Michael Hanselmann
  """Parses a LUXI request message.
269 231db3a5 Michael Hanselmann

270 231db3a5 Michael Hanselmann
  """
271 231db3a5 Michael Hanselmann
  try:
272 231db3a5 Michael Hanselmann
    request = serializer.LoadJson(msg)
273 231db3a5 Michael Hanselmann
  except ValueError, err:
274 231db3a5 Michael Hanselmann
    raise ProtocolError("Invalid LUXI request (parsing error): %s" % err)
275 231db3a5 Michael Hanselmann
276 231db3a5 Michael Hanselmann
  logging.debug("LUXI request: %s", request)
277 231db3a5 Michael Hanselmann
278 231db3a5 Michael Hanselmann
  if not isinstance(request, dict):
279 231db3a5 Michael Hanselmann
    logging.error("LUXI request not a dict: %r", msg)
280 231db3a5 Michael Hanselmann
    raise ProtocolError("Invalid LUXI request (not a dict)")
281 231db3a5 Michael Hanselmann
282 b459a848 Andrea Spadaccini
  method = request.get(KEY_METHOD, None) # pylint: disable=E1103
283 b459a848 Andrea Spadaccini
  args = request.get(KEY_ARGS, None) # pylint: disable=E1103
284 b459a848 Andrea Spadaccini
  version = request.get(KEY_VERSION, None) # pylint: disable=E1103
285 e7a25b08 Guido Trotter
286 231db3a5 Michael Hanselmann
  if method is None or args is None:
287 231db3a5 Michael Hanselmann
    logging.error("LUXI request missing method or arguments: %r", msg)
288 231db3a5 Michael Hanselmann
    raise ProtocolError(("Invalid LUXI request (no method or arguments"
289 231db3a5 Michael Hanselmann
                         " in request): %r") % msg)
290 231db3a5 Michael Hanselmann
291 e986f20c Michael Hanselmann
  return (method, args, version)
292 231db3a5 Michael Hanselmann
293 231db3a5 Michael Hanselmann
294 231db3a5 Michael Hanselmann
def ParseResponse(msg):
295 231db3a5 Michael Hanselmann
  """Parses a LUXI response message.
296 231db3a5 Michael Hanselmann

297 231db3a5 Michael Hanselmann
  """
298 231db3a5 Michael Hanselmann
  # Parse the result
299 231db3a5 Michael Hanselmann
  try:
300 231db3a5 Michael Hanselmann
    data = serializer.LoadJson(msg)
301 d143f2c6 Iustin Pop
  except KeyboardInterrupt:
302 d143f2c6 Iustin Pop
    raise
303 231db3a5 Michael Hanselmann
  except Exception, err:
304 231db3a5 Michael Hanselmann
    raise ProtocolError("Error while deserializing response: %s" % str(err))
305 231db3a5 Michael Hanselmann
306 231db3a5 Michael Hanselmann
  # Validate response
307 231db3a5 Michael Hanselmann
  if not (isinstance(data, dict) and
308 231db3a5 Michael Hanselmann
          KEY_SUCCESS in data and
309 231db3a5 Michael Hanselmann
          KEY_RESULT in data):
310 231db3a5 Michael Hanselmann
    raise ProtocolError("Invalid response from server: %r" % data)
311 231db3a5 Michael Hanselmann
312 2317945a Guido Trotter
  return (data[KEY_SUCCESS], data[KEY_RESULT],
313 b459a848 Andrea Spadaccini
          data.get(KEY_VERSION, None)) # pylint: disable=E1103
314 231db3a5 Michael Hanselmann
315 231db3a5 Michael Hanselmann
316 e986f20c Michael Hanselmann
def FormatResponse(success, result, version=None):
317 231db3a5 Michael Hanselmann
  """Formats a LUXI response message.
318 231db3a5 Michael Hanselmann

319 231db3a5 Michael Hanselmann
  """
320 231db3a5 Michael Hanselmann
  response = {
321 231db3a5 Michael Hanselmann
    KEY_SUCCESS: success,
322 231db3a5 Michael Hanselmann
    KEY_RESULT: result,
323 231db3a5 Michael Hanselmann
    }
324 231db3a5 Michael Hanselmann
325 e986f20c Michael Hanselmann
  if version is not None:
326 e986f20c Michael Hanselmann
    response[KEY_VERSION] = version
327 e986f20c Michael Hanselmann
328 231db3a5 Michael Hanselmann
  logging.debug("LUXI response: %s", response)
329 231db3a5 Michael Hanselmann
330 231db3a5 Michael Hanselmann
  return serializer.DumpJson(response)
331 231db3a5 Michael Hanselmann
332 231db3a5 Michael Hanselmann
333 e986f20c Michael Hanselmann
def FormatRequest(method, args, version=None):
334 231db3a5 Michael Hanselmann
  """Formats a LUXI request message.
335 231db3a5 Michael Hanselmann

336 231db3a5 Michael Hanselmann
  """
337 231db3a5 Michael Hanselmann
  # Build request
338 231db3a5 Michael Hanselmann
  request = {
339 231db3a5 Michael Hanselmann
    KEY_METHOD: method,
340 231db3a5 Michael Hanselmann
    KEY_ARGS: args,
341 231db3a5 Michael Hanselmann
    }
342 231db3a5 Michael Hanselmann
343 e986f20c Michael Hanselmann
  if version is not None:
344 e986f20c Michael Hanselmann
    request[KEY_VERSION] = version
345 e986f20c Michael Hanselmann
346 231db3a5 Michael Hanselmann
  # Serialize the request
347 a182a3ed Michael Hanselmann
  return serializer.DumpJson(request)
348 231db3a5 Michael Hanselmann
349 231db3a5 Michael Hanselmann
350 e986f20c Michael Hanselmann
def CallLuxiMethod(transport_cb, method, args, version=None):
351 231db3a5 Michael Hanselmann
  """Send a LUXI request via a transport and return the response.
352 231db3a5 Michael Hanselmann

353 231db3a5 Michael Hanselmann
  """
354 231db3a5 Michael Hanselmann
  assert callable(transport_cb)
355 231db3a5 Michael Hanselmann
356 e986f20c Michael Hanselmann
  request_msg = FormatRequest(method, args, version=version)
357 231db3a5 Michael Hanselmann
358 231db3a5 Michael Hanselmann
  # Send request and wait for response
359 231db3a5 Michael Hanselmann
  response_msg = transport_cb(request_msg)
360 231db3a5 Michael Hanselmann
361 e986f20c Michael Hanselmann
  (success, result, resp_version) = ParseResponse(response_msg)
362 e986f20c Michael Hanselmann
363 e986f20c Michael Hanselmann
  # Verify version if there was one in the response
364 e986f20c Michael Hanselmann
  if resp_version is not None and resp_version != version:
365 e986f20c Michael Hanselmann
    raise errors.LuxiError("LUXI version mismatch, client %s, response %s" %
366 e986f20c Michael Hanselmann
                           (version, resp_version))
367 231db3a5 Michael Hanselmann
368 231db3a5 Michael Hanselmann
  if success:
369 231db3a5 Michael Hanselmann
    return result
370 231db3a5 Michael Hanselmann
371 231db3a5 Michael Hanselmann
  errors.MaybeRaise(result)
372 231db3a5 Michael Hanselmann
  raise RequestError(result)
373 231db3a5 Michael Hanselmann
374 231db3a5 Michael Hanselmann
375 c2a03789 Iustin Pop
class Client(object):
376 c2a03789 Iustin Pop
  """High-level client implementation.
377 c2a03789 Iustin Pop

378 c2a03789 Iustin Pop
  This uses a backing Transport-like class on top of which it
379 c2a03789 Iustin Pop
  implements data serialization/deserialization.
380 c2a03789 Iustin Pop

381 c2a03789 Iustin Pop
  """
382 ceab32dd Iustin Pop
  def __init__(self, address=None, timeouts=None, transport=Transport):
383 c2a03789 Iustin Pop
    """Constructor for the Client class.
384 c2a03789 Iustin Pop

385 c2a03789 Iustin Pop
    Arguments:
386 c2a03789 Iustin Pop
      - address: a valid address the the used transport class
387 c2a03789 Iustin Pop
      - timeout: a list of timeouts, to be used on connect and read/write
388 c2a03789 Iustin Pop
      - transport: a Transport-like class
389 c2a03789 Iustin Pop

390 c2a03789 Iustin Pop

391 c2a03789 Iustin Pop
    If timeout is not passed, the default timeouts of the transport
392 c2a03789 Iustin Pop
    class are used.
393 c2a03789 Iustin Pop

394 c2a03789 Iustin Pop
    """
395 ceab32dd Iustin Pop
    if address is None:
396 b87ee98f Michael Hanselmann
      address = pathutils.MASTER_SOCKET
397 8d5b316c Iustin Pop
    self.address = address
398 8d5b316c Iustin Pop
    self.timeouts = timeouts
399 8d5b316c Iustin Pop
    self.transport_class = transport
400 8d5b316c Iustin Pop
    self.transport = None
401 8d5b316c Iustin Pop
    self._InitTransport()
402 8d5b316c Iustin Pop
403 8d5b316c Iustin Pop
  def _InitTransport(self):
404 8d5b316c Iustin Pop
    """(Re)initialize the transport if needed.
405 8d5b316c Iustin Pop

406 8d5b316c Iustin Pop
    """
407 8d5b316c Iustin Pop
    if self.transport is None:
408 8d5b316c Iustin Pop
      self.transport = self.transport_class(self.address,
409 8d5b316c Iustin Pop
                                            timeouts=self.timeouts)
410 8d5b316c Iustin Pop
411 8d5b316c Iustin Pop
  def _CloseTransport(self):
412 8d5b316c Iustin Pop
    """Close the transport, ignoring errors.
413 8d5b316c Iustin Pop

414 8d5b316c Iustin Pop
    """
415 8d5b316c Iustin Pop
    if self.transport is None:
416 8d5b316c Iustin Pop
      return
417 8d5b316c Iustin Pop
    try:
418 8d5b316c Iustin Pop
      old_transp = self.transport
419 8d5b316c Iustin Pop
      self.transport = None
420 8d5b316c Iustin Pop
      old_transp.Close()
421 b459a848 Andrea Spadaccini
    except Exception: # pylint: disable=W0703
422 8d5b316c Iustin Pop
      pass
423 c2a03789 Iustin Pop
424 231db3a5 Michael Hanselmann
  def _SendMethodCall(self, data):
425 3d8548c4 Michael Hanselmann
    # Send request and wait for response
426 8d5b316c Iustin Pop
    try:
427 8d5b316c Iustin Pop
      self._InitTransport()
428 231db3a5 Michael Hanselmann
      return self.transport.Call(data)
429 8d5b316c Iustin Pop
    except Exception:
430 8d5b316c Iustin Pop
      self._CloseTransport()
431 8d5b316c Iustin Pop
      raise
432 8d5b316c Iustin Pop
433 2a917701 Michael Hanselmann
  def Close(self):
434 2a917701 Michael Hanselmann
    """Close the underlying connection.
435 2a917701 Michael Hanselmann

436 2a917701 Michael Hanselmann
    """
437 2a917701 Michael Hanselmann
    self._CloseTransport()
438 2a917701 Michael Hanselmann
439 231db3a5 Michael Hanselmann
  def CallMethod(self, method, args):
440 231db3a5 Michael Hanselmann
    """Send a generic request and return the response.
441 3d8548c4 Michael Hanselmann

442 231db3a5 Michael Hanselmann
    """
443 a629ecb9 Iustin Pop
    if not isinstance(args, (list, tuple)):
444 a629ecb9 Iustin Pop
      raise errors.ProgrammerError("Invalid parameter passed to CallMethod:"
445 a629ecb9 Iustin Pop
                                   " expected list, got %s" % type(args))
446 e986f20c Michael Hanselmann
    return CallLuxiMethod(self._SendMethodCall, method, args,
447 e986f20c Michael Hanselmann
                          version=constants.LUXI_VERSION)
448 c2a03789 Iustin Pop
449 3ccafd0e Iustin Pop
  def SetQueueDrainFlag(self, drain_flag):
450 83c046a2 Iustin Pop
    return self.CallMethod(REQ_SET_DRAIN_FLAG, (drain_flag, ))
451 3ccafd0e Iustin Pop
452 05e50653 Michael Hanselmann
  def SetWatcherPause(self, until):
453 a629ecb9 Iustin Pop
    return self.CallMethod(REQ_SET_WATCHER_PAUSE, (until, ))
454 05e50653 Michael Hanselmann
455 0bbe448c Michael Hanselmann
  def SubmitJob(self, ops):
456 0bbe448c Michael Hanselmann
    ops_state = map(lambda op: op.__getstate__(), ops)
457 734a2a7c René Nussbaumer
    return self.CallMethod(REQ_SUBMIT_JOB, (ops_state, ))
458 0bbe448c Michael Hanselmann
459 346c3037 Klaus Aehlig
  def SubmitJobToDrainedQueue(self, ops):
460 346c3037 Klaus Aehlig
    ops_state = map(lambda op: op.__getstate__(), ops)
461 346c3037 Klaus Aehlig
    return self.CallMethod(REQ_SUBMIT_JOB_TO_DRAINED_QUEUE, (ops_state, ))
462 346c3037 Klaus Aehlig
463 2971c913 Iustin Pop
  def SubmitManyJobs(self, jobs):
464 2971c913 Iustin Pop
    jobs_state = []
465 2971c913 Iustin Pop
    for ops in jobs:
466 2971c913 Iustin Pop
      jobs_state.append([op.__getstate__() for op in ops])
467 734a2a7c René Nussbaumer
    return self.CallMethod(REQ_SUBMIT_MANY_JOBS, (jobs_state, ))
468 2971c913 Iustin Pop
469 0bbe448c Michael Hanselmann
  def CancelJob(self, job_id):
470 a629ecb9 Iustin Pop
    return self.CallMethod(REQ_CANCEL_JOB, (job_id, ))
471 0bbe448c Michael Hanselmann
472 0bbe448c Michael Hanselmann
  def ArchiveJob(self, job_id):
473 a629ecb9 Iustin Pop
    return self.CallMethod(REQ_ARCHIVE_JOB, (job_id, ))
474 0bbe448c Michael Hanselmann
475 f63ffb37 Michael Hanselmann
  def ChangeJobPriority(self, job_id, priority):
476 f63ffb37 Michael Hanselmann
    return self.CallMethod(REQ_CHANGE_JOB_PRIORITY, (job_id, priority))
477 f63ffb37 Michael Hanselmann
478 07cd723a Iustin Pop
  def AutoArchiveJobs(self, age):
479 f8ad5591 Michael Hanselmann
    timeout = (DEF_RWTO - 1) / 2
480 83c046a2 Iustin Pop
    return self.CallMethod(REQ_AUTO_ARCHIVE_JOBS, (age, timeout))
481 07cd723a Iustin Pop
482 f4484122 Michael Hanselmann
  def WaitForJobChangeOnce(self, job_id, fields,
483 793a8f7c Michael Hanselmann
                           prev_job_info, prev_log_serial,
484 793a8f7c Michael Hanselmann
                           timeout=WFJC_TIMEOUT):
485 793a8f7c Michael Hanselmann
    """Waits for changes on a job.
486 793a8f7c Michael Hanselmann

487 793a8f7c Michael Hanselmann
    @param job_id: Job ID
488 793a8f7c Michael Hanselmann
    @type fields: list
489 793a8f7c Michael Hanselmann
    @param fields: List of field names to be observed
490 793a8f7c Michael Hanselmann
    @type prev_job_info: None or list
491 793a8f7c Michael Hanselmann
    @param prev_job_info: Previously received job information
492 793a8f7c Michael Hanselmann
    @type prev_log_serial: None or int/long
493 793a8f7c Michael Hanselmann
    @param prev_log_serial: Highest log serial number previously received
494 793a8f7c Michael Hanselmann
    @type timeout: int/float
495 793a8f7c Michael Hanselmann
    @param timeout: Timeout in seconds (values larger than L{WFJC_TIMEOUT} will
496 793a8f7c Michael Hanselmann
                    be capped to that value)
497 793a8f7c Michael Hanselmann

498 793a8f7c Michael Hanselmann
    """
499 793a8f7c Michael Hanselmann
    assert timeout >= 0, "Timeout can not be negative"
500 f4484122 Michael Hanselmann
    return self.CallMethod(REQ_WAIT_FOR_JOB_CHANGE,
501 f4484122 Michael Hanselmann
                           (job_id, fields, prev_job_info,
502 793a8f7c Michael Hanselmann
                            prev_log_serial,
503 793a8f7c Michael Hanselmann
                            min(WFJC_TIMEOUT, timeout)))
504 f4484122 Michael Hanselmann
505 f4484122 Michael Hanselmann
  def WaitForJobChange(self, job_id, fields, prev_job_info, prev_log_serial):
506 5c735209 Iustin Pop
    while True:
507 f4484122 Michael Hanselmann
      result = self.WaitForJobChangeOnce(job_id, fields,
508 f4484122 Michael Hanselmann
                                         prev_job_info, prev_log_serial)
509 5c735209 Iustin Pop
      if result != constants.JOB_NOTCHANGED:
510 5c735209 Iustin Pop
        break
511 5c735209 Iustin Pop
    return result
512 dfe57c22 Michael Hanselmann
513 2e5c33db Iustin Pop
  def Query(self, what, fields, qfilter):
514 28b71a76 Michael Hanselmann
    """Query for resources/items.
515 28b71a76 Michael Hanselmann

516 abd66bf8 Michael Hanselmann
    @param what: One of L{constants.QR_VIA_LUXI}
517 28b71a76 Michael Hanselmann
    @type fields: List of strings
518 28b71a76 Michael Hanselmann
    @param fields: List of requested fields
519 2e5c33db Iustin Pop
    @type qfilter: None or list
520 2e5c33db Iustin Pop
    @param qfilter: Query filter
521 28b71a76 Michael Hanselmann
    @rtype: L{objects.QueryResponse}
522 28b71a76 Michael Hanselmann

523 28b71a76 Michael Hanselmann
    """
524 a629ecb9 Iustin Pop
    result = self.CallMethod(REQ_QUERY, (what, fields, qfilter))
525 28b71a76 Michael Hanselmann
    return objects.QueryResponse.FromDict(result)
526 28b71a76 Michael Hanselmann
527 28b71a76 Michael Hanselmann
  def QueryFields(self, what, fields):
528 28b71a76 Michael Hanselmann
    """Query for available fields.
529 28b71a76 Michael Hanselmann

530 abd66bf8 Michael Hanselmann
    @param what: One of L{constants.QR_VIA_LUXI}
531 28b71a76 Michael Hanselmann
    @type fields: None or list of strings
532 28b71a76 Michael Hanselmann
    @param fields: List of requested fields
533 28b71a76 Michael Hanselmann
    @rtype: L{objects.QueryFieldsResponse}
534 28b71a76 Michael Hanselmann

535 28b71a76 Michael Hanselmann
    """
536 a629ecb9 Iustin Pop
    result = self.CallMethod(REQ_QUERY_FIELDS, (what, fields))
537 28b71a76 Michael Hanselmann
    return objects.QueryFieldsResponse.FromDict(result)
538 28b71a76 Michael Hanselmann
539 0bbe448c Michael Hanselmann
  def QueryJobs(self, job_ids, fields):
540 0bbe448c Michael Hanselmann
    return self.CallMethod(REQ_QUERY_JOBS, (job_ids, fields))
541 3d8548c4 Michael Hanselmann
542 ec79568d Iustin Pop
  def QueryInstances(self, names, fields, use_locking):
543 ec79568d Iustin Pop
    return self.CallMethod(REQ_QUERY_INSTANCES, (names, fields, use_locking))
544 ee6c7b94 Michael Hanselmann
545 ec79568d Iustin Pop
  def QueryNodes(self, names, fields, use_locking):
546 ec79568d Iustin Pop
    return self.CallMethod(REQ_QUERY_NODES, (names, fields, use_locking))
547 02f7fe54 Michael Hanselmann
548 a79ef2a5 Adeodato Simo
  def QueryGroups(self, names, fields, use_locking):
549 a79ef2a5 Adeodato Simo
    return self.CallMethod(REQ_QUERY_GROUPS, (names, fields, use_locking))
550 a79ef2a5 Adeodato Simo
551 306bed0e Apollon Oikonomopoulos
  def QueryNetworks(self, names, fields, use_locking):
552 306bed0e Apollon Oikonomopoulos
    return self.CallMethod(REQ_QUERY_NETWORKS, (names, fields, use_locking))
553 306bed0e Apollon Oikonomopoulos
554 ec79568d Iustin Pop
  def QueryExports(self, nodes, use_locking):
555 ec79568d Iustin Pop
    return self.CallMethod(REQ_QUERY_EXPORTS, (nodes, use_locking))
556 32f93223 Michael Hanselmann
557 66baeccc Iustin Pop
  def QueryClusterInfo(self):
558 66baeccc Iustin Pop
    return self.CallMethod(REQ_QUERY_CLUSTER_INFO, ())
559 66baeccc Iustin Pop
560 ae5849b5 Michael Hanselmann
  def QueryConfigValues(self, fields):
561 a629ecb9 Iustin Pop
    return self.CallMethod(REQ_QUERY_CONFIG_VALUES, (fields, ))
562 ae5849b5 Michael Hanselmann
563 7699c3af Iustin Pop
  def QueryTags(self, kind, name):
564 7699c3af Iustin Pop
    return self.CallMethod(REQ_QUERY_TAGS, (kind, name))