Statistics
| Branch: | Tag: | Revision:

root / lib / rapi / client.py @ b939de46

History | View | Annotate | Download (35.2 kB)

1 95ab4de9 David Knowles
#
2 95ab4de9 David Knowles
#
3 95ab4de9 David Knowles
4 95ab4de9 David Knowles
# Copyright (C) 2010 Google Inc.
5 95ab4de9 David Knowles
#
6 95ab4de9 David Knowles
# This program is free software; you can redistribute it and/or modify
7 95ab4de9 David Knowles
# it under the terms of the GNU General Public License as published by
8 95ab4de9 David Knowles
# the Free Software Foundation; either version 2 of the License, or
9 95ab4de9 David Knowles
# (at your option) any later version.
10 95ab4de9 David Knowles
#
11 95ab4de9 David Knowles
# This program is distributed in the hope that it will be useful, but
12 95ab4de9 David Knowles
# WITHOUT ANY WARRANTY; without even the implied warranty of
13 95ab4de9 David Knowles
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 95ab4de9 David Knowles
# General Public License for more details.
15 95ab4de9 David Knowles
#
16 95ab4de9 David Knowles
# You should have received a copy of the GNU General Public License
17 95ab4de9 David Knowles
# along with this program; if not, write to the Free Software
18 95ab4de9 David Knowles
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 95ab4de9 David Knowles
# 02110-1301, USA.
20 95ab4de9 David Knowles
21 95ab4de9 David Knowles
22 95ab4de9 David Knowles
"""Ganeti RAPI client."""
23 95ab4de9 David Knowles
24 5ef5cfea Michael Hanselmann
# No Ganeti-specific modules should be imported. The RAPI client is supposed to
25 5ef5cfea Michael Hanselmann
# be standalone.
26 5ef5cfea Michael Hanselmann
27 beba56ae Michael Hanselmann
import sys
28 95ab4de9 David Knowles
import httplib
29 9279e986 Michael Hanselmann
import urllib2
30 9279e986 Michael Hanselmann
import logging
31 95ab4de9 David Knowles
import simplejson
32 95ab4de9 David Knowles
import socket
33 95ab4de9 David Knowles
import urllib
34 9279e986 Michael Hanselmann
import OpenSSL
35 9279e986 Michael Hanselmann
import distutils.version
36 95ab4de9 David Knowles
37 95ab4de9 David Knowles
38 9279e986 Michael Hanselmann
GANETI_RAPI_PORT = 5080
39 a198b2d9 Michael Hanselmann
GANETI_RAPI_VERSION = 2
40 9279e986 Michael Hanselmann
41 95ab4de9 David Knowles
HTTP_DELETE = "DELETE"
42 95ab4de9 David Knowles
HTTP_GET = "GET"
43 95ab4de9 David Knowles
HTTP_PUT = "PUT"
44 95ab4de9 David Knowles
HTTP_POST = "POST"
45 9279e986 Michael Hanselmann
HTTP_OK = 200
46 7eac4a4d Michael Hanselmann
HTTP_NOT_FOUND = 404
47 9279e986 Michael Hanselmann
HTTP_APP_JSON = "application/json"
48 9279e986 Michael Hanselmann
49 95ab4de9 David Knowles
REPLACE_DISK_PRI = "replace_on_primary"
50 95ab4de9 David Knowles
REPLACE_DISK_SECONDARY = "replace_on_secondary"
51 95ab4de9 David Knowles
REPLACE_DISK_CHG = "replace_new_secondary"
52 95ab4de9 David Knowles
REPLACE_DISK_AUTO = "replace_auto"
53 1068639f Michael Hanselmann
54 1068639f Michael Hanselmann
NODE_ROLE_DRAINED = "drained"
55 1068639f Michael Hanselmann
NODE_ROLE_MASTER_CANDIATE = "master-candidate"
56 1068639f Michael Hanselmann
NODE_ROLE_MASTER = "master"
57 1068639f Michael Hanselmann
NODE_ROLE_OFFLINE = "offline"
58 1068639f Michael Hanselmann
NODE_ROLE_REGULAR = "regular"
59 95ab4de9 David Knowles
60 8a47b447 Michael Hanselmann
# Internal constants
61 8a47b447 Michael Hanselmann
_REQ_DATA_VERSION_FIELD = "__version__"
62 8a47b447 Michael Hanselmann
_INST_CREATE_REQV1 = "instance-create-reqv1"
63 8a47b447 Michael Hanselmann
64 95ab4de9 David Knowles
65 95ab4de9 David Knowles
class Error(Exception):
66 95ab4de9 David Knowles
  """Base error class for this module.
67 95ab4de9 David Knowles

68 95ab4de9 David Knowles
  """
69 95ab4de9 David Knowles
  pass
70 95ab4de9 David Knowles
71 95ab4de9 David Knowles
72 95ab4de9 David Knowles
class CertificateError(Error):
73 95ab4de9 David Knowles
  """Raised when a problem is found with the SSL certificate.
74 95ab4de9 David Knowles

75 95ab4de9 David Knowles
  """
76 95ab4de9 David Knowles
  pass
77 95ab4de9 David Knowles
78 95ab4de9 David Knowles
79 95ab4de9 David Knowles
class GanetiApiError(Error):
80 95ab4de9 David Knowles
  """Generic error raised from Ganeti API.
81 95ab4de9 David Knowles

82 95ab4de9 David Knowles
  """
83 8a019a03 Michael Hanselmann
  def __init__(self, msg, code=None):
84 8a019a03 Michael Hanselmann
    Error.__init__(self, msg)
85 8a019a03 Michael Hanselmann
    self.code = code
86 95ab4de9 David Knowles
87 95ab4de9 David Knowles
88 9279e986 Michael Hanselmann
def FormatX509Name(x509_name):
89 9279e986 Michael Hanselmann
  """Formats an X509 name.
90 9279e986 Michael Hanselmann

91 9279e986 Michael Hanselmann
  @type x509_name: OpenSSL.crypto.X509Name
92 9279e986 Michael Hanselmann

93 9279e986 Michael Hanselmann
  """
94 9279e986 Michael Hanselmann
  try:
95 9279e986 Michael Hanselmann
    # Only supported in pyOpenSSL 0.7 and above
96 9279e986 Michael Hanselmann
    get_components_fn = x509_name.get_components
97 9279e986 Michael Hanselmann
  except AttributeError:
98 9279e986 Michael Hanselmann
    return repr(x509_name)
99 9279e986 Michael Hanselmann
  else:
100 9279e986 Michael Hanselmann
    return "".join("/%s=%s" % (name, value)
101 9279e986 Michael Hanselmann
                   for name, value in get_components_fn())
102 9279e986 Michael Hanselmann
103 9279e986 Michael Hanselmann
104 9279e986 Michael Hanselmann
class CertAuthorityVerify:
105 9279e986 Michael Hanselmann
  """Certificate verificator for SSL context.
106 9279e986 Michael Hanselmann

107 9279e986 Michael Hanselmann
  Configures SSL context to verify server's certificate.
108 9279e986 Michael Hanselmann

109 9279e986 Michael Hanselmann
  """
110 9279e986 Michael Hanselmann
  _CAPATH_MINVERSION = "0.9"
111 9279e986 Michael Hanselmann
  _DEFVFYPATHS_MINVERSION = "0.9"
112 9279e986 Michael Hanselmann
113 9279e986 Michael Hanselmann
  _PYOPENSSL_VERSION = OpenSSL.__version__
114 9279e986 Michael Hanselmann
  _PARSED_PYOPENSSL_VERSION = distutils.version.LooseVersion(_PYOPENSSL_VERSION)
115 9279e986 Michael Hanselmann
116 9279e986 Michael Hanselmann
  _SUPPORT_CAPATH = (_PARSED_PYOPENSSL_VERSION >= _CAPATH_MINVERSION)
117 9279e986 Michael Hanselmann
  _SUPPORT_DEFVFYPATHS = (_PARSED_PYOPENSSL_VERSION >= _DEFVFYPATHS_MINVERSION)
118 9279e986 Michael Hanselmann
119 9279e986 Michael Hanselmann
  def __init__(self, cafile=None, capath=None, use_default_verify_paths=False):
120 9279e986 Michael Hanselmann
    """Initializes this class.
121 9279e986 Michael Hanselmann

122 9279e986 Michael Hanselmann
    @type cafile: string
123 9279e986 Michael Hanselmann
    @param cafile: In which file we can find the certificates
124 9279e986 Michael Hanselmann
    @type capath: string
125 9279e986 Michael Hanselmann
    @param capath: In which directory we can find the certificates
126 9279e986 Michael Hanselmann
    @type use_default_verify_paths: bool
127 9279e986 Michael Hanselmann
    @param use_default_verify_paths: Whether the platform provided CA
128 9279e986 Michael Hanselmann
                                     certificates are to be used for
129 9279e986 Michael Hanselmann
                                     verification purposes
130 9279e986 Michael Hanselmann

131 9279e986 Michael Hanselmann
    """
132 9279e986 Michael Hanselmann
    self._cafile = cafile
133 9279e986 Michael Hanselmann
    self._capath = capath
134 9279e986 Michael Hanselmann
    self._use_default_verify_paths = use_default_verify_paths
135 9279e986 Michael Hanselmann
136 9279e986 Michael Hanselmann
    if self._capath is not None and not self._SUPPORT_CAPATH:
137 9279e986 Michael Hanselmann
      raise Error(("PyOpenSSL %s has no support for a CA directory,"
138 9279e986 Michael Hanselmann
                   " version %s or above is required") %
139 9279e986 Michael Hanselmann
                  (self._PYOPENSSL_VERSION, self._CAPATH_MINVERSION))
140 9279e986 Michael Hanselmann
141 9279e986 Michael Hanselmann
    if self._use_default_verify_paths and not self._SUPPORT_DEFVFYPATHS:
142 9279e986 Michael Hanselmann
      raise Error(("PyOpenSSL %s has no support for using default verification"
143 9279e986 Michael Hanselmann
                   " paths, version %s or above is required") %
144 9279e986 Michael Hanselmann
                  (self._PYOPENSSL_VERSION, self._DEFVFYPATHS_MINVERSION))
145 9279e986 Michael Hanselmann
146 9279e986 Michael Hanselmann
  @staticmethod
147 9279e986 Michael Hanselmann
  def _VerifySslCertCb(logger, _, cert, errnum, errdepth, ok):
148 9279e986 Michael Hanselmann
    """Callback for SSL certificate verification.
149 9279e986 Michael Hanselmann

150 9279e986 Michael Hanselmann
    @param logger: Logging object
151 9279e986 Michael Hanselmann

152 9279e986 Michael Hanselmann
    """
153 9279e986 Michael Hanselmann
    if ok:
154 9279e986 Michael Hanselmann
      log_fn = logger.debug
155 9279e986 Michael Hanselmann
    else:
156 9279e986 Michael Hanselmann
      log_fn = logger.error
157 9279e986 Michael Hanselmann
158 9279e986 Michael Hanselmann
    log_fn("Verifying SSL certificate at depth %s, subject '%s', issuer '%s'",
159 9279e986 Michael Hanselmann
           errdepth, FormatX509Name(cert.get_subject()),
160 9279e986 Michael Hanselmann
           FormatX509Name(cert.get_issuer()))
161 9279e986 Michael Hanselmann
162 9279e986 Michael Hanselmann
    if not ok:
163 9279e986 Michael Hanselmann
      try:
164 9279e986 Michael Hanselmann
        # Only supported in pyOpenSSL 0.7 and above
165 9279e986 Michael Hanselmann
        # pylint: disable-msg=E1101
166 9279e986 Michael Hanselmann
        fn = OpenSSL.crypto.X509_verify_cert_error_string
167 9279e986 Michael Hanselmann
      except AttributeError:
168 9279e986 Michael Hanselmann
        errmsg = ""
169 9279e986 Michael Hanselmann
      else:
170 9279e986 Michael Hanselmann
        errmsg = ":%s" % fn(errnum)
171 9279e986 Michael Hanselmann
172 9279e986 Michael Hanselmann
      logger.error("verify error:num=%s%s", errnum, errmsg)
173 9279e986 Michael Hanselmann
174 9279e986 Michael Hanselmann
    return ok
175 9279e986 Michael Hanselmann
176 9279e986 Michael Hanselmann
  def __call__(self, ctx, logger):
177 9279e986 Michael Hanselmann
    """Configures an SSL context to verify certificates.
178 9279e986 Michael Hanselmann

179 9279e986 Michael Hanselmann
    @type ctx: OpenSSL.SSL.Context
180 9279e986 Michael Hanselmann
    @param ctx: SSL context
181 9279e986 Michael Hanselmann

182 9279e986 Michael Hanselmann
    """
183 9279e986 Michael Hanselmann
    if self._use_default_verify_paths:
184 9279e986 Michael Hanselmann
      ctx.set_default_verify_paths()
185 9279e986 Michael Hanselmann
186 9279e986 Michael Hanselmann
    if self._cafile or self._capath:
187 9279e986 Michael Hanselmann
      if self._SUPPORT_CAPATH:
188 9279e986 Michael Hanselmann
        ctx.load_verify_locations(self._cafile, self._capath)
189 9279e986 Michael Hanselmann
      else:
190 9279e986 Michael Hanselmann
        ctx.load_verify_locations(self._cafile)
191 9279e986 Michael Hanselmann
192 9279e986 Michael Hanselmann
    ctx.set_verify(OpenSSL.SSL.VERIFY_PEER,
193 9279e986 Michael Hanselmann
                   lambda conn, cert, errnum, errdepth, ok: \
194 9279e986 Michael Hanselmann
                     self._VerifySslCertCb(logger, conn, cert,
195 9279e986 Michael Hanselmann
                                           errnum, errdepth, ok))
196 9279e986 Michael Hanselmann
197 9279e986 Michael Hanselmann
198 9279e986 Michael Hanselmann
class _HTTPSConnectionOpenSSL(httplib.HTTPSConnection):
199 9279e986 Michael Hanselmann
  """HTTPS Connection handler that verifies the SSL certificate.
200 9279e986 Michael Hanselmann

201 9279e986 Michael Hanselmann
  """
202 beba56ae Michael Hanselmann
  # Python before version 2.6 had its own httplib.FakeSocket wrapper for
203 beba56ae Michael Hanselmann
  # sockets
204 beba56ae Michael Hanselmann
  _SUPPORT_FAKESOCKET = (sys.hexversion < 0x2060000)
205 beba56ae Michael Hanselmann
206 9279e986 Michael Hanselmann
  def __init__(self, *args, **kwargs):
207 9279e986 Michael Hanselmann
    """Initializes this class.
208 9279e986 Michael Hanselmann

209 9279e986 Michael Hanselmann
    """
210 9279e986 Michael Hanselmann
    httplib.HTTPSConnection.__init__(self, *args, **kwargs)
211 9279e986 Michael Hanselmann
    self._logger = None
212 9279e986 Michael Hanselmann
    self._config_ssl_verification = None
213 9279e986 Michael Hanselmann
214 9279e986 Michael Hanselmann
  def Setup(self, logger, config_ssl_verification):
215 9279e986 Michael Hanselmann
    """Sets the SSL verification config function.
216 9279e986 Michael Hanselmann

217 9279e986 Michael Hanselmann
    @param logger: Logging object
218 9279e986 Michael Hanselmann
    @type config_ssl_verification: callable
219 9279e986 Michael Hanselmann

220 9279e986 Michael Hanselmann
    """
221 9279e986 Michael Hanselmann
    assert self._logger is None
222 9279e986 Michael Hanselmann
    assert self._config_ssl_verification is None
223 9279e986 Michael Hanselmann
224 9279e986 Michael Hanselmann
    self._logger = logger
225 9279e986 Michael Hanselmann
    self._config_ssl_verification = config_ssl_verification
226 9279e986 Michael Hanselmann
227 9279e986 Michael Hanselmann
  def connect(self):
228 9279e986 Michael Hanselmann
    """Connect to the server specified when the object was created.
229 9279e986 Michael Hanselmann

230 9279e986 Michael Hanselmann
    This ensures that SSL certificates are verified.
231 9279e986 Michael Hanselmann

232 9279e986 Michael Hanselmann
    """
233 9279e986 Michael Hanselmann
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
234 9279e986 Michael Hanselmann
235 9279e986 Michael Hanselmann
    ctx = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_METHOD)
236 9279e986 Michael Hanselmann
    ctx.set_options(OpenSSL.SSL.OP_NO_SSLv2)
237 9279e986 Michael Hanselmann
238 9279e986 Michael Hanselmann
    if self._config_ssl_verification:
239 9279e986 Michael Hanselmann
      self._config_ssl_verification(ctx, self._logger)
240 9279e986 Michael Hanselmann
241 9279e986 Michael Hanselmann
    ssl = OpenSSL.SSL.Connection(ctx, sock)
242 9279e986 Michael Hanselmann
    ssl.connect((self.host, self.port))
243 9279e986 Michael Hanselmann
244 beba56ae Michael Hanselmann
    if self._SUPPORT_FAKESOCKET:
245 beba56ae Michael Hanselmann
      self.sock = httplib.FakeSocket(sock, ssl)
246 beba56ae Michael Hanselmann
    else:
247 beba56ae Michael Hanselmann
      self.sock = _SslSocketWrapper(ssl)
248 beba56ae Michael Hanselmann
249 beba56ae Michael Hanselmann
250 beba56ae Michael Hanselmann
class _SslSocketWrapper(object):
251 beba56ae Michael Hanselmann
  def __init__(self, sock):
252 beba56ae Michael Hanselmann
    """Initializes this class.
253 beba56ae Michael Hanselmann

254 beba56ae Michael Hanselmann
    """
255 beba56ae Michael Hanselmann
    self._sock = sock
256 beba56ae Michael Hanselmann
257 beba56ae Michael Hanselmann
  def __getattr__(self, name):
258 beba56ae Michael Hanselmann
    """Forward everything to underlying socket.
259 beba56ae Michael Hanselmann

260 beba56ae Michael Hanselmann
    """
261 beba56ae Michael Hanselmann
    return getattr(self._sock, name)
262 beba56ae Michael Hanselmann
263 beba56ae Michael Hanselmann
  def makefile(self, mode, bufsize):
264 beba56ae Michael Hanselmann
    """Fake makefile method.
265 beba56ae Michael Hanselmann

266 beba56ae Michael Hanselmann
    makefile() on normal file descriptors uses dup2(2), which doesn't work with
267 beba56ae Michael Hanselmann
    SSL sockets and therefore is not implemented by pyOpenSSL. This fake method
268 beba56ae Michael Hanselmann
    works with the httplib module, but might not work for other modules.
269 beba56ae Michael Hanselmann

270 beba56ae Michael Hanselmann
    """
271 beba56ae Michael Hanselmann
    # pylint: disable-msg=W0212
272 beba56ae Michael Hanselmann
    return socket._fileobject(self._sock, mode, bufsize)
273 9279e986 Michael Hanselmann
274 9279e986 Michael Hanselmann
275 9279e986 Michael Hanselmann
class _HTTPSHandler(urllib2.HTTPSHandler):
276 9279e986 Michael Hanselmann
  def __init__(self, logger, config_ssl_verification):
277 9279e986 Michael Hanselmann
    """Initializes this class.
278 9279e986 Michael Hanselmann

279 9279e986 Michael Hanselmann
    @param logger: Logging object
280 9279e986 Michael Hanselmann
    @type config_ssl_verification: callable
281 9279e986 Michael Hanselmann
    @param config_ssl_verification: Function to configure SSL context for
282 9279e986 Michael Hanselmann
                                    certificate verification
283 9279e986 Michael Hanselmann

284 9279e986 Michael Hanselmann
    """
285 9279e986 Michael Hanselmann
    urllib2.HTTPSHandler.__init__(self)
286 9279e986 Michael Hanselmann
    self._logger = logger
287 9279e986 Michael Hanselmann
    self._config_ssl_verification = config_ssl_verification
288 9279e986 Michael Hanselmann
289 9279e986 Michael Hanselmann
  def _CreateHttpsConnection(self, *args, **kwargs):
290 9279e986 Michael Hanselmann
    """Wrapper around L{_HTTPSConnectionOpenSSL} to add SSL verification.
291 9279e986 Michael Hanselmann

292 9279e986 Michael Hanselmann
    This wrapper is necessary provide a compatible API to urllib2.
293 9279e986 Michael Hanselmann

294 9279e986 Michael Hanselmann
    """
295 9279e986 Michael Hanselmann
    conn = _HTTPSConnectionOpenSSL(*args, **kwargs)
296 9279e986 Michael Hanselmann
    conn.Setup(self._logger, self._config_ssl_verification)
297 9279e986 Michael Hanselmann
    return conn
298 9279e986 Michael Hanselmann
299 9279e986 Michael Hanselmann
  def https_open(self, req):
300 9279e986 Michael Hanselmann
    """Creates HTTPS connection.
301 9279e986 Michael Hanselmann

302 9279e986 Michael Hanselmann
    Called by urllib2.
303 9279e986 Michael Hanselmann

304 9279e986 Michael Hanselmann
    """
305 9279e986 Michael Hanselmann
    return self.do_open(self._CreateHttpsConnection, req)
306 9279e986 Michael Hanselmann
307 9279e986 Michael Hanselmann
308 9279e986 Michael Hanselmann
class _RapiRequest(urllib2.Request):
309 9279e986 Michael Hanselmann
  def __init__(self, method, url, headers, data):
310 9279e986 Michael Hanselmann
    """Initializes this class.
311 9279e986 Michael Hanselmann

312 9279e986 Michael Hanselmann
    """
313 9279e986 Michael Hanselmann
    urllib2.Request.__init__(self, url, data=data, headers=headers)
314 9279e986 Michael Hanselmann
    self._method = method
315 9279e986 Michael Hanselmann
316 9279e986 Michael Hanselmann
  def get_method(self):
317 9279e986 Michael Hanselmann
    """Returns the HTTP request method.
318 9279e986 Michael Hanselmann

319 9279e986 Michael Hanselmann
    """
320 9279e986 Michael Hanselmann
    return self._method
321 9279e986 Michael Hanselmann
322 9279e986 Michael Hanselmann
323 95ab4de9 David Knowles
class GanetiRapiClient(object):
324 95ab4de9 David Knowles
  """Ganeti RAPI client.
325 95ab4de9 David Knowles

326 95ab4de9 David Knowles
  """
327 95ab4de9 David Knowles
  USER_AGENT = "Ganeti RAPI Client"
328 d3844674 Michael Hanselmann
  _json_encoder = simplejson.JSONEncoder(sort_keys=True)
329 95ab4de9 David Knowles
330 9279e986 Michael Hanselmann
  def __init__(self, host, port=GANETI_RAPI_PORT,
331 9279e986 Michael Hanselmann
               username=None, password=None,
332 9279e986 Michael Hanselmann
               config_ssl_verification=None, ignore_proxy=False,
333 9279e986 Michael Hanselmann
               logger=logging):
334 95ab4de9 David Knowles
    """Constructor.
335 95ab4de9 David Knowles

336 9279e986 Michael Hanselmann
    @type host: string
337 9279e986 Michael Hanselmann
    @param host: the ganeti cluster master to interact with
338 95ab4de9 David Knowles
    @type port: int
339 9279e986 Michael Hanselmann
    @param port: the port on which the RAPI is running (default is 5080)
340 9279e986 Michael Hanselmann
    @type username: string
341 95ab4de9 David Knowles
    @param username: the username to connect with
342 9279e986 Michael Hanselmann
    @type password: string
343 95ab4de9 David Knowles
    @param password: the password to connect with
344 9279e986 Michael Hanselmann
    @type config_ssl_verification: callable
345 9279e986 Michael Hanselmann
    @param config_ssl_verification: Function to configure SSL context for
346 9279e986 Michael Hanselmann
                                    certificate verification
347 9279e986 Michael Hanselmann
    @type ignore_proxy: bool
348 9279e986 Michael Hanselmann
    @param ignore_proxy: Whether to ignore proxy settings
349 9279e986 Michael Hanselmann
    @param logger: Logging object
350 95ab4de9 David Knowles

351 95ab4de9 David Knowles
    """
352 9279e986 Michael Hanselmann
    self._host = host
353 95ab4de9 David Knowles
    self._port = port
354 9279e986 Michael Hanselmann
    self._logger = logger
355 95ab4de9 David Knowles
356 9279e986 Michael Hanselmann
    self._base_url = "https://%s:%s" % (host, port)
357 f2f88abf David Knowles
358 9279e986 Michael Hanselmann
    handlers = [_HTTPSHandler(self._logger, config_ssl_verification)]
359 9279e986 Michael Hanselmann
360 9279e986 Michael Hanselmann
    if username is not None:
361 9279e986 Michael Hanselmann
      pwmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
362 9279e986 Michael Hanselmann
      pwmgr.add_password(None, self._base_url, username, password)
363 9279e986 Michael Hanselmann
      handlers.append(urllib2.HTTPBasicAuthHandler(pwmgr))
364 9279e986 Michael Hanselmann
    elif password:
365 9279e986 Michael Hanselmann
      raise Error("Specified password without username")
366 9279e986 Michael Hanselmann
367 9279e986 Michael Hanselmann
    if ignore_proxy:
368 9279e986 Michael Hanselmann
      handlers.append(urllib2.ProxyHandler({}))
369 9279e986 Michael Hanselmann
370 9279e986 Michael Hanselmann
    self._http = urllib2.build_opener(*handlers) # pylint: disable-msg=W0142
371 f2f88abf David Knowles
372 9279e986 Michael Hanselmann
    self._headers = {
373 9279e986 Michael Hanselmann
      "Accept": HTTP_APP_JSON,
374 9279e986 Michael Hanselmann
      "Content-type": HTTP_APP_JSON,
375 9279e986 Michael Hanselmann
      "User-Agent": self.USER_AGENT,
376 9279e986 Michael Hanselmann
      }
377 95ab4de9 David Knowles
378 10f5ab6c Michael Hanselmann
  @staticmethod
379 10f5ab6c Michael Hanselmann
  def _EncodeQuery(query):
380 10f5ab6c Michael Hanselmann
    """Encode query values for RAPI URL.
381 10f5ab6c Michael Hanselmann

382 10f5ab6c Michael Hanselmann
    @type query: list of two-tuples
383 10f5ab6c Michael Hanselmann
    @param query: Query arguments
384 10f5ab6c Michael Hanselmann
    @rtype: list
385 10f5ab6c Michael Hanselmann
    @return: Query list with encoded values
386 10f5ab6c Michael Hanselmann

387 10f5ab6c Michael Hanselmann
    """
388 10f5ab6c Michael Hanselmann
    result = []
389 10f5ab6c Michael Hanselmann
390 10f5ab6c Michael Hanselmann
    for name, value in query:
391 10f5ab6c Michael Hanselmann
      if value is None:
392 10f5ab6c Michael Hanselmann
        result.append((name, ""))
393 10f5ab6c Michael Hanselmann
394 10f5ab6c Michael Hanselmann
      elif isinstance(value, bool):
395 10f5ab6c Michael Hanselmann
        # Boolean values must be encoded as 0 or 1
396 10f5ab6c Michael Hanselmann
        result.append((name, int(value)))
397 10f5ab6c Michael Hanselmann
398 10f5ab6c Michael Hanselmann
      elif isinstance(value, (list, tuple, dict)):
399 10f5ab6c Michael Hanselmann
        raise ValueError("Invalid query data type %r" % type(value).__name__)
400 10f5ab6c Michael Hanselmann
401 10f5ab6c Michael Hanselmann
      else:
402 10f5ab6c Michael Hanselmann
        result.append((name, value))
403 10f5ab6c Michael Hanselmann
404 10f5ab6c Michael Hanselmann
    return result
405 10f5ab6c Michael Hanselmann
406 768747ed Michael Hanselmann
  def _SendRequest(self, method, path, query, content):
407 95ab4de9 David Knowles
    """Sends an HTTP request.
408 95ab4de9 David Knowles

409 95ab4de9 David Knowles
    This constructs a full URL, encodes and decodes HTTP bodies, and
410 95ab4de9 David Knowles
    handles invalid responses in a pythonic way.
411 95ab4de9 David Knowles

412 768747ed Michael Hanselmann
    @type method: string
413 95ab4de9 David Knowles
    @param method: HTTP method to use
414 768747ed Michael Hanselmann
    @type path: string
415 95ab4de9 David Knowles
    @param path: HTTP URL path
416 95ab4de9 David Knowles
    @type query: list of two-tuples
417 95ab4de9 David Knowles
    @param query: query arguments to pass to urllib.urlencode
418 95ab4de9 David Knowles
    @type content: str or None
419 95ab4de9 David Knowles
    @param content: HTTP body content
420 95ab4de9 David Knowles

421 95ab4de9 David Knowles
    @rtype: str
422 95ab4de9 David Knowles
    @return: JSON-Decoded response
423 95ab4de9 David Knowles

424 f2f88abf David Knowles
    @raises CertificateError: If an invalid SSL certificate is found
425 95ab4de9 David Knowles
    @raises GanetiApiError: If an invalid response is returned
426 95ab4de9 David Knowles

427 95ab4de9 David Knowles
    """
428 ccd6b542 Michael Hanselmann
    assert path.startswith("/")
429 ccd6b542 Michael Hanselmann
430 95ab4de9 David Knowles
    if content:
431 d3844674 Michael Hanselmann
      encoded_content = self._json_encoder.encode(content)
432 d3844674 Michael Hanselmann
    else:
433 d3844674 Michael Hanselmann
      encoded_content = None
434 95ab4de9 David Knowles
435 ccd6b542 Michael Hanselmann
    # Build URL
436 f961e2ba Michael Hanselmann
    urlparts = [self._base_url, path]
437 ccd6b542 Michael Hanselmann
    if query:
438 f961e2ba Michael Hanselmann
      urlparts.append("?")
439 f961e2ba Michael Hanselmann
      urlparts.append(urllib.urlencode(self._EncodeQuery(query)))
440 9279e986 Michael Hanselmann
441 f961e2ba Michael Hanselmann
    url = "".join(urlparts)
442 f961e2ba Michael Hanselmann
443 f961e2ba Michael Hanselmann
    self._logger.debug("Sending request %s %s to %s:%s"
444 f961e2ba Michael Hanselmann
                       " (headers=%r, content=%r)",
445 f961e2ba Michael Hanselmann
                       method, url, self._host, self._port, self._headers,
446 f961e2ba Michael Hanselmann
                       encoded_content)
447 f961e2ba Michael Hanselmann
448 f961e2ba Michael Hanselmann
    req = _RapiRequest(method, url, self._headers, encoded_content)
449 9279e986 Michael Hanselmann
450 f2f88abf David Knowles
    try:
451 9279e986 Michael Hanselmann
      resp = self._http.open(req)
452 d3844674 Michael Hanselmann
      encoded_response_content = resp.read()
453 9279e986 Michael Hanselmann
    except (OpenSSL.SSL.Error, OpenSSL.crypto.Error), err:
454 0d9bc5d2 Michael Hanselmann
      raise CertificateError("SSL issue: %s (%r)" % (err, err))
455 5ed59e1e Michael Hanselmann
    except urllib2.HTTPError, err:
456 5ed59e1e Michael Hanselmann
      raise GanetiApiError(str(err), code=err.code)
457 2652b363 Tom Limoncelli
    except urllib2.URLError, err:
458 2652b363 Tom Limoncelli
      raise GanetiApiError(str(err))
459 95ab4de9 David Knowles
460 d3844674 Michael Hanselmann
    if encoded_response_content:
461 d3844674 Michael Hanselmann
      response_content = simplejson.loads(encoded_response_content)
462 d3844674 Michael Hanselmann
    else:
463 d3844674 Michael Hanselmann
      response_content = None
464 95ab4de9 David Knowles
465 95ab4de9 David Knowles
    # TODO: Are there other status codes that are valid? (redirect?)
466 9279e986 Michael Hanselmann
    if resp.code != HTTP_OK:
467 d3844674 Michael Hanselmann
      if isinstance(response_content, dict):
468 95ab4de9 David Knowles
        msg = ("%s %s: %s" %
469 d3844674 Michael Hanselmann
               (response_content["code"],
470 d3844674 Michael Hanselmann
                response_content["message"],
471 d3844674 Michael Hanselmann
                response_content["explain"]))
472 95ab4de9 David Knowles
      else:
473 d3844674 Michael Hanselmann
        msg = str(response_content)
474 d3844674 Michael Hanselmann
475 8a019a03 Michael Hanselmann
      raise GanetiApiError(msg, code=resp.code)
476 95ab4de9 David Knowles
477 d3844674 Michael Hanselmann
    return response_content
478 95ab4de9 David Knowles
479 95ab4de9 David Knowles
  def GetVersion(self):
480 cab667cc David Knowles
    """Gets the Remote API version running on the cluster.
481 95ab4de9 David Knowles

482 95ab4de9 David Knowles
    @rtype: int
483 f2f88abf David Knowles
    @return: Ganeti Remote API version
484 95ab4de9 David Knowles

485 95ab4de9 David Knowles
    """
486 768747ed Michael Hanselmann
    return self._SendRequest(HTTP_GET, "/version", None, None)
487 95ab4de9 David Knowles
488 7eac4a4d Michael Hanselmann
  def GetFeatures(self):
489 7eac4a4d Michael Hanselmann
    """Gets the list of optional features supported by RAPI server.
490 7eac4a4d Michael Hanselmann

491 7eac4a4d Michael Hanselmann
    @rtype: list
492 7eac4a4d Michael Hanselmann
    @return: List of optional features
493 7eac4a4d Michael Hanselmann

494 7eac4a4d Michael Hanselmann
    """
495 7eac4a4d Michael Hanselmann
    try:
496 7eac4a4d Michael Hanselmann
      return self._SendRequest(HTTP_GET, "/%s/features" % GANETI_RAPI_VERSION,
497 7eac4a4d Michael Hanselmann
                               None, None)
498 7eac4a4d Michael Hanselmann
    except GanetiApiError, err:
499 7eac4a4d Michael Hanselmann
      # Older RAPI servers don't support this resource
500 7eac4a4d Michael Hanselmann
      if err.code == HTTP_NOT_FOUND:
501 7eac4a4d Michael Hanselmann
        return []
502 7eac4a4d Michael Hanselmann
503 7eac4a4d Michael Hanselmann
      raise
504 7eac4a4d Michael Hanselmann
505 95ab4de9 David Knowles
  def GetOperatingSystems(self):
506 95ab4de9 David Knowles
    """Gets the Operating Systems running in the Ganeti cluster.
507 95ab4de9 David Knowles

508 95ab4de9 David Knowles
    @rtype: list of str
509 95ab4de9 David Knowles
    @return: operating systems
510 95ab4de9 David Knowles

511 95ab4de9 David Knowles
    """
512 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET, "/%s/os" % GANETI_RAPI_VERSION,
513 a198b2d9 Michael Hanselmann
                             None, None)
514 95ab4de9 David Knowles
515 95ab4de9 David Knowles
  def GetInfo(self):
516 95ab4de9 David Knowles
    """Gets info about the cluster.
517 95ab4de9 David Knowles

518 95ab4de9 David Knowles
    @rtype: dict
519 95ab4de9 David Knowles
    @return: information about the cluster
520 95ab4de9 David Knowles

521 95ab4de9 David Knowles
    """
522 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET, "/%s/info" % GANETI_RAPI_VERSION,
523 a198b2d9 Michael Hanselmann
                             None, None)
524 95ab4de9 David Knowles
525 95ab4de9 David Knowles
  def GetClusterTags(self):
526 95ab4de9 David Knowles
    """Gets the cluster tags.
527 95ab4de9 David Knowles

528 95ab4de9 David Knowles
    @rtype: list of str
529 95ab4de9 David Knowles
    @return: cluster tags
530 95ab4de9 David Knowles

531 95ab4de9 David Knowles
    """
532 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET, "/%s/tags" % GANETI_RAPI_VERSION,
533 a198b2d9 Michael Hanselmann
                             None, None)
534 95ab4de9 David Knowles
535 95ab4de9 David Knowles
  def AddClusterTags(self, tags, dry_run=False):
536 95ab4de9 David Knowles
    """Adds tags to the cluster.
537 95ab4de9 David Knowles

538 95ab4de9 David Knowles
    @type tags: list of str
539 95ab4de9 David Knowles
    @param tags: tags to add to the cluster
540 95ab4de9 David Knowles
    @type dry_run: bool
541 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
542 95ab4de9 David Knowles

543 95ab4de9 David Knowles
    @rtype: int
544 95ab4de9 David Knowles
    @return: job id
545 95ab4de9 David Knowles

546 95ab4de9 David Knowles
    """
547 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
548 95ab4de9 David Knowles
    if dry_run:
549 95ab4de9 David Knowles
      query.append(("dry-run", 1))
550 95ab4de9 David Knowles
551 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT, "/%s/tags" % GANETI_RAPI_VERSION,
552 a198b2d9 Michael Hanselmann
                             query, None)
553 95ab4de9 David Knowles
554 95ab4de9 David Knowles
  def DeleteClusterTags(self, tags, dry_run=False):
555 95ab4de9 David Knowles
    """Deletes tags from the cluster.
556 95ab4de9 David Knowles

557 95ab4de9 David Knowles
    @type tags: list of str
558 95ab4de9 David Knowles
    @param tags: tags to delete
559 95ab4de9 David Knowles
    @type dry_run: bool
560 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
561 95ab4de9 David Knowles

562 95ab4de9 David Knowles
    """
563 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
564 95ab4de9 David Knowles
    if dry_run:
565 95ab4de9 David Knowles
      query.append(("dry-run", 1))
566 95ab4de9 David Knowles
567 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE, "/%s/tags" % GANETI_RAPI_VERSION,
568 a198b2d9 Michael Hanselmann
                             query, None)
569 95ab4de9 David Knowles
570 95ab4de9 David Knowles
  def GetInstances(self, bulk=False):
571 95ab4de9 David Knowles
    """Gets information about instances on the cluster.
572 95ab4de9 David Knowles

573 95ab4de9 David Knowles
    @type bulk: bool
574 95ab4de9 David Knowles
    @param bulk: whether to return all information about all instances
575 95ab4de9 David Knowles

576 95ab4de9 David Knowles
    @rtype: list of dict or list of str
577 95ab4de9 David Knowles
    @return: if bulk is True, info about the instances, else a list of instances
578 95ab4de9 David Knowles

579 95ab4de9 David Knowles
    """
580 95ab4de9 David Knowles
    query = []
581 95ab4de9 David Knowles
    if bulk:
582 95ab4de9 David Knowles
      query.append(("bulk", 1))
583 95ab4de9 David Knowles
584 a198b2d9 Michael Hanselmann
    instances = self._SendRequest(HTTP_GET,
585 a198b2d9 Michael Hanselmann
                                  "/%s/instances" % GANETI_RAPI_VERSION,
586 a198b2d9 Michael Hanselmann
                                  query, None)
587 95ab4de9 David Knowles
    if bulk:
588 95ab4de9 David Knowles
      return instances
589 95ab4de9 David Knowles
    else:
590 95ab4de9 David Knowles
      return [i["id"] for i in instances]
591 95ab4de9 David Knowles
592 591e5103 Michael Hanselmann
  def GetInstance(self, instance):
593 95ab4de9 David Knowles
    """Gets information about an instance.
594 95ab4de9 David Knowles

595 95ab4de9 David Knowles
    @type instance: str
596 95ab4de9 David Knowles
    @param instance: instance whose info to return
597 95ab4de9 David Knowles

598 95ab4de9 David Knowles
    @rtype: dict
599 95ab4de9 David Knowles
    @return: info about the instance
600 95ab4de9 David Knowles

601 95ab4de9 David Knowles
    """
602 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
603 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s" %
604 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, None)
605 95ab4de9 David Knowles
606 591e5103 Michael Hanselmann
  def GetInstanceInfo(self, instance, static=None):
607 591e5103 Michael Hanselmann
    """Gets information about an instance.
608 591e5103 Michael Hanselmann

609 591e5103 Michael Hanselmann
    @type instance: string
610 591e5103 Michael Hanselmann
    @param instance: Instance name
611 591e5103 Michael Hanselmann
    @rtype: string
612 591e5103 Michael Hanselmann
    @return: Job ID
613 591e5103 Michael Hanselmann

614 591e5103 Michael Hanselmann
    """
615 591e5103 Michael Hanselmann
    if static is not None:
616 591e5103 Michael Hanselmann
      query = [("static", static)]
617 591e5103 Michael Hanselmann
    else:
618 591e5103 Michael Hanselmann
      query = None
619 591e5103 Michael Hanselmann
620 591e5103 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
621 591e5103 Michael Hanselmann
                             ("/%s/instances/%s/info" %
622 591e5103 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
623 591e5103 Michael Hanselmann
624 8a47b447 Michael Hanselmann
  def CreateInstance(self, mode, name, disk_template, disks, nics,
625 8a47b447 Michael Hanselmann
                     **kwargs):
626 95ab4de9 David Knowles
    """Creates a new instance.
627 95ab4de9 David Knowles

628 8a47b447 Michael Hanselmann
    More details for parameters can be found in the RAPI documentation.
629 8a47b447 Michael Hanselmann

630 8a47b447 Michael Hanselmann
    @type mode: string
631 8a47b447 Michael Hanselmann
    @param mode: Instance creation mode
632 8a47b447 Michael Hanselmann
    @type name: string
633 8a47b447 Michael Hanselmann
    @param name: Hostname of the instance to create
634 8a47b447 Michael Hanselmann
    @type disk_template: string
635 8a47b447 Michael Hanselmann
    @param disk_template: Disk template for instance (e.g. plain, diskless,
636 8a47b447 Michael Hanselmann
                          file, or drbd)
637 8a47b447 Michael Hanselmann
    @type disks: list of dicts
638 8a47b447 Michael Hanselmann
    @param disks: List of disk definitions
639 8a47b447 Michael Hanselmann
    @type nics: list of dicts
640 8a47b447 Michael Hanselmann
    @param nics: List of NIC definitions
641 95ab4de9 David Knowles
    @type dry_run: bool
642 8a47b447 Michael Hanselmann
    @keyword dry_run: whether to perform a dry run
643 95ab4de9 David Knowles

644 95ab4de9 David Knowles
    @rtype: int
645 95ab4de9 David Knowles
    @return: job id
646 95ab4de9 David Knowles

647 95ab4de9 David Knowles
    """
648 95ab4de9 David Knowles
    query = []
649 8a47b447 Michael Hanselmann
650 8a47b447 Michael Hanselmann
    if kwargs.get("dry_run"):
651 95ab4de9 David Knowles
      query.append(("dry-run", 1))
652 95ab4de9 David Knowles
653 8a47b447 Michael Hanselmann
    if _INST_CREATE_REQV1 in self.GetFeatures():
654 8a47b447 Michael Hanselmann
      # All required fields for request data version 1
655 8a47b447 Michael Hanselmann
      body = {
656 8a47b447 Michael Hanselmann
        _REQ_DATA_VERSION_FIELD: 1,
657 8a47b447 Michael Hanselmann
        "mode": mode,
658 8a47b447 Michael Hanselmann
        "name": name,
659 8a47b447 Michael Hanselmann
        "disk_template": disk_template,
660 8a47b447 Michael Hanselmann
        "disks": disks,
661 8a47b447 Michael Hanselmann
        "nics": nics,
662 8a47b447 Michael Hanselmann
        }
663 8a47b447 Michael Hanselmann
664 8a47b447 Michael Hanselmann
      conflicts = set(kwargs.iterkeys()) & set(body.iterkeys())
665 8a47b447 Michael Hanselmann
      if conflicts:
666 8a47b447 Michael Hanselmann
        raise GanetiApiError("Required fields can not be specified as"
667 8a47b447 Michael Hanselmann
                             " keywords: %s" % ", ".join(conflicts))
668 8a47b447 Michael Hanselmann
669 8a47b447 Michael Hanselmann
      body.update((key, value) for key, value in kwargs.iteritems()
670 8a47b447 Michael Hanselmann
                  if key != "dry_run")
671 8a47b447 Michael Hanselmann
    else:
672 8a47b447 Michael Hanselmann
      # TODO: Implement instance creation request data version 0
673 8a47b447 Michael Hanselmann
      # When implementing version 0, care should be taken to refuse unknown
674 8a47b447 Michael Hanselmann
      # parameters and invalid values. The interface of this function must stay
675 8a47b447 Michael Hanselmann
      # exactly the same for version 0 and 1 (e.g. they aren't allowed to
676 8a47b447 Michael Hanselmann
      # require different data types).
677 8a47b447 Michael Hanselmann
      raise NotImplementedError("Support for instance creation request data"
678 8a47b447 Michael Hanselmann
                                " version 0 is not yet implemented")
679 8a47b447 Michael Hanselmann
680 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_POST, "/%s/instances" % GANETI_RAPI_VERSION,
681 8a47b447 Michael Hanselmann
                             query, body)
682 95ab4de9 David Knowles
683 95ab4de9 David Knowles
  def DeleteInstance(self, instance, dry_run=False):
684 95ab4de9 David Knowles
    """Deletes an instance.
685 95ab4de9 David Knowles

686 95ab4de9 David Knowles
    @type instance: str
687 95ab4de9 David Knowles
    @param instance: the instance to delete
688 95ab4de9 David Knowles

689 cab667cc David Knowles
    @rtype: int
690 cab667cc David Knowles
    @return: job id
691 cab667cc David Knowles

692 95ab4de9 David Knowles
    """
693 95ab4de9 David Knowles
    query = []
694 95ab4de9 David Knowles
    if dry_run:
695 95ab4de9 David Knowles
      query.append(("dry-run", 1))
696 95ab4de9 David Knowles
697 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE,
698 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s" %
699 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
700 95ab4de9 David Knowles
701 95ab4de9 David Knowles
  def GetInstanceTags(self, instance):
702 95ab4de9 David Knowles
    """Gets tags for an instance.
703 95ab4de9 David Knowles

704 95ab4de9 David Knowles
    @type instance: str
705 95ab4de9 David Knowles
    @param instance: instance whose tags to return
706 95ab4de9 David Knowles

707 95ab4de9 David Knowles
    @rtype: list of str
708 95ab4de9 David Knowles
    @return: tags for the instance
709 95ab4de9 David Knowles

710 95ab4de9 David Knowles
    """
711 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
712 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/tags" %
713 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, None)
714 95ab4de9 David Knowles
715 95ab4de9 David Knowles
  def AddInstanceTags(self, instance, tags, dry_run=False):
716 95ab4de9 David Knowles
    """Adds tags to an instance.
717 95ab4de9 David Knowles

718 95ab4de9 David Knowles
    @type instance: str
719 95ab4de9 David Knowles
    @param instance: instance to add tags to
720 95ab4de9 David Knowles
    @type tags: list of str
721 95ab4de9 David Knowles
    @param tags: tags to add to the instance
722 95ab4de9 David Knowles
    @type dry_run: bool
723 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
724 95ab4de9 David Knowles

725 95ab4de9 David Knowles
    @rtype: int
726 95ab4de9 David Knowles
    @return: job id
727 95ab4de9 David Knowles

728 95ab4de9 David Knowles
    """
729 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
730 95ab4de9 David Knowles
    if dry_run:
731 95ab4de9 David Knowles
      query.append(("dry-run", 1))
732 95ab4de9 David Knowles
733 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
734 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/tags" %
735 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
736 95ab4de9 David Knowles
737 95ab4de9 David Knowles
  def DeleteInstanceTags(self, instance, tags, dry_run=False):
738 95ab4de9 David Knowles
    """Deletes tags from an instance.
739 95ab4de9 David Knowles

740 95ab4de9 David Knowles
    @type instance: str
741 95ab4de9 David Knowles
    @param instance: instance to delete tags from
742 95ab4de9 David Knowles
    @type tags: list of str
743 95ab4de9 David Knowles
    @param tags: tags to delete
744 95ab4de9 David Knowles
    @type dry_run: bool
745 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
746 95ab4de9 David Knowles

747 95ab4de9 David Knowles
    """
748 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
749 95ab4de9 David Knowles
    if dry_run:
750 95ab4de9 David Knowles
      query.append(("dry-run", 1))
751 95ab4de9 David Knowles
752 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE,
753 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/tags" %
754 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
755 95ab4de9 David Knowles
756 95ab4de9 David Knowles
  def RebootInstance(self, instance, reboot_type=None, ignore_secondaries=None,
757 95ab4de9 David Knowles
                     dry_run=False):
758 95ab4de9 David Knowles
    """Reboots an instance.
759 95ab4de9 David Knowles

760 95ab4de9 David Knowles
    @type instance: str
761 95ab4de9 David Knowles
    @param instance: instance to rebot
762 95ab4de9 David Knowles
    @type reboot_type: str
763 95ab4de9 David Knowles
    @param reboot_type: one of: hard, soft, full
764 95ab4de9 David Knowles
    @type ignore_secondaries: bool
765 95ab4de9 David Knowles
    @param ignore_secondaries: if True, ignores errors for the secondary node
766 95ab4de9 David Knowles
        while re-assembling disks (in hard-reboot mode only)
767 95ab4de9 David Knowles
    @type dry_run: bool
768 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
769 95ab4de9 David Knowles

770 95ab4de9 David Knowles
    """
771 95ab4de9 David Knowles
    query = []
772 95ab4de9 David Knowles
    if reboot_type:
773 95ab4de9 David Knowles
      query.append(("type", reboot_type))
774 95ab4de9 David Knowles
    if ignore_secondaries is not None:
775 95ab4de9 David Knowles
      query.append(("ignore_secondaries", ignore_secondaries))
776 95ab4de9 David Knowles
    if dry_run:
777 95ab4de9 David Knowles
      query.append(("dry-run", 1))
778 95ab4de9 David Knowles
779 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_POST,
780 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/reboot" %
781 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
782 95ab4de9 David Knowles
783 95ab4de9 David Knowles
  def ShutdownInstance(self, instance, dry_run=False):
784 95ab4de9 David Knowles
    """Shuts down an instance.
785 95ab4de9 David Knowles

786 95ab4de9 David Knowles
    @type instance: str
787 95ab4de9 David Knowles
    @param instance: the instance to shut down
788 95ab4de9 David Knowles
    @type dry_run: bool
789 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
790 95ab4de9 David Knowles

791 95ab4de9 David Knowles
    """
792 95ab4de9 David Knowles
    query = []
793 95ab4de9 David Knowles
    if dry_run:
794 95ab4de9 David Knowles
      query.append(("dry-run", 1))
795 95ab4de9 David Knowles
796 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
797 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/shutdown" %
798 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
799 95ab4de9 David Knowles
800 95ab4de9 David Knowles
  def StartupInstance(self, instance, dry_run=False):
801 95ab4de9 David Knowles
    """Starts up an instance.
802 95ab4de9 David Knowles

803 95ab4de9 David Knowles
    @type instance: str
804 95ab4de9 David Knowles
    @param instance: the instance to start up
805 95ab4de9 David Knowles
    @type dry_run: bool
806 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
807 95ab4de9 David Knowles

808 95ab4de9 David Knowles
    """
809 95ab4de9 David Knowles
    query = []
810 95ab4de9 David Knowles
    if dry_run:
811 95ab4de9 David Knowles
      query.append(("dry-run", 1))
812 95ab4de9 David Knowles
813 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
814 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/startup" %
815 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
816 95ab4de9 David Knowles
817 95ab4de9 David Knowles
  def ReinstallInstance(self, instance, os, no_startup=False):
818 95ab4de9 David Knowles
    """Reinstalls an instance.
819 95ab4de9 David Knowles

820 95ab4de9 David Knowles
    @type instance: str
821 95ab4de9 David Knowles
    @param instance: the instance to reinstall
822 95ab4de9 David Knowles
    @type os: str
823 95ab4de9 David Knowles
    @param os: the os to reinstall
824 95ab4de9 David Knowles
    @type no_startup: bool
825 95ab4de9 David Knowles
    @param no_startup: whether to start the instance automatically
826 95ab4de9 David Knowles

827 95ab4de9 David Knowles
    """
828 95ab4de9 David Knowles
    query = [("os", os)]
829 95ab4de9 David Knowles
    if no_startup:
830 95ab4de9 David Knowles
      query.append(("nostartup", 1))
831 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_POST,
832 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/reinstall" %
833 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
834 95ab4de9 David Knowles
835 bfc2002f Michael Hanselmann
  def ReplaceInstanceDisks(self, instance, disks=None, mode=REPLACE_DISK_AUTO,
836 cfc03c54 Michael Hanselmann
                           remote_node=None, iallocator=None, dry_run=False):
837 95ab4de9 David Knowles
    """Replaces disks on an instance.
838 95ab4de9 David Knowles

839 95ab4de9 David Knowles
    @type instance: str
840 95ab4de9 David Knowles
    @param instance: instance whose disks to replace
841 bfc2002f Michael Hanselmann
    @type disks: list of ints
842 bfc2002f Michael Hanselmann
    @param disks: Indexes of disks to replace
843 95ab4de9 David Knowles
    @type mode: str
844 cfc03c54 Michael Hanselmann
    @param mode: replacement mode to use (defaults to replace_auto)
845 95ab4de9 David Knowles
    @type remote_node: str or None
846 95ab4de9 David Knowles
    @param remote_node: new secondary node to use (for use with
847 cfc03c54 Michael Hanselmann
        replace_new_secondary mode)
848 95ab4de9 David Knowles
    @type iallocator: str or None
849 95ab4de9 David Knowles
    @param iallocator: instance allocator plugin to use (for use with
850 cfc03c54 Michael Hanselmann
                       replace_auto mode)
851 95ab4de9 David Knowles
    @type dry_run: bool
852 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
853 95ab4de9 David Knowles

854 95ab4de9 David Knowles
    @rtype: int
855 95ab4de9 David Knowles
    @return: job id
856 95ab4de9 David Knowles

857 95ab4de9 David Knowles
    """
858 cfc03c54 Michael Hanselmann
    query = [
859 cfc03c54 Michael Hanselmann
      ("mode", mode),
860 cfc03c54 Michael Hanselmann
      ]
861 95ab4de9 David Knowles
862 bfc2002f Michael Hanselmann
    if disks:
863 bfc2002f Michael Hanselmann
      query.append(("disks", ",".join(str(idx) for idx in disks)))
864 bfc2002f Michael Hanselmann
865 bfc2002f Michael Hanselmann
    if remote_node:
866 95ab4de9 David Knowles
      query.append(("remote_node", remote_node))
867 95ab4de9 David Knowles
868 bfc2002f Michael Hanselmann
    if iallocator:
869 bfc2002f Michael Hanselmann
      query.append(("iallocator", iallocator))
870 bfc2002f Michael Hanselmann
871 95ab4de9 David Knowles
    if dry_run:
872 95ab4de9 David Knowles
      query.append(("dry-run", 1))
873 95ab4de9 David Knowles
874 95ab4de9 David Knowles
    return self._SendRequest(HTTP_POST,
875 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/replace-disks" %
876 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
877 95ab4de9 David Knowles
878 ebeb600f Michael Hanselmann
  def PrepareExport(self, instance, mode):
879 ebeb600f Michael Hanselmann
    """Prepares an instance for an export.
880 ebeb600f Michael Hanselmann

881 ebeb600f Michael Hanselmann
    @type instance: string
882 ebeb600f Michael Hanselmann
    @param instance: Instance name
883 ebeb600f Michael Hanselmann
    @type mode: string
884 ebeb600f Michael Hanselmann
    @param mode: Export mode
885 ebeb600f Michael Hanselmann
    @rtype: string
886 ebeb600f Michael Hanselmann
    @return: Job ID
887 ebeb600f Michael Hanselmann

888 ebeb600f Michael Hanselmann
    """
889 ebeb600f Michael Hanselmann
    query = [("mode", mode)]
890 ebeb600f Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
891 ebeb600f Michael Hanselmann
                             ("/%s/instances/%s/prepare-export" %
892 ebeb600f Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
893 ebeb600f Michael Hanselmann
894 ebeb600f Michael Hanselmann
  def ExportInstance(self, instance, mode, destination, shutdown=None,
895 ebeb600f Michael Hanselmann
                     remove_instance=None,
896 ebeb600f Michael Hanselmann
                     x509_key_name=None, destination_x509_ca=None):
897 ebeb600f Michael Hanselmann
    """Exports an instance.
898 ebeb600f Michael Hanselmann

899 ebeb600f Michael Hanselmann
    @type instance: string
900 ebeb600f Michael Hanselmann
    @param instance: Instance name
901 ebeb600f Michael Hanselmann
    @type mode: string
902 ebeb600f Michael Hanselmann
    @param mode: Export mode
903 ebeb600f Michael Hanselmann
    @rtype: string
904 ebeb600f Michael Hanselmann
    @return: Job ID
905 ebeb600f Michael Hanselmann

906 ebeb600f Michael Hanselmann
    """
907 ebeb600f Michael Hanselmann
    body = {
908 ebeb600f Michael Hanselmann
      "destination": destination,
909 ebeb600f Michael Hanselmann
      "mode": mode,
910 ebeb600f Michael Hanselmann
      }
911 ebeb600f Michael Hanselmann
912 ebeb600f Michael Hanselmann
    if shutdown is not None:
913 ebeb600f Michael Hanselmann
      body["shutdown"] = shutdown
914 ebeb600f Michael Hanselmann
915 ebeb600f Michael Hanselmann
    if remove_instance is not None:
916 ebeb600f Michael Hanselmann
      body["remove_instance"] = remove_instance
917 ebeb600f Michael Hanselmann
918 ebeb600f Michael Hanselmann
    if x509_key_name is not None:
919 ebeb600f Michael Hanselmann
      body["x509_key_name"] = x509_key_name
920 ebeb600f Michael Hanselmann
921 ebeb600f Michael Hanselmann
    if destination_x509_ca is not None:
922 ebeb600f Michael Hanselmann
      body["destination_x509_ca"] = destination_x509_ca
923 ebeb600f Michael Hanselmann
924 ebeb600f Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
925 ebeb600f Michael Hanselmann
                             ("/%s/instances/%s/export" %
926 ebeb600f Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, body)
927 ebeb600f Michael Hanselmann
928 95ab4de9 David Knowles
  def GetJobs(self):
929 95ab4de9 David Knowles
    """Gets all jobs for the cluster.
930 95ab4de9 David Knowles

931 95ab4de9 David Knowles
    @rtype: list of int
932 95ab4de9 David Knowles
    @return: job ids for the cluster
933 95ab4de9 David Knowles

934 95ab4de9 David Knowles
    """
935 768747ed Michael Hanselmann
    return [int(j["id"])
936 a198b2d9 Michael Hanselmann
            for j in self._SendRequest(HTTP_GET,
937 a198b2d9 Michael Hanselmann
                                       "/%s/jobs" % GANETI_RAPI_VERSION,
938 a198b2d9 Michael Hanselmann
                                       None, None)]
939 95ab4de9 David Knowles
940 95ab4de9 David Knowles
  def GetJobStatus(self, job_id):
941 95ab4de9 David Knowles
    """Gets the status of a job.
942 95ab4de9 David Knowles

943 95ab4de9 David Knowles
    @type job_id: int
944 95ab4de9 David Knowles
    @param job_id: job id whose status to query
945 95ab4de9 David Knowles

946 95ab4de9 David Knowles
    @rtype: dict
947 95ab4de9 David Knowles
    @return: job status
948 95ab4de9 David Knowles

949 95ab4de9 David Knowles
    """
950 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
951 a198b2d9 Michael Hanselmann
                             "/%s/jobs/%s" % (GANETI_RAPI_VERSION, job_id),
952 a198b2d9 Michael Hanselmann
                             None, None)
953 95ab4de9 David Knowles
954 d9b67f70 Michael Hanselmann
  def WaitForJobChange(self, job_id, fields, prev_job_info, prev_log_serial):
955 d9b67f70 Michael Hanselmann
    """Waits for job changes.
956 d9b67f70 Michael Hanselmann

957 d9b67f70 Michael Hanselmann
    @type job_id: int
958 d9b67f70 Michael Hanselmann
    @param job_id: Job ID for which to wait
959 d9b67f70 Michael Hanselmann

960 d9b67f70 Michael Hanselmann
    """
961 d9b67f70 Michael Hanselmann
    body = {
962 d9b67f70 Michael Hanselmann
      "fields": fields,
963 d9b67f70 Michael Hanselmann
      "previous_job_info": prev_job_info,
964 d9b67f70 Michael Hanselmann
      "previous_log_serial": prev_log_serial,
965 d9b67f70 Michael Hanselmann
      }
966 d9b67f70 Michael Hanselmann
967 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
968 a198b2d9 Michael Hanselmann
                             "/%s/jobs/%s/wait" % (GANETI_RAPI_VERSION, job_id),
969 a198b2d9 Michael Hanselmann
                             None, body)
970 d9b67f70 Michael Hanselmann
971 cf9ada49 Michael Hanselmann
  def CancelJob(self, job_id, dry_run=False):
972 cf9ada49 Michael Hanselmann
    """Cancels a job.
973 95ab4de9 David Knowles

974 95ab4de9 David Knowles
    @type job_id: int
975 95ab4de9 David Knowles
    @param job_id: id of the job to delete
976 95ab4de9 David Knowles
    @type dry_run: bool
977 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
978 95ab4de9 David Knowles

979 95ab4de9 David Knowles
    """
980 95ab4de9 David Knowles
    query = []
981 95ab4de9 David Knowles
    if dry_run:
982 95ab4de9 David Knowles
      query.append(("dry-run", 1))
983 95ab4de9 David Knowles
984 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE,
985 a198b2d9 Michael Hanselmann
                             "/%s/jobs/%s" % (GANETI_RAPI_VERSION, job_id),
986 a198b2d9 Michael Hanselmann
                             query, None)
987 95ab4de9 David Knowles
988 95ab4de9 David Knowles
  def GetNodes(self, bulk=False):
989 95ab4de9 David Knowles
    """Gets all nodes in the cluster.
990 95ab4de9 David Knowles

991 95ab4de9 David Knowles
    @type bulk: bool
992 95ab4de9 David Knowles
    @param bulk: whether to return all information about all instances
993 95ab4de9 David Knowles

994 95ab4de9 David Knowles
    @rtype: list of dict or str
995 95ab4de9 David Knowles
    @return: if bulk is true, info about nodes in the cluster,
996 95ab4de9 David Knowles
        else list of nodes in the cluster
997 95ab4de9 David Knowles

998 95ab4de9 David Knowles
    """
999 95ab4de9 David Knowles
    query = []
1000 95ab4de9 David Knowles
    if bulk:
1001 95ab4de9 David Knowles
      query.append(("bulk", 1))
1002 95ab4de9 David Knowles
1003 a198b2d9 Michael Hanselmann
    nodes = self._SendRequest(HTTP_GET, "/%s/nodes" % GANETI_RAPI_VERSION,
1004 a198b2d9 Michael Hanselmann
                              query, None)
1005 95ab4de9 David Knowles
    if bulk:
1006 95ab4de9 David Knowles
      return nodes
1007 95ab4de9 David Knowles
    else:
1008 95ab4de9 David Knowles
      return [n["id"] for n in nodes]
1009 95ab4de9 David Knowles
1010 591e5103 Michael Hanselmann
  def GetNode(self, node):
1011 95ab4de9 David Knowles
    """Gets information about a node.
1012 95ab4de9 David Knowles

1013 95ab4de9 David Knowles
    @type node: str
1014 95ab4de9 David Knowles
    @param node: node whose info to return
1015 95ab4de9 David Knowles

1016 95ab4de9 David Knowles
    @rtype: dict
1017 95ab4de9 David Knowles
    @return: info about the node
1018 95ab4de9 David Knowles

1019 95ab4de9 David Knowles
    """
1020 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1021 a198b2d9 Michael Hanselmann
                             "/%s/nodes/%s" % (GANETI_RAPI_VERSION, node),
1022 a198b2d9 Michael Hanselmann
                             None, None)
1023 95ab4de9 David Knowles
1024 95ab4de9 David Knowles
  def EvacuateNode(self, node, iallocator=None, remote_node=None,
1025 941b9309 Iustin Pop
                   dry_run=False, early_release=False):
1026 95ab4de9 David Knowles
    """Evacuates instances from a Ganeti node.
1027 95ab4de9 David Knowles

1028 95ab4de9 David Knowles
    @type node: str
1029 95ab4de9 David Knowles
    @param node: node to evacuate
1030 95ab4de9 David Knowles
    @type iallocator: str or None
1031 95ab4de9 David Knowles
    @param iallocator: instance allocator to use
1032 95ab4de9 David Knowles
    @type remote_node: str
1033 95ab4de9 David Knowles
    @param remote_node: node to evaucate to
1034 95ab4de9 David Knowles
    @type dry_run: bool
1035 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1036 941b9309 Iustin Pop
    @type early_release: bool
1037 941b9309 Iustin Pop
    @param early_release: whether to enable parallelization
1038 95ab4de9 David Knowles

1039 941b9309 Iustin Pop
    @rtype: list
1040 941b9309 Iustin Pop
    @return: list of (job ID, instance name, new secondary node); if
1041 941b9309 Iustin Pop
        dry_run was specified, then the actual move jobs were not
1042 941b9309 Iustin Pop
        submitted and the job IDs will be C{None}
1043 95ab4de9 David Knowles

1044 941b9309 Iustin Pop
    @raises GanetiApiError: if an iallocator and remote_node are both
1045 941b9309 Iustin Pop
        specified
1046 95ab4de9 David Knowles

1047 95ab4de9 David Knowles
    """
1048 95ab4de9 David Knowles
    if iallocator and remote_node:
1049 cfc03c54 Michael Hanselmann
      raise GanetiApiError("Only one of iallocator or remote_node can be used")
1050 95ab4de9 David Knowles
1051 cfc03c54 Michael Hanselmann
    query = []
1052 95ab4de9 David Knowles
    if iallocator:
1053 95ab4de9 David Knowles
      query.append(("iallocator", iallocator))
1054 95ab4de9 David Knowles
    if remote_node:
1055 95ab4de9 David Knowles
      query.append(("remote_node", remote_node))
1056 95ab4de9 David Knowles
    if dry_run:
1057 95ab4de9 David Knowles
      query.append(("dry-run", 1))
1058 941b9309 Iustin Pop
    if early_release:
1059 941b9309 Iustin Pop
      query.append(("early_release", 1))
1060 95ab4de9 David Knowles
1061 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_POST,
1062 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/evacuate" %
1063 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1064 95ab4de9 David Knowles
1065 95ab4de9 David Knowles
  def MigrateNode(self, node, live=True, dry_run=False):
1066 95ab4de9 David Knowles
    """Migrates all primary instances from a node.
1067 95ab4de9 David Knowles

1068 95ab4de9 David Knowles
    @type node: str
1069 95ab4de9 David Knowles
    @param node: node to migrate
1070 95ab4de9 David Knowles
    @type live: bool
1071 95ab4de9 David Knowles
    @param live: whether to use live migration
1072 95ab4de9 David Knowles
    @type dry_run: bool
1073 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1074 95ab4de9 David Knowles

1075 95ab4de9 David Knowles
    @rtype: int
1076 95ab4de9 David Knowles
    @return: job id
1077 95ab4de9 David Knowles

1078 95ab4de9 David Knowles
    """
1079 95ab4de9 David Knowles
    query = []
1080 95ab4de9 David Knowles
    if live:
1081 95ab4de9 David Knowles
      query.append(("live", 1))
1082 95ab4de9 David Knowles
    if dry_run:
1083 95ab4de9 David Knowles
      query.append(("dry-run", 1))
1084 95ab4de9 David Knowles
1085 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_POST,
1086 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/migrate" %
1087 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1088 95ab4de9 David Knowles
1089 95ab4de9 David Knowles
  def GetNodeRole(self, node):
1090 95ab4de9 David Knowles
    """Gets the current role for a node.
1091 95ab4de9 David Knowles

1092 95ab4de9 David Knowles
    @type node: str
1093 95ab4de9 David Knowles
    @param node: node whose role to return
1094 95ab4de9 David Knowles

1095 95ab4de9 David Knowles
    @rtype: str
1096 95ab4de9 David Knowles
    @return: the current role for a node
1097 95ab4de9 David Knowles

1098 95ab4de9 David Knowles
    """
1099 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1100 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/role" %
1101 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), None, None)
1102 95ab4de9 David Knowles
1103 95ab4de9 David Knowles
  def SetNodeRole(self, node, role, force=False):
1104 95ab4de9 David Knowles
    """Sets the role for a node.
1105 95ab4de9 David Knowles

1106 95ab4de9 David Knowles
    @type node: str
1107 95ab4de9 David Knowles
    @param node: the node whose role to set
1108 95ab4de9 David Knowles
    @type role: str
1109 95ab4de9 David Knowles
    @param role: the role to set for the node
1110 95ab4de9 David Knowles
    @type force: bool
1111 95ab4de9 David Knowles
    @param force: whether to force the role change
1112 95ab4de9 David Knowles

1113 95ab4de9 David Knowles
    @rtype: int
1114 95ab4de9 David Knowles
    @return: job id
1115 95ab4de9 David Knowles

1116 95ab4de9 David Knowles
    """
1117 1068639f Michael Hanselmann
    query = [
1118 1068639f Michael Hanselmann
      ("force", force),
1119 1068639f Michael Hanselmann
      ]
1120 cfc03c54 Michael Hanselmann
1121 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1122 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/role" %
1123 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, role)
1124 95ab4de9 David Knowles
1125 95ab4de9 David Knowles
  def GetNodeStorageUnits(self, node, storage_type, output_fields):
1126 95ab4de9 David Knowles
    """Gets the storage units for a node.
1127 95ab4de9 David Knowles

1128 95ab4de9 David Knowles
    @type node: str
1129 95ab4de9 David Knowles
    @param node: the node whose storage units to return
1130 95ab4de9 David Knowles
    @type storage_type: str
1131 95ab4de9 David Knowles
    @param storage_type: storage type whose units to return
1132 95ab4de9 David Knowles
    @type output_fields: str
1133 95ab4de9 David Knowles
    @param output_fields: storage type fields to return
1134 95ab4de9 David Knowles

1135 95ab4de9 David Knowles
    @rtype: int
1136 95ab4de9 David Knowles
    @return: job id where results can be retrieved
1137 95ab4de9 David Knowles

1138 95ab4de9 David Knowles
    """
1139 cfc03c54 Michael Hanselmann
    query = [
1140 cfc03c54 Michael Hanselmann
      ("storage_type", storage_type),
1141 cfc03c54 Michael Hanselmann
      ("output_fields", output_fields),
1142 cfc03c54 Michael Hanselmann
      ]
1143 95ab4de9 David Knowles
1144 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1145 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/storage" %
1146 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1147 95ab4de9 David Knowles
1148 fde28316 Michael Hanselmann
  def ModifyNodeStorageUnits(self, node, storage_type, name, allocatable=None):
1149 95ab4de9 David Knowles
    """Modifies parameters of storage units on the node.
1150 95ab4de9 David Knowles

1151 95ab4de9 David Knowles
    @type node: str
1152 95ab4de9 David Knowles
    @param node: node whose storage units to modify
1153 95ab4de9 David Knowles
    @type storage_type: str
1154 95ab4de9 David Knowles
    @param storage_type: storage type whose units to modify
1155 95ab4de9 David Knowles
    @type name: str
1156 95ab4de9 David Knowles
    @param name: name of the storage unit
1157 fde28316 Michael Hanselmann
    @type allocatable: bool or None
1158 fde28316 Michael Hanselmann
    @param allocatable: Whether to set the "allocatable" flag on the storage
1159 fde28316 Michael Hanselmann
                        unit (None=no modification, True=set, False=unset)
1160 95ab4de9 David Knowles

1161 95ab4de9 David Knowles
    @rtype: int
1162 95ab4de9 David Knowles
    @return: job id
1163 95ab4de9 David Knowles

1164 95ab4de9 David Knowles
    """
1165 95ab4de9 David Knowles
    query = [
1166 cfc03c54 Michael Hanselmann
      ("storage_type", storage_type),
1167 cfc03c54 Michael Hanselmann
      ("name", name),
1168 cfc03c54 Michael Hanselmann
      ]
1169 cfc03c54 Michael Hanselmann
1170 fde28316 Michael Hanselmann
    if allocatable is not None:
1171 fde28316 Michael Hanselmann
      query.append(("allocatable", allocatable))
1172 fde28316 Michael Hanselmann
1173 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1174 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/storage/modify" %
1175 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1176 95ab4de9 David Knowles
1177 95ab4de9 David Knowles
  def RepairNodeStorageUnits(self, node, storage_type, name):
1178 95ab4de9 David Knowles
    """Repairs a storage unit on the node.
1179 95ab4de9 David Knowles

1180 95ab4de9 David Knowles
    @type node: str
1181 95ab4de9 David Knowles
    @param node: node whose storage units to repair
1182 95ab4de9 David Knowles
    @type storage_type: str
1183 95ab4de9 David Knowles
    @param storage_type: storage type to repair
1184 95ab4de9 David Knowles
    @type name: str
1185 95ab4de9 David Knowles
    @param name: name of the storage unit to repair
1186 95ab4de9 David Knowles

1187 95ab4de9 David Knowles
    @rtype: int
1188 95ab4de9 David Knowles
    @return: job id
1189 95ab4de9 David Knowles

1190 95ab4de9 David Knowles
    """
1191 cfc03c54 Michael Hanselmann
    query = [
1192 cfc03c54 Michael Hanselmann
      ("storage_type", storage_type),
1193 cfc03c54 Michael Hanselmann
      ("name", name),
1194 cfc03c54 Michael Hanselmann
      ]
1195 95ab4de9 David Knowles
1196 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1197 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/storage/repair" %
1198 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1199 95ab4de9 David Knowles
1200 95ab4de9 David Knowles
  def GetNodeTags(self, node):
1201 95ab4de9 David Knowles
    """Gets the tags for a node.
1202 95ab4de9 David Knowles

1203 95ab4de9 David Knowles
    @type node: str
1204 95ab4de9 David Knowles
    @param node: node whose tags to return
1205 95ab4de9 David Knowles

1206 95ab4de9 David Knowles
    @rtype: list of str
1207 95ab4de9 David Knowles
    @return: tags for the node
1208 95ab4de9 David Knowles

1209 95ab4de9 David Knowles
    """
1210 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1211 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/tags" %
1212 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), None, None)
1213 95ab4de9 David Knowles
1214 95ab4de9 David Knowles
  def AddNodeTags(self, node, tags, dry_run=False):
1215 95ab4de9 David Knowles
    """Adds tags to a node.
1216 95ab4de9 David Knowles

1217 95ab4de9 David Knowles
    @type node: str
1218 95ab4de9 David Knowles
    @param node: node to add tags to
1219 95ab4de9 David Knowles
    @type tags: list of str
1220 95ab4de9 David Knowles
    @param tags: tags to add to the node
1221 95ab4de9 David Knowles
    @type dry_run: bool
1222 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1223 95ab4de9 David Knowles

1224 95ab4de9 David Knowles
    @rtype: int
1225 95ab4de9 David Knowles
    @return: job id
1226 95ab4de9 David Knowles

1227 95ab4de9 David Knowles
    """
1228 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
1229 95ab4de9 David Knowles
    if dry_run:
1230 95ab4de9 David Knowles
      query.append(("dry-run", 1))
1231 95ab4de9 David Knowles
1232 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1233 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/tags" %
1234 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, tags)
1235 95ab4de9 David Knowles
1236 95ab4de9 David Knowles
  def DeleteNodeTags(self, node, tags, dry_run=False):
1237 95ab4de9 David Knowles
    """Delete tags from a node.
1238 95ab4de9 David Knowles

1239 95ab4de9 David Knowles
    @type node: str
1240 95ab4de9 David Knowles
    @param node: node to remove tags from
1241 95ab4de9 David Knowles
    @type tags: list of str
1242 95ab4de9 David Knowles
    @param tags: tags to remove from the node
1243 95ab4de9 David Knowles
    @type dry_run: bool
1244 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1245 95ab4de9 David Knowles

1246 95ab4de9 David Knowles
    @rtype: int
1247 95ab4de9 David Knowles
    @return: job id
1248 95ab4de9 David Knowles

1249 95ab4de9 David Knowles
    """
1250 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
1251 95ab4de9 David Knowles
    if dry_run:
1252 95ab4de9 David Knowles
      query.append(("dry-run", 1))
1253 95ab4de9 David Knowles
1254 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE,
1255 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/tags" %
1256 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)