Statistics
| Branch: | Tag: | Revision:

root / lib / luxi.py @ 7142485a

History | View | Annotate | Download (15.5 kB)

1 c2a03789 Iustin Pop
#
2 c2a03789 Iustin Pop
#
3 c2a03789 Iustin Pop
4 d143f2c6 Iustin Pop
# Copyright (C) 2006, 2007, 2011 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 c2a03789 Iustin Pop
44 c2a03789 Iustin Pop
45 231db3a5 Michael Hanselmann
KEY_METHOD = "method"
46 231db3a5 Michael Hanselmann
KEY_ARGS = "args"
47 3d8548c4 Michael Hanselmann
KEY_SUCCESS = "success"
48 3d8548c4 Michael Hanselmann
KEY_RESULT = "result"
49 e986f20c Michael Hanselmann
KEY_VERSION = "version"
50 3d8548c4 Michael Hanselmann
51 0bbe448c Michael Hanselmann
REQ_SUBMIT_JOB = "SubmitJob"
52 2971c913 Iustin Pop
REQ_SUBMIT_MANY_JOBS = "SubmitManyJobs"
53 dfe57c22 Michael Hanselmann
REQ_WAIT_FOR_JOB_CHANGE = "WaitForJobChange"
54 0bbe448c Michael Hanselmann
REQ_CANCEL_JOB = "CancelJob"
55 0bbe448c Michael Hanselmann
REQ_ARCHIVE_JOB = "ArchiveJob"
56 07cd723a Iustin Pop
REQ_AUTOARCHIVE_JOBS = "AutoArchiveJobs"
57 28b71a76 Michael Hanselmann
REQ_QUERY = "Query"
58 28b71a76 Michael Hanselmann
REQ_QUERY_FIELDS = "QueryFields"
59 0bbe448c Michael Hanselmann
REQ_QUERY_JOBS = "QueryJobs"
60 ee6c7b94 Michael Hanselmann
REQ_QUERY_INSTANCES = "QueryInstances"
61 02f7fe54 Michael Hanselmann
REQ_QUERY_NODES = "QueryNodes"
62 a79ef2a5 Adeodato Simo
REQ_QUERY_GROUPS = "QueryGroups"
63 32f93223 Michael Hanselmann
REQ_QUERY_EXPORTS = "QueryExports"
64 ae5849b5 Michael Hanselmann
REQ_QUERY_CONFIG_VALUES = "QueryConfigValues"
65 66baeccc Iustin Pop
REQ_QUERY_CLUSTER_INFO = "QueryClusterInfo"
66 7699c3af Iustin Pop
REQ_QUERY_TAGS = "QueryTags"
67 3ccafd0e Iustin Pop
REQ_QUEUE_SET_DRAIN_FLAG = "SetDrainFlag"
68 05e50653 Michael Hanselmann
REQ_SET_WATCHER_PAUSE = "SetWatcherPause"
69 c2a03789 Iustin Pop
70 c2a03789 Iustin Pop
DEF_CTMO = 10
71 c2a03789 Iustin Pop
DEF_RWTO = 60
72 c2a03789 Iustin Pop
73 793a8f7c Michael Hanselmann
# WaitForJobChange timeout
74 793a8f7c Michael Hanselmann
WFJC_TIMEOUT = (DEF_RWTO - 1) / 2
75 793a8f7c Michael Hanselmann
76 c2a03789 Iustin Pop
77 7a8bda3f Michael Hanselmann
class ProtocolError(errors.LuxiError):
78 5a1c22fe Iustin Pop
  """Denotes an error in the LUXI protocol."""
79 c2a03789 Iustin Pop
80 c2a03789 Iustin Pop
81 c2a03789 Iustin Pop
class ConnectionClosedError(ProtocolError):
82 5a1c22fe Iustin Pop
  """Connection closed error."""
83 c2a03789 Iustin Pop
84 c2a03789 Iustin Pop
85 c2a03789 Iustin Pop
class TimeoutError(ProtocolError):
86 5a1c22fe Iustin Pop
  """Operation timeout error."""
87 c2a03789 Iustin Pop
88 c2a03789 Iustin Pop
89 b77acb3e Iustin Pop
class RequestError(ProtocolError):
90 5a1c22fe Iustin Pop
  """Error on request.
91 b77acb3e Iustin Pop

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

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

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

105 03a8dbdc Iustin Pop
  This means that the master daemon is not running or the socket has
106 03a8dbdc Iustin Pop
  been removed.
107 03a8dbdc Iustin Pop

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

114 5a1c22fe Iustin Pop
  This means the user doesn't have the proper rights.
115 5a1c22fe Iustin Pop

116 5a1c22fe Iustin Pop
  """
117 5a1c22fe Iustin Pop
118 5a1c22fe Iustin Pop
119 c2a03789 Iustin Pop
class Transport:
120 c2a03789 Iustin Pop
  """Low-level transport class.
121 c2a03789 Iustin Pop

122 c2a03789 Iustin Pop
  This is used on the client side.
123 c2a03789 Iustin Pop

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

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

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

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

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

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

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

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

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

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

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

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

251 c2a03789 Iustin Pop
    This is just a wrapper over Send and Recv.
252 c2a03789 Iustin Pop

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

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

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

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

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

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

375 c2a03789 Iustin Pop
  This uses a backing Transport-like class on top of which it
376 c2a03789 Iustin Pop
  implements data serialization/deserialization.
377 c2a03789 Iustin Pop

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

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

387 c2a03789 Iustin Pop

388 c2a03789 Iustin Pop
    If timeout is not passed, the default timeouts of the transport
389 c2a03789 Iustin Pop
    class are used.
390 c2a03789 Iustin Pop

391 c2a03789 Iustin Pop
    """
392 ceab32dd Iustin Pop
    if address is None:
393 ceab32dd Iustin Pop
      address = constants.MASTER_SOCKET
394 8d5b316c Iustin Pop
    self.address = address
395 8d5b316c Iustin Pop
    self.timeouts = timeouts
396 8d5b316c Iustin Pop
    self.transport_class = transport
397 8d5b316c Iustin Pop
    self.transport = None
398 8d5b316c Iustin Pop
    self._InitTransport()
399 8d5b316c Iustin Pop
400 8d5b316c Iustin Pop
  def _InitTransport(self):
401 8d5b316c Iustin Pop
    """(Re)initialize the transport if needed.
402 8d5b316c Iustin Pop

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

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

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

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

477 793a8f7c Michael Hanselmann
    @param job_id: Job ID
478 793a8f7c Michael Hanselmann
    @type fields: list
479 793a8f7c Michael Hanselmann
    @param fields: List of field names to be observed
480 793a8f7c Michael Hanselmann
    @type prev_job_info: None or list
481 793a8f7c Michael Hanselmann
    @param prev_job_info: Previously received job information
482 793a8f7c Michael Hanselmann
    @type prev_log_serial: None or int/long
483 793a8f7c Michael Hanselmann
    @param prev_log_serial: Highest log serial number previously received
484 793a8f7c Michael Hanselmann
    @type timeout: int/float
485 793a8f7c Michael Hanselmann
    @param timeout: Timeout in seconds (values larger than L{WFJC_TIMEOUT} will
486 793a8f7c Michael Hanselmann
                    be capped to that value)
487 793a8f7c Michael Hanselmann

488 793a8f7c Michael Hanselmann
    """
489 793a8f7c Michael Hanselmann
    assert timeout >= 0, "Timeout can not be negative"
490 f4484122 Michael Hanselmann
    return self.CallMethod(REQ_WAIT_FOR_JOB_CHANGE,
491 f4484122 Michael Hanselmann
                           (job_id, fields, prev_job_info,
492 793a8f7c Michael Hanselmann
                            prev_log_serial,
493 793a8f7c Michael Hanselmann
                            min(WFJC_TIMEOUT, timeout)))
494 f4484122 Michael Hanselmann
495 f4484122 Michael Hanselmann
  def WaitForJobChange(self, job_id, fields, prev_job_info, prev_log_serial):
496 5c735209 Iustin Pop
    while True:
497 f4484122 Michael Hanselmann
      result = self.WaitForJobChangeOnce(job_id, fields,
498 f4484122 Michael Hanselmann
                                         prev_job_info, prev_log_serial)
499 5c735209 Iustin Pop
      if result != constants.JOB_NOTCHANGED:
500 5c735209 Iustin Pop
        break
501 5c735209 Iustin Pop
    return result
502 dfe57c22 Michael Hanselmann
503 2e5c33db Iustin Pop
  def Query(self, what, fields, qfilter):
504 28b71a76 Michael Hanselmann
    """Query for resources/items.
505 28b71a76 Michael Hanselmann

506 abd66bf8 Michael Hanselmann
    @param what: One of L{constants.QR_VIA_LUXI}
507 28b71a76 Michael Hanselmann
    @type fields: List of strings
508 28b71a76 Michael Hanselmann
    @param fields: List of requested fields
509 2e5c33db Iustin Pop
    @type qfilter: None or list
510 2e5c33db Iustin Pop
    @param qfilter: Query filter
511 28b71a76 Michael Hanselmann
    @rtype: L{objects.QueryResponse}
512 28b71a76 Michael Hanselmann

513 28b71a76 Michael Hanselmann
    """
514 a629ecb9 Iustin Pop
    result = self.CallMethod(REQ_QUERY, (what, fields, qfilter))
515 28b71a76 Michael Hanselmann
    return objects.QueryResponse.FromDict(result)
516 28b71a76 Michael Hanselmann
517 28b71a76 Michael Hanselmann
  def QueryFields(self, what, fields):
518 28b71a76 Michael Hanselmann
    """Query for available fields.
519 28b71a76 Michael Hanselmann

520 abd66bf8 Michael Hanselmann
    @param what: One of L{constants.QR_VIA_LUXI}
521 28b71a76 Michael Hanselmann
    @type fields: None or list of strings
522 28b71a76 Michael Hanselmann
    @param fields: List of requested fields
523 28b71a76 Michael Hanselmann
    @rtype: L{objects.QueryFieldsResponse}
524 28b71a76 Michael Hanselmann

525 28b71a76 Michael Hanselmann
    """
526 a629ecb9 Iustin Pop
    result = self.CallMethod(REQ_QUERY_FIELDS, (what, fields))
527 28b71a76 Michael Hanselmann
    return objects.QueryFieldsResponse.FromDict(result)
528 28b71a76 Michael Hanselmann
529 0bbe448c Michael Hanselmann
  def QueryJobs(self, job_ids, fields):
530 0bbe448c Michael Hanselmann
    return self.CallMethod(REQ_QUERY_JOBS, (job_ids, fields))
531 3d8548c4 Michael Hanselmann
532 ec79568d Iustin Pop
  def QueryInstances(self, names, fields, use_locking):
533 ec79568d Iustin Pop
    return self.CallMethod(REQ_QUERY_INSTANCES, (names, fields, use_locking))
534 ee6c7b94 Michael Hanselmann
535 ec79568d Iustin Pop
  def QueryNodes(self, names, fields, use_locking):
536 ec79568d Iustin Pop
    return self.CallMethod(REQ_QUERY_NODES, (names, fields, use_locking))
537 02f7fe54 Michael Hanselmann
538 a79ef2a5 Adeodato Simo
  def QueryGroups(self, names, fields, use_locking):
539 a79ef2a5 Adeodato Simo
    return self.CallMethod(REQ_QUERY_GROUPS, (names, fields, use_locking))
540 a79ef2a5 Adeodato Simo
541 ec79568d Iustin Pop
  def QueryExports(self, nodes, use_locking):
542 ec79568d Iustin Pop
    return self.CallMethod(REQ_QUERY_EXPORTS, (nodes, use_locking))
543 32f93223 Michael Hanselmann
544 66baeccc Iustin Pop
  def QueryClusterInfo(self):
545 66baeccc Iustin Pop
    return self.CallMethod(REQ_QUERY_CLUSTER_INFO, ())
546 66baeccc Iustin Pop
547 ae5849b5 Michael Hanselmann
  def QueryConfigValues(self, fields):
548 a629ecb9 Iustin Pop
    return self.CallMethod(REQ_QUERY_CONFIG_VALUES, (fields, ))
549 ae5849b5 Michael Hanselmann
550 7699c3af Iustin Pop
  def QueryTags(self, kind, name):
551 7699c3af Iustin Pop
    return self.CallMethod(REQ_QUERY_TAGS, (kind, name))