Statistics
| Branch: | Tag: | Revision:

root / lib / luxi.py @ fb62843c

History | View | Annotate | Download (16.4 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 b8028dcf Michael Hanselmann
from ganeti import compat
39 fad50141 Michael Hanselmann
from ganeti import serializer
40 ceab32dd Iustin Pop
from ganeti import constants
41 6797ec29 Iustin Pop
from ganeti import errors
42 cb462b06 Michael Hanselmann
from ganeti import utils
43 28b71a76 Michael Hanselmann
from ganeti import objects
44 b87ee98f Michael Hanselmann
from ganeti import pathutils
45 c2a03789 Iustin Pop
46 c2a03789 Iustin Pop
47 231db3a5 Michael Hanselmann
KEY_METHOD = "method"
48 231db3a5 Michael Hanselmann
KEY_ARGS = "args"
49 3d8548c4 Michael Hanselmann
KEY_SUCCESS = "success"
50 3d8548c4 Michael Hanselmann
KEY_RESULT = "result"
51 e986f20c Michael Hanselmann
KEY_VERSION = "version"
52 3d8548c4 Michael Hanselmann
53 0bbe448c Michael Hanselmann
REQ_SUBMIT_JOB = "SubmitJob"
54 2971c913 Iustin Pop
REQ_SUBMIT_MANY_JOBS = "SubmitManyJobs"
55 dfe57c22 Michael Hanselmann
REQ_WAIT_FOR_JOB_CHANGE = "WaitForJobChange"
56 0bbe448c Michael Hanselmann
REQ_CANCEL_JOB = "CancelJob"
57 0bbe448c Michael Hanselmann
REQ_ARCHIVE_JOB = "ArchiveJob"
58 f63ffb37 Michael Hanselmann
REQ_CHANGE_JOB_PRIORITY = "ChangeJobPriority"
59 83c046a2 Iustin Pop
REQ_AUTO_ARCHIVE_JOBS = "AutoArchiveJobs"
60 28b71a76 Michael Hanselmann
REQ_QUERY = "Query"
61 28b71a76 Michael Hanselmann
REQ_QUERY_FIELDS = "QueryFields"
62 0bbe448c Michael Hanselmann
REQ_QUERY_JOBS = "QueryJobs"
63 ee6c7b94 Michael Hanselmann
REQ_QUERY_INSTANCES = "QueryInstances"
64 02f7fe54 Michael Hanselmann
REQ_QUERY_NODES = "QueryNodes"
65 a79ef2a5 Adeodato Simo
REQ_QUERY_GROUPS = "QueryGroups"
66 306bed0e Apollon Oikonomopoulos
REQ_QUERY_NETWORKS = "QueryNetworks"
67 32f93223 Michael Hanselmann
REQ_QUERY_EXPORTS = "QueryExports"
68 ae5849b5 Michael Hanselmann
REQ_QUERY_CONFIG_VALUES = "QueryConfigValues"
69 66baeccc Iustin Pop
REQ_QUERY_CLUSTER_INFO = "QueryClusterInfo"
70 7699c3af Iustin Pop
REQ_QUERY_TAGS = "QueryTags"
71 83c046a2 Iustin Pop
REQ_SET_DRAIN_FLAG = "SetDrainFlag"
72 05e50653 Michael Hanselmann
REQ_SET_WATCHER_PAUSE = "SetWatcherPause"
73 c2a03789 Iustin Pop
74 e3a25810 Michael Hanselmann
#: List of all LUXI requests
75 b8028dcf Michael Hanselmann
REQ_ALL = compat.UniqueFrozenset([
76 e3a25810 Michael Hanselmann
  REQ_ARCHIVE_JOB,
77 83c046a2 Iustin Pop
  REQ_AUTO_ARCHIVE_JOBS,
78 e3a25810 Michael Hanselmann
  REQ_CANCEL_JOB,
79 f63ffb37 Michael Hanselmann
  REQ_CHANGE_JOB_PRIORITY,
80 e3a25810 Michael Hanselmann
  REQ_QUERY,
81 e3a25810 Michael Hanselmann
  REQ_QUERY_CLUSTER_INFO,
82 e3a25810 Michael Hanselmann
  REQ_QUERY_CONFIG_VALUES,
83 e3a25810 Michael Hanselmann
  REQ_QUERY_EXPORTS,
84 e3a25810 Michael Hanselmann
  REQ_QUERY_FIELDS,
85 e3a25810 Michael Hanselmann
  REQ_QUERY_GROUPS,
86 e3a25810 Michael Hanselmann
  REQ_QUERY_INSTANCES,
87 e3a25810 Michael Hanselmann
  REQ_QUERY_JOBS,
88 e3a25810 Michael Hanselmann
  REQ_QUERY_NODES,
89 795d035d Klaus Aehlig
  REQ_QUERY_NETWORKS,
90 e3a25810 Michael Hanselmann
  REQ_QUERY_TAGS,
91 83c046a2 Iustin Pop
  REQ_SET_DRAIN_FLAG,
92 e3a25810 Michael Hanselmann
  REQ_SET_WATCHER_PAUSE,
93 e3a25810 Michael Hanselmann
  REQ_SUBMIT_JOB,
94 e3a25810 Michael Hanselmann
  REQ_SUBMIT_MANY_JOBS,
95 e3a25810 Michael Hanselmann
  REQ_WAIT_FOR_JOB_CHANGE,
96 e3a25810 Michael Hanselmann
  ])
97 e3a25810 Michael Hanselmann
98 c2a03789 Iustin Pop
DEF_CTMO = 10
99 c2a03789 Iustin Pop
DEF_RWTO = 60
100 c2a03789 Iustin Pop
101 793a8f7c Michael Hanselmann
# WaitForJobChange timeout
102 793a8f7c Michael Hanselmann
WFJC_TIMEOUT = (DEF_RWTO - 1) / 2
103 793a8f7c Michael Hanselmann
104 c2a03789 Iustin Pop
105 7a8bda3f Michael Hanselmann
class ProtocolError(errors.LuxiError):
106 5a1c22fe Iustin Pop
  """Denotes an error in the LUXI protocol."""
107 c2a03789 Iustin Pop
108 c2a03789 Iustin Pop
109 c2a03789 Iustin Pop
class ConnectionClosedError(ProtocolError):
110 5a1c22fe Iustin Pop
  """Connection closed error."""
111 c2a03789 Iustin Pop
112 c2a03789 Iustin Pop
113 c2a03789 Iustin Pop
class TimeoutError(ProtocolError):
114 5a1c22fe Iustin Pop
  """Operation timeout error."""
115 c2a03789 Iustin Pop
116 c2a03789 Iustin Pop
117 b77acb3e Iustin Pop
class RequestError(ProtocolError):
118 5a1c22fe Iustin Pop
  """Error on request.
119 b77acb3e Iustin Pop

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

123 b77acb3e Iustin Pop
  Some common conditions that can trigger this exception:
124 b77acb3e Iustin Pop
    - job submission failed because the job data was wrong
125 b77acb3e Iustin Pop
    - query failed because required fields were missing
126 b77acb3e Iustin Pop

127 b77acb3e Iustin Pop
  """
128 b77acb3e Iustin Pop
129 3d8548c4 Michael Hanselmann
130 03a8dbdc Iustin Pop
class NoMasterError(ProtocolError):
131 5a1c22fe Iustin Pop
  """The master cannot be reached.
132 03a8dbdc Iustin Pop

133 03a8dbdc Iustin Pop
  This means that the master daemon is not running or the socket has
134 03a8dbdc Iustin Pop
  been removed.
135 03a8dbdc Iustin Pop

136 03a8dbdc Iustin Pop
  """
137 03a8dbdc Iustin Pop
138 b77acb3e Iustin Pop
139 5a1c22fe Iustin Pop
class PermissionError(ProtocolError):
140 5a1c22fe Iustin Pop
  """Permission denied while connecting to the master socket.
141 5a1c22fe Iustin Pop

142 5a1c22fe Iustin Pop
  This means the user doesn't have the proper rights.
143 5a1c22fe Iustin Pop

144 5a1c22fe Iustin Pop
  """
145 5a1c22fe Iustin Pop
146 5a1c22fe Iustin Pop
147 c2a03789 Iustin Pop
class Transport:
148 c2a03789 Iustin Pop
  """Low-level transport class.
149 c2a03789 Iustin Pop

150 c2a03789 Iustin Pop
  This is used on the client side.
151 c2a03789 Iustin Pop

152 c2a03789 Iustin Pop
  This could be replace by any other class that provides the same
153 c2a03789 Iustin Pop
  semantics to the Client. This means:
154 c2a03789 Iustin Pop
    - can send messages and receive messages
155 c2a03789 Iustin Pop
    - safe for multithreading
156 c2a03789 Iustin Pop

157 c2a03789 Iustin Pop
  """
158 c2a03789 Iustin Pop
159 25942a6c Guido Trotter
  def __init__(self, address, timeouts=None):
160 c2a03789 Iustin Pop
    """Constructor for the Client class.
161 c2a03789 Iustin Pop

162 c2a03789 Iustin Pop
    Arguments:
163 c2a03789 Iustin Pop
      - address: a valid address the the used transport class
164 c2a03789 Iustin Pop
      - timeout: a list of timeouts, to be used on connect and read/write
165 c2a03789 Iustin Pop

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

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

171 c2a03789 Iustin Pop
    Note that on reading data, since the timeout applies to an
172 c2a03789 Iustin Pop
    invidual receive, it might be that the total duration is longer
173 c2a03789 Iustin Pop
    than timeout value passed (we make a hard limit at twice the read
174 c2a03789 Iustin Pop
    timeout).
175 c2a03789 Iustin Pop

176 c2a03789 Iustin Pop
    """
177 c2a03789 Iustin Pop
    self.address = address
178 c2a03789 Iustin Pop
    if timeouts is None:
179 c2a03789 Iustin Pop
      self._ctimeout, self._rwtimeout = DEF_CTMO, DEF_RWTO
180 c2a03789 Iustin Pop
    else:
181 c2a03789 Iustin Pop
      self._ctimeout, self._rwtimeout = timeouts
182 c2a03789 Iustin Pop
183 c2a03789 Iustin Pop
    self.socket = None
184 c2a03789 Iustin Pop
    self._buffer = ""
185 c2a03789 Iustin Pop
    self._msgs = collections.deque()
186 c2a03789 Iustin Pop
187 c2a03789 Iustin Pop
    try:
188 c2a03789 Iustin Pop
      self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
189 cb462b06 Michael Hanselmann
190 cb462b06 Michael Hanselmann
      # Try to connect
191 c2a03789 Iustin Pop
      try:
192 cb462b06 Michael Hanselmann
        utils.Retry(self._Connect, 1.0, self._ctimeout,
193 cb462b06 Michael Hanselmann
                    args=(self.socket, address, self._ctimeout))
194 cb462b06 Michael Hanselmann
      except utils.RetryTimeout:
195 cb462b06 Michael Hanselmann
        raise TimeoutError("Connect timed out")
196 cb462b06 Michael Hanselmann
197 c2a03789 Iustin Pop
      self.socket.settimeout(self._rwtimeout)
198 03a8dbdc Iustin Pop
    except (socket.error, NoMasterError):
199 c2a03789 Iustin Pop
      if self.socket is not None:
200 c2a03789 Iustin Pop
        self.socket.close()
201 c2a03789 Iustin Pop
      self.socket = None
202 c2a03789 Iustin Pop
      raise
203 c2a03789 Iustin Pop
204 cb462b06 Michael Hanselmann
  @staticmethod
205 cb462b06 Michael Hanselmann
  def _Connect(sock, address, timeout):
206 cb462b06 Michael Hanselmann
    sock.settimeout(timeout)
207 cb462b06 Michael Hanselmann
    try:
208 cb462b06 Michael Hanselmann
      sock.connect(address)
209 cb462b06 Michael Hanselmann
    except socket.timeout, err:
210 cb462b06 Michael Hanselmann
      raise TimeoutError("Connect timed out: %s" % str(err))
211 cb462b06 Michael Hanselmann
    except socket.error, err:
212 5a1c22fe Iustin Pop
      error_code = err.args[0]
213 5a1c22fe Iustin Pop
      if error_code in (errno.ENOENT, errno.ECONNREFUSED):
214 cb462b06 Michael Hanselmann
        raise NoMasterError(address)
215 5a1c22fe Iustin Pop
      elif error_code in (errno.EPERM, errno.EACCES):
216 5a1c22fe Iustin Pop
        raise PermissionError(address)
217 5a1c22fe Iustin Pop
      elif error_code == errno.EAGAIN:
218 cb462b06 Michael Hanselmann
        # Server's socket backlog is full at the moment
219 cb462b06 Michael Hanselmann
        raise utils.RetryAgain()
220 cb462b06 Michael Hanselmann
      raise
221 cb462b06 Michael Hanselmann
222 c2a03789 Iustin Pop
  def _CheckSocket(self):
223 c2a03789 Iustin Pop
    """Make sure we are connected.
224 c2a03789 Iustin Pop

225 c2a03789 Iustin Pop
    """
226 c2a03789 Iustin Pop
    if self.socket is None:
227 c2a03789 Iustin Pop
      raise ProtocolError("Connection is closed")
228 c2a03789 Iustin Pop
229 c2a03789 Iustin Pop
  def Send(self, msg):
230 c2a03789 Iustin Pop
    """Send a message.
231 c2a03789 Iustin Pop

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

234 c2a03789 Iustin Pop
    """
235 25942a6c Guido Trotter
    if constants.LUXI_EOM in msg:
236 797506fc Michael Hanselmann
      raise ProtocolError("Message terminator found in payload")
237 797506fc Michael Hanselmann
238 c2a03789 Iustin Pop
    self._CheckSocket()
239 c2a03789 Iustin Pop
    try:
240 6096ee13 Michael Hanselmann
      # TODO: sendall is not guaranteed to send everything
241 25942a6c Guido Trotter
      self.socket.sendall(msg + constants.LUXI_EOM)
242 c2a03789 Iustin Pop
    except socket.timeout, err:
243 c2a03789 Iustin Pop
      raise TimeoutError("Sending timeout: %s" % str(err))
244 c2a03789 Iustin Pop
245 c2a03789 Iustin Pop
  def Recv(self):
246 5bbd3f7f Michael Hanselmann
    """Try to receive a message from the socket.
247 c2a03789 Iustin Pop

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

253 c2a03789 Iustin Pop
    """
254 c2a03789 Iustin Pop
    self._CheckSocket()
255 c2a03789 Iustin Pop
    etime = time.time() + self._rwtimeout
256 c2a03789 Iustin Pop
    while not self._msgs:
257 c2a03789 Iustin Pop
      if time.time() > etime:
258 c2a03789 Iustin Pop
        raise TimeoutError("Extended receive timeout")
259 6096ee13 Michael Hanselmann
      while True:
260 6096ee13 Michael Hanselmann
        try:
261 6096ee13 Michael Hanselmann
          data = self.socket.recv(4096)
262 28e3e216 Michael Hanselmann
        except socket.timeout, err:
263 28e3e216 Michael Hanselmann
          raise TimeoutError("Receive timeout: %s" % str(err))
264 6096ee13 Michael Hanselmann
        except socket.error, err:
265 6096ee13 Michael Hanselmann
          if err.args and err.args[0] == errno.EAGAIN:
266 6096ee13 Michael Hanselmann
            continue
267 6096ee13 Michael Hanselmann
          raise
268 6096ee13 Michael Hanselmann
        break
269 c2a03789 Iustin Pop
      if not data:
270 c2a03789 Iustin Pop
        raise ConnectionClosedError("Connection closed while reading")
271 25942a6c Guido Trotter
      new_msgs = (self._buffer + data).split(constants.LUXI_EOM)
272 c2a03789 Iustin Pop
      self._buffer = new_msgs.pop()
273 c2a03789 Iustin Pop
      self._msgs.extend(new_msgs)
274 c2a03789 Iustin Pop
    return self._msgs.popleft()
275 c2a03789 Iustin Pop
276 c2a03789 Iustin Pop
  def Call(self, msg):
277 c2a03789 Iustin Pop
    """Send a message and wait for the response.
278 c2a03789 Iustin Pop

279 c2a03789 Iustin Pop
    This is just a wrapper over Send and Recv.
280 c2a03789 Iustin Pop

281 c2a03789 Iustin Pop
    """
282 c2a03789 Iustin Pop
    self.Send(msg)
283 c2a03789 Iustin Pop
    return self.Recv()
284 c2a03789 Iustin Pop
285 c2a03789 Iustin Pop
  def Close(self):
286 c2a03789 Iustin Pop
    """Close the socket"""
287 c2a03789 Iustin Pop
    if self.socket is not None:
288 c2a03789 Iustin Pop
      self.socket.close()
289 c2a03789 Iustin Pop
      self.socket = None
290 c2a03789 Iustin Pop
291 c2a03789 Iustin Pop
292 231db3a5 Michael Hanselmann
def ParseRequest(msg):
293 231db3a5 Michael Hanselmann
  """Parses a LUXI request message.
294 231db3a5 Michael Hanselmann

295 231db3a5 Michael Hanselmann
  """
296 231db3a5 Michael Hanselmann
  try:
297 231db3a5 Michael Hanselmann
    request = serializer.LoadJson(msg)
298 231db3a5 Michael Hanselmann
  except ValueError, err:
299 231db3a5 Michael Hanselmann
    raise ProtocolError("Invalid LUXI request (parsing error): %s" % err)
300 231db3a5 Michael Hanselmann
301 231db3a5 Michael Hanselmann
  logging.debug("LUXI request: %s", request)
302 231db3a5 Michael Hanselmann
303 231db3a5 Michael Hanselmann
  if not isinstance(request, dict):
304 231db3a5 Michael Hanselmann
    logging.error("LUXI request not a dict: %r", msg)
305 231db3a5 Michael Hanselmann
    raise ProtocolError("Invalid LUXI request (not a dict)")
306 231db3a5 Michael Hanselmann
307 b459a848 Andrea Spadaccini
  method = request.get(KEY_METHOD, None) # pylint: disable=E1103
308 b459a848 Andrea Spadaccini
  args = request.get(KEY_ARGS, None) # pylint: disable=E1103
309 b459a848 Andrea Spadaccini
  version = request.get(KEY_VERSION, None) # pylint: disable=E1103
310 e7a25b08 Guido Trotter
311 231db3a5 Michael Hanselmann
  if method is None or args is None:
312 231db3a5 Michael Hanselmann
    logging.error("LUXI request missing method or arguments: %r", msg)
313 231db3a5 Michael Hanselmann
    raise ProtocolError(("Invalid LUXI request (no method or arguments"
314 231db3a5 Michael Hanselmann
                         " in request): %r") % msg)
315 231db3a5 Michael Hanselmann
316 e986f20c Michael Hanselmann
  return (method, args, version)
317 231db3a5 Michael Hanselmann
318 231db3a5 Michael Hanselmann
319 231db3a5 Michael Hanselmann
def ParseResponse(msg):
320 231db3a5 Michael Hanselmann
  """Parses a LUXI response message.
321 231db3a5 Michael Hanselmann

322 231db3a5 Michael Hanselmann
  """
323 231db3a5 Michael Hanselmann
  # Parse the result
324 231db3a5 Michael Hanselmann
  try:
325 231db3a5 Michael Hanselmann
    data = serializer.LoadJson(msg)
326 d143f2c6 Iustin Pop
  except KeyboardInterrupt:
327 d143f2c6 Iustin Pop
    raise
328 231db3a5 Michael Hanselmann
  except Exception, err:
329 231db3a5 Michael Hanselmann
    raise ProtocolError("Error while deserializing response: %s" % str(err))
330 231db3a5 Michael Hanselmann
331 231db3a5 Michael Hanselmann
  # Validate response
332 231db3a5 Michael Hanselmann
  if not (isinstance(data, dict) and
333 231db3a5 Michael Hanselmann
          KEY_SUCCESS in data and
334 231db3a5 Michael Hanselmann
          KEY_RESULT in data):
335 231db3a5 Michael Hanselmann
    raise ProtocolError("Invalid response from server: %r" % data)
336 231db3a5 Michael Hanselmann
337 2317945a Guido Trotter
  return (data[KEY_SUCCESS], data[KEY_RESULT],
338 b459a848 Andrea Spadaccini
          data.get(KEY_VERSION, None)) # pylint: disable=E1103
339 231db3a5 Michael Hanselmann
340 231db3a5 Michael Hanselmann
341 e986f20c Michael Hanselmann
def FormatResponse(success, result, version=None):
342 231db3a5 Michael Hanselmann
  """Formats a LUXI response message.
343 231db3a5 Michael Hanselmann

344 231db3a5 Michael Hanselmann
  """
345 231db3a5 Michael Hanselmann
  response = {
346 231db3a5 Michael Hanselmann
    KEY_SUCCESS: success,
347 231db3a5 Michael Hanselmann
    KEY_RESULT: result,
348 231db3a5 Michael Hanselmann
    }
349 231db3a5 Michael Hanselmann
350 e986f20c Michael Hanselmann
  if version is not None:
351 e986f20c Michael Hanselmann
    response[KEY_VERSION] = version
352 e986f20c Michael Hanselmann
353 231db3a5 Michael Hanselmann
  logging.debug("LUXI response: %s", response)
354 231db3a5 Michael Hanselmann
355 231db3a5 Michael Hanselmann
  return serializer.DumpJson(response)
356 231db3a5 Michael Hanselmann
357 231db3a5 Michael Hanselmann
358 e986f20c Michael Hanselmann
def FormatRequest(method, args, version=None):
359 231db3a5 Michael Hanselmann
  """Formats a LUXI request message.
360 231db3a5 Michael Hanselmann

361 231db3a5 Michael Hanselmann
  """
362 231db3a5 Michael Hanselmann
  # Build request
363 231db3a5 Michael Hanselmann
  request = {
364 231db3a5 Michael Hanselmann
    KEY_METHOD: method,
365 231db3a5 Michael Hanselmann
    KEY_ARGS: args,
366 231db3a5 Michael Hanselmann
    }
367 231db3a5 Michael Hanselmann
368 e986f20c Michael Hanselmann
  if version is not None:
369 e986f20c Michael Hanselmann
    request[KEY_VERSION] = version
370 e986f20c Michael Hanselmann
371 231db3a5 Michael Hanselmann
  # Serialize the request
372 a182a3ed Michael Hanselmann
  return serializer.DumpJson(request)
373 231db3a5 Michael Hanselmann
374 231db3a5 Michael Hanselmann
375 e986f20c Michael Hanselmann
def CallLuxiMethod(transport_cb, method, args, version=None):
376 231db3a5 Michael Hanselmann
  """Send a LUXI request via a transport and return the response.
377 231db3a5 Michael Hanselmann

378 231db3a5 Michael Hanselmann
  """
379 231db3a5 Michael Hanselmann
  assert callable(transport_cb)
380 231db3a5 Michael Hanselmann
381 e986f20c Michael Hanselmann
  request_msg = FormatRequest(method, args, version=version)
382 231db3a5 Michael Hanselmann
383 231db3a5 Michael Hanselmann
  # Send request and wait for response
384 231db3a5 Michael Hanselmann
  response_msg = transport_cb(request_msg)
385 231db3a5 Michael Hanselmann
386 e986f20c Michael Hanselmann
  (success, result, resp_version) = ParseResponse(response_msg)
387 e986f20c Michael Hanselmann
388 e986f20c Michael Hanselmann
  # Verify version if there was one in the response
389 e986f20c Michael Hanselmann
  if resp_version is not None and resp_version != version:
390 e986f20c Michael Hanselmann
    raise errors.LuxiError("LUXI version mismatch, client %s, response %s" %
391 e986f20c Michael Hanselmann
                           (version, resp_version))
392 231db3a5 Michael Hanselmann
393 231db3a5 Michael Hanselmann
  if success:
394 231db3a5 Michael Hanselmann
    return result
395 231db3a5 Michael Hanselmann
396 231db3a5 Michael Hanselmann
  errors.MaybeRaise(result)
397 231db3a5 Michael Hanselmann
  raise RequestError(result)
398 231db3a5 Michael Hanselmann
399 231db3a5 Michael Hanselmann
400 c2a03789 Iustin Pop
class Client(object):
401 c2a03789 Iustin Pop
  """High-level client implementation.
402 c2a03789 Iustin Pop

403 c2a03789 Iustin Pop
  This uses a backing Transport-like class on top of which it
404 c2a03789 Iustin Pop
  implements data serialization/deserialization.
405 c2a03789 Iustin Pop

406 c2a03789 Iustin Pop
  """
407 ceab32dd Iustin Pop
  def __init__(self, address=None, timeouts=None, transport=Transport):
408 c2a03789 Iustin Pop
    """Constructor for the Client class.
409 c2a03789 Iustin Pop

410 c2a03789 Iustin Pop
    Arguments:
411 c2a03789 Iustin Pop
      - address: a valid address the the used transport class
412 c2a03789 Iustin Pop
      - timeout: a list of timeouts, to be used on connect and read/write
413 c2a03789 Iustin Pop
      - transport: a Transport-like class
414 c2a03789 Iustin Pop

415 c2a03789 Iustin Pop

416 c2a03789 Iustin Pop
    If timeout is not passed, the default timeouts of the transport
417 c2a03789 Iustin Pop
    class are used.
418 c2a03789 Iustin Pop

419 c2a03789 Iustin Pop
    """
420 ceab32dd Iustin Pop
    if address is None:
421 b87ee98f Michael Hanselmann
      address = pathutils.MASTER_SOCKET
422 8d5b316c Iustin Pop
    self.address = address
423 8d5b316c Iustin Pop
    self.timeouts = timeouts
424 8d5b316c Iustin Pop
    self.transport_class = transport
425 8d5b316c Iustin Pop
    self.transport = None
426 8d5b316c Iustin Pop
    self._InitTransport()
427 8d5b316c Iustin Pop
428 8d5b316c Iustin Pop
  def _InitTransport(self):
429 8d5b316c Iustin Pop
    """(Re)initialize the transport if needed.
430 8d5b316c Iustin Pop

431 8d5b316c Iustin Pop
    """
432 8d5b316c Iustin Pop
    if self.transport is None:
433 8d5b316c Iustin Pop
      self.transport = self.transport_class(self.address,
434 8d5b316c Iustin Pop
                                            timeouts=self.timeouts)
435 8d5b316c Iustin Pop
436 8d5b316c Iustin Pop
  def _CloseTransport(self):
437 8d5b316c Iustin Pop
    """Close the transport, ignoring errors.
438 8d5b316c Iustin Pop

439 8d5b316c Iustin Pop
    """
440 8d5b316c Iustin Pop
    if self.transport is None:
441 8d5b316c Iustin Pop
      return
442 8d5b316c Iustin Pop
    try:
443 8d5b316c Iustin Pop
      old_transp = self.transport
444 8d5b316c Iustin Pop
      self.transport = None
445 8d5b316c Iustin Pop
      old_transp.Close()
446 b459a848 Andrea Spadaccini
    except Exception: # pylint: disable=W0703
447 8d5b316c Iustin Pop
      pass
448 c2a03789 Iustin Pop
449 231db3a5 Michael Hanselmann
  def _SendMethodCall(self, data):
450 3d8548c4 Michael Hanselmann
    # Send request and wait for response
451 8d5b316c Iustin Pop
    try:
452 8d5b316c Iustin Pop
      self._InitTransport()
453 231db3a5 Michael Hanselmann
      return self.transport.Call(data)
454 8d5b316c Iustin Pop
    except Exception:
455 8d5b316c Iustin Pop
      self._CloseTransport()
456 8d5b316c Iustin Pop
      raise
457 8d5b316c Iustin Pop
458 2a917701 Michael Hanselmann
  def Close(self):
459 2a917701 Michael Hanselmann
    """Close the underlying connection.
460 2a917701 Michael Hanselmann

461 2a917701 Michael Hanselmann
    """
462 2a917701 Michael Hanselmann
    self._CloseTransport()
463 2a917701 Michael Hanselmann
464 231db3a5 Michael Hanselmann
  def CallMethod(self, method, args):
465 231db3a5 Michael Hanselmann
    """Send a generic request and return the response.
466 3d8548c4 Michael Hanselmann

467 231db3a5 Michael Hanselmann
    """
468 a629ecb9 Iustin Pop
    if not isinstance(args, (list, tuple)):
469 a629ecb9 Iustin Pop
      raise errors.ProgrammerError("Invalid parameter passed to CallMethod:"
470 a629ecb9 Iustin Pop
                                   " expected list, got %s" % type(args))
471 e986f20c Michael Hanselmann
    return CallLuxiMethod(self._SendMethodCall, method, args,
472 e986f20c Michael Hanselmann
                          version=constants.LUXI_VERSION)
473 c2a03789 Iustin Pop
474 3ccafd0e Iustin Pop
  def SetQueueDrainFlag(self, drain_flag):
475 83c046a2 Iustin Pop
    return self.CallMethod(REQ_SET_DRAIN_FLAG, (drain_flag, ))
476 3ccafd0e Iustin Pop
477 05e50653 Michael Hanselmann
  def SetWatcherPause(self, until):
478 a629ecb9 Iustin Pop
    return self.CallMethod(REQ_SET_WATCHER_PAUSE, (until, ))
479 05e50653 Michael Hanselmann
480 0bbe448c Michael Hanselmann
  def SubmitJob(self, ops):
481 0bbe448c Michael Hanselmann
    ops_state = map(lambda op: op.__getstate__(), ops)
482 734a2a7c René Nussbaumer
    return self.CallMethod(REQ_SUBMIT_JOB, (ops_state, ))
483 0bbe448c Michael Hanselmann
484 2971c913 Iustin Pop
  def SubmitManyJobs(self, jobs):
485 2971c913 Iustin Pop
    jobs_state = []
486 2971c913 Iustin Pop
    for ops in jobs:
487 2971c913 Iustin Pop
      jobs_state.append([op.__getstate__() for op in ops])
488 734a2a7c René Nussbaumer
    return self.CallMethod(REQ_SUBMIT_MANY_JOBS, (jobs_state, ))
489 2971c913 Iustin Pop
490 0bbe448c Michael Hanselmann
  def CancelJob(self, job_id):
491 a629ecb9 Iustin Pop
    return self.CallMethod(REQ_CANCEL_JOB, (job_id, ))
492 0bbe448c Michael Hanselmann
493 0bbe448c Michael Hanselmann
  def ArchiveJob(self, job_id):
494 a629ecb9 Iustin Pop
    return self.CallMethod(REQ_ARCHIVE_JOB, (job_id, ))
495 0bbe448c Michael Hanselmann
496 f63ffb37 Michael Hanselmann
  def ChangeJobPriority(self, job_id, priority):
497 f63ffb37 Michael Hanselmann
    return self.CallMethod(REQ_CHANGE_JOB_PRIORITY, (job_id, priority))
498 f63ffb37 Michael Hanselmann
499 07cd723a Iustin Pop
  def AutoArchiveJobs(self, age):
500 f8ad5591 Michael Hanselmann
    timeout = (DEF_RWTO - 1) / 2
501 83c046a2 Iustin Pop
    return self.CallMethod(REQ_AUTO_ARCHIVE_JOBS, (age, timeout))
502 07cd723a Iustin Pop
503 f4484122 Michael Hanselmann
  def WaitForJobChangeOnce(self, job_id, fields,
504 793a8f7c Michael Hanselmann
                           prev_job_info, prev_log_serial,
505 793a8f7c Michael Hanselmann
                           timeout=WFJC_TIMEOUT):
506 793a8f7c Michael Hanselmann
    """Waits for changes on a job.
507 793a8f7c Michael Hanselmann

508 793a8f7c Michael Hanselmann
    @param job_id: Job ID
509 793a8f7c Michael Hanselmann
    @type fields: list
510 793a8f7c Michael Hanselmann
    @param fields: List of field names to be observed
511 793a8f7c Michael Hanselmann
    @type prev_job_info: None or list
512 793a8f7c Michael Hanselmann
    @param prev_job_info: Previously received job information
513 793a8f7c Michael Hanselmann
    @type prev_log_serial: None or int/long
514 793a8f7c Michael Hanselmann
    @param prev_log_serial: Highest log serial number previously received
515 793a8f7c Michael Hanselmann
    @type timeout: int/float
516 793a8f7c Michael Hanselmann
    @param timeout: Timeout in seconds (values larger than L{WFJC_TIMEOUT} will
517 793a8f7c Michael Hanselmann
                    be capped to that value)
518 793a8f7c Michael Hanselmann

519 793a8f7c Michael Hanselmann
    """
520 793a8f7c Michael Hanselmann
    assert timeout >= 0, "Timeout can not be negative"
521 f4484122 Michael Hanselmann
    return self.CallMethod(REQ_WAIT_FOR_JOB_CHANGE,
522 f4484122 Michael Hanselmann
                           (job_id, fields, prev_job_info,
523 793a8f7c Michael Hanselmann
                            prev_log_serial,
524 793a8f7c Michael Hanselmann
                            min(WFJC_TIMEOUT, timeout)))
525 f4484122 Michael Hanselmann
526 f4484122 Michael Hanselmann
  def WaitForJobChange(self, job_id, fields, prev_job_info, prev_log_serial):
527 5c735209 Iustin Pop
    while True:
528 f4484122 Michael Hanselmann
      result = self.WaitForJobChangeOnce(job_id, fields,
529 f4484122 Michael Hanselmann
                                         prev_job_info, prev_log_serial)
530 5c735209 Iustin Pop
      if result != constants.JOB_NOTCHANGED:
531 5c735209 Iustin Pop
        break
532 5c735209 Iustin Pop
    return result
533 dfe57c22 Michael Hanselmann
534 2e5c33db Iustin Pop
  def Query(self, what, fields, qfilter):
535 28b71a76 Michael Hanselmann
    """Query for resources/items.
536 28b71a76 Michael Hanselmann

537 abd66bf8 Michael Hanselmann
    @param what: One of L{constants.QR_VIA_LUXI}
538 28b71a76 Michael Hanselmann
    @type fields: List of strings
539 28b71a76 Michael Hanselmann
    @param fields: List of requested fields
540 2e5c33db Iustin Pop
    @type qfilter: None or list
541 2e5c33db Iustin Pop
    @param qfilter: Query filter
542 28b71a76 Michael Hanselmann
    @rtype: L{objects.QueryResponse}
543 28b71a76 Michael Hanselmann

544 28b71a76 Michael Hanselmann
    """
545 a629ecb9 Iustin Pop
    result = self.CallMethod(REQ_QUERY, (what, fields, qfilter))
546 28b71a76 Michael Hanselmann
    return objects.QueryResponse.FromDict(result)
547 28b71a76 Michael Hanselmann
548 28b71a76 Michael Hanselmann
  def QueryFields(self, what, fields):
549 28b71a76 Michael Hanselmann
    """Query for available fields.
550 28b71a76 Michael Hanselmann

551 abd66bf8 Michael Hanselmann
    @param what: One of L{constants.QR_VIA_LUXI}
552 28b71a76 Michael Hanselmann
    @type fields: None or list of strings
553 28b71a76 Michael Hanselmann
    @param fields: List of requested fields
554 28b71a76 Michael Hanselmann
    @rtype: L{objects.QueryFieldsResponse}
555 28b71a76 Michael Hanselmann

556 28b71a76 Michael Hanselmann
    """
557 a629ecb9 Iustin Pop
    result = self.CallMethod(REQ_QUERY_FIELDS, (what, fields))
558 28b71a76 Michael Hanselmann
    return objects.QueryFieldsResponse.FromDict(result)
559 28b71a76 Michael Hanselmann
560 0bbe448c Michael Hanselmann
  def QueryJobs(self, job_ids, fields):
561 0bbe448c Michael Hanselmann
    return self.CallMethod(REQ_QUERY_JOBS, (job_ids, fields))
562 3d8548c4 Michael Hanselmann
563 ec79568d Iustin Pop
  def QueryInstances(self, names, fields, use_locking):
564 ec79568d Iustin Pop
    return self.CallMethod(REQ_QUERY_INSTANCES, (names, fields, use_locking))
565 ee6c7b94 Michael Hanselmann
566 ec79568d Iustin Pop
  def QueryNodes(self, names, fields, use_locking):
567 ec79568d Iustin Pop
    return self.CallMethod(REQ_QUERY_NODES, (names, fields, use_locking))
568 02f7fe54 Michael Hanselmann
569 a79ef2a5 Adeodato Simo
  def QueryGroups(self, names, fields, use_locking):
570 a79ef2a5 Adeodato Simo
    return self.CallMethod(REQ_QUERY_GROUPS, (names, fields, use_locking))
571 a79ef2a5 Adeodato Simo
572 306bed0e Apollon Oikonomopoulos
  def QueryNetworks(self, names, fields, use_locking):
573 306bed0e Apollon Oikonomopoulos
    return self.CallMethod(REQ_QUERY_NETWORKS, (names, fields, use_locking))
574 306bed0e Apollon Oikonomopoulos
575 ec79568d Iustin Pop
  def QueryExports(self, nodes, use_locking):
576 ec79568d Iustin Pop
    return self.CallMethod(REQ_QUERY_EXPORTS, (nodes, use_locking))
577 32f93223 Michael Hanselmann
578 66baeccc Iustin Pop
  def QueryClusterInfo(self):
579 66baeccc Iustin Pop
    return self.CallMethod(REQ_QUERY_CLUSTER_INFO, ())
580 66baeccc Iustin Pop
581 ae5849b5 Michael Hanselmann
  def QueryConfigValues(self, fields):
582 a629ecb9 Iustin Pop
    return self.CallMethod(REQ_QUERY_CONFIG_VALUES, (fields, ))
583 ae5849b5 Michael Hanselmann
584 7699c3af Iustin Pop
  def QueryTags(self, kind, name):
585 7699c3af Iustin Pop
    return self.CallMethod(REQ_QUERY_TAGS, (kind, name))