Statistics
| Branch: | Tag: | Revision:

root / lib / rapi / client.py @ cfc03c54

History | View | Annotate | Download (27.8 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 95ab4de9 David Knowles
import httplib
25 9279e986 Michael Hanselmann
import urllib2
26 9279e986 Michael Hanselmann
import logging
27 95ab4de9 David Knowles
import simplejson
28 95ab4de9 David Knowles
import socket
29 95ab4de9 David Knowles
import urllib
30 9279e986 Michael Hanselmann
import OpenSSL
31 9279e986 Michael Hanselmann
import distutils.version
32 95ab4de9 David Knowles
33 95ab4de9 David Knowles
34 9279e986 Michael Hanselmann
GANETI_RAPI_PORT = 5080
35 9279e986 Michael Hanselmann
36 95ab4de9 David Knowles
HTTP_DELETE = "DELETE"
37 95ab4de9 David Knowles
HTTP_GET = "GET"
38 95ab4de9 David Knowles
HTTP_PUT = "PUT"
39 95ab4de9 David Knowles
HTTP_POST = "POST"
40 9279e986 Michael Hanselmann
HTTP_OK = 200
41 9279e986 Michael Hanselmann
HTTP_APP_JSON = "application/json"
42 9279e986 Michael Hanselmann
43 95ab4de9 David Knowles
REPLACE_DISK_PRI = "replace_on_primary"
44 95ab4de9 David Knowles
REPLACE_DISK_SECONDARY = "replace_on_secondary"
45 95ab4de9 David Knowles
REPLACE_DISK_CHG = "replace_new_secondary"
46 95ab4de9 David Knowles
REPLACE_DISK_AUTO = "replace_auto"
47 95ab4de9 David Knowles
VALID_REPLACEMENT_MODES = frozenset([
48 9279e986 Michael Hanselmann
  REPLACE_DISK_PRI,
49 9279e986 Michael Hanselmann
  REPLACE_DISK_SECONDARY,
50 9279e986 Michael Hanselmann
  REPLACE_DISK_CHG,
51 9279e986 Michael Hanselmann
  REPLACE_DISK_AUTO,
52 9279e986 Michael Hanselmann
  ])
53 95ab4de9 David Knowles
VALID_NODE_ROLES = frozenset([
54 9279e986 Michael Hanselmann
  "drained", "master", "master-candidate", "offline", "regular",
55 9279e986 Michael Hanselmann
  ])
56 95ab4de9 David Knowles
VALID_STORAGE_TYPES = frozenset(["file", "lvm-pv", "lvm-vg"])
57 95ab4de9 David Knowles
58 95ab4de9 David Knowles
59 95ab4de9 David Knowles
class Error(Exception):
60 95ab4de9 David Knowles
  """Base error class for this module.
61 95ab4de9 David Knowles

62 95ab4de9 David Knowles
  """
63 95ab4de9 David Knowles
  pass
64 95ab4de9 David Knowles
65 95ab4de9 David Knowles
66 95ab4de9 David Knowles
class CertificateError(Error):
67 95ab4de9 David Knowles
  """Raised when a problem is found with the SSL certificate.
68 95ab4de9 David Knowles

69 95ab4de9 David Knowles
  """
70 95ab4de9 David Knowles
  pass
71 95ab4de9 David Knowles
72 95ab4de9 David Knowles
73 95ab4de9 David Knowles
class GanetiApiError(Error):
74 95ab4de9 David Knowles
  """Generic error raised from Ganeti API.
75 95ab4de9 David Knowles

76 95ab4de9 David Knowles
  """
77 95ab4de9 David Knowles
  pass
78 95ab4de9 David Knowles
79 95ab4de9 David Knowles
80 95ab4de9 David Knowles
class InvalidReplacementMode(Error):
81 95ab4de9 David Knowles
  """Raised when an invalid disk replacement mode is attempted.
82 95ab4de9 David Knowles

83 95ab4de9 David Knowles
  """
84 95ab4de9 David Knowles
  pass
85 95ab4de9 David Knowles
86 95ab4de9 David Knowles
87 95ab4de9 David Knowles
class InvalidStorageType(Error):
88 95ab4de9 David Knowles
  """Raised when an invalid storage type is used.
89 95ab4de9 David Knowles

90 95ab4de9 David Knowles
  """
91 95ab4de9 David Knowles
  pass
92 95ab4de9 David Knowles
93 95ab4de9 David Knowles
94 95ab4de9 David Knowles
class InvalidNodeRole(Error):
95 95ab4de9 David Knowles
  """Raised when an invalid node role is used.
96 95ab4de9 David Knowles

97 95ab4de9 David Knowles
  """
98 95ab4de9 David Knowles
  pass
99 95ab4de9 David Knowles
100 95ab4de9 David Knowles
101 9279e986 Michael Hanselmann
def FormatX509Name(x509_name):
102 9279e986 Michael Hanselmann
  """Formats an X509 name.
103 9279e986 Michael Hanselmann

104 9279e986 Michael Hanselmann
  @type x509_name: OpenSSL.crypto.X509Name
105 9279e986 Michael Hanselmann

106 9279e986 Michael Hanselmann
  """
107 9279e986 Michael Hanselmann
  try:
108 9279e986 Michael Hanselmann
    # Only supported in pyOpenSSL 0.7 and above
109 9279e986 Michael Hanselmann
    get_components_fn = x509_name.get_components
110 9279e986 Michael Hanselmann
  except AttributeError:
111 9279e986 Michael Hanselmann
    return repr(x509_name)
112 9279e986 Michael Hanselmann
  else:
113 9279e986 Michael Hanselmann
    return "".join("/%s=%s" % (name, value)
114 9279e986 Michael Hanselmann
                   for name, value in get_components_fn())
115 9279e986 Michael Hanselmann
116 9279e986 Michael Hanselmann
117 9279e986 Michael Hanselmann
class CertAuthorityVerify:
118 9279e986 Michael Hanselmann
  """Certificate verificator for SSL context.
119 9279e986 Michael Hanselmann

120 9279e986 Michael Hanselmann
  Configures SSL context to verify server's certificate.
121 9279e986 Michael Hanselmann

122 9279e986 Michael Hanselmann
  """
123 9279e986 Michael Hanselmann
  _CAPATH_MINVERSION = "0.9"
124 9279e986 Michael Hanselmann
  _DEFVFYPATHS_MINVERSION = "0.9"
125 9279e986 Michael Hanselmann
126 9279e986 Michael Hanselmann
  _PYOPENSSL_VERSION = OpenSSL.__version__
127 9279e986 Michael Hanselmann
  _PARSED_PYOPENSSL_VERSION = distutils.version.LooseVersion(_PYOPENSSL_VERSION)
128 9279e986 Michael Hanselmann
129 9279e986 Michael Hanselmann
  _SUPPORT_CAPATH = (_PARSED_PYOPENSSL_VERSION >= _CAPATH_MINVERSION)
130 9279e986 Michael Hanselmann
  _SUPPORT_DEFVFYPATHS = (_PARSED_PYOPENSSL_VERSION >= _DEFVFYPATHS_MINVERSION)
131 9279e986 Michael Hanselmann
132 9279e986 Michael Hanselmann
  def __init__(self, cafile=None, capath=None, use_default_verify_paths=False):
133 9279e986 Michael Hanselmann
    """Initializes this class.
134 9279e986 Michael Hanselmann

135 9279e986 Michael Hanselmann
    @type cafile: string
136 9279e986 Michael Hanselmann
    @param cafile: In which file we can find the certificates
137 9279e986 Michael Hanselmann
    @type capath: string
138 9279e986 Michael Hanselmann
    @param capath: In which directory we can find the certificates
139 9279e986 Michael Hanselmann
    @type use_default_verify_paths: bool
140 9279e986 Michael Hanselmann
    @param use_default_verify_paths: Whether the platform provided CA
141 9279e986 Michael Hanselmann
                                     certificates are to be used for
142 9279e986 Michael Hanselmann
                                     verification purposes
143 9279e986 Michael Hanselmann

144 9279e986 Michael Hanselmann
    """
145 9279e986 Michael Hanselmann
    self._cafile = cafile
146 9279e986 Michael Hanselmann
    self._capath = capath
147 9279e986 Michael Hanselmann
    self._use_default_verify_paths = use_default_verify_paths
148 9279e986 Michael Hanselmann
149 9279e986 Michael Hanselmann
    if self._capath is not None and not self._SUPPORT_CAPATH:
150 9279e986 Michael Hanselmann
      raise Error(("PyOpenSSL %s has no support for a CA directory,"
151 9279e986 Michael Hanselmann
                   " version %s or above is required") %
152 9279e986 Michael Hanselmann
                  (self._PYOPENSSL_VERSION, self._CAPATH_MINVERSION))
153 9279e986 Michael Hanselmann
154 9279e986 Michael Hanselmann
    if self._use_default_verify_paths and not self._SUPPORT_DEFVFYPATHS:
155 9279e986 Michael Hanselmann
      raise Error(("PyOpenSSL %s has no support for using default verification"
156 9279e986 Michael Hanselmann
                   " paths, version %s or above is required") %
157 9279e986 Michael Hanselmann
                  (self._PYOPENSSL_VERSION, self._DEFVFYPATHS_MINVERSION))
158 9279e986 Michael Hanselmann
159 9279e986 Michael Hanselmann
  @staticmethod
160 9279e986 Michael Hanselmann
  def _VerifySslCertCb(logger, _, cert, errnum, errdepth, ok):
161 9279e986 Michael Hanselmann
    """Callback for SSL certificate verification.
162 9279e986 Michael Hanselmann

163 9279e986 Michael Hanselmann
    @param logger: Logging object
164 9279e986 Michael Hanselmann

165 9279e986 Michael Hanselmann
    """
166 9279e986 Michael Hanselmann
    if ok:
167 9279e986 Michael Hanselmann
      log_fn = logger.debug
168 9279e986 Michael Hanselmann
    else:
169 9279e986 Michael Hanselmann
      log_fn = logger.error
170 9279e986 Michael Hanselmann
171 9279e986 Michael Hanselmann
    log_fn("Verifying SSL certificate at depth %s, subject '%s', issuer '%s'",
172 9279e986 Michael Hanselmann
           errdepth, FormatX509Name(cert.get_subject()),
173 9279e986 Michael Hanselmann
           FormatX509Name(cert.get_issuer()))
174 9279e986 Michael Hanselmann
175 9279e986 Michael Hanselmann
    if not ok:
176 9279e986 Michael Hanselmann
      try:
177 9279e986 Michael Hanselmann
        # Only supported in pyOpenSSL 0.7 and above
178 9279e986 Michael Hanselmann
        # pylint: disable-msg=E1101
179 9279e986 Michael Hanselmann
        fn = OpenSSL.crypto.X509_verify_cert_error_string
180 9279e986 Michael Hanselmann
      except AttributeError:
181 9279e986 Michael Hanselmann
        errmsg = ""
182 9279e986 Michael Hanselmann
      else:
183 9279e986 Michael Hanselmann
        errmsg = ":%s" % fn(errnum)
184 9279e986 Michael Hanselmann
185 9279e986 Michael Hanselmann
      logger.error("verify error:num=%s%s", errnum, errmsg)
186 9279e986 Michael Hanselmann
187 9279e986 Michael Hanselmann
    return ok
188 9279e986 Michael Hanselmann
189 9279e986 Michael Hanselmann
  def __call__(self, ctx, logger):
190 9279e986 Michael Hanselmann
    """Configures an SSL context to verify certificates.
191 9279e986 Michael Hanselmann

192 9279e986 Michael Hanselmann
    @type ctx: OpenSSL.SSL.Context
193 9279e986 Michael Hanselmann
    @param ctx: SSL context
194 9279e986 Michael Hanselmann

195 9279e986 Michael Hanselmann
    """
196 9279e986 Michael Hanselmann
    if self._use_default_verify_paths:
197 9279e986 Michael Hanselmann
      ctx.set_default_verify_paths()
198 9279e986 Michael Hanselmann
199 9279e986 Michael Hanselmann
    if self._cafile or self._capath:
200 9279e986 Michael Hanselmann
      if self._SUPPORT_CAPATH:
201 9279e986 Michael Hanselmann
        ctx.load_verify_locations(self._cafile, self._capath)
202 9279e986 Michael Hanselmann
      else:
203 9279e986 Michael Hanselmann
        ctx.load_verify_locations(self._cafile)
204 9279e986 Michael Hanselmann
205 9279e986 Michael Hanselmann
    ctx.set_verify(OpenSSL.SSL.VERIFY_PEER,
206 9279e986 Michael Hanselmann
                   lambda conn, cert, errnum, errdepth, ok: \
207 9279e986 Michael Hanselmann
                     self._VerifySslCertCb(logger, conn, cert,
208 9279e986 Michael Hanselmann
                                           errnum, errdepth, ok))
209 9279e986 Michael Hanselmann
210 9279e986 Michael Hanselmann
211 9279e986 Michael Hanselmann
class _HTTPSConnectionOpenSSL(httplib.HTTPSConnection):
212 9279e986 Michael Hanselmann
  """HTTPS Connection handler that verifies the SSL certificate.
213 9279e986 Michael Hanselmann

214 9279e986 Michael Hanselmann
  """
215 9279e986 Michael Hanselmann
  def __init__(self, *args, **kwargs):
216 9279e986 Michael Hanselmann
    """Initializes this class.
217 9279e986 Michael Hanselmann

218 9279e986 Michael Hanselmann
    """
219 9279e986 Michael Hanselmann
    httplib.HTTPSConnection.__init__(self, *args, **kwargs)
220 9279e986 Michael Hanselmann
    self._logger = None
221 9279e986 Michael Hanselmann
    self._config_ssl_verification = None
222 9279e986 Michael Hanselmann
223 9279e986 Michael Hanselmann
  def Setup(self, logger, config_ssl_verification):
224 9279e986 Michael Hanselmann
    """Sets the SSL verification config function.
225 9279e986 Michael Hanselmann

226 9279e986 Michael Hanselmann
    @param logger: Logging object
227 9279e986 Michael Hanselmann
    @type config_ssl_verification: callable
228 9279e986 Michael Hanselmann

229 9279e986 Michael Hanselmann
    """
230 9279e986 Michael Hanselmann
    assert self._logger is None
231 9279e986 Michael Hanselmann
    assert self._config_ssl_verification is None
232 9279e986 Michael Hanselmann
233 9279e986 Michael Hanselmann
    self._logger = logger
234 9279e986 Michael Hanselmann
    self._config_ssl_verification = config_ssl_verification
235 9279e986 Michael Hanselmann
236 9279e986 Michael Hanselmann
  def connect(self):
237 9279e986 Michael Hanselmann
    """Connect to the server specified when the object was created.
238 9279e986 Michael Hanselmann

239 9279e986 Michael Hanselmann
    This ensures that SSL certificates are verified.
240 9279e986 Michael Hanselmann

241 9279e986 Michael Hanselmann
    """
242 9279e986 Michael Hanselmann
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
243 9279e986 Michael Hanselmann
244 9279e986 Michael Hanselmann
    ctx = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_METHOD)
245 9279e986 Michael Hanselmann
    ctx.set_options(OpenSSL.SSL.OP_NO_SSLv2)
246 9279e986 Michael Hanselmann
247 9279e986 Michael Hanselmann
    if self._config_ssl_verification:
248 9279e986 Michael Hanselmann
      self._config_ssl_verification(ctx, self._logger)
249 9279e986 Michael Hanselmann
250 9279e986 Michael Hanselmann
    ssl = OpenSSL.SSL.Connection(ctx, sock)
251 9279e986 Michael Hanselmann
    ssl.connect((self.host, self.port))
252 9279e986 Michael Hanselmann
253 9279e986 Michael Hanselmann
    self.sock = httplib.FakeSocket(sock, ssl)
254 9279e986 Michael Hanselmann
255 9279e986 Michael Hanselmann
256 9279e986 Michael Hanselmann
class _HTTPSHandler(urllib2.HTTPSHandler):
257 9279e986 Michael Hanselmann
  def __init__(self, logger, config_ssl_verification):
258 9279e986 Michael Hanselmann
    """Initializes this class.
259 9279e986 Michael Hanselmann

260 9279e986 Michael Hanselmann
    @param logger: Logging object
261 9279e986 Michael Hanselmann
    @type config_ssl_verification: callable
262 9279e986 Michael Hanselmann
    @param config_ssl_verification: Function to configure SSL context for
263 9279e986 Michael Hanselmann
                                    certificate verification
264 9279e986 Michael Hanselmann

265 9279e986 Michael Hanselmann
    """
266 9279e986 Michael Hanselmann
    urllib2.HTTPSHandler.__init__(self)
267 9279e986 Michael Hanselmann
    self._logger = logger
268 9279e986 Michael Hanselmann
    self._config_ssl_verification = config_ssl_verification
269 9279e986 Michael Hanselmann
270 9279e986 Michael Hanselmann
  def _CreateHttpsConnection(self, *args, **kwargs):
271 9279e986 Michael Hanselmann
    """Wrapper around L{_HTTPSConnectionOpenSSL} to add SSL verification.
272 9279e986 Michael Hanselmann

273 9279e986 Michael Hanselmann
    This wrapper is necessary provide a compatible API to urllib2.
274 9279e986 Michael Hanselmann

275 9279e986 Michael Hanselmann
    """
276 9279e986 Michael Hanselmann
    conn = _HTTPSConnectionOpenSSL(*args, **kwargs)
277 9279e986 Michael Hanselmann
    conn.Setup(self._logger, self._config_ssl_verification)
278 9279e986 Michael Hanselmann
    return conn
279 9279e986 Michael Hanselmann
280 9279e986 Michael Hanselmann
  def https_open(self, req):
281 9279e986 Michael Hanselmann
    """Creates HTTPS connection.
282 9279e986 Michael Hanselmann

283 9279e986 Michael Hanselmann
    Called by urllib2.
284 9279e986 Michael Hanselmann

285 9279e986 Michael Hanselmann
    """
286 9279e986 Michael Hanselmann
    return self.do_open(self._CreateHttpsConnection, req)
287 9279e986 Michael Hanselmann
288 9279e986 Michael Hanselmann
289 9279e986 Michael Hanselmann
class _RapiRequest(urllib2.Request):
290 9279e986 Michael Hanselmann
  def __init__(self, method, url, headers, data):
291 9279e986 Michael Hanselmann
    """Initializes this class.
292 9279e986 Michael Hanselmann

293 9279e986 Michael Hanselmann
    """
294 9279e986 Michael Hanselmann
    urllib2.Request.__init__(self, url, data=data, headers=headers)
295 9279e986 Michael Hanselmann
    self._method = method
296 9279e986 Michael Hanselmann
297 9279e986 Michael Hanselmann
  def get_method(self):
298 9279e986 Michael Hanselmann
    """Returns the HTTP request method.
299 9279e986 Michael Hanselmann

300 9279e986 Michael Hanselmann
    """
301 9279e986 Michael Hanselmann
    return self._method
302 9279e986 Michael Hanselmann
303 9279e986 Michael Hanselmann
304 95ab4de9 David Knowles
class GanetiRapiClient(object):
305 95ab4de9 David Knowles
  """Ganeti RAPI client.
306 95ab4de9 David Knowles

307 95ab4de9 David Knowles
  """
308 95ab4de9 David Knowles
  USER_AGENT = "Ganeti RAPI Client"
309 d3844674 Michael Hanselmann
  _json_encoder = simplejson.JSONEncoder(sort_keys=True)
310 95ab4de9 David Knowles
311 9279e986 Michael Hanselmann
  def __init__(self, host, port=GANETI_RAPI_PORT,
312 9279e986 Michael Hanselmann
               username=None, password=None,
313 9279e986 Michael Hanselmann
               config_ssl_verification=None, ignore_proxy=False,
314 9279e986 Michael Hanselmann
               logger=logging):
315 95ab4de9 David Knowles
    """Constructor.
316 95ab4de9 David Knowles

317 9279e986 Michael Hanselmann
    @type host: string
318 9279e986 Michael Hanselmann
    @param host: the ganeti cluster master to interact with
319 95ab4de9 David Knowles
    @type port: int
320 9279e986 Michael Hanselmann
    @param port: the port on which the RAPI is running (default is 5080)
321 9279e986 Michael Hanselmann
    @type username: string
322 95ab4de9 David Knowles
    @param username: the username to connect with
323 9279e986 Michael Hanselmann
    @type password: string
324 95ab4de9 David Knowles
    @param password: the password to connect with
325 9279e986 Michael Hanselmann
    @type config_ssl_verification: callable
326 9279e986 Michael Hanselmann
    @param config_ssl_verification: Function to configure SSL context for
327 9279e986 Michael Hanselmann
                                    certificate verification
328 9279e986 Michael Hanselmann
    @type ignore_proxy: bool
329 9279e986 Michael Hanselmann
    @param ignore_proxy: Whether to ignore proxy settings
330 9279e986 Michael Hanselmann
    @param logger: Logging object
331 95ab4de9 David Knowles

332 95ab4de9 David Knowles
    """
333 9279e986 Michael Hanselmann
    self._host = host
334 95ab4de9 David Knowles
    self._port = port
335 9279e986 Michael Hanselmann
    self._logger = logger
336 95ab4de9 David Knowles
337 9279e986 Michael Hanselmann
    self._base_url = "https://%s:%s" % (host, port)
338 f2f88abf David Knowles
339 9279e986 Michael Hanselmann
    handlers = [_HTTPSHandler(self._logger, config_ssl_verification)]
340 9279e986 Michael Hanselmann
341 9279e986 Michael Hanselmann
    if username is not None:
342 9279e986 Michael Hanselmann
      pwmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
343 9279e986 Michael Hanselmann
      pwmgr.add_password(None, self._base_url, username, password)
344 9279e986 Michael Hanselmann
      handlers.append(urllib2.HTTPBasicAuthHandler(pwmgr))
345 9279e986 Michael Hanselmann
    elif password:
346 9279e986 Michael Hanselmann
      raise Error("Specified password without username")
347 9279e986 Michael Hanselmann
348 9279e986 Michael Hanselmann
    if ignore_proxy:
349 9279e986 Michael Hanselmann
      handlers.append(urllib2.ProxyHandler({}))
350 9279e986 Michael Hanselmann
351 9279e986 Michael Hanselmann
    self._http = urllib2.build_opener(*handlers) # pylint: disable-msg=W0142
352 f2f88abf David Knowles
353 9279e986 Michael Hanselmann
    self._headers = {
354 9279e986 Michael Hanselmann
      "Accept": HTTP_APP_JSON,
355 9279e986 Michael Hanselmann
      "Content-type": HTTP_APP_JSON,
356 9279e986 Michael Hanselmann
      "User-Agent": self.USER_AGENT,
357 9279e986 Michael Hanselmann
      }
358 95ab4de9 David Knowles
359 768747ed Michael Hanselmann
  def _SendRequest(self, method, path, query, content):
360 95ab4de9 David Knowles
    """Sends an HTTP request.
361 95ab4de9 David Knowles

362 95ab4de9 David Knowles
    This constructs a full URL, encodes and decodes HTTP bodies, and
363 95ab4de9 David Knowles
    handles invalid responses in a pythonic way.
364 95ab4de9 David Knowles

365 768747ed Michael Hanselmann
    @type method: string
366 95ab4de9 David Knowles
    @param method: HTTP method to use
367 768747ed Michael Hanselmann
    @type path: string
368 95ab4de9 David Knowles
    @param path: HTTP URL path
369 95ab4de9 David Knowles
    @type query: list of two-tuples
370 95ab4de9 David Knowles
    @param query: query arguments to pass to urllib.urlencode
371 95ab4de9 David Knowles
    @type content: str or None
372 95ab4de9 David Knowles
    @param content: HTTP body content
373 95ab4de9 David Knowles

374 95ab4de9 David Knowles
    @rtype: str
375 95ab4de9 David Knowles
    @return: JSON-Decoded response
376 95ab4de9 David Knowles

377 f2f88abf David Knowles
    @raises CertificateError: If an invalid SSL certificate is found
378 95ab4de9 David Knowles
    @raises GanetiApiError: If an invalid response is returned
379 95ab4de9 David Knowles

380 95ab4de9 David Knowles
    """
381 ccd6b542 Michael Hanselmann
    assert path.startswith("/")
382 ccd6b542 Michael Hanselmann
383 95ab4de9 David Knowles
    if content:
384 d3844674 Michael Hanselmann
      encoded_content = self._json_encoder.encode(content)
385 d3844674 Michael Hanselmann
    else:
386 d3844674 Michael Hanselmann
      encoded_content = None
387 95ab4de9 David Knowles
388 ccd6b542 Michael Hanselmann
    # Build URL
389 ccd6b542 Michael Hanselmann
    url = [self._base_url, path]
390 ccd6b542 Michael Hanselmann
    if query:
391 ccd6b542 Michael Hanselmann
      url.append("?")
392 ccd6b542 Michael Hanselmann
      url.append(urllib.urlencode(query))
393 9279e986 Michael Hanselmann
394 ccd6b542 Michael Hanselmann
    req = _RapiRequest(method, "".join(url), self._headers, encoded_content)
395 9279e986 Michael Hanselmann
396 f2f88abf David Knowles
    try:
397 9279e986 Michael Hanselmann
      resp = self._http.open(req)
398 d3844674 Michael Hanselmann
      encoded_response_content = resp.read()
399 9279e986 Michael Hanselmann
    except (OpenSSL.SSL.Error, OpenSSL.crypto.Error), err:
400 9279e986 Michael Hanselmann
      raise CertificateError("SSL issue: %s" % err)
401 95ab4de9 David Knowles
402 d3844674 Michael Hanselmann
    if encoded_response_content:
403 d3844674 Michael Hanselmann
      response_content = simplejson.loads(encoded_response_content)
404 d3844674 Michael Hanselmann
    else:
405 d3844674 Michael Hanselmann
      response_content = None
406 95ab4de9 David Knowles
407 95ab4de9 David Knowles
    # TODO: Are there other status codes that are valid? (redirect?)
408 9279e986 Michael Hanselmann
    if resp.code != HTTP_OK:
409 d3844674 Michael Hanselmann
      if isinstance(response_content, dict):
410 95ab4de9 David Knowles
        msg = ("%s %s: %s" %
411 d3844674 Michael Hanselmann
               (response_content["code"],
412 d3844674 Michael Hanselmann
                response_content["message"],
413 d3844674 Michael Hanselmann
                response_content["explain"]))
414 95ab4de9 David Knowles
      else:
415 d3844674 Michael Hanselmann
        msg = str(response_content)
416 d3844674 Michael Hanselmann
417 95ab4de9 David Knowles
      raise GanetiApiError(msg)
418 95ab4de9 David Knowles
419 d3844674 Michael Hanselmann
    return response_content
420 95ab4de9 David Knowles
421 cfc03c54 Michael Hanselmann
  @staticmethod
422 cfc03c54 Michael Hanselmann
  def _CheckStorageType(storage_type):
423 cfc03c54 Michael Hanselmann
    """Checks a storage type for validity.
424 cfc03c54 Michael Hanselmann

425 cfc03c54 Michael Hanselmann
    """
426 cfc03c54 Michael Hanselmann
    if storage_type not in VALID_STORAGE_TYPES:
427 cfc03c54 Michael Hanselmann
      raise InvalidStorageType("%s is an invalid storage type" % storage_type)
428 cfc03c54 Michael Hanselmann
429 95ab4de9 David Knowles
  def GetVersion(self):
430 cab667cc David Knowles
    """Gets the Remote API version running on the cluster.
431 95ab4de9 David Knowles

432 95ab4de9 David Knowles
    @rtype: int
433 f2f88abf David Knowles
    @return: Ganeti Remote API version
434 95ab4de9 David Knowles

435 95ab4de9 David Knowles
    """
436 768747ed Michael Hanselmann
    return self._SendRequest(HTTP_GET, "/version", None, None)
437 95ab4de9 David Knowles
438 95ab4de9 David Knowles
  def GetOperatingSystems(self):
439 95ab4de9 David Knowles
    """Gets the Operating Systems running in the Ganeti cluster.
440 95ab4de9 David Knowles

441 95ab4de9 David Knowles
    @rtype: list of str
442 95ab4de9 David Knowles
    @return: operating systems
443 95ab4de9 David Knowles

444 95ab4de9 David Knowles
    """
445 768747ed Michael Hanselmann
    return self._SendRequest(HTTP_GET, "/2/os", None, None)
446 95ab4de9 David Knowles
447 95ab4de9 David Knowles
  def GetInfo(self):
448 95ab4de9 David Knowles
    """Gets info about the cluster.
449 95ab4de9 David Knowles

450 95ab4de9 David Knowles
    @rtype: dict
451 95ab4de9 David Knowles
    @return: information about the cluster
452 95ab4de9 David Knowles

453 95ab4de9 David Knowles
    """
454 768747ed Michael Hanselmann
    return self._SendRequest(HTTP_GET, "/2/info", None, None)
455 95ab4de9 David Knowles
456 95ab4de9 David Knowles
  def GetClusterTags(self):
457 95ab4de9 David Knowles
    """Gets the cluster tags.
458 95ab4de9 David Knowles

459 95ab4de9 David Knowles
    @rtype: list of str
460 95ab4de9 David Knowles
    @return: cluster tags
461 95ab4de9 David Knowles

462 95ab4de9 David Knowles
    """
463 768747ed Michael Hanselmann
    return self._SendRequest(HTTP_GET, "/2/tags", None, None)
464 95ab4de9 David Knowles
465 95ab4de9 David Knowles
  def AddClusterTags(self, tags, dry_run=False):
466 95ab4de9 David Knowles
    """Adds tags to the cluster.
467 95ab4de9 David Knowles

468 95ab4de9 David Knowles
    @type tags: list of str
469 95ab4de9 David Knowles
    @param tags: tags to add to the cluster
470 95ab4de9 David Knowles
    @type dry_run: bool
471 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
472 95ab4de9 David Knowles

473 95ab4de9 David Knowles
    @rtype: int
474 95ab4de9 David Knowles
    @return: job id
475 95ab4de9 David Knowles

476 95ab4de9 David Knowles
    """
477 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
478 95ab4de9 David Knowles
    if dry_run:
479 95ab4de9 David Knowles
      query.append(("dry-run", 1))
480 95ab4de9 David Knowles
481 768747ed Michael Hanselmann
    return self._SendRequest(HTTP_PUT, "/2/tags", query, None)
482 95ab4de9 David Knowles
483 95ab4de9 David Knowles
  def DeleteClusterTags(self, tags, dry_run=False):
484 95ab4de9 David Knowles
    """Deletes tags from the cluster.
485 95ab4de9 David Knowles

486 95ab4de9 David Knowles
    @type tags: list of str
487 95ab4de9 David Knowles
    @param tags: tags to delete
488 95ab4de9 David Knowles
    @type dry_run: bool
489 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
490 95ab4de9 David Knowles

491 95ab4de9 David Knowles
    """
492 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
493 95ab4de9 David Knowles
    if dry_run:
494 95ab4de9 David Knowles
      query.append(("dry-run", 1))
495 95ab4de9 David Knowles
496 768747ed Michael Hanselmann
    return self._SendRequest(HTTP_DELETE, "/2/tags", query, None)
497 95ab4de9 David Knowles
498 95ab4de9 David Knowles
  def GetInstances(self, bulk=False):
499 95ab4de9 David Knowles
    """Gets information about instances on the cluster.
500 95ab4de9 David Knowles

501 95ab4de9 David Knowles
    @type bulk: bool
502 95ab4de9 David Knowles
    @param bulk: whether to return all information about all instances
503 95ab4de9 David Knowles

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

507 95ab4de9 David Knowles
    """
508 95ab4de9 David Knowles
    query = []
509 95ab4de9 David Knowles
    if bulk:
510 95ab4de9 David Knowles
      query.append(("bulk", 1))
511 95ab4de9 David Knowles
512 768747ed Michael Hanselmann
    instances = self._SendRequest(HTTP_GET, "/2/instances", query, None)
513 95ab4de9 David Knowles
    if bulk:
514 95ab4de9 David Knowles
      return instances
515 95ab4de9 David Knowles
    else:
516 95ab4de9 David Knowles
      return [i["id"] for i in instances]
517 95ab4de9 David Knowles
518 95ab4de9 David Knowles
  def GetInstanceInfo(self, instance):
519 95ab4de9 David Knowles
    """Gets information about an instance.
520 95ab4de9 David Knowles

521 95ab4de9 David Knowles
    @type instance: str
522 95ab4de9 David Knowles
    @param instance: instance whose info to return
523 95ab4de9 David Knowles

524 95ab4de9 David Knowles
    @rtype: dict
525 95ab4de9 David Knowles
    @return: info about the instance
526 95ab4de9 David Knowles

527 95ab4de9 David Knowles
    """
528 768747ed Michael Hanselmann
    return self._SendRequest(HTTP_GET, "/2/instances/%s" % instance, None, None)
529 95ab4de9 David Knowles
530 95ab4de9 David Knowles
  def CreateInstance(self, dry_run=False):
531 95ab4de9 David Knowles
    """Creates a new instance.
532 95ab4de9 David Knowles

533 95ab4de9 David Knowles
    @type dry_run: bool
534 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
535 95ab4de9 David Knowles

536 95ab4de9 David Knowles
    @rtype: int
537 95ab4de9 David Knowles
    @return: job id
538 95ab4de9 David Knowles

539 95ab4de9 David Knowles
    """
540 95ab4de9 David Knowles
    # TODO: Pass arguments needed to actually create an instance.
541 95ab4de9 David Knowles
    query = []
542 95ab4de9 David Knowles
    if dry_run:
543 95ab4de9 David Knowles
      query.append(("dry-run", 1))
544 95ab4de9 David Knowles
545 768747ed Michael Hanselmann
    return self._SendRequest(HTTP_POST, "/2/instances", query, None)
546 95ab4de9 David Knowles
547 95ab4de9 David Knowles
  def DeleteInstance(self, instance, dry_run=False):
548 95ab4de9 David Knowles
    """Deletes an instance.
549 95ab4de9 David Knowles

550 95ab4de9 David Knowles
    @type instance: str
551 95ab4de9 David Knowles
    @param instance: the instance to delete
552 95ab4de9 David Knowles

553 cab667cc David Knowles
    @rtype: int
554 cab667cc David Knowles
    @return: job id
555 cab667cc David Knowles

556 95ab4de9 David Knowles
    """
557 95ab4de9 David Knowles
    query = []
558 95ab4de9 David Knowles
    if dry_run:
559 95ab4de9 David Knowles
      query.append(("dry-run", 1))
560 95ab4de9 David Knowles
561 768747ed Michael Hanselmann
    return self._SendRequest(HTTP_DELETE, "/2/instances/%s" % instance,
562 768747ed Michael Hanselmann
                             query, None)
563 95ab4de9 David Knowles
564 95ab4de9 David Knowles
  def GetInstanceTags(self, instance):
565 95ab4de9 David Knowles
    """Gets tags for an instance.
566 95ab4de9 David Knowles

567 95ab4de9 David Knowles
    @type instance: str
568 95ab4de9 David Knowles
    @param instance: instance whose tags to return
569 95ab4de9 David Knowles

570 95ab4de9 David Knowles
    @rtype: list of str
571 95ab4de9 David Knowles
    @return: tags for the instance
572 95ab4de9 David Knowles

573 95ab4de9 David Knowles
    """
574 768747ed Michael Hanselmann
    return self._SendRequest(HTTP_GET, "/2/instances/%s/tags" % instance,
575 768747ed Michael Hanselmann
                             None, None)
576 95ab4de9 David Knowles
577 95ab4de9 David Knowles
  def AddInstanceTags(self, instance, tags, dry_run=False):
578 95ab4de9 David Knowles
    """Adds tags to an instance.
579 95ab4de9 David Knowles

580 95ab4de9 David Knowles
    @type instance: str
581 95ab4de9 David Knowles
    @param instance: instance to add tags to
582 95ab4de9 David Knowles
    @type tags: list of str
583 95ab4de9 David Knowles
    @param tags: tags to add to the instance
584 95ab4de9 David Knowles
    @type dry_run: bool
585 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
586 95ab4de9 David Knowles

587 95ab4de9 David Knowles
    @rtype: int
588 95ab4de9 David Knowles
    @return: job id
589 95ab4de9 David Knowles

590 95ab4de9 David Knowles
    """
591 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
592 95ab4de9 David Knowles
    if dry_run:
593 95ab4de9 David Knowles
      query.append(("dry-run", 1))
594 95ab4de9 David Knowles
595 768747ed Michael Hanselmann
    return self._SendRequest(HTTP_PUT, "/2/instances/%s/tags" % instance,
596 768747ed Michael Hanselmann
                             query, None)
597 95ab4de9 David Knowles
598 95ab4de9 David Knowles
  def DeleteInstanceTags(self, instance, tags, dry_run=False):
599 95ab4de9 David Knowles
    """Deletes tags from an instance.
600 95ab4de9 David Knowles

601 95ab4de9 David Knowles
    @type instance: str
602 95ab4de9 David Knowles
    @param instance: instance to delete tags from
603 95ab4de9 David Knowles
    @type tags: list of str
604 95ab4de9 David Knowles
    @param tags: tags to delete
605 95ab4de9 David Knowles
    @type dry_run: bool
606 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
607 95ab4de9 David Knowles

608 95ab4de9 David Knowles
    """
609 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
610 95ab4de9 David Knowles
    if dry_run:
611 95ab4de9 David Knowles
      query.append(("dry-run", 1))
612 95ab4de9 David Knowles
613 cd577680 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE, "/2/instances/%s/tags" % instance,
614 768747ed Michael Hanselmann
                             query, None)
615 95ab4de9 David Knowles
616 95ab4de9 David Knowles
  def RebootInstance(self, instance, reboot_type=None, ignore_secondaries=None,
617 95ab4de9 David Knowles
                     dry_run=False):
618 95ab4de9 David Knowles
    """Reboots an instance.
619 95ab4de9 David Knowles

620 95ab4de9 David Knowles
    @type instance: str
621 95ab4de9 David Knowles
    @param instance: instance to rebot
622 95ab4de9 David Knowles
    @type reboot_type: str
623 95ab4de9 David Knowles
    @param reboot_type: one of: hard, soft, full
624 95ab4de9 David Knowles
    @type ignore_secondaries: bool
625 95ab4de9 David Knowles
    @param ignore_secondaries: if True, ignores errors for the secondary node
626 95ab4de9 David Knowles
        while re-assembling disks (in hard-reboot mode only)
627 95ab4de9 David Knowles
    @type dry_run: bool
628 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
629 95ab4de9 David Knowles

630 95ab4de9 David Knowles
    """
631 95ab4de9 David Knowles
    query = []
632 95ab4de9 David Knowles
    if reboot_type:
633 95ab4de9 David Knowles
      query.append(("type", reboot_type))
634 95ab4de9 David Knowles
    if ignore_secondaries is not None:
635 95ab4de9 David Knowles
      query.append(("ignore_secondaries", ignore_secondaries))
636 95ab4de9 David Knowles
    if dry_run:
637 95ab4de9 David Knowles
      query.append(("dry-run", 1))
638 95ab4de9 David Knowles
639 cd577680 Michael Hanselmann
    return self._SendRequest(HTTP_POST, "/2/instances/%s/reboot" % instance,
640 768747ed Michael Hanselmann
                             query, None)
641 95ab4de9 David Knowles
642 95ab4de9 David Knowles
  def ShutdownInstance(self, instance, dry_run=False):
643 95ab4de9 David Knowles
    """Shuts down an instance.
644 95ab4de9 David Knowles

645 95ab4de9 David Knowles
    @type instance: str
646 95ab4de9 David Knowles
    @param instance: the instance to shut down
647 95ab4de9 David Knowles
    @type dry_run: bool
648 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
649 95ab4de9 David Knowles

650 95ab4de9 David Knowles
    """
651 95ab4de9 David Knowles
    query = []
652 95ab4de9 David Knowles
    if dry_run:
653 95ab4de9 David Knowles
      query.append(("dry-run", 1))
654 95ab4de9 David Knowles
655 cd577680 Michael Hanselmann
    return self._SendRequest(HTTP_PUT, "/2/instances/%s/shutdown" % instance,
656 768747ed Michael Hanselmann
                             query, None)
657 95ab4de9 David Knowles
658 95ab4de9 David Knowles
  def StartupInstance(self, instance, dry_run=False):
659 95ab4de9 David Knowles
    """Starts up an instance.
660 95ab4de9 David Knowles

661 95ab4de9 David Knowles
    @type instance: str
662 95ab4de9 David Knowles
    @param instance: the instance to start up
663 95ab4de9 David Knowles
    @type dry_run: bool
664 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
665 95ab4de9 David Knowles

666 95ab4de9 David Knowles
    """
667 95ab4de9 David Knowles
    query = []
668 95ab4de9 David Knowles
    if dry_run:
669 95ab4de9 David Knowles
      query.append(("dry-run", 1))
670 95ab4de9 David Knowles
671 cd577680 Michael Hanselmann
    return self._SendRequest(HTTP_PUT, "/2/instances/%s/startup" % instance,
672 768747ed Michael Hanselmann
                             query, None)
673 95ab4de9 David Knowles
674 95ab4de9 David Knowles
  def ReinstallInstance(self, instance, os, no_startup=False):
675 95ab4de9 David Knowles
    """Reinstalls an instance.
676 95ab4de9 David Knowles

677 95ab4de9 David Knowles
    @type instance: str
678 95ab4de9 David Knowles
    @param instance: the instance to reinstall
679 95ab4de9 David Knowles
    @type os: str
680 95ab4de9 David Knowles
    @param os: the os to reinstall
681 95ab4de9 David Knowles
    @type no_startup: bool
682 95ab4de9 David Knowles
    @param no_startup: whether to start the instance automatically
683 95ab4de9 David Knowles

684 95ab4de9 David Knowles
    """
685 95ab4de9 David Knowles
    query = [("os", os)]
686 95ab4de9 David Knowles
    if no_startup:
687 95ab4de9 David Knowles
      query.append(("nostartup", 1))
688 cd577680 Michael Hanselmann
    return self._SendRequest(HTTP_POST, "/2/instances/%s/reinstall" % instance,
689 768747ed Michael Hanselmann
                             query, None)
690 95ab4de9 David Knowles
691 cfc03c54 Michael Hanselmann
  def ReplaceInstanceDisks(self, instance, disks, mode=REPLACE_DISK_AUTO,
692 cfc03c54 Michael Hanselmann
                           remote_node=None, iallocator=None, dry_run=False):
693 95ab4de9 David Knowles
    """Replaces disks on an instance.
694 95ab4de9 David Knowles

695 95ab4de9 David Knowles
    @type instance: str
696 95ab4de9 David Knowles
    @param instance: instance whose disks to replace
697 95ab4de9 David Knowles
    @type disks: list of str
698 95ab4de9 David Knowles
    @param disks: disks to replace
699 95ab4de9 David Knowles
    @type mode: str
700 cfc03c54 Michael Hanselmann
    @param mode: replacement mode to use (defaults to replace_auto)
701 95ab4de9 David Knowles
    @type remote_node: str or None
702 95ab4de9 David Knowles
    @param remote_node: new secondary node to use (for use with
703 cfc03c54 Michael Hanselmann
        replace_new_secondary mode)
704 95ab4de9 David Knowles
    @type iallocator: str or None
705 95ab4de9 David Knowles
    @param iallocator: instance allocator plugin to use (for use with
706 cfc03c54 Michael Hanselmann
                       replace_auto mode)
707 95ab4de9 David Knowles
    @type dry_run: bool
708 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
709 95ab4de9 David Knowles

710 95ab4de9 David Knowles
    @rtype: int
711 95ab4de9 David Knowles
    @return: job id
712 95ab4de9 David Knowles

713 95ab4de9 David Knowles
    @raises InvalidReplacementMode: If an invalid disk replacement mode is given
714 95ab4de9 David Knowles
    @raises GanetiApiError: If no secondary node is given with a non-auto
715 95ab4de9 David Knowles
        replacement mode is requested.
716 95ab4de9 David Knowles

717 95ab4de9 David Knowles
    """
718 95ab4de9 David Knowles
    if mode not in VALID_REPLACEMENT_MODES:
719 cfc03c54 Michael Hanselmann
      raise InvalidReplacementMode("%s is not a valid disk replacement mode" %
720 95ab4de9 David Knowles
                                   mode)
721 95ab4de9 David Knowles
722 cfc03c54 Michael Hanselmann
    query = [
723 cfc03c54 Michael Hanselmann
      ("mode", mode),
724 cfc03c54 Michael Hanselmann
      ("disks", ",".join(disks)),
725 cfc03c54 Michael Hanselmann
      ]
726 95ab4de9 David Knowles
727 cfc03c54 Michael Hanselmann
    if mode == REPLACE_DISK_AUTO:
728 95ab4de9 David Knowles
      query.append(("iallocator", iallocator))
729 cfc03c54 Michael Hanselmann
    elif mode == REPLACE_DISK_SECONDARY:
730 95ab4de9 David Knowles
      if remote_node is None:
731 cfc03c54 Michael Hanselmann
        raise GanetiApiError("Missing secondary node")
732 95ab4de9 David Knowles
      query.append(("remote_node", remote_node))
733 95ab4de9 David Knowles
734 95ab4de9 David Knowles
    if dry_run:
735 95ab4de9 David Knowles
      query.append(("dry-run", 1))
736 95ab4de9 David Knowles
737 95ab4de9 David Knowles
    return self._SendRequest(HTTP_POST,
738 768747ed Michael Hanselmann
                             "/2/instances/%s/replace-disks" % instance,
739 768747ed Michael Hanselmann
                             query, None)
740 95ab4de9 David Knowles
741 95ab4de9 David Knowles
  def GetJobs(self):
742 95ab4de9 David Knowles
    """Gets all jobs for the cluster.
743 95ab4de9 David Knowles

744 95ab4de9 David Knowles
    @rtype: list of int
745 95ab4de9 David Knowles
    @return: job ids for the cluster
746 95ab4de9 David Knowles

747 95ab4de9 David Knowles
    """
748 768747ed Michael Hanselmann
    return [int(j["id"])
749 768747ed Michael Hanselmann
            for j in self._SendRequest(HTTP_GET, "/2/jobs", None, None)]
750 95ab4de9 David Knowles
751 95ab4de9 David Knowles
  def GetJobStatus(self, job_id):
752 95ab4de9 David Knowles
    """Gets the status of a job.
753 95ab4de9 David Knowles

754 95ab4de9 David Knowles
    @type job_id: int
755 95ab4de9 David Knowles
    @param job_id: job id whose status to query
756 95ab4de9 David Knowles

757 95ab4de9 David Knowles
    @rtype: dict
758 95ab4de9 David Knowles
    @return: job status
759 95ab4de9 David Knowles

760 95ab4de9 David Knowles
    """
761 768747ed Michael Hanselmann
    return self._SendRequest(HTTP_GET, "/2/jobs/%d" % job_id, None, None)
762 95ab4de9 David Knowles
763 95ab4de9 David Knowles
  def DeleteJob(self, job_id, dry_run=False):
764 95ab4de9 David Knowles
    """Deletes a job.
765 95ab4de9 David Knowles

766 95ab4de9 David Knowles
    @type job_id: int
767 95ab4de9 David Knowles
    @param job_id: id of the job to delete
768 95ab4de9 David Knowles
    @type dry_run: bool
769 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
770 95ab4de9 David Knowles

771 95ab4de9 David Knowles
    """
772 95ab4de9 David Knowles
    query = []
773 95ab4de9 David Knowles
    if dry_run:
774 95ab4de9 David Knowles
      query.append(("dry-run", 1))
775 95ab4de9 David Knowles
776 768747ed Michael Hanselmann
    return self._SendRequest(HTTP_DELETE, "/2/jobs/%d" % job_id, query, None)
777 95ab4de9 David Knowles
778 95ab4de9 David Knowles
  def GetNodes(self, bulk=False):
779 95ab4de9 David Knowles
    """Gets all nodes in the cluster.
780 95ab4de9 David Knowles

781 95ab4de9 David Knowles
    @type bulk: bool
782 95ab4de9 David Knowles
    @param bulk: whether to return all information about all instances
783 95ab4de9 David Knowles

784 95ab4de9 David Knowles
    @rtype: list of dict or str
785 95ab4de9 David Knowles
    @return: if bulk is true, info about nodes in the cluster,
786 95ab4de9 David Knowles
        else list of nodes in the cluster
787 95ab4de9 David Knowles

788 95ab4de9 David Knowles
    """
789 95ab4de9 David Knowles
    query = []
790 95ab4de9 David Knowles
    if bulk:
791 95ab4de9 David Knowles
      query.append(("bulk", 1))
792 95ab4de9 David Knowles
793 768747ed Michael Hanselmann
    nodes = self._SendRequest(HTTP_GET, "/2/nodes", query, None)
794 95ab4de9 David Knowles
    if bulk:
795 95ab4de9 David Knowles
      return nodes
796 95ab4de9 David Knowles
    else:
797 95ab4de9 David Knowles
      return [n["id"] for n in nodes]
798 95ab4de9 David Knowles
799 95ab4de9 David Knowles
  def GetNodeInfo(self, node):
800 95ab4de9 David Knowles
    """Gets information about a node.
801 95ab4de9 David Knowles

802 95ab4de9 David Knowles
    @type node: str
803 95ab4de9 David Knowles
    @param node: node whose info to return
804 95ab4de9 David Knowles

805 95ab4de9 David Knowles
    @rtype: dict
806 95ab4de9 David Knowles
    @return: info about the node
807 95ab4de9 David Knowles

808 95ab4de9 David Knowles
    """
809 768747ed Michael Hanselmann
    return self._SendRequest(HTTP_GET, "/2/nodes/%s" % node, None, None)
810 95ab4de9 David Knowles
811 95ab4de9 David Knowles
  def EvacuateNode(self, node, iallocator=None, remote_node=None,
812 95ab4de9 David Knowles
                   dry_run=False):
813 95ab4de9 David Knowles
    """Evacuates instances from a Ganeti node.
814 95ab4de9 David Knowles

815 95ab4de9 David Knowles
    @type node: str
816 95ab4de9 David Knowles
    @param node: node to evacuate
817 95ab4de9 David Knowles
    @type iallocator: str or None
818 95ab4de9 David Knowles
    @param iallocator: instance allocator to use
819 95ab4de9 David Knowles
    @type remote_node: str
820 95ab4de9 David Knowles
    @param remote_node: node to evaucate to
821 95ab4de9 David Knowles
    @type dry_run: bool
822 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
823 95ab4de9 David Knowles

824 95ab4de9 David Knowles
    @rtype: int
825 95ab4de9 David Knowles
    @return: job id
826 95ab4de9 David Knowles

827 95ab4de9 David Knowles
    @raises GanetiApiError: if an iallocator and remote_node are both specified
828 95ab4de9 David Knowles

829 95ab4de9 David Knowles
    """
830 95ab4de9 David Knowles
    if iallocator and remote_node:
831 cfc03c54 Michael Hanselmann
      raise GanetiApiError("Only one of iallocator or remote_node can be used")
832 95ab4de9 David Knowles
833 cfc03c54 Michael Hanselmann
    query = []
834 95ab4de9 David Knowles
    if iallocator:
835 95ab4de9 David Knowles
      query.append(("iallocator", iallocator))
836 95ab4de9 David Knowles
    if remote_node:
837 95ab4de9 David Knowles
      query.append(("remote_node", remote_node))
838 95ab4de9 David Knowles
    if dry_run:
839 95ab4de9 David Knowles
      query.append(("dry-run", 1))
840 95ab4de9 David Knowles
841 768747ed Michael Hanselmann
    return self._SendRequest(HTTP_POST, "/2/nodes/%s/evacuate" % node,
842 768747ed Michael Hanselmann
                             query, None)
843 95ab4de9 David Knowles
844 95ab4de9 David Knowles
  def MigrateNode(self, node, live=True, dry_run=False):
845 95ab4de9 David Knowles
    """Migrates all primary instances from a node.
846 95ab4de9 David Knowles

847 95ab4de9 David Knowles
    @type node: str
848 95ab4de9 David Knowles
    @param node: node to migrate
849 95ab4de9 David Knowles
    @type live: bool
850 95ab4de9 David Knowles
    @param live: whether to use live migration
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 95ab4de9 David Knowles
    query = []
859 95ab4de9 David Knowles
    if live:
860 95ab4de9 David Knowles
      query.append(("live", 1))
861 95ab4de9 David Knowles
    if dry_run:
862 95ab4de9 David Knowles
      query.append(("dry-run", 1))
863 95ab4de9 David Knowles
864 768747ed Michael Hanselmann
    return self._SendRequest(HTTP_POST, "/2/nodes/%s/migrate" % node,
865 768747ed Michael Hanselmann
                             query, None)
866 95ab4de9 David Knowles
867 95ab4de9 David Knowles
  def GetNodeRole(self, node):
868 95ab4de9 David Knowles
    """Gets the current role for a node.
869 95ab4de9 David Knowles

870 95ab4de9 David Knowles
    @type node: str
871 95ab4de9 David Knowles
    @param node: node whose role to return
872 95ab4de9 David Knowles

873 95ab4de9 David Knowles
    @rtype: str
874 95ab4de9 David Knowles
    @return: the current role for a node
875 95ab4de9 David Knowles

876 95ab4de9 David Knowles
    """
877 768747ed Michael Hanselmann
    return self._SendRequest(HTTP_GET, "/2/nodes/%s/role" % node, None, None)
878 95ab4de9 David Knowles
879 95ab4de9 David Knowles
  def SetNodeRole(self, node, role, force=False):
880 95ab4de9 David Knowles
    """Sets the role for a node.
881 95ab4de9 David Knowles

882 95ab4de9 David Knowles
    @type node: str
883 95ab4de9 David Knowles
    @param node: the node whose role to set
884 95ab4de9 David Knowles
    @type role: str
885 95ab4de9 David Knowles
    @param role: the role to set for the node
886 95ab4de9 David Knowles
    @type force: bool
887 95ab4de9 David Knowles
    @param force: whether to force the role change
888 95ab4de9 David Knowles

889 95ab4de9 David Knowles
    @rtype: int
890 95ab4de9 David Knowles
    @return: job id
891 95ab4de9 David Knowles

892 95ab4de9 David Knowles
    @raise InvalidNodeRole: If an invalid node role is specified
893 95ab4de9 David Knowles

894 95ab4de9 David Knowles
    """
895 95ab4de9 David Knowles
    if role not in VALID_NODE_ROLES:
896 cfc03c54 Michael Hanselmann
      raise InvalidNodeRole("%s is not a valid node role" % role)
897 95ab4de9 David Knowles
898 95ab4de9 David Knowles
    query = [("force", force)]
899 cfc03c54 Michael Hanselmann
900 768747ed Michael Hanselmann
    return self._SendRequest(HTTP_PUT, "/2/nodes/%s/role" % node,
901 768747ed Michael Hanselmann
                             query, role)
902 95ab4de9 David Knowles
903 95ab4de9 David Knowles
  def GetNodeStorageUnits(self, node, storage_type, output_fields):
904 95ab4de9 David Knowles
    """Gets the storage units for a node.
905 95ab4de9 David Knowles

906 95ab4de9 David Knowles
    @type node: str
907 95ab4de9 David Knowles
    @param node: the node whose storage units to return
908 95ab4de9 David Knowles
    @type storage_type: str
909 95ab4de9 David Knowles
    @param storage_type: storage type whose units to return
910 95ab4de9 David Knowles
    @type output_fields: str
911 95ab4de9 David Knowles
    @param output_fields: storage type fields to return
912 95ab4de9 David Knowles

913 95ab4de9 David Knowles
    @rtype: int
914 95ab4de9 David Knowles
    @return: job id where results can be retrieved
915 95ab4de9 David Knowles

916 95ab4de9 David Knowles
    @raise InvalidStorageType: If an invalid storage type is specified
917 95ab4de9 David Knowles

918 95ab4de9 David Knowles
    """
919 95ab4de9 David Knowles
    # TODO: Add default for storage_type & output_fields
920 cfc03c54 Michael Hanselmann
    self._CheckStorageType(storage_type)
921 cfc03c54 Michael Hanselmann
922 cfc03c54 Michael Hanselmann
    query = [
923 cfc03c54 Michael Hanselmann
      ("storage_type", storage_type),
924 cfc03c54 Michael Hanselmann
      ("output_fields", output_fields),
925 cfc03c54 Michael Hanselmann
      ]
926 95ab4de9 David Knowles
927 768747ed Michael Hanselmann
    return self._SendRequest(HTTP_GET, "/2/nodes/%s/storage" % node,
928 768747ed Michael Hanselmann
                             query, None)
929 95ab4de9 David Knowles
930 95ab4de9 David Knowles
  def ModifyNodeStorageUnits(self, node, storage_type, name, allocatable=True):
931 95ab4de9 David Knowles
    """Modifies parameters of storage units on the node.
932 95ab4de9 David Knowles

933 95ab4de9 David Knowles
    @type node: str
934 95ab4de9 David Knowles
    @param node: node whose storage units to modify
935 95ab4de9 David Knowles
    @type storage_type: str
936 95ab4de9 David Knowles
    @param storage_type: storage type whose units to modify
937 95ab4de9 David Knowles
    @type name: str
938 95ab4de9 David Knowles
    @param name: name of the storage unit
939 95ab4de9 David Knowles
    @type allocatable: bool
940 95ab4de9 David Knowles
    @param allocatable: TODO: Document me
941 95ab4de9 David Knowles

942 95ab4de9 David Knowles
    @rtype: int
943 95ab4de9 David Knowles
    @return: job id
944 95ab4de9 David Knowles

945 95ab4de9 David Knowles
    @raise InvalidStorageType: If an invalid storage type is specified
946 95ab4de9 David Knowles

947 95ab4de9 David Knowles
    """
948 cfc03c54 Michael Hanselmann
    self._CheckStorageType(storage_type)
949 95ab4de9 David Knowles
950 95ab4de9 David Knowles
    query = [
951 cfc03c54 Michael Hanselmann
      ("storage_type", storage_type),
952 cfc03c54 Michael Hanselmann
      ("name", name),
953 cfc03c54 Michael Hanselmann
      ("allocatable", allocatable),
954 cfc03c54 Michael Hanselmann
      ]
955 cfc03c54 Michael Hanselmann
956 a60e3cb0 Michael Hanselmann
    return self._SendRequest(HTTP_PUT, "/2/nodes/%s/storage/modify" % node,
957 768747ed Michael Hanselmann
                             query, None)
958 95ab4de9 David Knowles
959 95ab4de9 David Knowles
  def RepairNodeStorageUnits(self, node, storage_type, name):
960 95ab4de9 David Knowles
    """Repairs a storage unit on the node.
961 95ab4de9 David Knowles

962 95ab4de9 David Knowles
    @type node: str
963 95ab4de9 David Knowles
    @param node: node whose storage units to repair
964 95ab4de9 David Knowles
    @type storage_type: str
965 95ab4de9 David Knowles
    @param storage_type: storage type to repair
966 95ab4de9 David Knowles
    @type name: str
967 95ab4de9 David Knowles
    @param name: name of the storage unit to repair
968 95ab4de9 David Knowles

969 95ab4de9 David Knowles
    @rtype: int
970 95ab4de9 David Knowles
    @return: job id
971 95ab4de9 David Knowles

972 95ab4de9 David Knowles
    @raise InvalidStorageType: If an invalid storage type is specified
973 95ab4de9 David Knowles

974 95ab4de9 David Knowles
    """
975 cfc03c54 Michael Hanselmann
    self._CheckStorageType(storage_type)
976 cfc03c54 Michael Hanselmann
977 cfc03c54 Michael Hanselmann
    query = [
978 cfc03c54 Michael Hanselmann
      ("storage_type", storage_type),
979 cfc03c54 Michael Hanselmann
      ("name", name),
980 cfc03c54 Michael Hanselmann
      ]
981 95ab4de9 David Knowles
982 a60e3cb0 Michael Hanselmann
    return self._SendRequest(HTTP_PUT, "/2/nodes/%s/storage/repair" % node,
983 768747ed Michael Hanselmann
                             query, None)
984 95ab4de9 David Knowles
985 95ab4de9 David Knowles
  def GetNodeTags(self, node):
986 95ab4de9 David Knowles
    """Gets the tags for a node.
987 95ab4de9 David Knowles

988 95ab4de9 David Knowles
    @type node: str
989 95ab4de9 David Knowles
    @param node: node whose tags to return
990 95ab4de9 David Knowles

991 95ab4de9 David Knowles
    @rtype: list of str
992 95ab4de9 David Knowles
    @return: tags for the node
993 95ab4de9 David Knowles

994 95ab4de9 David Knowles
    """
995 768747ed Michael Hanselmann
    return self._SendRequest(HTTP_GET, "/2/nodes/%s/tags" % node, None, None)
996 95ab4de9 David Knowles
997 95ab4de9 David Knowles
  def AddNodeTags(self, node, tags, dry_run=False):
998 95ab4de9 David Knowles
    """Adds tags to a node.
999 95ab4de9 David Knowles

1000 95ab4de9 David Knowles
    @type node: str
1001 95ab4de9 David Knowles
    @param node: node to add tags to
1002 95ab4de9 David Knowles
    @type tags: list of str
1003 95ab4de9 David Knowles
    @param tags: tags to add to the node
1004 95ab4de9 David Knowles
    @type dry_run: bool
1005 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1006 95ab4de9 David Knowles

1007 95ab4de9 David Knowles
    @rtype: int
1008 95ab4de9 David Knowles
    @return: job id
1009 95ab4de9 David Knowles

1010 95ab4de9 David Knowles
    """
1011 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
1012 95ab4de9 David Knowles
    if dry_run:
1013 95ab4de9 David Knowles
      query.append(("dry-run", 1))
1014 95ab4de9 David Knowles
1015 768747ed Michael Hanselmann
    return self._SendRequest(HTTP_PUT, "/2/nodes/%s/tags" % node,
1016 768747ed Michael Hanselmann
                             query, tags)
1017 95ab4de9 David Knowles
1018 95ab4de9 David Knowles
  def DeleteNodeTags(self, node, tags, dry_run=False):
1019 95ab4de9 David Knowles
    """Delete tags from a node.
1020 95ab4de9 David Knowles

1021 95ab4de9 David Knowles
    @type node: str
1022 95ab4de9 David Knowles
    @param node: node to remove tags from
1023 95ab4de9 David Knowles
    @type tags: list of str
1024 95ab4de9 David Knowles
    @param tags: tags to remove from the node
1025 95ab4de9 David Knowles
    @type dry_run: bool
1026 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1027 95ab4de9 David Knowles

1028 95ab4de9 David Knowles
    @rtype: int
1029 95ab4de9 David Knowles
    @return: job id
1030 95ab4de9 David Knowles

1031 95ab4de9 David Knowles
    """
1032 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
1033 95ab4de9 David Knowles
    if dry_run:
1034 95ab4de9 David Knowles
      query.append(("dry-run", 1))
1035 95ab4de9 David Knowles
1036 768747ed Michael Hanselmann
    return self._SendRequest(HTTP_DELETE, "/2/nodes/%s/tags" % node,
1037 768747ed Michael Hanselmann
                             query, None)