Statistics
| Branch: | Tag: | Revision:

root / lib / rapi / client.py @ 2652b363

History | View | Annotate | Download (32.6 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 95ab4de9 David Knowles
import httplib
28 9279e986 Michael Hanselmann
import urllib2
29 9279e986 Michael Hanselmann
import logging
30 95ab4de9 David Knowles
import simplejson
31 95ab4de9 David Knowles
import socket
32 95ab4de9 David Knowles
import urllib
33 9279e986 Michael Hanselmann
import OpenSSL
34 9279e986 Michael Hanselmann
import distutils.version
35 95ab4de9 David Knowles
36 95ab4de9 David Knowles
37 9279e986 Michael Hanselmann
GANETI_RAPI_PORT = 5080
38 a198b2d9 Michael Hanselmann
GANETI_RAPI_VERSION = 2
39 9279e986 Michael Hanselmann
40 95ab4de9 David Knowles
HTTP_DELETE = "DELETE"
41 95ab4de9 David Knowles
HTTP_GET = "GET"
42 95ab4de9 David Knowles
HTTP_PUT = "PUT"
43 95ab4de9 David Knowles
HTTP_POST = "POST"
44 9279e986 Michael Hanselmann
HTTP_OK = 200
45 7eac4a4d Michael Hanselmann
HTTP_NOT_FOUND = 404
46 9279e986 Michael Hanselmann
HTTP_APP_JSON = "application/json"
47 9279e986 Michael Hanselmann
48 95ab4de9 David Knowles
REPLACE_DISK_PRI = "replace_on_primary"
49 95ab4de9 David Knowles
REPLACE_DISK_SECONDARY = "replace_on_secondary"
50 95ab4de9 David Knowles
REPLACE_DISK_CHG = "replace_new_secondary"
51 95ab4de9 David Knowles
REPLACE_DISK_AUTO = "replace_auto"
52 1068639f Michael Hanselmann
53 1068639f Michael Hanselmann
NODE_ROLE_DRAINED = "drained"
54 1068639f Michael Hanselmann
NODE_ROLE_MASTER_CANDIATE = "master-candidate"
55 1068639f Michael Hanselmann
NODE_ROLE_MASTER = "master"
56 1068639f Michael Hanselmann
NODE_ROLE_OFFLINE = "offline"
57 1068639f Michael Hanselmann
NODE_ROLE_REGULAR = "regular"
58 95ab4de9 David Knowles
59 8a47b447 Michael Hanselmann
# Internal constants
60 8a47b447 Michael Hanselmann
_REQ_DATA_VERSION_FIELD = "__version__"
61 8a47b447 Michael Hanselmann
_INST_CREATE_REQV1 = "instance-create-reqv1"
62 8a47b447 Michael Hanselmann
63 95ab4de9 David Knowles
64 95ab4de9 David Knowles
class Error(Exception):
65 95ab4de9 David Knowles
  """Base error class for this module.
66 95ab4de9 David Knowles

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

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

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

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

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

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

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

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

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

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

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

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

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

200 9279e986 Michael Hanselmann
  """
201 9279e986 Michael Hanselmann
  def __init__(self, *args, **kwargs):
202 9279e986 Michael Hanselmann
    """Initializes this class.
203 9279e986 Michael Hanselmann

204 9279e986 Michael Hanselmann
    """
205 9279e986 Michael Hanselmann
    httplib.HTTPSConnection.__init__(self, *args, **kwargs)
206 9279e986 Michael Hanselmann
    self._logger = None
207 9279e986 Michael Hanselmann
    self._config_ssl_verification = None
208 9279e986 Michael Hanselmann
209 9279e986 Michael Hanselmann
  def Setup(self, logger, config_ssl_verification):
210 9279e986 Michael Hanselmann
    """Sets the SSL verification config function.
211 9279e986 Michael Hanselmann

212 9279e986 Michael Hanselmann
    @param logger: Logging object
213 9279e986 Michael Hanselmann
    @type config_ssl_verification: callable
214 9279e986 Michael Hanselmann

215 9279e986 Michael Hanselmann
    """
216 9279e986 Michael Hanselmann
    assert self._logger is None
217 9279e986 Michael Hanselmann
    assert self._config_ssl_verification is None
218 9279e986 Michael Hanselmann
219 9279e986 Michael Hanselmann
    self._logger = logger
220 9279e986 Michael Hanselmann
    self._config_ssl_verification = config_ssl_verification
221 9279e986 Michael Hanselmann
222 9279e986 Michael Hanselmann
  def connect(self):
223 9279e986 Michael Hanselmann
    """Connect to the server specified when the object was created.
224 9279e986 Michael Hanselmann

225 9279e986 Michael Hanselmann
    This ensures that SSL certificates are verified.
226 9279e986 Michael Hanselmann

227 9279e986 Michael Hanselmann
    """
228 9279e986 Michael Hanselmann
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
229 9279e986 Michael Hanselmann
230 9279e986 Michael Hanselmann
    ctx = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_METHOD)
231 9279e986 Michael Hanselmann
    ctx.set_options(OpenSSL.SSL.OP_NO_SSLv2)
232 9279e986 Michael Hanselmann
233 9279e986 Michael Hanselmann
    if self._config_ssl_verification:
234 9279e986 Michael Hanselmann
      self._config_ssl_verification(ctx, self._logger)
235 9279e986 Michael Hanselmann
236 9279e986 Michael Hanselmann
    ssl = OpenSSL.SSL.Connection(ctx, sock)
237 9279e986 Michael Hanselmann
    ssl.connect((self.host, self.port))
238 9279e986 Michael Hanselmann
239 9279e986 Michael Hanselmann
    self.sock = httplib.FakeSocket(sock, ssl)
240 9279e986 Michael Hanselmann
241 9279e986 Michael Hanselmann
242 9279e986 Michael Hanselmann
class _HTTPSHandler(urllib2.HTTPSHandler):
243 9279e986 Michael Hanselmann
  def __init__(self, logger, config_ssl_verification):
244 9279e986 Michael Hanselmann
    """Initializes this class.
245 9279e986 Michael Hanselmann

246 9279e986 Michael Hanselmann
    @param logger: Logging object
247 9279e986 Michael Hanselmann
    @type config_ssl_verification: callable
248 9279e986 Michael Hanselmann
    @param config_ssl_verification: Function to configure SSL context for
249 9279e986 Michael Hanselmann
                                    certificate verification
250 9279e986 Michael Hanselmann

251 9279e986 Michael Hanselmann
    """
252 9279e986 Michael Hanselmann
    urllib2.HTTPSHandler.__init__(self)
253 9279e986 Michael Hanselmann
    self._logger = logger
254 9279e986 Michael Hanselmann
    self._config_ssl_verification = config_ssl_verification
255 9279e986 Michael Hanselmann
256 9279e986 Michael Hanselmann
  def _CreateHttpsConnection(self, *args, **kwargs):
257 9279e986 Michael Hanselmann
    """Wrapper around L{_HTTPSConnectionOpenSSL} to add SSL verification.
258 9279e986 Michael Hanselmann

259 9279e986 Michael Hanselmann
    This wrapper is necessary provide a compatible API to urllib2.
260 9279e986 Michael Hanselmann

261 9279e986 Michael Hanselmann
    """
262 9279e986 Michael Hanselmann
    conn = _HTTPSConnectionOpenSSL(*args, **kwargs)
263 9279e986 Michael Hanselmann
    conn.Setup(self._logger, self._config_ssl_verification)
264 9279e986 Michael Hanselmann
    return conn
265 9279e986 Michael Hanselmann
266 9279e986 Michael Hanselmann
  def https_open(self, req):
267 9279e986 Michael Hanselmann
    """Creates HTTPS connection.
268 9279e986 Michael Hanselmann

269 9279e986 Michael Hanselmann
    Called by urllib2.
270 9279e986 Michael Hanselmann

271 9279e986 Michael Hanselmann
    """
272 9279e986 Michael Hanselmann
    return self.do_open(self._CreateHttpsConnection, req)
273 9279e986 Michael Hanselmann
274 9279e986 Michael Hanselmann
275 9279e986 Michael Hanselmann
class _RapiRequest(urllib2.Request):
276 9279e986 Michael Hanselmann
  def __init__(self, method, url, headers, data):
277 9279e986 Michael Hanselmann
    """Initializes this class.
278 9279e986 Michael Hanselmann

279 9279e986 Michael Hanselmann
    """
280 9279e986 Michael Hanselmann
    urllib2.Request.__init__(self, url, data=data, headers=headers)
281 9279e986 Michael Hanselmann
    self._method = method
282 9279e986 Michael Hanselmann
283 9279e986 Michael Hanselmann
  def get_method(self):
284 9279e986 Michael Hanselmann
    """Returns the HTTP request method.
285 9279e986 Michael Hanselmann

286 9279e986 Michael Hanselmann
    """
287 9279e986 Michael Hanselmann
    return self._method
288 9279e986 Michael Hanselmann
289 9279e986 Michael Hanselmann
290 95ab4de9 David Knowles
class GanetiRapiClient(object):
291 95ab4de9 David Knowles
  """Ganeti RAPI client.
292 95ab4de9 David Knowles

293 95ab4de9 David Knowles
  """
294 95ab4de9 David Knowles
  USER_AGENT = "Ganeti RAPI Client"
295 d3844674 Michael Hanselmann
  _json_encoder = simplejson.JSONEncoder(sort_keys=True)
296 95ab4de9 David Knowles
297 9279e986 Michael Hanselmann
  def __init__(self, host, port=GANETI_RAPI_PORT,
298 9279e986 Michael Hanselmann
               username=None, password=None,
299 9279e986 Michael Hanselmann
               config_ssl_verification=None, ignore_proxy=False,
300 9279e986 Michael Hanselmann
               logger=logging):
301 95ab4de9 David Knowles
    """Constructor.
302 95ab4de9 David Knowles

303 9279e986 Michael Hanselmann
    @type host: string
304 9279e986 Michael Hanselmann
    @param host: the ganeti cluster master to interact with
305 95ab4de9 David Knowles
    @type port: int
306 9279e986 Michael Hanselmann
    @param port: the port on which the RAPI is running (default is 5080)
307 9279e986 Michael Hanselmann
    @type username: string
308 95ab4de9 David Knowles
    @param username: the username to connect with
309 9279e986 Michael Hanselmann
    @type password: string
310 95ab4de9 David Knowles
    @param password: the password to connect with
311 9279e986 Michael Hanselmann
    @type config_ssl_verification: callable
312 9279e986 Michael Hanselmann
    @param config_ssl_verification: Function to configure SSL context for
313 9279e986 Michael Hanselmann
                                    certificate verification
314 9279e986 Michael Hanselmann
    @type ignore_proxy: bool
315 9279e986 Michael Hanselmann
    @param ignore_proxy: Whether to ignore proxy settings
316 9279e986 Michael Hanselmann
    @param logger: Logging object
317 95ab4de9 David Knowles

318 95ab4de9 David Knowles
    """
319 9279e986 Michael Hanselmann
    self._host = host
320 95ab4de9 David Knowles
    self._port = port
321 9279e986 Michael Hanselmann
    self._logger = logger
322 95ab4de9 David Knowles
323 9279e986 Michael Hanselmann
    self._base_url = "https://%s:%s" % (host, port)
324 f2f88abf David Knowles
325 9279e986 Michael Hanselmann
    handlers = [_HTTPSHandler(self._logger, config_ssl_verification)]
326 9279e986 Michael Hanselmann
327 9279e986 Michael Hanselmann
    if username is not None:
328 9279e986 Michael Hanselmann
      pwmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
329 9279e986 Michael Hanselmann
      pwmgr.add_password(None, self._base_url, username, password)
330 9279e986 Michael Hanselmann
      handlers.append(urllib2.HTTPBasicAuthHandler(pwmgr))
331 9279e986 Michael Hanselmann
    elif password:
332 9279e986 Michael Hanselmann
      raise Error("Specified password without username")
333 9279e986 Michael Hanselmann
334 9279e986 Michael Hanselmann
    if ignore_proxy:
335 9279e986 Michael Hanselmann
      handlers.append(urllib2.ProxyHandler({}))
336 9279e986 Michael Hanselmann
337 9279e986 Michael Hanselmann
    self._http = urllib2.build_opener(*handlers) # pylint: disable-msg=W0142
338 f2f88abf David Knowles
339 9279e986 Michael Hanselmann
    self._headers = {
340 9279e986 Michael Hanselmann
      "Accept": HTTP_APP_JSON,
341 9279e986 Michael Hanselmann
      "Content-type": HTTP_APP_JSON,
342 9279e986 Michael Hanselmann
      "User-Agent": self.USER_AGENT,
343 9279e986 Michael Hanselmann
      }
344 95ab4de9 David Knowles
345 10f5ab6c Michael Hanselmann
  @staticmethod
346 10f5ab6c Michael Hanselmann
  def _EncodeQuery(query):
347 10f5ab6c Michael Hanselmann
    """Encode query values for RAPI URL.
348 10f5ab6c Michael Hanselmann

349 10f5ab6c Michael Hanselmann
    @type query: list of two-tuples
350 10f5ab6c Michael Hanselmann
    @param query: Query arguments
351 10f5ab6c Michael Hanselmann
    @rtype: list
352 10f5ab6c Michael Hanselmann
    @return: Query list with encoded values
353 10f5ab6c Michael Hanselmann

354 10f5ab6c Michael Hanselmann
    """
355 10f5ab6c Michael Hanselmann
    result = []
356 10f5ab6c Michael Hanselmann
357 10f5ab6c Michael Hanselmann
    for name, value in query:
358 10f5ab6c Michael Hanselmann
      if value is None:
359 10f5ab6c Michael Hanselmann
        result.append((name, ""))
360 10f5ab6c Michael Hanselmann
361 10f5ab6c Michael Hanselmann
      elif isinstance(value, bool):
362 10f5ab6c Michael Hanselmann
        # Boolean values must be encoded as 0 or 1
363 10f5ab6c Michael Hanselmann
        result.append((name, int(value)))
364 10f5ab6c Michael Hanselmann
365 10f5ab6c Michael Hanselmann
      elif isinstance(value, (list, tuple, dict)):
366 10f5ab6c Michael Hanselmann
        raise ValueError("Invalid query data type %r" % type(value).__name__)
367 10f5ab6c Michael Hanselmann
368 10f5ab6c Michael Hanselmann
      else:
369 10f5ab6c Michael Hanselmann
        result.append((name, value))
370 10f5ab6c Michael Hanselmann
371 10f5ab6c Michael Hanselmann
    return result
372 10f5ab6c Michael Hanselmann
373 768747ed Michael Hanselmann
  def _SendRequest(self, method, path, query, content):
374 95ab4de9 David Knowles
    """Sends an HTTP request.
375 95ab4de9 David Knowles

376 95ab4de9 David Knowles
    This constructs a full URL, encodes and decodes HTTP bodies, and
377 95ab4de9 David Knowles
    handles invalid responses in a pythonic way.
378 95ab4de9 David Knowles

379 768747ed Michael Hanselmann
    @type method: string
380 95ab4de9 David Knowles
    @param method: HTTP method to use
381 768747ed Michael Hanselmann
    @type path: string
382 95ab4de9 David Knowles
    @param path: HTTP URL path
383 95ab4de9 David Knowles
    @type query: list of two-tuples
384 95ab4de9 David Knowles
    @param query: query arguments to pass to urllib.urlencode
385 95ab4de9 David Knowles
    @type content: str or None
386 95ab4de9 David Knowles
    @param content: HTTP body content
387 95ab4de9 David Knowles

388 95ab4de9 David Knowles
    @rtype: str
389 95ab4de9 David Knowles
    @return: JSON-Decoded response
390 95ab4de9 David Knowles

391 f2f88abf David Knowles
    @raises CertificateError: If an invalid SSL certificate is found
392 95ab4de9 David Knowles
    @raises GanetiApiError: If an invalid response is returned
393 95ab4de9 David Knowles

394 95ab4de9 David Knowles
    """
395 ccd6b542 Michael Hanselmann
    assert path.startswith("/")
396 ccd6b542 Michael Hanselmann
397 95ab4de9 David Knowles
    if content:
398 d3844674 Michael Hanselmann
      encoded_content = self._json_encoder.encode(content)
399 d3844674 Michael Hanselmann
    else:
400 d3844674 Michael Hanselmann
      encoded_content = None
401 95ab4de9 David Knowles
402 ccd6b542 Michael Hanselmann
    # Build URL
403 f961e2ba Michael Hanselmann
    urlparts = [self._base_url, path]
404 ccd6b542 Michael Hanselmann
    if query:
405 f961e2ba Michael Hanselmann
      urlparts.append("?")
406 f961e2ba Michael Hanselmann
      urlparts.append(urllib.urlencode(self._EncodeQuery(query)))
407 9279e986 Michael Hanselmann
408 f961e2ba Michael Hanselmann
    url = "".join(urlparts)
409 f961e2ba Michael Hanselmann
410 f961e2ba Michael Hanselmann
    self._logger.debug("Sending request %s %s to %s:%s"
411 f961e2ba Michael Hanselmann
                       " (headers=%r, content=%r)",
412 f961e2ba Michael Hanselmann
                       method, url, self._host, self._port, self._headers,
413 f961e2ba Michael Hanselmann
                       encoded_content)
414 f961e2ba Michael Hanselmann
415 f961e2ba Michael Hanselmann
    req = _RapiRequest(method, url, self._headers, encoded_content)
416 9279e986 Michael Hanselmann
417 f2f88abf David Knowles
    try:
418 9279e986 Michael Hanselmann
      resp = self._http.open(req)
419 d3844674 Michael Hanselmann
      encoded_response_content = resp.read()
420 9279e986 Michael Hanselmann
    except (OpenSSL.SSL.Error, OpenSSL.crypto.Error), err:
421 0d9bc5d2 Michael Hanselmann
      raise CertificateError("SSL issue: %s (%r)" % (err, err))
422 5ed59e1e Michael Hanselmann
    except urllib2.HTTPError, err:
423 5ed59e1e Michael Hanselmann
      raise GanetiApiError(str(err), code=err.code)
424 2652b363 Tom Limoncelli
    except urllib2.URLError, err:
425 2652b363 Tom Limoncelli
      raise GanetiApiError(str(err))
426 95ab4de9 David Knowles
427 d3844674 Michael Hanselmann
    if encoded_response_content:
428 d3844674 Michael Hanselmann
      response_content = simplejson.loads(encoded_response_content)
429 d3844674 Michael Hanselmann
    else:
430 d3844674 Michael Hanselmann
      response_content = None
431 95ab4de9 David Knowles
432 95ab4de9 David Knowles
    # TODO: Are there other status codes that are valid? (redirect?)
433 9279e986 Michael Hanselmann
    if resp.code != HTTP_OK:
434 d3844674 Michael Hanselmann
      if isinstance(response_content, dict):
435 95ab4de9 David Knowles
        msg = ("%s %s: %s" %
436 d3844674 Michael Hanselmann
               (response_content["code"],
437 d3844674 Michael Hanselmann
                response_content["message"],
438 d3844674 Michael Hanselmann
                response_content["explain"]))
439 95ab4de9 David Knowles
      else:
440 d3844674 Michael Hanselmann
        msg = str(response_content)
441 d3844674 Michael Hanselmann
442 8a019a03 Michael Hanselmann
      raise GanetiApiError(msg, code=resp.code)
443 95ab4de9 David Knowles
444 d3844674 Michael Hanselmann
    return response_content
445 95ab4de9 David Knowles
446 95ab4de9 David Knowles
  def GetVersion(self):
447 cab667cc David Knowles
    """Gets the Remote API version running on the cluster.
448 95ab4de9 David Knowles

449 95ab4de9 David Knowles
    @rtype: int
450 f2f88abf David Knowles
    @return: Ganeti Remote API version
451 95ab4de9 David Knowles

452 95ab4de9 David Knowles
    """
453 768747ed Michael Hanselmann
    return self._SendRequest(HTTP_GET, "/version", None, None)
454 95ab4de9 David Knowles
455 7eac4a4d Michael Hanselmann
  def GetFeatures(self):
456 7eac4a4d Michael Hanselmann
    """Gets the list of optional features supported by RAPI server.
457 7eac4a4d Michael Hanselmann

458 7eac4a4d Michael Hanselmann
    @rtype: list
459 7eac4a4d Michael Hanselmann
    @return: List of optional features
460 7eac4a4d Michael Hanselmann

461 7eac4a4d Michael Hanselmann
    """
462 7eac4a4d Michael Hanselmann
    try:
463 7eac4a4d Michael Hanselmann
      return self._SendRequest(HTTP_GET, "/%s/features" % GANETI_RAPI_VERSION,
464 7eac4a4d Michael Hanselmann
                               None, None)
465 7eac4a4d Michael Hanselmann
    except GanetiApiError, err:
466 7eac4a4d Michael Hanselmann
      # Older RAPI servers don't support this resource
467 7eac4a4d Michael Hanselmann
      if err.code == HTTP_NOT_FOUND:
468 7eac4a4d Michael Hanselmann
        return []
469 7eac4a4d Michael Hanselmann
470 7eac4a4d Michael Hanselmann
      raise
471 7eac4a4d Michael Hanselmann
472 95ab4de9 David Knowles
  def GetOperatingSystems(self):
473 95ab4de9 David Knowles
    """Gets the Operating Systems running in the Ganeti cluster.
474 95ab4de9 David Knowles

475 95ab4de9 David Knowles
    @rtype: list of str
476 95ab4de9 David Knowles
    @return: operating systems
477 95ab4de9 David Knowles

478 95ab4de9 David Knowles
    """
479 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET, "/%s/os" % GANETI_RAPI_VERSION,
480 a198b2d9 Michael Hanselmann
                             None, None)
481 95ab4de9 David Knowles
482 95ab4de9 David Knowles
  def GetInfo(self):
483 95ab4de9 David Knowles
    """Gets info about the cluster.
484 95ab4de9 David Knowles

485 95ab4de9 David Knowles
    @rtype: dict
486 95ab4de9 David Knowles
    @return: information about the cluster
487 95ab4de9 David Knowles

488 95ab4de9 David Knowles
    """
489 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET, "/%s/info" % GANETI_RAPI_VERSION,
490 a198b2d9 Michael Hanselmann
                             None, None)
491 95ab4de9 David Knowles
492 95ab4de9 David Knowles
  def GetClusterTags(self):
493 95ab4de9 David Knowles
    """Gets the cluster tags.
494 95ab4de9 David Knowles

495 95ab4de9 David Knowles
    @rtype: list of str
496 95ab4de9 David Knowles
    @return: cluster tags
497 95ab4de9 David Knowles

498 95ab4de9 David Knowles
    """
499 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET, "/%s/tags" % GANETI_RAPI_VERSION,
500 a198b2d9 Michael Hanselmann
                             None, None)
501 95ab4de9 David Knowles
502 95ab4de9 David Knowles
  def AddClusterTags(self, tags, dry_run=False):
503 95ab4de9 David Knowles
    """Adds tags to the cluster.
504 95ab4de9 David Knowles

505 95ab4de9 David Knowles
    @type tags: list of str
506 95ab4de9 David Knowles
    @param tags: tags to add to the cluster
507 95ab4de9 David Knowles
    @type dry_run: bool
508 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
509 95ab4de9 David Knowles

510 95ab4de9 David Knowles
    @rtype: int
511 95ab4de9 David Knowles
    @return: job id
512 95ab4de9 David Knowles

513 95ab4de9 David Knowles
    """
514 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
515 95ab4de9 David Knowles
    if dry_run:
516 95ab4de9 David Knowles
      query.append(("dry-run", 1))
517 95ab4de9 David Knowles
518 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT, "/%s/tags" % GANETI_RAPI_VERSION,
519 a198b2d9 Michael Hanselmann
                             query, None)
520 95ab4de9 David Knowles
521 95ab4de9 David Knowles
  def DeleteClusterTags(self, tags, dry_run=False):
522 95ab4de9 David Knowles
    """Deletes tags from the cluster.
523 95ab4de9 David Knowles

524 95ab4de9 David Knowles
    @type tags: list of str
525 95ab4de9 David Knowles
    @param tags: tags to delete
526 95ab4de9 David Knowles
    @type dry_run: bool
527 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
528 95ab4de9 David Knowles

529 95ab4de9 David Knowles
    """
530 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
531 95ab4de9 David Knowles
    if dry_run:
532 95ab4de9 David Knowles
      query.append(("dry-run", 1))
533 95ab4de9 David Knowles
534 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE, "/%s/tags" % GANETI_RAPI_VERSION,
535 a198b2d9 Michael Hanselmann
                             query, None)
536 95ab4de9 David Knowles
537 95ab4de9 David Knowles
  def GetInstances(self, bulk=False):
538 95ab4de9 David Knowles
    """Gets information about instances on the cluster.
539 95ab4de9 David Knowles

540 95ab4de9 David Knowles
    @type bulk: bool
541 95ab4de9 David Knowles
    @param bulk: whether to return all information about all instances
542 95ab4de9 David Knowles

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

546 95ab4de9 David Knowles
    """
547 95ab4de9 David Knowles
    query = []
548 95ab4de9 David Knowles
    if bulk:
549 95ab4de9 David Knowles
      query.append(("bulk", 1))
550 95ab4de9 David Knowles
551 a198b2d9 Michael Hanselmann
    instances = self._SendRequest(HTTP_GET,
552 a198b2d9 Michael Hanselmann
                                  "/%s/instances" % GANETI_RAPI_VERSION,
553 a198b2d9 Michael Hanselmann
                                  query, None)
554 95ab4de9 David Knowles
    if bulk:
555 95ab4de9 David Knowles
      return instances
556 95ab4de9 David Knowles
    else:
557 95ab4de9 David Knowles
      return [i["id"] for i in instances]
558 95ab4de9 David Knowles
559 591e5103 Michael Hanselmann
  def GetInstance(self, instance):
560 95ab4de9 David Knowles
    """Gets information about an instance.
561 95ab4de9 David Knowles

562 95ab4de9 David Knowles
    @type instance: str
563 95ab4de9 David Knowles
    @param instance: instance whose info to return
564 95ab4de9 David Knowles

565 95ab4de9 David Knowles
    @rtype: dict
566 95ab4de9 David Knowles
    @return: info about the instance
567 95ab4de9 David Knowles

568 95ab4de9 David Knowles
    """
569 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
570 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s" %
571 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, None)
572 95ab4de9 David Knowles
573 591e5103 Michael Hanselmann
  def GetInstanceInfo(self, instance, static=None):
574 591e5103 Michael Hanselmann
    """Gets information about an instance.
575 591e5103 Michael Hanselmann

576 591e5103 Michael Hanselmann
    @type instance: string
577 591e5103 Michael Hanselmann
    @param instance: Instance name
578 591e5103 Michael Hanselmann
    @rtype: string
579 591e5103 Michael Hanselmann
    @return: Job ID
580 591e5103 Michael Hanselmann

581 591e5103 Michael Hanselmann
    """
582 591e5103 Michael Hanselmann
    if static is not None:
583 591e5103 Michael Hanselmann
      query = [("static", static)]
584 591e5103 Michael Hanselmann
    else:
585 591e5103 Michael Hanselmann
      query = None
586 591e5103 Michael Hanselmann
587 591e5103 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
588 591e5103 Michael Hanselmann
                             ("/%s/instances/%s/info" %
589 591e5103 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
590 591e5103 Michael Hanselmann
591 8a47b447 Michael Hanselmann
  def CreateInstance(self, mode, name, disk_template, disks, nics,
592 8a47b447 Michael Hanselmann
                     **kwargs):
593 95ab4de9 David Knowles
    """Creates a new instance.
594 95ab4de9 David Knowles

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

597 8a47b447 Michael Hanselmann
    @type mode: string
598 8a47b447 Michael Hanselmann
    @param mode: Instance creation mode
599 8a47b447 Michael Hanselmann
    @type name: string
600 8a47b447 Michael Hanselmann
    @param name: Hostname of the instance to create
601 8a47b447 Michael Hanselmann
    @type disk_template: string
602 8a47b447 Michael Hanselmann
    @param disk_template: Disk template for instance (e.g. plain, diskless,
603 8a47b447 Michael Hanselmann
                          file, or drbd)
604 8a47b447 Michael Hanselmann
    @type disks: list of dicts
605 8a47b447 Michael Hanselmann
    @param disks: List of disk definitions
606 8a47b447 Michael Hanselmann
    @type nics: list of dicts
607 8a47b447 Michael Hanselmann
    @param nics: List of NIC definitions
608 95ab4de9 David Knowles
    @type dry_run: bool
609 8a47b447 Michael Hanselmann
    @keyword dry_run: whether to perform a dry run
610 95ab4de9 David Knowles

611 95ab4de9 David Knowles
    @rtype: int
612 95ab4de9 David Knowles
    @return: job id
613 95ab4de9 David Knowles

614 95ab4de9 David Knowles
    """
615 95ab4de9 David Knowles
    query = []
616 8a47b447 Michael Hanselmann
617 8a47b447 Michael Hanselmann
    if kwargs.get("dry_run"):
618 95ab4de9 David Knowles
      query.append(("dry-run", 1))
619 95ab4de9 David Knowles
620 8a47b447 Michael Hanselmann
    if _INST_CREATE_REQV1 in self.GetFeatures():
621 8a47b447 Michael Hanselmann
      # All required fields for request data version 1
622 8a47b447 Michael Hanselmann
      body = {
623 8a47b447 Michael Hanselmann
        _REQ_DATA_VERSION_FIELD: 1,
624 8a47b447 Michael Hanselmann
        "mode": mode,
625 8a47b447 Michael Hanselmann
        "name": name,
626 8a47b447 Michael Hanselmann
        "disk_template": disk_template,
627 8a47b447 Michael Hanselmann
        "disks": disks,
628 8a47b447 Michael Hanselmann
        "nics": nics,
629 8a47b447 Michael Hanselmann
        }
630 8a47b447 Michael Hanselmann
631 8a47b447 Michael Hanselmann
      conflicts = set(kwargs.iterkeys()) & set(body.iterkeys())
632 8a47b447 Michael Hanselmann
      if conflicts:
633 8a47b447 Michael Hanselmann
        raise GanetiApiError("Required fields can not be specified as"
634 8a47b447 Michael Hanselmann
                             " keywords: %s" % ", ".join(conflicts))
635 8a47b447 Michael Hanselmann
636 8a47b447 Michael Hanselmann
      body.update((key, value) for key, value in kwargs.iteritems()
637 8a47b447 Michael Hanselmann
                  if key != "dry_run")
638 8a47b447 Michael Hanselmann
    else:
639 8a47b447 Michael Hanselmann
      # TODO: Implement instance creation request data version 0
640 8a47b447 Michael Hanselmann
      # When implementing version 0, care should be taken to refuse unknown
641 8a47b447 Michael Hanselmann
      # parameters and invalid values. The interface of this function must stay
642 8a47b447 Michael Hanselmann
      # exactly the same for version 0 and 1 (e.g. they aren't allowed to
643 8a47b447 Michael Hanselmann
      # require different data types).
644 8a47b447 Michael Hanselmann
      raise NotImplementedError("Support for instance creation request data"
645 8a47b447 Michael Hanselmann
                                " version 0 is not yet implemented")
646 8a47b447 Michael Hanselmann
647 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_POST, "/%s/instances" % GANETI_RAPI_VERSION,
648 8a47b447 Michael Hanselmann
                             query, body)
649 95ab4de9 David Knowles
650 95ab4de9 David Knowles
  def DeleteInstance(self, instance, dry_run=False):
651 95ab4de9 David Knowles
    """Deletes an instance.
652 95ab4de9 David Knowles

653 95ab4de9 David Knowles
    @type instance: str
654 95ab4de9 David Knowles
    @param instance: the instance to delete
655 95ab4de9 David Knowles

656 cab667cc David Knowles
    @rtype: int
657 cab667cc David Knowles
    @return: job id
658 cab667cc David Knowles

659 95ab4de9 David Knowles
    """
660 95ab4de9 David Knowles
    query = []
661 95ab4de9 David Knowles
    if dry_run:
662 95ab4de9 David Knowles
      query.append(("dry-run", 1))
663 95ab4de9 David Knowles
664 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE,
665 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s" %
666 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
667 95ab4de9 David Knowles
668 95ab4de9 David Knowles
  def GetInstanceTags(self, instance):
669 95ab4de9 David Knowles
    """Gets tags for an instance.
670 95ab4de9 David Knowles

671 95ab4de9 David Knowles
    @type instance: str
672 95ab4de9 David Knowles
    @param instance: instance whose tags to return
673 95ab4de9 David Knowles

674 95ab4de9 David Knowles
    @rtype: list of str
675 95ab4de9 David Knowles
    @return: tags for the instance
676 95ab4de9 David Knowles

677 95ab4de9 David Knowles
    """
678 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
679 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/tags" %
680 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, None)
681 95ab4de9 David Knowles
682 95ab4de9 David Knowles
  def AddInstanceTags(self, instance, tags, dry_run=False):
683 95ab4de9 David Knowles
    """Adds tags to an instance.
684 95ab4de9 David Knowles

685 95ab4de9 David Knowles
    @type instance: str
686 95ab4de9 David Knowles
    @param instance: instance to add tags to
687 95ab4de9 David Knowles
    @type tags: list of str
688 95ab4de9 David Knowles
    @param tags: tags to add to the instance
689 95ab4de9 David Knowles
    @type dry_run: bool
690 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
691 95ab4de9 David Knowles

692 95ab4de9 David Knowles
    @rtype: int
693 95ab4de9 David Knowles
    @return: job id
694 95ab4de9 David Knowles

695 95ab4de9 David Knowles
    """
696 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
697 95ab4de9 David Knowles
    if dry_run:
698 95ab4de9 David Knowles
      query.append(("dry-run", 1))
699 95ab4de9 David Knowles
700 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
701 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/tags" %
702 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
703 95ab4de9 David Knowles
704 95ab4de9 David Knowles
  def DeleteInstanceTags(self, instance, tags, dry_run=False):
705 95ab4de9 David Knowles
    """Deletes tags from an instance.
706 95ab4de9 David Knowles

707 95ab4de9 David Knowles
    @type instance: str
708 95ab4de9 David Knowles
    @param instance: instance to delete tags from
709 95ab4de9 David Knowles
    @type tags: list of str
710 95ab4de9 David Knowles
    @param tags: tags to delete
711 95ab4de9 David Knowles
    @type dry_run: bool
712 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
713 95ab4de9 David Knowles

714 95ab4de9 David Knowles
    """
715 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
716 95ab4de9 David Knowles
    if dry_run:
717 95ab4de9 David Knowles
      query.append(("dry-run", 1))
718 95ab4de9 David Knowles
719 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE,
720 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/tags" %
721 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
722 95ab4de9 David Knowles
723 95ab4de9 David Knowles
  def RebootInstance(self, instance, reboot_type=None, ignore_secondaries=None,
724 95ab4de9 David Knowles
                     dry_run=False):
725 95ab4de9 David Knowles
    """Reboots an instance.
726 95ab4de9 David Knowles

727 95ab4de9 David Knowles
    @type instance: str
728 95ab4de9 David Knowles
    @param instance: instance to rebot
729 95ab4de9 David Knowles
    @type reboot_type: str
730 95ab4de9 David Knowles
    @param reboot_type: one of: hard, soft, full
731 95ab4de9 David Knowles
    @type ignore_secondaries: bool
732 95ab4de9 David Knowles
    @param ignore_secondaries: if True, ignores errors for the secondary node
733 95ab4de9 David Knowles
        while re-assembling disks (in hard-reboot mode only)
734 95ab4de9 David Knowles
    @type dry_run: bool
735 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
736 95ab4de9 David Knowles

737 95ab4de9 David Knowles
    """
738 95ab4de9 David Knowles
    query = []
739 95ab4de9 David Knowles
    if reboot_type:
740 95ab4de9 David Knowles
      query.append(("type", reboot_type))
741 95ab4de9 David Knowles
    if ignore_secondaries is not None:
742 95ab4de9 David Knowles
      query.append(("ignore_secondaries", ignore_secondaries))
743 95ab4de9 David Knowles
    if dry_run:
744 95ab4de9 David Knowles
      query.append(("dry-run", 1))
745 95ab4de9 David Knowles
746 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_POST,
747 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/reboot" %
748 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
749 95ab4de9 David Knowles
750 95ab4de9 David Knowles
  def ShutdownInstance(self, instance, dry_run=False):
751 95ab4de9 David Knowles
    """Shuts down an instance.
752 95ab4de9 David Knowles

753 95ab4de9 David Knowles
    @type instance: str
754 95ab4de9 David Knowles
    @param instance: the instance to shut down
755 95ab4de9 David Knowles
    @type dry_run: bool
756 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
757 95ab4de9 David Knowles

758 95ab4de9 David Knowles
    """
759 95ab4de9 David Knowles
    query = []
760 95ab4de9 David Knowles
    if dry_run:
761 95ab4de9 David Knowles
      query.append(("dry-run", 1))
762 95ab4de9 David Knowles
763 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
764 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/shutdown" %
765 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
766 95ab4de9 David Knowles
767 95ab4de9 David Knowles
  def StartupInstance(self, instance, dry_run=False):
768 95ab4de9 David Knowles
    """Starts up an instance.
769 95ab4de9 David Knowles

770 95ab4de9 David Knowles
    @type instance: str
771 95ab4de9 David Knowles
    @param instance: the instance to start up
772 95ab4de9 David Knowles
    @type dry_run: bool
773 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
774 95ab4de9 David Knowles

775 95ab4de9 David Knowles
    """
776 95ab4de9 David Knowles
    query = []
777 95ab4de9 David Knowles
    if dry_run:
778 95ab4de9 David Knowles
      query.append(("dry-run", 1))
779 95ab4de9 David Knowles
780 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
781 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/startup" %
782 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
783 95ab4de9 David Knowles
784 95ab4de9 David Knowles
  def ReinstallInstance(self, instance, os, no_startup=False):
785 95ab4de9 David Knowles
    """Reinstalls an instance.
786 95ab4de9 David Knowles

787 95ab4de9 David Knowles
    @type instance: str
788 95ab4de9 David Knowles
    @param instance: the instance to reinstall
789 95ab4de9 David Knowles
    @type os: str
790 95ab4de9 David Knowles
    @param os: the os to reinstall
791 95ab4de9 David Knowles
    @type no_startup: bool
792 95ab4de9 David Knowles
    @param no_startup: whether to start the instance automatically
793 95ab4de9 David Knowles

794 95ab4de9 David Knowles
    """
795 95ab4de9 David Knowles
    query = [("os", os)]
796 95ab4de9 David Knowles
    if no_startup:
797 95ab4de9 David Knowles
      query.append(("nostartup", 1))
798 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_POST,
799 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/reinstall" %
800 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
801 95ab4de9 David Knowles
802 bfc2002f Michael Hanselmann
  def ReplaceInstanceDisks(self, instance, disks=None, mode=REPLACE_DISK_AUTO,
803 cfc03c54 Michael Hanselmann
                           remote_node=None, iallocator=None, dry_run=False):
804 95ab4de9 David Knowles
    """Replaces disks on an instance.
805 95ab4de9 David Knowles

806 95ab4de9 David Knowles
    @type instance: str
807 95ab4de9 David Knowles
    @param instance: instance whose disks to replace
808 bfc2002f Michael Hanselmann
    @type disks: list of ints
809 bfc2002f Michael Hanselmann
    @param disks: Indexes of disks to replace
810 95ab4de9 David Knowles
    @type mode: str
811 cfc03c54 Michael Hanselmann
    @param mode: replacement mode to use (defaults to replace_auto)
812 95ab4de9 David Knowles
    @type remote_node: str or None
813 95ab4de9 David Knowles
    @param remote_node: new secondary node to use (for use with
814 cfc03c54 Michael Hanselmann
        replace_new_secondary mode)
815 95ab4de9 David Knowles
    @type iallocator: str or None
816 95ab4de9 David Knowles
    @param iallocator: instance allocator plugin to use (for use with
817 cfc03c54 Michael Hanselmann
                       replace_auto mode)
818 95ab4de9 David Knowles
    @type dry_run: bool
819 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
820 95ab4de9 David Knowles

821 95ab4de9 David Knowles
    @rtype: int
822 95ab4de9 David Knowles
    @return: job id
823 95ab4de9 David Knowles

824 95ab4de9 David Knowles
    """
825 cfc03c54 Michael Hanselmann
    query = [
826 cfc03c54 Michael Hanselmann
      ("mode", mode),
827 cfc03c54 Michael Hanselmann
      ]
828 95ab4de9 David Knowles
829 bfc2002f Michael Hanselmann
    if disks:
830 bfc2002f Michael Hanselmann
      query.append(("disks", ",".join(str(idx) for idx in disks)))
831 bfc2002f Michael Hanselmann
832 bfc2002f Michael Hanselmann
    if remote_node:
833 95ab4de9 David Knowles
      query.append(("remote_node", remote_node))
834 95ab4de9 David Knowles
835 bfc2002f Michael Hanselmann
    if iallocator:
836 bfc2002f Michael Hanselmann
      query.append(("iallocator", iallocator))
837 bfc2002f Michael Hanselmann
838 95ab4de9 David Knowles
    if dry_run:
839 95ab4de9 David Knowles
      query.append(("dry-run", 1))
840 95ab4de9 David Knowles
841 95ab4de9 David Knowles
    return self._SendRequest(HTTP_POST,
842 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/replace-disks" %
843 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
844 95ab4de9 David Knowles
845 95ab4de9 David Knowles
  def GetJobs(self):
846 95ab4de9 David Knowles
    """Gets all jobs for the cluster.
847 95ab4de9 David Knowles

848 95ab4de9 David Knowles
    @rtype: list of int
849 95ab4de9 David Knowles
    @return: job ids for the cluster
850 95ab4de9 David Knowles

851 95ab4de9 David Knowles
    """
852 768747ed Michael Hanselmann
    return [int(j["id"])
853 a198b2d9 Michael Hanselmann
            for j in self._SendRequest(HTTP_GET,
854 a198b2d9 Michael Hanselmann
                                       "/%s/jobs" % GANETI_RAPI_VERSION,
855 a198b2d9 Michael Hanselmann
                                       None, None)]
856 95ab4de9 David Knowles
857 95ab4de9 David Knowles
  def GetJobStatus(self, job_id):
858 95ab4de9 David Knowles
    """Gets the status of a job.
859 95ab4de9 David Knowles

860 95ab4de9 David Knowles
    @type job_id: int
861 95ab4de9 David Knowles
    @param job_id: job id whose status to query
862 95ab4de9 David Knowles

863 95ab4de9 David Knowles
    @rtype: dict
864 95ab4de9 David Knowles
    @return: job status
865 95ab4de9 David Knowles

866 95ab4de9 David Knowles
    """
867 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
868 a198b2d9 Michael Hanselmann
                             "/%s/jobs/%s" % (GANETI_RAPI_VERSION, job_id),
869 a198b2d9 Michael Hanselmann
                             None, None)
870 95ab4de9 David Knowles
871 d9b67f70 Michael Hanselmann
  def WaitForJobChange(self, job_id, fields, prev_job_info, prev_log_serial):
872 d9b67f70 Michael Hanselmann
    """Waits for job changes.
873 d9b67f70 Michael Hanselmann

874 d9b67f70 Michael Hanselmann
    @type job_id: int
875 d9b67f70 Michael Hanselmann
    @param job_id: Job ID for which to wait
876 d9b67f70 Michael Hanselmann

877 d9b67f70 Michael Hanselmann
    """
878 d9b67f70 Michael Hanselmann
    body = {
879 d9b67f70 Michael Hanselmann
      "fields": fields,
880 d9b67f70 Michael Hanselmann
      "previous_job_info": prev_job_info,
881 d9b67f70 Michael Hanselmann
      "previous_log_serial": prev_log_serial,
882 d9b67f70 Michael Hanselmann
      }
883 d9b67f70 Michael Hanselmann
884 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
885 a198b2d9 Michael Hanselmann
                             "/%s/jobs/%s/wait" % (GANETI_RAPI_VERSION, job_id),
886 a198b2d9 Michael Hanselmann
                             None, body)
887 d9b67f70 Michael Hanselmann
888 cf9ada49 Michael Hanselmann
  def CancelJob(self, job_id, dry_run=False):
889 cf9ada49 Michael Hanselmann
    """Cancels a job.
890 95ab4de9 David Knowles

891 95ab4de9 David Knowles
    @type job_id: int
892 95ab4de9 David Knowles
    @param job_id: id of the job to delete
893 95ab4de9 David Knowles
    @type dry_run: bool
894 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
895 95ab4de9 David Knowles

896 95ab4de9 David Knowles
    """
897 95ab4de9 David Knowles
    query = []
898 95ab4de9 David Knowles
    if dry_run:
899 95ab4de9 David Knowles
      query.append(("dry-run", 1))
900 95ab4de9 David Knowles
901 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE,
902 a198b2d9 Michael Hanselmann
                             "/%s/jobs/%s" % (GANETI_RAPI_VERSION, job_id),
903 a198b2d9 Michael Hanselmann
                             query, None)
904 95ab4de9 David Knowles
905 95ab4de9 David Knowles
  def GetNodes(self, bulk=False):
906 95ab4de9 David Knowles
    """Gets all nodes in the cluster.
907 95ab4de9 David Knowles

908 95ab4de9 David Knowles
    @type bulk: bool
909 95ab4de9 David Knowles
    @param bulk: whether to return all information about all instances
910 95ab4de9 David Knowles

911 95ab4de9 David Knowles
    @rtype: list of dict or str
912 95ab4de9 David Knowles
    @return: if bulk is true, info about nodes in the cluster,
913 95ab4de9 David Knowles
        else list of nodes in the cluster
914 95ab4de9 David Knowles

915 95ab4de9 David Knowles
    """
916 95ab4de9 David Knowles
    query = []
917 95ab4de9 David Knowles
    if bulk:
918 95ab4de9 David Knowles
      query.append(("bulk", 1))
919 95ab4de9 David Knowles
920 a198b2d9 Michael Hanselmann
    nodes = self._SendRequest(HTTP_GET, "/%s/nodes" % GANETI_RAPI_VERSION,
921 a198b2d9 Michael Hanselmann
                              query, None)
922 95ab4de9 David Knowles
    if bulk:
923 95ab4de9 David Knowles
      return nodes
924 95ab4de9 David Knowles
    else:
925 95ab4de9 David Knowles
      return [n["id"] for n in nodes]
926 95ab4de9 David Knowles
927 591e5103 Michael Hanselmann
  def GetNode(self, node):
928 95ab4de9 David Knowles
    """Gets information about a node.
929 95ab4de9 David Knowles

930 95ab4de9 David Knowles
    @type node: str
931 95ab4de9 David Knowles
    @param node: node whose info to return
932 95ab4de9 David Knowles

933 95ab4de9 David Knowles
    @rtype: dict
934 95ab4de9 David Knowles
    @return: info about the node
935 95ab4de9 David Knowles

936 95ab4de9 David Knowles
    """
937 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
938 a198b2d9 Michael Hanselmann
                             "/%s/nodes/%s" % (GANETI_RAPI_VERSION, node),
939 a198b2d9 Michael Hanselmann
                             None, None)
940 95ab4de9 David Knowles
941 95ab4de9 David Knowles
  def EvacuateNode(self, node, iallocator=None, remote_node=None,
942 95ab4de9 David Knowles
                   dry_run=False):
943 95ab4de9 David Knowles
    """Evacuates instances from a Ganeti node.
944 95ab4de9 David Knowles

945 95ab4de9 David Knowles
    @type node: str
946 95ab4de9 David Knowles
    @param node: node to evacuate
947 95ab4de9 David Knowles
    @type iallocator: str or None
948 95ab4de9 David Knowles
    @param iallocator: instance allocator to use
949 95ab4de9 David Knowles
    @type remote_node: str
950 95ab4de9 David Knowles
    @param remote_node: node to evaucate to
951 95ab4de9 David Knowles
    @type dry_run: bool
952 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
953 95ab4de9 David Knowles

954 95ab4de9 David Knowles
    @rtype: int
955 95ab4de9 David Knowles
    @return: job id
956 95ab4de9 David Knowles

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

959 95ab4de9 David Knowles
    """
960 95ab4de9 David Knowles
    if iallocator and remote_node:
961 cfc03c54 Michael Hanselmann
      raise GanetiApiError("Only one of iallocator or remote_node can be used")
962 95ab4de9 David Knowles
963 cfc03c54 Michael Hanselmann
    query = []
964 95ab4de9 David Knowles
    if iallocator:
965 95ab4de9 David Knowles
      query.append(("iallocator", iallocator))
966 95ab4de9 David Knowles
    if remote_node:
967 95ab4de9 David Knowles
      query.append(("remote_node", remote_node))
968 95ab4de9 David Knowles
    if dry_run:
969 95ab4de9 David Knowles
      query.append(("dry-run", 1))
970 95ab4de9 David Knowles
971 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_POST,
972 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/evacuate" %
973 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
974 95ab4de9 David Knowles
975 95ab4de9 David Knowles
  def MigrateNode(self, node, live=True, dry_run=False):
976 95ab4de9 David Knowles
    """Migrates all primary instances from a node.
977 95ab4de9 David Knowles

978 95ab4de9 David Knowles
    @type node: str
979 95ab4de9 David Knowles
    @param node: node to migrate
980 95ab4de9 David Knowles
    @type live: bool
981 95ab4de9 David Knowles
    @param live: whether to use live migration
982 95ab4de9 David Knowles
    @type dry_run: bool
983 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
984 95ab4de9 David Knowles

985 95ab4de9 David Knowles
    @rtype: int
986 95ab4de9 David Knowles
    @return: job id
987 95ab4de9 David Knowles

988 95ab4de9 David Knowles
    """
989 95ab4de9 David Knowles
    query = []
990 95ab4de9 David Knowles
    if live:
991 95ab4de9 David Knowles
      query.append(("live", 1))
992 95ab4de9 David Knowles
    if dry_run:
993 95ab4de9 David Knowles
      query.append(("dry-run", 1))
994 95ab4de9 David Knowles
995 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_POST,
996 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/migrate" %
997 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
998 95ab4de9 David Knowles
999 95ab4de9 David Knowles
  def GetNodeRole(self, node):
1000 95ab4de9 David Knowles
    """Gets the current role for a node.
1001 95ab4de9 David Knowles

1002 95ab4de9 David Knowles
    @type node: str
1003 95ab4de9 David Knowles
    @param node: node whose role to return
1004 95ab4de9 David Knowles

1005 95ab4de9 David Knowles
    @rtype: str
1006 95ab4de9 David Knowles
    @return: the current role for a node
1007 95ab4de9 David Knowles

1008 95ab4de9 David Knowles
    """
1009 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1010 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/role" %
1011 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), None, None)
1012 95ab4de9 David Knowles
1013 95ab4de9 David Knowles
  def SetNodeRole(self, node, role, force=False):
1014 95ab4de9 David Knowles
    """Sets the role for a node.
1015 95ab4de9 David Knowles

1016 95ab4de9 David Knowles
    @type node: str
1017 95ab4de9 David Knowles
    @param node: the node whose role to set
1018 95ab4de9 David Knowles
    @type role: str
1019 95ab4de9 David Knowles
    @param role: the role to set for the node
1020 95ab4de9 David Knowles
    @type force: bool
1021 95ab4de9 David Knowles
    @param force: whether to force the role change
1022 95ab4de9 David Knowles

1023 95ab4de9 David Knowles
    @rtype: int
1024 95ab4de9 David Knowles
    @return: job id
1025 95ab4de9 David Knowles

1026 95ab4de9 David Knowles
    """
1027 1068639f Michael Hanselmann
    query = [
1028 1068639f Michael Hanselmann
      ("force", force),
1029 1068639f Michael Hanselmann
      ]
1030 cfc03c54 Michael Hanselmann
1031 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1032 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/role" %
1033 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, role)
1034 95ab4de9 David Knowles
1035 95ab4de9 David Knowles
  def GetNodeStorageUnits(self, node, storage_type, output_fields):
1036 95ab4de9 David Knowles
    """Gets the storage units for a node.
1037 95ab4de9 David Knowles

1038 95ab4de9 David Knowles
    @type node: str
1039 95ab4de9 David Knowles
    @param node: the node whose storage units to return
1040 95ab4de9 David Knowles
    @type storage_type: str
1041 95ab4de9 David Knowles
    @param storage_type: storage type whose units to return
1042 95ab4de9 David Knowles
    @type output_fields: str
1043 95ab4de9 David Knowles
    @param output_fields: storage type fields to return
1044 95ab4de9 David Knowles

1045 95ab4de9 David Knowles
    @rtype: int
1046 95ab4de9 David Knowles
    @return: job id where results can be retrieved
1047 95ab4de9 David Knowles

1048 95ab4de9 David Knowles
    """
1049 cfc03c54 Michael Hanselmann
    query = [
1050 cfc03c54 Michael Hanselmann
      ("storage_type", storage_type),
1051 cfc03c54 Michael Hanselmann
      ("output_fields", output_fields),
1052 cfc03c54 Michael Hanselmann
      ]
1053 95ab4de9 David Knowles
1054 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1055 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/storage" %
1056 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1057 95ab4de9 David Knowles
1058 fde28316 Michael Hanselmann
  def ModifyNodeStorageUnits(self, node, storage_type, name, allocatable=None):
1059 95ab4de9 David Knowles
    """Modifies parameters of storage units on the node.
1060 95ab4de9 David Knowles

1061 95ab4de9 David Knowles
    @type node: str
1062 95ab4de9 David Knowles
    @param node: node whose storage units to modify
1063 95ab4de9 David Knowles
    @type storage_type: str
1064 95ab4de9 David Knowles
    @param storage_type: storage type whose units to modify
1065 95ab4de9 David Knowles
    @type name: str
1066 95ab4de9 David Knowles
    @param name: name of the storage unit
1067 fde28316 Michael Hanselmann
    @type allocatable: bool or None
1068 fde28316 Michael Hanselmann
    @param allocatable: Whether to set the "allocatable" flag on the storage
1069 fde28316 Michael Hanselmann
                        unit (None=no modification, True=set, False=unset)
1070 95ab4de9 David Knowles

1071 95ab4de9 David Knowles
    @rtype: int
1072 95ab4de9 David Knowles
    @return: job id
1073 95ab4de9 David Knowles

1074 95ab4de9 David Knowles
    """
1075 95ab4de9 David Knowles
    query = [
1076 cfc03c54 Michael Hanselmann
      ("storage_type", storage_type),
1077 cfc03c54 Michael Hanselmann
      ("name", name),
1078 cfc03c54 Michael Hanselmann
      ]
1079 cfc03c54 Michael Hanselmann
1080 fde28316 Michael Hanselmann
    if allocatable is not None:
1081 fde28316 Michael Hanselmann
      query.append(("allocatable", allocatable))
1082 fde28316 Michael Hanselmann
1083 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1084 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/storage/modify" %
1085 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1086 95ab4de9 David Knowles
1087 95ab4de9 David Knowles
  def RepairNodeStorageUnits(self, node, storage_type, name):
1088 95ab4de9 David Knowles
    """Repairs a storage unit on the node.
1089 95ab4de9 David Knowles

1090 95ab4de9 David Knowles
    @type node: str
1091 95ab4de9 David Knowles
    @param node: node whose storage units to repair
1092 95ab4de9 David Knowles
    @type storage_type: str
1093 95ab4de9 David Knowles
    @param storage_type: storage type to repair
1094 95ab4de9 David Knowles
    @type name: str
1095 95ab4de9 David Knowles
    @param name: name of the storage unit to repair
1096 95ab4de9 David Knowles

1097 95ab4de9 David Knowles
    @rtype: int
1098 95ab4de9 David Knowles
    @return: job id
1099 95ab4de9 David Knowles

1100 95ab4de9 David Knowles
    """
1101 cfc03c54 Michael Hanselmann
    query = [
1102 cfc03c54 Michael Hanselmann
      ("storage_type", storage_type),
1103 cfc03c54 Michael Hanselmann
      ("name", name),
1104 cfc03c54 Michael Hanselmann
      ]
1105 95ab4de9 David Knowles
1106 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1107 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/storage/repair" %
1108 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1109 95ab4de9 David Knowles
1110 95ab4de9 David Knowles
  def GetNodeTags(self, node):
1111 95ab4de9 David Knowles
    """Gets the tags for a node.
1112 95ab4de9 David Knowles

1113 95ab4de9 David Knowles
    @type node: str
1114 95ab4de9 David Knowles
    @param node: node whose tags to return
1115 95ab4de9 David Knowles

1116 95ab4de9 David Knowles
    @rtype: list of str
1117 95ab4de9 David Knowles
    @return: tags for the node
1118 95ab4de9 David Knowles

1119 95ab4de9 David Knowles
    """
1120 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1121 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/tags" %
1122 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), None, None)
1123 95ab4de9 David Knowles
1124 95ab4de9 David Knowles
  def AddNodeTags(self, node, tags, dry_run=False):
1125 95ab4de9 David Knowles
    """Adds tags to a node.
1126 95ab4de9 David Knowles

1127 95ab4de9 David Knowles
    @type node: str
1128 95ab4de9 David Knowles
    @param node: node to add tags to
1129 95ab4de9 David Knowles
    @type tags: list of str
1130 95ab4de9 David Knowles
    @param tags: tags to add to the node
1131 95ab4de9 David Knowles
    @type dry_run: bool
1132 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1133 95ab4de9 David Knowles

1134 95ab4de9 David Knowles
    @rtype: int
1135 95ab4de9 David Knowles
    @return: job id
1136 95ab4de9 David Knowles

1137 95ab4de9 David Knowles
    """
1138 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
1139 95ab4de9 David Knowles
    if dry_run:
1140 95ab4de9 David Knowles
      query.append(("dry-run", 1))
1141 95ab4de9 David Knowles
1142 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1143 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/tags" %
1144 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, tags)
1145 95ab4de9 David Knowles
1146 95ab4de9 David Knowles
  def DeleteNodeTags(self, node, tags, dry_run=False):
1147 95ab4de9 David Knowles
    """Delete tags from a node.
1148 95ab4de9 David Knowles

1149 95ab4de9 David Knowles
    @type node: str
1150 95ab4de9 David Knowles
    @param node: node to remove tags from
1151 95ab4de9 David Knowles
    @type tags: list of str
1152 95ab4de9 David Knowles
    @param tags: tags to remove from the node
1153 95ab4de9 David Knowles
    @type dry_run: bool
1154 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1155 95ab4de9 David Knowles

1156 95ab4de9 David Knowles
    @rtype: int
1157 95ab4de9 David Knowles
    @return: job id
1158 95ab4de9 David Knowles

1159 95ab4de9 David Knowles
    """
1160 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
1161 95ab4de9 David Knowles
    if dry_run:
1162 95ab4de9 David Knowles
      query.append(("dry-run", 1))
1163 95ab4de9 David Knowles
1164 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE,
1165 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/tags" %
1166 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)