Statistics
| Branch: | Tag: | Revision:

root / lib / rapi / client.py @ a57981c5

History | View | Annotate | Download (49.8 kB)

1 95ab4de9 David Knowles
#
2 95ab4de9 David Knowles
#
3 95ab4de9 David Knowles
4 16c13387 Theo Van Dinter
# Copyright (C) 2010, 2011 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 2a7c3583 Michael Hanselmann
"""Ganeti RAPI client.
23 2a7c3583 Michael Hanselmann

24 2a7c3583 Michael Hanselmann
@attention: To use the RAPI client, the application B{must} call
25 2a7c3583 Michael Hanselmann
            C{pycurl.global_init} during initialization and
26 2a7c3583 Michael Hanselmann
            C{pycurl.global_cleanup} before exiting the process. This is very
27 2a7c3583 Michael Hanselmann
            important in multi-threaded programs. See curl_global_init(3) and
28 2a7c3583 Michael Hanselmann
            curl_global_cleanup(3) for details. The decorator L{UsesRapiClient}
29 2a7c3583 Michael Hanselmann
            can be used.
30 2a7c3583 Michael Hanselmann

31 2a7c3583 Michael Hanselmann
"""
32 95ab4de9 David Knowles
33 5ef5cfea Michael Hanselmann
# No Ganeti-specific modules should be imported. The RAPI client is supposed to
34 5ef5cfea Michael Hanselmann
# be standalone.
35 5ef5cfea Michael Hanselmann
36 9279e986 Michael Hanselmann
import logging
37 95ab4de9 David Knowles
import simplejson
38 1a8337f2 Manuel Franceschini
import socket
39 95ab4de9 David Knowles
import urllib
40 2a7c3583 Michael Hanselmann
import threading
41 2a7c3583 Michael Hanselmann
import pycurl
42 16c13387 Theo Van Dinter
import time
43 2a7c3583 Michael Hanselmann
44 2a7c3583 Michael Hanselmann
try:
45 2a7c3583 Michael Hanselmann
  from cStringIO import StringIO
46 2a7c3583 Michael Hanselmann
except ImportError:
47 2a7c3583 Michael Hanselmann
  from StringIO import StringIO
48 95ab4de9 David Knowles
49 95ab4de9 David Knowles
50 9279e986 Michael Hanselmann
GANETI_RAPI_PORT = 5080
51 a198b2d9 Michael Hanselmann
GANETI_RAPI_VERSION = 2
52 9279e986 Michael Hanselmann
53 95ab4de9 David Knowles
HTTP_DELETE = "DELETE"
54 95ab4de9 David Knowles
HTTP_GET = "GET"
55 95ab4de9 David Knowles
HTTP_PUT = "PUT"
56 95ab4de9 David Knowles
HTTP_POST = "POST"
57 9279e986 Michael Hanselmann
HTTP_OK = 200
58 7eac4a4d Michael Hanselmann
HTTP_NOT_FOUND = 404
59 9279e986 Michael Hanselmann
HTTP_APP_JSON = "application/json"
60 9279e986 Michael Hanselmann
61 95ab4de9 David Knowles
REPLACE_DISK_PRI = "replace_on_primary"
62 95ab4de9 David Knowles
REPLACE_DISK_SECONDARY = "replace_on_secondary"
63 95ab4de9 David Knowles
REPLACE_DISK_CHG = "replace_new_secondary"
64 95ab4de9 David Knowles
REPLACE_DISK_AUTO = "replace_auto"
65 1068639f Michael Hanselmann
66 1068639f Michael Hanselmann
NODE_ROLE_DRAINED = "drained"
67 1068639f Michael Hanselmann
NODE_ROLE_MASTER_CANDIATE = "master-candidate"
68 1068639f Michael Hanselmann
NODE_ROLE_MASTER = "master"
69 1068639f Michael Hanselmann
NODE_ROLE_OFFLINE = "offline"
70 1068639f Michael Hanselmann
NODE_ROLE_REGULAR = "regular"
71 95ab4de9 David Knowles
72 63d5eb8a Michael Hanselmann
JOB_STATUS_QUEUED = "queued"
73 63d5eb8a Michael Hanselmann
JOB_STATUS_WAITLOCK = "waiting"
74 63d5eb8a Michael Hanselmann
JOB_STATUS_CANCELING = "canceling"
75 63d5eb8a Michael Hanselmann
JOB_STATUS_RUNNING = "running"
76 63d5eb8a Michael Hanselmann
JOB_STATUS_CANCELED = "canceled"
77 63d5eb8a Michael Hanselmann
JOB_STATUS_SUCCESS = "success"
78 63d5eb8a Michael Hanselmann
JOB_STATUS_ERROR = "error"
79 63d5eb8a Michael Hanselmann
JOB_STATUS_FINALIZED = frozenset([
80 63d5eb8a Michael Hanselmann
  JOB_STATUS_CANCELED,
81 63d5eb8a Michael Hanselmann
  JOB_STATUS_SUCCESS,
82 63d5eb8a Michael Hanselmann
  JOB_STATUS_ERROR,
83 63d5eb8a Michael Hanselmann
  ])
84 63d5eb8a Michael Hanselmann
JOB_STATUS_ALL = frozenset([
85 63d5eb8a Michael Hanselmann
  JOB_STATUS_QUEUED,
86 63d5eb8a Michael Hanselmann
  JOB_STATUS_WAITLOCK,
87 63d5eb8a Michael Hanselmann
  JOB_STATUS_CANCELING,
88 63d5eb8a Michael Hanselmann
  JOB_STATUS_RUNNING,
89 63d5eb8a Michael Hanselmann
  ]) | JOB_STATUS_FINALIZED
90 63d5eb8a Michael Hanselmann
91 8a47b447 Michael Hanselmann
# Internal constants
92 8a47b447 Michael Hanselmann
_REQ_DATA_VERSION_FIELD = "__version__"
93 8a47b447 Michael Hanselmann
_INST_CREATE_REQV1 = "instance-create-reqv1"
94 c744425f Michael Hanselmann
_INST_REINSTALL_REQV1 = "instance-reinstall-reqv1"
95 b7a1c816 Michael Hanselmann
_NODE_MIGRATE_REQV1 = "node-migrate-reqv1"
96 c97fcab8 Guido Trotter
_INST_NIC_PARAMS = frozenset(["mac", "ip", "mode", "link"])
97 48436b97 Michael Hanselmann
_INST_CREATE_V0_DISK_PARAMS = frozenset(["size"])
98 48436b97 Michael Hanselmann
_INST_CREATE_V0_PARAMS = frozenset([
99 48436b97 Michael Hanselmann
  "os", "pnode", "snode", "iallocator", "start", "ip_check", "name_check",
100 48436b97 Michael Hanselmann
  "hypervisor", "file_storage_dir", "file_driver", "dry_run",
101 48436b97 Michael Hanselmann
  ])
102 48436b97 Michael Hanselmann
_INST_CREATE_V0_DPARAMS = frozenset(["beparams", "hvparams"])
103 8a47b447 Michael Hanselmann
104 2a7c3583 Michael Hanselmann
# Older pycURL versions don't have all error constants
105 2a7c3583 Michael Hanselmann
try:
106 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT = pycurl.E_SSL_CACERT
107 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT_BADFILE = pycurl.E_SSL_CACERT_BADFILE
108 2a7c3583 Michael Hanselmann
except AttributeError:
109 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT = 60
110 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT_BADFILE = 77
111 2a7c3583 Michael Hanselmann
112 2a7c3583 Michael Hanselmann
_CURL_SSL_CERT_ERRORS = frozenset([
113 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT,
114 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT_BADFILE,
115 2a7c3583 Michael Hanselmann
  ])
116 2a7c3583 Michael Hanselmann
117 95ab4de9 David Knowles
118 95ab4de9 David Knowles
class Error(Exception):
119 95ab4de9 David Knowles
  """Base error class for this module.
120 95ab4de9 David Knowles

121 95ab4de9 David Knowles
  """
122 95ab4de9 David Knowles
  pass
123 95ab4de9 David Knowles
124 95ab4de9 David Knowles
125 95ab4de9 David Knowles
class CertificateError(Error):
126 95ab4de9 David Knowles
  """Raised when a problem is found with the SSL certificate.
127 95ab4de9 David Knowles

128 95ab4de9 David Knowles
  """
129 95ab4de9 David Knowles
  pass
130 95ab4de9 David Knowles
131 95ab4de9 David Knowles
132 95ab4de9 David Knowles
class GanetiApiError(Error):
133 95ab4de9 David Knowles
  """Generic error raised from Ganeti API.
134 95ab4de9 David Knowles

135 95ab4de9 David Knowles
  """
136 8a019a03 Michael Hanselmann
  def __init__(self, msg, code=None):
137 8a019a03 Michael Hanselmann
    Error.__init__(self, msg)
138 8a019a03 Michael Hanselmann
    self.code = code
139 95ab4de9 David Knowles
140 95ab4de9 David Knowles
141 2a7c3583 Michael Hanselmann
def UsesRapiClient(fn):
142 2a7c3583 Michael Hanselmann
  """Decorator for code using RAPI client to initialize pycURL.
143 9279e986 Michael Hanselmann

144 9279e986 Michael Hanselmann
  """
145 2a7c3583 Michael Hanselmann
  def wrapper(*args, **kwargs):
146 2a7c3583 Michael Hanselmann
    # curl_global_init(3) and curl_global_cleanup(3) must be called with only
147 2a7c3583 Michael Hanselmann
    # one thread running. This check is just a safety measure -- it doesn't
148 2a7c3583 Michael Hanselmann
    # cover all cases.
149 2a7c3583 Michael Hanselmann
    assert threading.activeCount() == 1, \
150 2a7c3583 Michael Hanselmann
           "Found active threads when initializing pycURL"
151 2a7c3583 Michael Hanselmann
152 2a7c3583 Michael Hanselmann
    pycurl.global_init(pycurl.GLOBAL_ALL)
153 2a7c3583 Michael Hanselmann
    try:
154 2a7c3583 Michael Hanselmann
      return fn(*args, **kwargs)
155 2a7c3583 Michael Hanselmann
    finally:
156 2a7c3583 Michael Hanselmann
      pycurl.global_cleanup()
157 2a7c3583 Michael Hanselmann
158 2a7c3583 Michael Hanselmann
  return wrapper
159 2a7c3583 Michael Hanselmann
160 2a7c3583 Michael Hanselmann
161 2a7c3583 Michael Hanselmann
def GenericCurlConfig(verbose=False, use_signal=False,
162 2a7c3583 Michael Hanselmann
                      use_curl_cabundle=False, cafile=None, capath=None,
163 2a7c3583 Michael Hanselmann
                      proxy=None, verify_hostname=False,
164 2a7c3583 Michael Hanselmann
                      connect_timeout=None, timeout=None,
165 2a7c3583 Michael Hanselmann
                      _pycurl_version_fn=pycurl.version_info):
166 2a7c3583 Michael Hanselmann
  """Curl configuration function generator.
167 2a7c3583 Michael Hanselmann

168 2a7c3583 Michael Hanselmann
  @type verbose: bool
169 2a7c3583 Michael Hanselmann
  @param verbose: Whether to set cURL to verbose mode
170 2a7c3583 Michael Hanselmann
  @type use_signal: bool
171 2a7c3583 Michael Hanselmann
  @param use_signal: Whether to allow cURL to use signals
172 2a7c3583 Michael Hanselmann
  @type use_curl_cabundle: bool
173 2a7c3583 Michael Hanselmann
  @param use_curl_cabundle: Whether to use cURL's default CA bundle
174 2a7c3583 Michael Hanselmann
  @type cafile: string
175 2a7c3583 Michael Hanselmann
  @param cafile: In which file we can find the certificates
176 2a7c3583 Michael Hanselmann
  @type capath: string
177 2a7c3583 Michael Hanselmann
  @param capath: In which directory we can find the certificates
178 2a7c3583 Michael Hanselmann
  @type proxy: string
179 2a7c3583 Michael Hanselmann
  @param proxy: Proxy to use, None for default behaviour and empty string for
180 2a7c3583 Michael Hanselmann
                disabling proxies (see curl_easy_setopt(3))
181 2a7c3583 Michael Hanselmann
  @type verify_hostname: bool
182 2a7c3583 Michael Hanselmann
  @param verify_hostname: Whether to verify the remote peer certificate's
183 2a7c3583 Michael Hanselmann
                          commonName
184 2a7c3583 Michael Hanselmann
  @type connect_timeout: number
185 2a7c3583 Michael Hanselmann
  @param connect_timeout: Timeout for establishing connection in seconds
186 2a7c3583 Michael Hanselmann
  @type timeout: number
187 2a7c3583 Michael Hanselmann
  @param timeout: Timeout for complete transfer in seconds (see
188 2a7c3583 Michael Hanselmann
                  curl_easy_setopt(3)).
189 9279e986 Michael Hanselmann

190 9279e986 Michael Hanselmann
  """
191 2a7c3583 Michael Hanselmann
  if use_curl_cabundle and (cafile or capath):
192 2a7c3583 Michael Hanselmann
    raise Error("Can not use default CA bundle when CA file or path is set")
193 9279e986 Michael Hanselmann
194 2a7c3583 Michael Hanselmann
  def _ConfigCurl(curl, logger):
195 2a7c3583 Michael Hanselmann
    """Configures a cURL object
196 9279e986 Michael Hanselmann

197 2a7c3583 Michael Hanselmann
    @type curl: pycurl.Curl
198 2a7c3583 Michael Hanselmann
    @param curl: cURL object
199 9279e986 Michael Hanselmann

200 9279e986 Michael Hanselmann
    """
201 2a7c3583 Michael Hanselmann
    logger.debug("Using cURL version %s", pycurl.version)
202 2a7c3583 Michael Hanselmann
203 2a7c3583 Michael Hanselmann
    # pycurl.version_info returns a tuple with information about the used
204 2a7c3583 Michael Hanselmann
    # version of libcurl. Item 5 is the SSL library linked to it.
205 2a7c3583 Michael Hanselmann
    # e.g.: (3, '7.18.0', 463360, 'x86_64-pc-linux-gnu', 1581, 'GnuTLS/2.0.4',
206 2a7c3583 Michael Hanselmann
    # 0, '1.2.3.3', ...)
207 2a7c3583 Michael Hanselmann
    sslver = _pycurl_version_fn()[5]
208 2a7c3583 Michael Hanselmann
    if not sslver:
209 2a7c3583 Michael Hanselmann
      raise Error("No SSL support in cURL")
210 2a7c3583 Michael Hanselmann
211 2a7c3583 Michael Hanselmann
    lcsslver = sslver.lower()
212 2a7c3583 Michael Hanselmann
    if lcsslver.startswith("openssl/"):
213 2a7c3583 Michael Hanselmann
      pass
214 2a7c3583 Michael Hanselmann
    elif lcsslver.startswith("gnutls/"):
215 2a7c3583 Michael Hanselmann
      if capath:
216 2a7c3583 Michael Hanselmann
        raise Error("cURL linked against GnuTLS has no support for a"
217 2a7c3583 Michael Hanselmann
                    " CA path (%s)" % (pycurl.version, ))
218 9279e986 Michael Hanselmann
    else:
219 2a7c3583 Michael Hanselmann
      raise NotImplementedError("cURL uses unsupported SSL version '%s'" %
220 2a7c3583 Michael Hanselmann
                                sslver)
221 2a7c3583 Michael Hanselmann
222 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.VERBOSE, verbose)
223 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.NOSIGNAL, not use_signal)
224 2a7c3583 Michael Hanselmann
225 2a7c3583 Michael Hanselmann
    # Whether to verify remote peer's CN
226 2a7c3583 Michael Hanselmann
    if verify_hostname:
227 2a7c3583 Michael Hanselmann
      # curl_easy_setopt(3): "When CURLOPT_SSL_VERIFYHOST is 2, that
228 2a7c3583 Michael Hanselmann
      # certificate must indicate that the server is the server to which you
229 2a7c3583 Michael Hanselmann
      # meant to connect, or the connection fails. [...] When the value is 1,
230 2a7c3583 Michael Hanselmann
      # the certificate must contain a Common Name field, but it doesn't matter
231 2a7c3583 Michael Hanselmann
      # what name it says. [...]"
232 2a7c3583 Michael Hanselmann
      curl.setopt(pycurl.SSL_VERIFYHOST, 2)
233 beba56ae Michael Hanselmann
    else:
234 2a7c3583 Michael Hanselmann
      curl.setopt(pycurl.SSL_VERIFYHOST, 0)
235 2a7c3583 Michael Hanselmann
236 2a7c3583 Michael Hanselmann
    if cafile or capath or use_curl_cabundle:
237 2a7c3583 Michael Hanselmann
      # Require certificates to be checked
238 2a7c3583 Michael Hanselmann
      curl.setopt(pycurl.SSL_VERIFYPEER, True)
239 2a7c3583 Michael Hanselmann
      if cafile:
240 2a7c3583 Michael Hanselmann
        curl.setopt(pycurl.CAINFO, str(cafile))
241 2a7c3583 Michael Hanselmann
      if capath:
242 2a7c3583 Michael Hanselmann
        curl.setopt(pycurl.CAPATH, str(capath))
243 2a7c3583 Michael Hanselmann
      # Not changing anything for using default CA bundle
244 2a7c3583 Michael Hanselmann
    else:
245 2a7c3583 Michael Hanselmann
      # Disable SSL certificate verification
246 2a7c3583 Michael Hanselmann
      curl.setopt(pycurl.SSL_VERIFYPEER, False)
247 9279e986 Michael Hanselmann
248 2a7c3583 Michael Hanselmann
    if proxy is not None:
249 2a7c3583 Michael Hanselmann
      curl.setopt(pycurl.PROXY, str(proxy))
250 9279e986 Michael Hanselmann
251 2a7c3583 Michael Hanselmann
    # Timeouts
252 2a7c3583 Michael Hanselmann
    if connect_timeout is not None:
253 2a7c3583 Michael Hanselmann
      curl.setopt(pycurl.CONNECTTIMEOUT, connect_timeout)
254 2a7c3583 Michael Hanselmann
    if timeout is not None:
255 2a7c3583 Michael Hanselmann
      curl.setopt(pycurl.TIMEOUT, timeout)
256 9279e986 Michael Hanselmann
257 2a7c3583 Michael Hanselmann
  return _ConfigCurl
258 9279e986 Michael Hanselmann
259 9279e986 Michael Hanselmann
260 54d4c13b Michael Hanselmann
class GanetiRapiClient(object): # pylint: disable-msg=R0904
261 95ab4de9 David Knowles
  """Ganeti RAPI client.
262 95ab4de9 David Knowles

263 95ab4de9 David Knowles
  """
264 95ab4de9 David Knowles
  USER_AGENT = "Ganeti RAPI Client"
265 d3844674 Michael Hanselmann
  _json_encoder = simplejson.JSONEncoder(sort_keys=True)
266 95ab4de9 David Knowles
267 9279e986 Michael Hanselmann
  def __init__(self, host, port=GANETI_RAPI_PORT,
268 2a7c3583 Michael Hanselmann
               username=None, password=None, logger=logging,
269 a5eba783 Michael Hanselmann
               curl_config_fn=None, curl_factory=None):
270 2a7c3583 Michael Hanselmann
    """Initializes this class.
271 95ab4de9 David Knowles

272 9279e986 Michael Hanselmann
    @type host: string
273 9279e986 Michael Hanselmann
    @param host: the ganeti cluster master to interact with
274 95ab4de9 David Knowles
    @type port: int
275 9279e986 Michael Hanselmann
    @param port: the port on which the RAPI is running (default is 5080)
276 9279e986 Michael Hanselmann
    @type username: string
277 95ab4de9 David Knowles
    @param username: the username to connect with
278 9279e986 Michael Hanselmann
    @type password: string
279 95ab4de9 David Knowles
    @param password: the password to connect with
280 2a7c3583 Michael Hanselmann
    @type curl_config_fn: callable
281 2a7c3583 Michael Hanselmann
    @param curl_config_fn: Function to configure C{pycurl.Curl} object
282 9279e986 Michael Hanselmann
    @param logger: Logging object
283 95ab4de9 David Knowles

284 95ab4de9 David Knowles
    """
285 a5eba783 Michael Hanselmann
    self._username = username
286 a5eba783 Michael Hanselmann
    self._password = password
287 9279e986 Michael Hanselmann
    self._logger = logger
288 a5eba783 Michael Hanselmann
    self._curl_config_fn = curl_config_fn
289 a5eba783 Michael Hanselmann
    self._curl_factory = curl_factory
290 95ab4de9 David Knowles
291 1a8337f2 Manuel Franceschini
    try:
292 1a8337f2 Manuel Franceschini
      socket.inet_pton(socket.AF_INET6, host)
293 1a8337f2 Manuel Franceschini
      address = "[%s]:%s" % (host, port)
294 1a8337f2 Manuel Franceschini
    except socket.error:
295 1a8337f2 Manuel Franceschini
      address = "%s:%s" % (host, port)
296 1a8337f2 Manuel Franceschini
297 1a8337f2 Manuel Franceschini
    self._base_url = "https://%s" % address
298 f2f88abf David Knowles
299 a5eba783 Michael Hanselmann
    if username is not None:
300 a5eba783 Michael Hanselmann
      if password is None:
301 a5eba783 Michael Hanselmann
        raise Error("Password not specified")
302 a5eba783 Michael Hanselmann
    elif password:
303 a5eba783 Michael Hanselmann
      raise Error("Specified password without username")
304 a5eba783 Michael Hanselmann
305 a5eba783 Michael Hanselmann
  def _CreateCurl(self):
306 a5eba783 Michael Hanselmann
    """Creates a cURL object.
307 a5eba783 Michael Hanselmann

308 a5eba783 Michael Hanselmann
    """
309 a5eba783 Michael Hanselmann
    # Create pycURL object if no factory is provided
310 a5eba783 Michael Hanselmann
    if self._curl_factory:
311 a5eba783 Michael Hanselmann
      curl = self._curl_factory()
312 a5eba783 Michael Hanselmann
    else:
313 2a7c3583 Michael Hanselmann
      curl = pycurl.Curl()
314 2a7c3583 Michael Hanselmann
315 2a7c3583 Michael Hanselmann
    # Default cURL settings
316 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.VERBOSE, False)
317 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.FOLLOWLOCATION, False)
318 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.MAXREDIRS, 5)
319 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.NOSIGNAL, True)
320 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.USERAGENT, self.USER_AGENT)
321 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.SSL_VERIFYHOST, 0)
322 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.SSL_VERIFYPEER, False)
323 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.HTTPHEADER, [
324 2a7c3583 Michael Hanselmann
      "Accept: %s" % HTTP_APP_JSON,
325 2a7c3583 Michael Hanselmann
      "Content-type: %s" % HTTP_APP_JSON,
326 2a7c3583 Michael Hanselmann
      ])
327 2a7c3583 Michael Hanselmann
328 a5eba783 Michael Hanselmann
    assert ((self._username is None and self._password is None) ^
329 a5eba783 Michael Hanselmann
            (self._username is not None and self._password is not None))
330 a5eba783 Michael Hanselmann
331 a5eba783 Michael Hanselmann
    if self._username:
332 a5eba783 Michael Hanselmann
      # Setup authentication
333 2a7c3583 Michael Hanselmann
      curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_BASIC)
334 a5eba783 Michael Hanselmann
      curl.setopt(pycurl.USERPWD,
335 a5eba783 Michael Hanselmann
                  str("%s:%s" % (self._username, self._password)))
336 9279e986 Michael Hanselmann
337 2a7c3583 Michael Hanselmann
    # Call external configuration function
338 a5eba783 Michael Hanselmann
    if self._curl_config_fn:
339 a5eba783 Michael Hanselmann
      self._curl_config_fn(curl, self._logger)
340 f2f88abf David Knowles
341 a5eba783 Michael Hanselmann
    return curl
342 95ab4de9 David Knowles
343 10f5ab6c Michael Hanselmann
  @staticmethod
344 10f5ab6c Michael Hanselmann
  def _EncodeQuery(query):
345 10f5ab6c Michael Hanselmann
    """Encode query values for RAPI URL.
346 10f5ab6c Michael Hanselmann

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

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

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

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

386 95ab4de9 David Knowles
    @rtype: str
387 95ab4de9 David Knowles
    @return: JSON-Decoded response
388 95ab4de9 David Knowles

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

392 95ab4de9 David Knowles
    """
393 ccd6b542 Michael Hanselmann
    assert path.startswith("/")
394 ccd6b542 Michael Hanselmann
395 a5eba783 Michael Hanselmann
    curl = self._CreateCurl()
396 2a7c3583 Michael Hanselmann
397 8306e0e4 Michael Hanselmann
    if content is not None:
398 d3844674 Michael Hanselmann
      encoded_content = self._json_encoder.encode(content)
399 d3844674 Michael Hanselmann
    else:
400 2a7c3583 Michael Hanselmann
      encoded_content = ""
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 a5eba783 Michael Hanselmann
    self._logger.debug("Sending request %s %s (content=%r)",
411 a5eba783 Michael Hanselmann
                       method, url, encoded_content)
412 2a7c3583 Michael Hanselmann
413 2a7c3583 Michael Hanselmann
    # Buffer for response
414 2a7c3583 Michael Hanselmann
    encoded_resp_body = StringIO()
415 f961e2ba Michael Hanselmann
416 2a7c3583 Michael Hanselmann
    # Configure cURL
417 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.CUSTOMREQUEST, str(method))
418 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.URL, str(url))
419 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.POSTFIELDS, str(encoded_content))
420 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.WRITEFUNCTION, encoded_resp_body.write)
421 9279e986 Michael Hanselmann
422 f2f88abf David Knowles
    try:
423 2a7c3583 Michael Hanselmann
      # Send request and wait for response
424 2a7c3583 Michael Hanselmann
      try:
425 2a7c3583 Michael Hanselmann
        curl.perform()
426 2a7c3583 Michael Hanselmann
      except pycurl.error, err:
427 2a7c3583 Michael Hanselmann
        if err.args[0] in _CURL_SSL_CERT_ERRORS:
428 2a7c3583 Michael Hanselmann
          raise CertificateError("SSL certificate error %s" % err)
429 2a7c3583 Michael Hanselmann
430 2a7c3583 Michael Hanselmann
        raise GanetiApiError(str(err))
431 2a7c3583 Michael Hanselmann
    finally:
432 2a7c3583 Michael Hanselmann
      # Reset settings to not keep references to large objects in memory
433 2a7c3583 Michael Hanselmann
      # between requests
434 2a7c3583 Michael Hanselmann
      curl.setopt(pycurl.POSTFIELDS, "")
435 2a7c3583 Michael Hanselmann
      curl.setopt(pycurl.WRITEFUNCTION, lambda _: None)
436 2a7c3583 Michael Hanselmann
437 2a7c3583 Michael Hanselmann
    # Get HTTP response code
438 2a7c3583 Michael Hanselmann
    http_code = curl.getinfo(pycurl.RESPONSE_CODE)
439 2a7c3583 Michael Hanselmann
440 2a7c3583 Michael Hanselmann
    # Was anything written to the response buffer?
441 2a7c3583 Michael Hanselmann
    if encoded_resp_body.tell():
442 2a7c3583 Michael Hanselmann
      response_content = simplejson.loads(encoded_resp_body.getvalue())
443 d3844674 Michael Hanselmann
    else:
444 d3844674 Michael Hanselmann
      response_content = None
445 95ab4de9 David Knowles
446 2a7c3583 Michael Hanselmann
    if http_code != HTTP_OK:
447 d3844674 Michael Hanselmann
      if isinstance(response_content, dict):
448 95ab4de9 David Knowles
        msg = ("%s %s: %s" %
449 d3844674 Michael Hanselmann
               (response_content["code"],
450 d3844674 Michael Hanselmann
                response_content["message"],
451 d3844674 Michael Hanselmann
                response_content["explain"]))
452 95ab4de9 David Knowles
      else:
453 d3844674 Michael Hanselmann
        msg = str(response_content)
454 d3844674 Michael Hanselmann
455 2a7c3583 Michael Hanselmann
      raise GanetiApiError(msg, code=http_code)
456 95ab4de9 David Knowles
457 d3844674 Michael Hanselmann
    return response_content
458 95ab4de9 David Knowles
459 95ab4de9 David Knowles
  def GetVersion(self):
460 cab667cc David Knowles
    """Gets the Remote API version running on the cluster.
461 95ab4de9 David Knowles

462 95ab4de9 David Knowles
    @rtype: int
463 f2f88abf David Knowles
    @return: Ganeti Remote API version
464 95ab4de9 David Knowles

465 95ab4de9 David Knowles
    """
466 768747ed Michael Hanselmann
    return self._SendRequest(HTTP_GET, "/version", None, None)
467 95ab4de9 David Knowles
468 7eac4a4d Michael Hanselmann
  def GetFeatures(self):
469 7eac4a4d Michael Hanselmann
    """Gets the list of optional features supported by RAPI server.
470 7eac4a4d Michael Hanselmann

471 7eac4a4d Michael Hanselmann
    @rtype: list
472 7eac4a4d Michael Hanselmann
    @return: List of optional features
473 7eac4a4d Michael Hanselmann

474 7eac4a4d Michael Hanselmann
    """
475 7eac4a4d Michael Hanselmann
    try:
476 7eac4a4d Michael Hanselmann
      return self._SendRequest(HTTP_GET, "/%s/features" % GANETI_RAPI_VERSION,
477 7eac4a4d Michael Hanselmann
                               None, None)
478 7eac4a4d Michael Hanselmann
    except GanetiApiError, err:
479 7eac4a4d Michael Hanselmann
      # Older RAPI servers don't support this resource
480 7eac4a4d Michael Hanselmann
      if err.code == HTTP_NOT_FOUND:
481 7eac4a4d Michael Hanselmann
        return []
482 7eac4a4d Michael Hanselmann
483 7eac4a4d Michael Hanselmann
      raise
484 7eac4a4d Michael Hanselmann
485 95ab4de9 David Knowles
  def GetOperatingSystems(self):
486 95ab4de9 David Knowles
    """Gets the Operating Systems running in the Ganeti cluster.
487 95ab4de9 David Knowles

488 95ab4de9 David Knowles
    @rtype: list of str
489 95ab4de9 David Knowles
    @return: operating systems
490 95ab4de9 David Knowles

491 95ab4de9 David Knowles
    """
492 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET, "/%s/os" % GANETI_RAPI_VERSION,
493 a198b2d9 Michael Hanselmann
                             None, None)
494 95ab4de9 David Knowles
495 95ab4de9 David Knowles
  def GetInfo(self):
496 95ab4de9 David Knowles
    """Gets info about the cluster.
497 95ab4de9 David Knowles

498 95ab4de9 David Knowles
    @rtype: dict
499 95ab4de9 David Knowles
    @return: information about the cluster
500 95ab4de9 David Knowles

501 95ab4de9 David Knowles
    """
502 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET, "/%s/info" % GANETI_RAPI_VERSION,
503 a198b2d9 Michael Hanselmann
                             None, None)
504 95ab4de9 David Knowles
505 54d4c13b Michael Hanselmann
  def RedistributeConfig(self):
506 54d4c13b Michael Hanselmann
    """Tells the cluster to redistribute its configuration files.
507 54d4c13b Michael Hanselmann

508 d914c76f Simeon Miteff
    @rtype: string
509 54d4c13b Michael Hanselmann
    @return: job id
510 54d4c13b Michael Hanselmann

511 54d4c13b Michael Hanselmann
    """
512 54d4c13b Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
513 54d4c13b Michael Hanselmann
                             "/%s/redistribute-config" % GANETI_RAPI_VERSION,
514 54d4c13b Michael Hanselmann
                             None, None)
515 54d4c13b Michael Hanselmann
516 62e999a5 Michael Hanselmann
  def ModifyCluster(self, **kwargs):
517 62e999a5 Michael Hanselmann
    """Modifies cluster parameters.
518 62e999a5 Michael Hanselmann

519 62e999a5 Michael Hanselmann
    More details for parameters can be found in the RAPI documentation.
520 62e999a5 Michael Hanselmann

521 98805538 Michael Hanselmann
    @rtype: string
522 62e999a5 Michael Hanselmann
    @return: job id
523 62e999a5 Michael Hanselmann

524 62e999a5 Michael Hanselmann
    """
525 62e999a5 Michael Hanselmann
    body = kwargs
526 62e999a5 Michael Hanselmann
527 62e999a5 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
528 62e999a5 Michael Hanselmann
                             "/%s/modify" % GANETI_RAPI_VERSION, None, body)
529 62e999a5 Michael Hanselmann
530 95ab4de9 David Knowles
  def GetClusterTags(self):
531 95ab4de9 David Knowles
    """Gets the cluster tags.
532 95ab4de9 David Knowles

533 95ab4de9 David Knowles
    @rtype: list of str
534 95ab4de9 David Knowles
    @return: cluster tags
535 95ab4de9 David Knowles

536 95ab4de9 David Knowles
    """
537 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET, "/%s/tags" % GANETI_RAPI_VERSION,
538 a198b2d9 Michael Hanselmann
                             None, None)
539 95ab4de9 David Knowles
540 95ab4de9 David Knowles
  def AddClusterTags(self, tags, dry_run=False):
541 95ab4de9 David Knowles
    """Adds tags to the cluster.
542 95ab4de9 David Knowles

543 95ab4de9 David Knowles
    @type tags: list of str
544 95ab4de9 David Knowles
    @param tags: tags to add to the cluster
545 95ab4de9 David Knowles
    @type dry_run: bool
546 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
547 95ab4de9 David Knowles

548 98805538 Michael Hanselmann
    @rtype: string
549 95ab4de9 David Knowles
    @return: job id
550 95ab4de9 David Knowles

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

562 95ab4de9 David Knowles
    @type tags: list of str
563 95ab4de9 David Knowles
    @param tags: tags to delete
564 95ab4de9 David Knowles
    @type dry_run: bool
565 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
566 d914c76f Simeon Miteff
    @rtype: string
567 d914c76f Simeon Miteff
    @return: job id
568 95ab4de9 David Knowles

569 95ab4de9 David Knowles
    """
570 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
571 95ab4de9 David Knowles
    if dry_run:
572 95ab4de9 David Knowles
      query.append(("dry-run", 1))
573 95ab4de9 David Knowles
574 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE, "/%s/tags" % GANETI_RAPI_VERSION,
575 a198b2d9 Michael Hanselmann
                             query, None)
576 95ab4de9 David Knowles
577 95ab4de9 David Knowles
  def GetInstances(self, bulk=False):
578 95ab4de9 David Knowles
    """Gets information about instances on the cluster.
579 95ab4de9 David Knowles

580 95ab4de9 David Knowles
    @type bulk: bool
581 95ab4de9 David Knowles
    @param bulk: whether to return all information about all instances
582 95ab4de9 David Knowles

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

586 95ab4de9 David Knowles
    """
587 95ab4de9 David Knowles
    query = []
588 95ab4de9 David Knowles
    if bulk:
589 95ab4de9 David Knowles
      query.append(("bulk", 1))
590 95ab4de9 David Knowles
591 a198b2d9 Michael Hanselmann
    instances = self._SendRequest(HTTP_GET,
592 a198b2d9 Michael Hanselmann
                                  "/%s/instances" % GANETI_RAPI_VERSION,
593 a198b2d9 Michael Hanselmann
                                  query, None)
594 95ab4de9 David Knowles
    if bulk:
595 95ab4de9 David Knowles
      return instances
596 95ab4de9 David Knowles
    else:
597 95ab4de9 David Knowles
      return [i["id"] for i in instances]
598 95ab4de9 David Knowles
599 591e5103 Michael Hanselmann
  def GetInstance(self, instance):
600 95ab4de9 David Knowles
    """Gets information about an instance.
601 95ab4de9 David Knowles

602 95ab4de9 David Knowles
    @type instance: str
603 95ab4de9 David Knowles
    @param instance: instance whose info to return
604 95ab4de9 David Knowles

605 95ab4de9 David Knowles
    @rtype: dict
606 95ab4de9 David Knowles
    @return: info about the instance
607 95ab4de9 David Knowles

608 95ab4de9 David Knowles
    """
609 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
610 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s" %
611 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, None)
612 95ab4de9 David Knowles
613 591e5103 Michael Hanselmann
  def GetInstanceInfo(self, instance, static=None):
614 591e5103 Michael Hanselmann
    """Gets information about an instance.
615 591e5103 Michael Hanselmann

616 591e5103 Michael Hanselmann
    @type instance: string
617 591e5103 Michael Hanselmann
    @param instance: Instance name
618 591e5103 Michael Hanselmann
    @rtype: string
619 591e5103 Michael Hanselmann
    @return: Job ID
620 591e5103 Michael Hanselmann

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

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

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

651 98805538 Michael Hanselmann
    @rtype: string
652 95ab4de9 David Knowles
    @return: job id
653 95ab4de9 David Knowles

654 95ab4de9 David Knowles
    """
655 95ab4de9 David Knowles
    query = []
656 8a47b447 Michael Hanselmann
657 8a47b447 Michael Hanselmann
    if kwargs.get("dry_run"):
658 95ab4de9 David Knowles
      query.append(("dry-run", 1))
659 95ab4de9 David Knowles
660 8a47b447 Michael Hanselmann
    if _INST_CREATE_REQV1 in self.GetFeatures():
661 8a47b447 Michael Hanselmann
      # All required fields for request data version 1
662 8a47b447 Michael Hanselmann
      body = {
663 8a47b447 Michael Hanselmann
        _REQ_DATA_VERSION_FIELD: 1,
664 8a47b447 Michael Hanselmann
        "mode": mode,
665 8a47b447 Michael Hanselmann
        "name": name,
666 8a47b447 Michael Hanselmann
        "disk_template": disk_template,
667 8a47b447 Michael Hanselmann
        "disks": disks,
668 8a47b447 Michael Hanselmann
        "nics": nics,
669 8a47b447 Michael Hanselmann
        }
670 8a47b447 Michael Hanselmann
671 8a47b447 Michael Hanselmann
      conflicts = set(kwargs.iterkeys()) & set(body.iterkeys())
672 8a47b447 Michael Hanselmann
      if conflicts:
673 8a47b447 Michael Hanselmann
        raise GanetiApiError("Required fields can not be specified as"
674 8a47b447 Michael Hanselmann
                             " keywords: %s" % ", ".join(conflicts))
675 8a47b447 Michael Hanselmann
676 8a47b447 Michael Hanselmann
      body.update((key, value) for key, value in kwargs.iteritems()
677 8a47b447 Michael Hanselmann
                  if key != "dry_run")
678 8a47b447 Michael Hanselmann
    else:
679 9a8ae794 Michael Hanselmann
      raise GanetiApiError("Server does not support new-style (version 1)"
680 9a8ae794 Michael Hanselmann
                           " instance creation requests")
681 8a47b447 Michael Hanselmann
682 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_POST, "/%s/instances" % GANETI_RAPI_VERSION,
683 8a47b447 Michael Hanselmann
                             query, body)
684 95ab4de9 David Knowles
685 95ab4de9 David Knowles
  def DeleteInstance(self, instance, dry_run=False):
686 95ab4de9 David Knowles
    """Deletes an instance.
687 95ab4de9 David Knowles

688 95ab4de9 David Knowles
    @type instance: str
689 95ab4de9 David Knowles
    @param instance: the instance to delete
690 95ab4de9 David Knowles

691 98805538 Michael Hanselmann
    @rtype: string
692 cab667cc David Knowles
    @return: job id
693 cab667cc David Knowles

694 95ab4de9 David Knowles
    """
695 95ab4de9 David Knowles
    query = []
696 95ab4de9 David Knowles
    if dry_run:
697 95ab4de9 David Knowles
      query.append(("dry-run", 1))
698 95ab4de9 David Knowles
699 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE,
700 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s" %
701 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
702 95ab4de9 David Knowles
703 3b7158ef Michael Hanselmann
  def ModifyInstance(self, instance, **kwargs):
704 3b7158ef Michael Hanselmann
    """Modifies an instance.
705 3b7158ef Michael Hanselmann

706 3b7158ef Michael Hanselmann
    More details for parameters can be found in the RAPI documentation.
707 3b7158ef Michael Hanselmann

708 3b7158ef Michael Hanselmann
    @type instance: string
709 3b7158ef Michael Hanselmann
    @param instance: Instance name
710 98805538 Michael Hanselmann
    @rtype: string
711 3b7158ef Michael Hanselmann
    @return: job id
712 3b7158ef Michael Hanselmann

713 3b7158ef Michael Hanselmann
    """
714 3b7158ef Michael Hanselmann
    body = kwargs
715 3b7158ef Michael Hanselmann
716 3b7158ef Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
717 3b7158ef Michael Hanselmann
                             ("/%s/instances/%s/modify" %
718 3b7158ef Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, body)
719 3b7158ef Michael Hanselmann
720 b680c8be Michael Hanselmann
  def ActivateInstanceDisks(self, instance, ignore_size=None):
721 b680c8be Michael Hanselmann
    """Activates an instance's disks.
722 b680c8be Michael Hanselmann

723 b680c8be Michael Hanselmann
    @type instance: string
724 b680c8be Michael Hanselmann
    @param instance: Instance name
725 b680c8be Michael Hanselmann
    @type ignore_size: bool
726 b680c8be Michael Hanselmann
    @param ignore_size: Whether to ignore recorded size
727 d914c76f Simeon Miteff
    @rtype: string
728 b680c8be Michael Hanselmann
    @return: job id
729 b680c8be Michael Hanselmann

730 b680c8be Michael Hanselmann
    """
731 b680c8be Michael Hanselmann
    query = []
732 b680c8be Michael Hanselmann
    if ignore_size:
733 b680c8be Michael Hanselmann
      query.append(("ignore_size", 1))
734 b680c8be Michael Hanselmann
735 b680c8be Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
736 b680c8be Michael Hanselmann
                             ("/%s/instances/%s/activate-disks" %
737 b680c8be Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
738 b680c8be Michael Hanselmann
739 b680c8be Michael Hanselmann
  def DeactivateInstanceDisks(self, instance):
740 b680c8be Michael Hanselmann
    """Deactivates an instance's disks.
741 b680c8be Michael Hanselmann

742 b680c8be Michael Hanselmann
    @type instance: string
743 b680c8be Michael Hanselmann
    @param instance: Instance name
744 d914c76f Simeon Miteff
    @rtype: string
745 b680c8be Michael Hanselmann
    @return: job id
746 b680c8be Michael Hanselmann

747 b680c8be Michael Hanselmann
    """
748 b680c8be Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
749 b680c8be Michael Hanselmann
                             ("/%s/instances/%s/deactivate-disks" %
750 b680c8be Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, None)
751 b680c8be Michael Hanselmann
752 e23881ed Michael Hanselmann
  def GrowInstanceDisk(self, instance, disk, amount, wait_for_sync=None):
753 e23881ed Michael Hanselmann
    """Grows a disk of an instance.
754 e23881ed Michael Hanselmann

755 e23881ed Michael Hanselmann
    More details for parameters can be found in the RAPI documentation.
756 e23881ed Michael Hanselmann

757 e23881ed Michael Hanselmann
    @type instance: string
758 e23881ed Michael Hanselmann
    @param instance: Instance name
759 e23881ed Michael Hanselmann
    @type disk: integer
760 e23881ed Michael Hanselmann
    @param disk: Disk index
761 e23881ed Michael Hanselmann
    @type amount: integer
762 e23881ed Michael Hanselmann
    @param amount: Grow disk by this amount (MiB)
763 e23881ed Michael Hanselmann
    @type wait_for_sync: bool
764 e23881ed Michael Hanselmann
    @param wait_for_sync: Wait for disk to synchronize
765 98805538 Michael Hanselmann
    @rtype: string
766 e23881ed Michael Hanselmann
    @return: job id
767 e23881ed Michael Hanselmann

768 e23881ed Michael Hanselmann
    """
769 e23881ed Michael Hanselmann
    body = {
770 e23881ed Michael Hanselmann
      "amount": amount,
771 e23881ed Michael Hanselmann
      }
772 e23881ed Michael Hanselmann
773 e23881ed Michael Hanselmann
    if wait_for_sync is not None:
774 e23881ed Michael Hanselmann
      body["wait_for_sync"] = wait_for_sync
775 e23881ed Michael Hanselmann
776 e23881ed Michael Hanselmann
    return self._SendRequest(HTTP_POST,
777 e23881ed Michael Hanselmann
                             ("/%s/instances/%s/disk/%s/grow" %
778 e23881ed Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance, disk)),
779 e23881ed Michael Hanselmann
                             None, body)
780 e23881ed Michael Hanselmann
781 95ab4de9 David Knowles
  def GetInstanceTags(self, instance):
782 95ab4de9 David Knowles
    """Gets tags for an instance.
783 95ab4de9 David Knowles

784 95ab4de9 David Knowles
    @type instance: str
785 95ab4de9 David Knowles
    @param instance: instance whose tags to return
786 95ab4de9 David Knowles

787 95ab4de9 David Knowles
    @rtype: list of str
788 95ab4de9 David Knowles
    @return: tags for the instance
789 95ab4de9 David Knowles

790 95ab4de9 David Knowles
    """
791 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
792 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/tags" %
793 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, None)
794 95ab4de9 David Knowles
795 95ab4de9 David Knowles
  def AddInstanceTags(self, instance, tags, dry_run=False):
796 95ab4de9 David Knowles
    """Adds tags to an instance.
797 95ab4de9 David Knowles

798 95ab4de9 David Knowles
    @type instance: str
799 95ab4de9 David Knowles
    @param instance: instance to add tags to
800 95ab4de9 David Knowles
    @type tags: list of str
801 95ab4de9 David Knowles
    @param tags: tags to add to the instance
802 95ab4de9 David Knowles
    @type dry_run: bool
803 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
804 95ab4de9 David Knowles

805 98805538 Michael Hanselmann
    @rtype: string
806 95ab4de9 David Knowles
    @return: job id
807 95ab4de9 David Knowles

808 95ab4de9 David Knowles
    """
809 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
810 95ab4de9 David Knowles
    if dry_run:
811 95ab4de9 David Knowles
      query.append(("dry-run", 1))
812 95ab4de9 David Knowles
813 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
814 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/tags" %
815 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
816 95ab4de9 David Knowles
817 95ab4de9 David Knowles
  def DeleteInstanceTags(self, instance, tags, dry_run=False):
818 95ab4de9 David Knowles
    """Deletes tags from an instance.
819 95ab4de9 David Knowles

820 95ab4de9 David Knowles
    @type instance: str
821 95ab4de9 David Knowles
    @param instance: instance to delete tags from
822 95ab4de9 David Knowles
    @type tags: list of str
823 95ab4de9 David Knowles
    @param tags: tags to delete
824 95ab4de9 David Knowles
    @type dry_run: bool
825 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
826 d914c76f Simeon Miteff
    @rtype: string
827 d914c76f Simeon Miteff
    @return: job id
828 95ab4de9 David Knowles

829 95ab4de9 David Knowles
    """
830 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
831 95ab4de9 David Knowles
    if dry_run:
832 95ab4de9 David Knowles
      query.append(("dry-run", 1))
833 95ab4de9 David Knowles
834 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE,
835 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/tags" %
836 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
837 95ab4de9 David Knowles
838 95ab4de9 David Knowles
  def RebootInstance(self, instance, reboot_type=None, ignore_secondaries=None,
839 95ab4de9 David Knowles
                     dry_run=False):
840 95ab4de9 David Knowles
    """Reboots an instance.
841 95ab4de9 David Knowles

842 95ab4de9 David Knowles
    @type instance: str
843 95ab4de9 David Knowles
    @param instance: instance to rebot
844 95ab4de9 David Knowles
    @type reboot_type: str
845 95ab4de9 David Knowles
    @param reboot_type: one of: hard, soft, full
846 95ab4de9 David Knowles
    @type ignore_secondaries: bool
847 95ab4de9 David Knowles
    @param ignore_secondaries: if True, ignores errors for the secondary node
848 95ab4de9 David Knowles
        while re-assembling disks (in hard-reboot mode only)
849 95ab4de9 David Knowles
    @type dry_run: bool
850 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
851 d914c76f Simeon Miteff
    @rtype: string
852 d914c76f Simeon Miteff
    @return: job id
853 95ab4de9 David Knowles

854 95ab4de9 David Knowles
    """
855 95ab4de9 David Knowles
    query = []
856 95ab4de9 David Knowles
    if reboot_type:
857 95ab4de9 David Knowles
      query.append(("type", reboot_type))
858 95ab4de9 David Knowles
    if ignore_secondaries is not None:
859 95ab4de9 David Knowles
      query.append(("ignore_secondaries", ignore_secondaries))
860 95ab4de9 David Knowles
    if dry_run:
861 95ab4de9 David Knowles
      query.append(("dry-run", 1))
862 95ab4de9 David Knowles
863 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_POST,
864 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/reboot" %
865 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
866 95ab4de9 David Knowles
867 2ba39b8f Iustin Pop
  def ShutdownInstance(self, instance, dry_run=False, no_remember=False):
868 95ab4de9 David Knowles
    """Shuts down an instance.
869 95ab4de9 David Knowles

870 95ab4de9 David Knowles
    @type instance: str
871 95ab4de9 David Knowles
    @param instance: the instance to shut down
872 95ab4de9 David Knowles
    @type dry_run: bool
873 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
874 2ba39b8f Iustin Pop
    @type no_remember: bool
875 2ba39b8f Iustin Pop
    @param no_remember: if true, will not record the state change
876 d914c76f Simeon Miteff
    @rtype: string
877 d914c76f Simeon Miteff
    @return: job id
878 95ab4de9 David Knowles

879 95ab4de9 David Knowles
    """
880 95ab4de9 David Knowles
    query = []
881 95ab4de9 David Knowles
    if dry_run:
882 95ab4de9 David Knowles
      query.append(("dry-run", 1))
883 2ba39b8f Iustin Pop
    if no_remember:
884 2ba39b8f Iustin Pop
      query.append(("no-remember", 1))
885 95ab4de9 David Knowles
886 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
887 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/shutdown" %
888 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
889 95ab4de9 David Knowles
890 2ba39b8f Iustin Pop
  def StartupInstance(self, instance, dry_run=False, no_remember=False):
891 95ab4de9 David Knowles
    """Starts up an instance.
892 95ab4de9 David Knowles

893 95ab4de9 David Knowles
    @type instance: str
894 95ab4de9 David Knowles
    @param instance: the instance to start up
895 95ab4de9 David Knowles
    @type dry_run: bool
896 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
897 2ba39b8f Iustin Pop
    @type no_remember: bool
898 2ba39b8f Iustin Pop
    @param no_remember: if true, will not record the state change
899 d914c76f Simeon Miteff
    @rtype: string
900 d914c76f Simeon Miteff
    @return: job id
901 95ab4de9 David Knowles

902 95ab4de9 David Knowles
    """
903 95ab4de9 David Knowles
    query = []
904 95ab4de9 David Knowles
    if dry_run:
905 95ab4de9 David Knowles
      query.append(("dry-run", 1))
906 2ba39b8f Iustin Pop
    if no_remember:
907 2ba39b8f Iustin Pop
      query.append(("no-remember", 1))
908 95ab4de9 David Knowles
909 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
910 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/startup" %
911 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
912 95ab4de9 David Knowles
913 c744425f Michael Hanselmann
  def ReinstallInstance(self, instance, os=None, no_startup=False,
914 c744425f Michael Hanselmann
                        osparams=None):
915 95ab4de9 David Knowles
    """Reinstalls an instance.
916 95ab4de9 David Knowles

917 95ab4de9 David Knowles
    @type instance: str
918 fcee9675 David Knowles
    @param instance: The instance to reinstall
919 fcee9675 David Knowles
    @type os: str or None
920 fcee9675 David Knowles
    @param os: The operating system to reinstall. If None, the instance's
921 fcee9675 David Knowles
        current operating system will be installed again
922 95ab4de9 David Knowles
    @type no_startup: bool
923 fcee9675 David Knowles
    @param no_startup: Whether to start the instance automatically
924 d914c76f Simeon Miteff
    @rtype: string
925 d914c76f Simeon Miteff
    @return: job id
926 95ab4de9 David Knowles

927 95ab4de9 David Knowles
    """
928 c744425f Michael Hanselmann
    if _INST_REINSTALL_REQV1 in self.GetFeatures():
929 c744425f Michael Hanselmann
      body = {
930 c744425f Michael Hanselmann
        "start": not no_startup,
931 c744425f Michael Hanselmann
        }
932 c744425f Michael Hanselmann
      if os is not None:
933 c744425f Michael Hanselmann
        body["os"] = os
934 c744425f Michael Hanselmann
      if osparams is not None:
935 c744425f Michael Hanselmann
        body["osparams"] = osparams
936 c744425f Michael Hanselmann
      return self._SendRequest(HTTP_POST,
937 c744425f Michael Hanselmann
                               ("/%s/instances/%s/reinstall" %
938 c744425f Michael Hanselmann
                                (GANETI_RAPI_VERSION, instance)), None, body)
939 c744425f Michael Hanselmann
940 c744425f Michael Hanselmann
    # Use old request format
941 c744425f Michael Hanselmann
    if osparams:
942 c744425f Michael Hanselmann
      raise GanetiApiError("Server does not support specifying OS parameters"
943 c744425f Michael Hanselmann
                           " for instance reinstallation")
944 c744425f Michael Hanselmann
945 fcee9675 David Knowles
    query = []
946 fcee9675 David Knowles
    if os:
947 fcee9675 David Knowles
      query.append(("os", os))
948 95ab4de9 David Knowles
    if no_startup:
949 95ab4de9 David Knowles
      query.append(("nostartup", 1))
950 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_POST,
951 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/reinstall" %
952 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
953 95ab4de9 David Knowles
954 bfc2002f Michael Hanselmann
  def ReplaceInstanceDisks(self, instance, disks=None, mode=REPLACE_DISK_AUTO,
955 cfc03c54 Michael Hanselmann
                           remote_node=None, iallocator=None, dry_run=False):
956 95ab4de9 David Knowles
    """Replaces disks on an instance.
957 95ab4de9 David Knowles

958 95ab4de9 David Knowles
    @type instance: str
959 95ab4de9 David Knowles
    @param instance: instance whose disks to replace
960 bfc2002f Michael Hanselmann
    @type disks: list of ints
961 bfc2002f Michael Hanselmann
    @param disks: Indexes of disks to replace
962 95ab4de9 David Knowles
    @type mode: str
963 cfc03c54 Michael Hanselmann
    @param mode: replacement mode to use (defaults to replace_auto)
964 95ab4de9 David Knowles
    @type remote_node: str or None
965 95ab4de9 David Knowles
    @param remote_node: new secondary node to use (for use with
966 cfc03c54 Michael Hanselmann
        replace_new_secondary mode)
967 95ab4de9 David Knowles
    @type iallocator: str or None
968 95ab4de9 David Knowles
    @param iallocator: instance allocator plugin to use (for use with
969 cfc03c54 Michael Hanselmann
                       replace_auto mode)
970 95ab4de9 David Knowles
    @type dry_run: bool
971 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
972 95ab4de9 David Knowles

973 98805538 Michael Hanselmann
    @rtype: string
974 95ab4de9 David Knowles
    @return: job id
975 95ab4de9 David Knowles

976 95ab4de9 David Knowles
    """
977 cfc03c54 Michael Hanselmann
    query = [
978 cfc03c54 Michael Hanselmann
      ("mode", mode),
979 cfc03c54 Michael Hanselmann
      ]
980 95ab4de9 David Knowles
981 bfc2002f Michael Hanselmann
    if disks:
982 bfc2002f Michael Hanselmann
      query.append(("disks", ",".join(str(idx) for idx in disks)))
983 bfc2002f Michael Hanselmann
984 bfc2002f Michael Hanselmann
    if remote_node:
985 95ab4de9 David Knowles
      query.append(("remote_node", remote_node))
986 95ab4de9 David Knowles
987 bfc2002f Michael Hanselmann
    if iallocator:
988 bfc2002f Michael Hanselmann
      query.append(("iallocator", iallocator))
989 bfc2002f Michael Hanselmann
990 95ab4de9 David Knowles
    if dry_run:
991 95ab4de9 David Knowles
      query.append(("dry-run", 1))
992 95ab4de9 David Knowles
993 95ab4de9 David Knowles
    return self._SendRequest(HTTP_POST,
994 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/replace-disks" %
995 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
996 95ab4de9 David Knowles
997 ebeb600f Michael Hanselmann
  def PrepareExport(self, instance, mode):
998 ebeb600f Michael Hanselmann
    """Prepares an instance for an export.
999 ebeb600f Michael Hanselmann

1000 ebeb600f Michael Hanselmann
    @type instance: string
1001 ebeb600f Michael Hanselmann
    @param instance: Instance name
1002 ebeb600f Michael Hanselmann
    @type mode: string
1003 ebeb600f Michael Hanselmann
    @param mode: Export mode
1004 ebeb600f Michael Hanselmann
    @rtype: string
1005 ebeb600f Michael Hanselmann
    @return: Job ID
1006 ebeb600f Michael Hanselmann

1007 ebeb600f Michael Hanselmann
    """
1008 ebeb600f Michael Hanselmann
    query = [("mode", mode)]
1009 ebeb600f Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1010 ebeb600f Michael Hanselmann
                             ("/%s/instances/%s/prepare-export" %
1011 ebeb600f Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
1012 ebeb600f Michael Hanselmann
1013 ebeb600f Michael Hanselmann
  def ExportInstance(self, instance, mode, destination, shutdown=None,
1014 ebeb600f Michael Hanselmann
                     remove_instance=None,
1015 ebeb600f Michael Hanselmann
                     x509_key_name=None, destination_x509_ca=None):
1016 ebeb600f Michael Hanselmann
    """Exports an instance.
1017 ebeb600f Michael Hanselmann

1018 ebeb600f Michael Hanselmann
    @type instance: string
1019 ebeb600f Michael Hanselmann
    @param instance: Instance name
1020 ebeb600f Michael Hanselmann
    @type mode: string
1021 ebeb600f Michael Hanselmann
    @param mode: Export mode
1022 ebeb600f Michael Hanselmann
    @rtype: string
1023 ebeb600f Michael Hanselmann
    @return: Job ID
1024 ebeb600f Michael Hanselmann

1025 ebeb600f Michael Hanselmann
    """
1026 ebeb600f Michael Hanselmann
    body = {
1027 ebeb600f Michael Hanselmann
      "destination": destination,
1028 ebeb600f Michael Hanselmann
      "mode": mode,
1029 ebeb600f Michael Hanselmann
      }
1030 ebeb600f Michael Hanselmann
1031 ebeb600f Michael Hanselmann
    if shutdown is not None:
1032 ebeb600f Michael Hanselmann
      body["shutdown"] = shutdown
1033 ebeb600f Michael Hanselmann
1034 ebeb600f Michael Hanselmann
    if remove_instance is not None:
1035 ebeb600f Michael Hanselmann
      body["remove_instance"] = remove_instance
1036 ebeb600f Michael Hanselmann
1037 ebeb600f Michael Hanselmann
    if x509_key_name is not None:
1038 ebeb600f Michael Hanselmann
      body["x509_key_name"] = x509_key_name
1039 ebeb600f Michael Hanselmann
1040 ebeb600f Michael Hanselmann
    if destination_x509_ca is not None:
1041 ebeb600f Michael Hanselmann
      body["destination_x509_ca"] = destination_x509_ca
1042 ebeb600f Michael Hanselmann
1043 ebeb600f Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1044 ebeb600f Michael Hanselmann
                             ("/%s/instances/%s/export" %
1045 ebeb600f Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, body)
1046 ebeb600f Michael Hanselmann
1047 e0ac6ce6 Michael Hanselmann
  def MigrateInstance(self, instance, mode=None, cleanup=None):
1048 c63eb9c0 Michael Hanselmann
    """Migrates an instance.
1049 e0ac6ce6 Michael Hanselmann

1050 e0ac6ce6 Michael Hanselmann
    @type instance: string
1051 e0ac6ce6 Michael Hanselmann
    @param instance: Instance name
1052 e0ac6ce6 Michael Hanselmann
    @type mode: string
1053 e0ac6ce6 Michael Hanselmann
    @param mode: Migration mode
1054 e0ac6ce6 Michael Hanselmann
    @type cleanup: bool
1055 e0ac6ce6 Michael Hanselmann
    @param cleanup: Whether to clean up a previously failed migration
1056 d914c76f Simeon Miteff
    @rtype: string
1057 d914c76f Simeon Miteff
    @return: job id
1058 e0ac6ce6 Michael Hanselmann

1059 e0ac6ce6 Michael Hanselmann
    """
1060 e0ac6ce6 Michael Hanselmann
    body = {}
1061 e0ac6ce6 Michael Hanselmann
1062 e0ac6ce6 Michael Hanselmann
    if mode is not None:
1063 e0ac6ce6 Michael Hanselmann
      body["mode"] = mode
1064 e0ac6ce6 Michael Hanselmann
1065 e0ac6ce6 Michael Hanselmann
    if cleanup is not None:
1066 e0ac6ce6 Michael Hanselmann
      body["cleanup"] = cleanup
1067 e0ac6ce6 Michael Hanselmann
1068 e0ac6ce6 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1069 e0ac6ce6 Michael Hanselmann
                             ("/%s/instances/%s/migrate" %
1070 e0ac6ce6 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, body)
1071 e0ac6ce6 Michael Hanselmann
1072 d654aae1 Michael Hanselmann
  def RenameInstance(self, instance, new_name, ip_check=None, name_check=None):
1073 d654aae1 Michael Hanselmann
    """Changes the name of an instance.
1074 d654aae1 Michael Hanselmann

1075 d654aae1 Michael Hanselmann
    @type instance: string
1076 d654aae1 Michael Hanselmann
    @param instance: Instance name
1077 d654aae1 Michael Hanselmann
    @type new_name: string
1078 d654aae1 Michael Hanselmann
    @param new_name: New instance name
1079 d654aae1 Michael Hanselmann
    @type ip_check: bool
1080 d654aae1 Michael Hanselmann
    @param ip_check: Whether to ensure instance's IP address is inactive
1081 d654aae1 Michael Hanselmann
    @type name_check: bool
1082 d654aae1 Michael Hanselmann
    @param name_check: Whether to ensure instance's name is resolvable
1083 d914c76f Simeon Miteff
    @rtype: string
1084 d914c76f Simeon Miteff
    @return: job id
1085 d654aae1 Michael Hanselmann

1086 d654aae1 Michael Hanselmann
    """
1087 d654aae1 Michael Hanselmann
    body = {
1088 d654aae1 Michael Hanselmann
      "new_name": new_name,
1089 d654aae1 Michael Hanselmann
      }
1090 d654aae1 Michael Hanselmann
1091 d654aae1 Michael Hanselmann
    if ip_check is not None:
1092 d654aae1 Michael Hanselmann
      body["ip_check"] = ip_check
1093 d654aae1 Michael Hanselmann
1094 d654aae1 Michael Hanselmann
    if name_check is not None:
1095 d654aae1 Michael Hanselmann
      body["name_check"] = name_check
1096 d654aae1 Michael Hanselmann
1097 d654aae1 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1098 d654aae1 Michael Hanselmann
                             ("/%s/instances/%s/rename" %
1099 d654aae1 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, body)
1100 d654aae1 Michael Hanselmann
1101 b82d4c5e Michael Hanselmann
  def GetInstanceConsole(self, instance):
1102 b82d4c5e Michael Hanselmann
    """Request information for connecting to instance's console.
1103 b82d4c5e Michael Hanselmann

1104 b82d4c5e Michael Hanselmann
    @type instance: string
1105 b82d4c5e Michael Hanselmann
    @param instance: Instance name
1106 d914c76f Simeon Miteff
    @rtype: dict
1107 d914c76f Simeon Miteff
    @return: dictionary containing information about instance's console
1108 b82d4c5e Michael Hanselmann

1109 b82d4c5e Michael Hanselmann
    """
1110 b82d4c5e Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1111 b82d4c5e Michael Hanselmann
                             ("/%s/instances/%s/console" %
1112 b82d4c5e Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, None)
1113 b82d4c5e Michael Hanselmann
1114 95ab4de9 David Knowles
  def GetJobs(self):
1115 95ab4de9 David Knowles
    """Gets all jobs for the cluster.
1116 95ab4de9 David Knowles

1117 95ab4de9 David Knowles
    @rtype: list of int
1118 95ab4de9 David Knowles
    @return: job ids for the cluster
1119 95ab4de9 David Knowles

1120 95ab4de9 David Knowles
    """
1121 768747ed Michael Hanselmann
    return [int(j["id"])
1122 a198b2d9 Michael Hanselmann
            for j in self._SendRequest(HTTP_GET,
1123 a198b2d9 Michael Hanselmann
                                       "/%s/jobs" % GANETI_RAPI_VERSION,
1124 a198b2d9 Michael Hanselmann
                                       None, None)]
1125 95ab4de9 David Knowles
1126 95ab4de9 David Knowles
  def GetJobStatus(self, job_id):
1127 95ab4de9 David Knowles
    """Gets the status of a job.
1128 95ab4de9 David Knowles

1129 98805538 Michael Hanselmann
    @type job_id: string
1130 95ab4de9 David Knowles
    @param job_id: job id whose status to query
1131 95ab4de9 David Knowles

1132 95ab4de9 David Knowles
    @rtype: dict
1133 95ab4de9 David Knowles
    @return: job status
1134 95ab4de9 David Knowles

1135 95ab4de9 David Knowles
    """
1136 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1137 a198b2d9 Michael Hanselmann
                             "/%s/jobs/%s" % (GANETI_RAPI_VERSION, job_id),
1138 a198b2d9 Michael Hanselmann
                             None, None)
1139 95ab4de9 David Knowles
1140 16c13387 Theo Van Dinter
  def WaitForJobCompletion(self, job_id, period=5, retries=-1):
1141 16c13387 Theo Van Dinter
    """Polls cluster for job status until completion.
1142 16c13387 Theo Van Dinter

1143 dde0e97c Michael Hanselmann
    Completion is defined as any of the following states listed in
1144 dde0e97c Michael Hanselmann
    L{JOB_STATUS_FINALIZED}.
1145 16c13387 Theo Van Dinter

1146 cfda0e48 Iustin Pop
    @type job_id: string
1147 16c13387 Theo Van Dinter
    @param job_id: job id to watch
1148 16c13387 Theo Van Dinter
    @type period: int
1149 16c13387 Theo Van Dinter
    @param period: how often to poll for status (optional, default 5s)
1150 16c13387 Theo Van Dinter
    @type retries: int
1151 16c13387 Theo Van Dinter
    @param retries: how many time to poll before giving up
1152 16c13387 Theo Van Dinter
                    (optional, default -1 means unlimited)
1153 16c13387 Theo Van Dinter

1154 16c13387 Theo Van Dinter
    @rtype: bool
1155 dde0e97c Michael Hanselmann
    @return: C{True} if job succeeded or C{False} if failed/status timeout
1156 dde0e97c Michael Hanselmann
    @deprecated: It is recommended to use L{WaitForJobChange} wherever
1157 dde0e97c Michael Hanselmann
      possible; L{WaitForJobChange} returns immediately after a job changed and
1158 dde0e97c Michael Hanselmann
      does not use polling
1159 cfda0e48 Iustin Pop

1160 16c13387 Theo Van Dinter
    """
1161 16c13387 Theo Van Dinter
    while retries != 0:
1162 16c13387 Theo Van Dinter
      job_result = self.GetJobStatus(job_id)
1163 dde0e97c Michael Hanselmann
1164 dde0e97c Michael Hanselmann
      if job_result and job_result["status"] == JOB_STATUS_SUCCESS:
1165 16c13387 Theo Van Dinter
        return True
1166 dde0e97c Michael Hanselmann
      elif not job_result or job_result["status"] in JOB_STATUS_FINALIZED:
1167 dde0e97c Michael Hanselmann
        return False
1168 dde0e97c Michael Hanselmann
1169 dde0e97c Michael Hanselmann
      if period:
1170 dde0e97c Michael Hanselmann
        time.sleep(period)
1171 dde0e97c Michael Hanselmann
1172 16c13387 Theo Van Dinter
      if retries > 0:
1173 16c13387 Theo Van Dinter
        retries -= 1
1174 dde0e97c Michael Hanselmann
1175 16c13387 Theo Van Dinter
    return False
1176 16c13387 Theo Van Dinter
1177 d9b67f70 Michael Hanselmann
  def WaitForJobChange(self, job_id, fields, prev_job_info, prev_log_serial):
1178 d9b67f70 Michael Hanselmann
    """Waits for job changes.
1179 d9b67f70 Michael Hanselmann

1180 98805538 Michael Hanselmann
    @type job_id: string
1181 d9b67f70 Michael Hanselmann
    @param job_id: Job ID for which to wait
1182 d914c76f Simeon Miteff
    @return: C{None} if no changes have been detected and a dict with two keys,
1183 d914c76f Simeon Miteff
      C{job_info} and C{log_entries} otherwise.
1184 d914c76f Simeon Miteff
    @rtype: dict
1185 d9b67f70 Michael Hanselmann

1186 d9b67f70 Michael Hanselmann
    """
1187 d9b67f70 Michael Hanselmann
    body = {
1188 d9b67f70 Michael Hanselmann
      "fields": fields,
1189 d9b67f70 Michael Hanselmann
      "previous_job_info": prev_job_info,
1190 d9b67f70 Michael Hanselmann
      "previous_log_serial": prev_log_serial,
1191 d9b67f70 Michael Hanselmann
      }
1192 d9b67f70 Michael Hanselmann
1193 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1194 a198b2d9 Michael Hanselmann
                             "/%s/jobs/%s/wait" % (GANETI_RAPI_VERSION, job_id),
1195 a198b2d9 Michael Hanselmann
                             None, body)
1196 d9b67f70 Michael Hanselmann
1197 cf9ada49 Michael Hanselmann
  def CancelJob(self, job_id, dry_run=False):
1198 cf9ada49 Michael Hanselmann
    """Cancels a job.
1199 95ab4de9 David Knowles

1200 98805538 Michael Hanselmann
    @type job_id: string
1201 95ab4de9 David Knowles
    @param job_id: id of the job to delete
1202 95ab4de9 David Knowles
    @type dry_run: bool
1203 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1204 d914c76f Simeon Miteff
    @rtype: tuple
1205 d914c76f Simeon Miteff
    @return: tuple containing the result, and a message (bool, string)
1206 95ab4de9 David Knowles

1207 95ab4de9 David Knowles
    """
1208 95ab4de9 David Knowles
    query = []
1209 95ab4de9 David Knowles
    if dry_run:
1210 95ab4de9 David Knowles
      query.append(("dry-run", 1))
1211 95ab4de9 David Knowles
1212 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE,
1213 a198b2d9 Michael Hanselmann
                             "/%s/jobs/%s" % (GANETI_RAPI_VERSION, job_id),
1214 a198b2d9 Michael Hanselmann
                             query, None)
1215 95ab4de9 David Knowles
1216 95ab4de9 David Knowles
  def GetNodes(self, bulk=False):
1217 95ab4de9 David Knowles
    """Gets all nodes in the cluster.
1218 95ab4de9 David Knowles

1219 95ab4de9 David Knowles
    @type bulk: bool
1220 95ab4de9 David Knowles
    @param bulk: whether to return all information about all instances
1221 95ab4de9 David Knowles

1222 95ab4de9 David Knowles
    @rtype: list of dict or str
1223 95ab4de9 David Knowles
    @return: if bulk is true, info about nodes in the cluster,
1224 95ab4de9 David Knowles
        else list of nodes in the cluster
1225 95ab4de9 David Knowles

1226 95ab4de9 David Knowles
    """
1227 95ab4de9 David Knowles
    query = []
1228 95ab4de9 David Knowles
    if bulk:
1229 95ab4de9 David Knowles
      query.append(("bulk", 1))
1230 95ab4de9 David Knowles
1231 a198b2d9 Michael Hanselmann
    nodes = self._SendRequest(HTTP_GET, "/%s/nodes" % GANETI_RAPI_VERSION,
1232 a198b2d9 Michael Hanselmann
                              query, None)
1233 95ab4de9 David Knowles
    if bulk:
1234 95ab4de9 David Knowles
      return nodes
1235 95ab4de9 David Knowles
    else:
1236 95ab4de9 David Knowles
      return [n["id"] for n in nodes]
1237 95ab4de9 David Knowles
1238 591e5103 Michael Hanselmann
  def GetNode(self, node):
1239 95ab4de9 David Knowles
    """Gets information about a node.
1240 95ab4de9 David Knowles

1241 95ab4de9 David Knowles
    @type node: str
1242 95ab4de9 David Knowles
    @param node: node whose info to return
1243 95ab4de9 David Knowles

1244 95ab4de9 David Knowles
    @rtype: dict
1245 95ab4de9 David Knowles
    @return: info about the node
1246 95ab4de9 David Knowles

1247 95ab4de9 David Knowles
    """
1248 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1249 a198b2d9 Michael Hanselmann
                             "/%s/nodes/%s" % (GANETI_RAPI_VERSION, node),
1250 a198b2d9 Michael Hanselmann
                             None, None)
1251 95ab4de9 David Knowles
1252 95ab4de9 David Knowles
  def EvacuateNode(self, node, iallocator=None, remote_node=None,
1253 941b9309 Iustin Pop
                   dry_run=False, early_release=False):
1254 95ab4de9 David Knowles
    """Evacuates instances from a Ganeti node.
1255 95ab4de9 David Knowles

1256 95ab4de9 David Knowles
    @type node: str
1257 95ab4de9 David Knowles
    @param node: node to evacuate
1258 95ab4de9 David Knowles
    @type iallocator: str or None
1259 95ab4de9 David Knowles
    @param iallocator: instance allocator to use
1260 95ab4de9 David Knowles
    @type remote_node: str
1261 95ab4de9 David Knowles
    @param remote_node: node to evaucate to
1262 95ab4de9 David Knowles
    @type dry_run: bool
1263 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1264 941b9309 Iustin Pop
    @type early_release: bool
1265 941b9309 Iustin Pop
    @param early_release: whether to enable parallelization
1266 95ab4de9 David Knowles

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

1272 941b9309 Iustin Pop
    @raises GanetiApiError: if an iallocator and remote_node are both
1273 941b9309 Iustin Pop
        specified
1274 95ab4de9 David Knowles

1275 95ab4de9 David Knowles
    """
1276 95ab4de9 David Knowles
    if iallocator and remote_node:
1277 cfc03c54 Michael Hanselmann
      raise GanetiApiError("Only one of iallocator or remote_node can be used")
1278 95ab4de9 David Knowles
1279 cfc03c54 Michael Hanselmann
    query = []
1280 95ab4de9 David Knowles
    if iallocator:
1281 95ab4de9 David Knowles
      query.append(("iallocator", iallocator))
1282 95ab4de9 David Knowles
    if remote_node:
1283 95ab4de9 David Knowles
      query.append(("remote_node", remote_node))
1284 95ab4de9 David Knowles
    if dry_run:
1285 95ab4de9 David Knowles
      query.append(("dry-run", 1))
1286 941b9309 Iustin Pop
    if early_release:
1287 941b9309 Iustin Pop
      query.append(("early_release", 1))
1288 95ab4de9 David Knowles
1289 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_POST,
1290 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/evacuate" %
1291 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1292 95ab4de9 David Knowles
1293 b7a1c816 Michael Hanselmann
  def MigrateNode(self, node, mode=None, dry_run=False, iallocator=None,
1294 b7a1c816 Michael Hanselmann
                  target_node=None):
1295 95ab4de9 David Knowles
    """Migrates all primary instances from a node.
1296 95ab4de9 David Knowles

1297 95ab4de9 David Knowles
    @type node: str
1298 95ab4de9 David Knowles
    @param node: node to migrate
1299 1f334d96 Iustin Pop
    @type mode: string
1300 1f334d96 Iustin Pop
    @param mode: if passed, it will overwrite the live migration type,
1301 1f334d96 Iustin Pop
        otherwise the hypervisor default will be used
1302 95ab4de9 David Knowles
    @type dry_run: bool
1303 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1304 b7a1c816 Michael Hanselmann
    @type iallocator: string
1305 b7a1c816 Michael Hanselmann
    @param iallocator: instance allocator to use
1306 b7a1c816 Michael Hanselmann
    @type target_node: string
1307 b7a1c816 Michael Hanselmann
    @param target_node: Target node for shared-storage instances
1308 95ab4de9 David Knowles

1309 98805538 Michael Hanselmann
    @rtype: string
1310 95ab4de9 David Knowles
    @return: job id
1311 95ab4de9 David Knowles

1312 95ab4de9 David Knowles
    """
1313 95ab4de9 David Knowles
    query = []
1314 95ab4de9 David Knowles
    if dry_run:
1315 95ab4de9 David Knowles
      query.append(("dry-run", 1))
1316 95ab4de9 David Knowles
1317 b7a1c816 Michael Hanselmann
    if _NODE_MIGRATE_REQV1 in self.GetFeatures():
1318 b7a1c816 Michael Hanselmann
      body = {}
1319 b7a1c816 Michael Hanselmann
1320 b7a1c816 Michael Hanselmann
      if mode is not None:
1321 b7a1c816 Michael Hanselmann
        body["mode"] = mode
1322 b7a1c816 Michael Hanselmann
      if iallocator is not None:
1323 b7a1c816 Michael Hanselmann
        body["iallocator"] = iallocator
1324 b7a1c816 Michael Hanselmann
      if target_node is not None:
1325 b7a1c816 Michael Hanselmann
        body["target_node"] = target_node
1326 b7a1c816 Michael Hanselmann
1327 b7a1c816 Michael Hanselmann
      assert len(query) <= 1
1328 b7a1c816 Michael Hanselmann
1329 b7a1c816 Michael Hanselmann
      return self._SendRequest(HTTP_POST,
1330 b7a1c816 Michael Hanselmann
                               ("/%s/nodes/%s/migrate" %
1331 b7a1c816 Michael Hanselmann
                                (GANETI_RAPI_VERSION, node)), query, body)
1332 b7a1c816 Michael Hanselmann
    else:
1333 b7a1c816 Michael Hanselmann
      # Use old request format
1334 b7a1c816 Michael Hanselmann
      if target_node is not None:
1335 b7a1c816 Michael Hanselmann
        raise GanetiApiError("Server does not support specifying target node"
1336 b7a1c816 Michael Hanselmann
                             " for node migration")
1337 b7a1c816 Michael Hanselmann
1338 b7a1c816 Michael Hanselmann
      if mode is not None:
1339 b7a1c816 Michael Hanselmann
        query.append(("mode", mode))
1340 b7a1c816 Michael Hanselmann
1341 b7a1c816 Michael Hanselmann
      return self._SendRequest(HTTP_POST,
1342 b7a1c816 Michael Hanselmann
                               ("/%s/nodes/%s/migrate" %
1343 b7a1c816 Michael Hanselmann
                                (GANETI_RAPI_VERSION, node)), query, None)
1344 95ab4de9 David Knowles
1345 95ab4de9 David Knowles
  def GetNodeRole(self, node):
1346 95ab4de9 David Knowles
    """Gets the current role for a node.
1347 95ab4de9 David Knowles

1348 95ab4de9 David Knowles
    @type node: str
1349 95ab4de9 David Knowles
    @param node: node whose role to return
1350 95ab4de9 David Knowles

1351 95ab4de9 David Knowles
    @rtype: str
1352 95ab4de9 David Knowles
    @return: the current role for a node
1353 95ab4de9 David Knowles

1354 95ab4de9 David Knowles
    """
1355 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1356 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/role" %
1357 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), None, None)
1358 95ab4de9 David Knowles
1359 95ab4de9 David Knowles
  def SetNodeRole(self, node, role, force=False):
1360 95ab4de9 David Knowles
    """Sets the role for a node.
1361 95ab4de9 David Knowles

1362 95ab4de9 David Knowles
    @type node: str
1363 95ab4de9 David Knowles
    @param node: the node whose role to set
1364 95ab4de9 David Knowles
    @type role: str
1365 95ab4de9 David Knowles
    @param role: the role to set for the node
1366 95ab4de9 David Knowles
    @type force: bool
1367 95ab4de9 David Knowles
    @param force: whether to force the role change
1368 95ab4de9 David Knowles

1369 98805538 Michael Hanselmann
    @rtype: string
1370 95ab4de9 David Knowles
    @return: job id
1371 95ab4de9 David Knowles

1372 95ab4de9 David Knowles
    """
1373 1068639f Michael Hanselmann
    query = [
1374 1068639f Michael Hanselmann
      ("force", force),
1375 1068639f Michael Hanselmann
      ]
1376 cfc03c54 Michael Hanselmann
1377 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1378 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/role" %
1379 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, role)
1380 95ab4de9 David Knowles
1381 95ab4de9 David Knowles
  def GetNodeStorageUnits(self, node, storage_type, output_fields):
1382 95ab4de9 David Knowles
    """Gets the storage units for a node.
1383 95ab4de9 David Knowles

1384 95ab4de9 David Knowles
    @type node: str
1385 95ab4de9 David Knowles
    @param node: the node whose storage units to return
1386 95ab4de9 David Knowles
    @type storage_type: str
1387 95ab4de9 David Knowles
    @param storage_type: storage type whose units to return
1388 95ab4de9 David Knowles
    @type output_fields: str
1389 95ab4de9 David Knowles
    @param output_fields: storage type fields to return
1390 95ab4de9 David Knowles

1391 98805538 Michael Hanselmann
    @rtype: string
1392 95ab4de9 David Knowles
    @return: job id where results can be retrieved
1393 95ab4de9 David Knowles

1394 95ab4de9 David Knowles
    """
1395 cfc03c54 Michael Hanselmann
    query = [
1396 cfc03c54 Michael Hanselmann
      ("storage_type", storage_type),
1397 cfc03c54 Michael Hanselmann
      ("output_fields", output_fields),
1398 cfc03c54 Michael Hanselmann
      ]
1399 95ab4de9 David Knowles
1400 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1401 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/storage" %
1402 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1403 95ab4de9 David Knowles
1404 fde28316 Michael Hanselmann
  def ModifyNodeStorageUnits(self, node, storage_type, name, allocatable=None):
1405 95ab4de9 David Knowles
    """Modifies parameters of storage units on the node.
1406 95ab4de9 David Knowles

1407 95ab4de9 David Knowles
    @type node: str
1408 95ab4de9 David Knowles
    @param node: node whose storage units to modify
1409 95ab4de9 David Knowles
    @type storage_type: str
1410 95ab4de9 David Knowles
    @param storage_type: storage type whose units to modify
1411 95ab4de9 David Knowles
    @type name: str
1412 95ab4de9 David Knowles
    @param name: name of the storage unit
1413 fde28316 Michael Hanselmann
    @type allocatable: bool or None
1414 fde28316 Michael Hanselmann
    @param allocatable: Whether to set the "allocatable" flag on the storage
1415 fde28316 Michael Hanselmann
                        unit (None=no modification, True=set, False=unset)
1416 95ab4de9 David Knowles

1417 98805538 Michael Hanselmann
    @rtype: string
1418 95ab4de9 David Knowles
    @return: job id
1419 95ab4de9 David Knowles

1420 95ab4de9 David Knowles
    """
1421 95ab4de9 David Knowles
    query = [
1422 cfc03c54 Michael Hanselmann
      ("storage_type", storage_type),
1423 cfc03c54 Michael Hanselmann
      ("name", name),
1424 cfc03c54 Michael Hanselmann
      ]
1425 cfc03c54 Michael Hanselmann
1426 fde28316 Michael Hanselmann
    if allocatable is not None:
1427 fde28316 Michael Hanselmann
      query.append(("allocatable", allocatable))
1428 fde28316 Michael Hanselmann
1429 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1430 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/storage/modify" %
1431 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1432 95ab4de9 David Knowles
1433 95ab4de9 David Knowles
  def RepairNodeStorageUnits(self, node, storage_type, name):
1434 95ab4de9 David Knowles
    """Repairs a storage unit on the node.
1435 95ab4de9 David Knowles

1436 95ab4de9 David Knowles
    @type node: str
1437 95ab4de9 David Knowles
    @param node: node whose storage units to repair
1438 95ab4de9 David Knowles
    @type storage_type: str
1439 95ab4de9 David Knowles
    @param storage_type: storage type to repair
1440 95ab4de9 David Knowles
    @type name: str
1441 95ab4de9 David Knowles
    @param name: name of the storage unit to repair
1442 95ab4de9 David Knowles

1443 98805538 Michael Hanselmann
    @rtype: string
1444 95ab4de9 David Knowles
    @return: job id
1445 95ab4de9 David Knowles

1446 95ab4de9 David Knowles
    """
1447 cfc03c54 Michael Hanselmann
    query = [
1448 cfc03c54 Michael Hanselmann
      ("storage_type", storage_type),
1449 cfc03c54 Michael Hanselmann
      ("name", name),
1450 cfc03c54 Michael Hanselmann
      ]
1451 95ab4de9 David Knowles
1452 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1453 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/storage/repair" %
1454 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1455 95ab4de9 David Knowles
1456 95ab4de9 David Knowles
  def GetNodeTags(self, node):
1457 95ab4de9 David Knowles
    """Gets the tags for a node.
1458 95ab4de9 David Knowles

1459 95ab4de9 David Knowles
    @type node: str
1460 95ab4de9 David Knowles
    @param node: node whose tags to return
1461 95ab4de9 David Knowles

1462 95ab4de9 David Knowles
    @rtype: list of str
1463 95ab4de9 David Knowles
    @return: tags for the node
1464 95ab4de9 David Knowles

1465 95ab4de9 David Knowles
    """
1466 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1467 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/tags" %
1468 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), None, None)
1469 95ab4de9 David Knowles
1470 95ab4de9 David Knowles
  def AddNodeTags(self, node, tags, dry_run=False):
1471 95ab4de9 David Knowles
    """Adds tags to a node.
1472 95ab4de9 David Knowles

1473 95ab4de9 David Knowles
    @type node: str
1474 95ab4de9 David Knowles
    @param node: node to add tags to
1475 95ab4de9 David Knowles
    @type tags: list of str
1476 95ab4de9 David Knowles
    @param tags: tags to add to the node
1477 95ab4de9 David Knowles
    @type dry_run: bool
1478 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1479 95ab4de9 David Knowles

1480 98805538 Michael Hanselmann
    @rtype: string
1481 95ab4de9 David Knowles
    @return: job id
1482 95ab4de9 David Knowles

1483 95ab4de9 David Knowles
    """
1484 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
1485 95ab4de9 David Knowles
    if dry_run:
1486 95ab4de9 David Knowles
      query.append(("dry-run", 1))
1487 95ab4de9 David Knowles
1488 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1489 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/tags" %
1490 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, tags)
1491 95ab4de9 David Knowles
1492 95ab4de9 David Knowles
  def DeleteNodeTags(self, node, tags, dry_run=False):
1493 95ab4de9 David Knowles
    """Delete tags from a node.
1494 95ab4de9 David Knowles

1495 95ab4de9 David Knowles
    @type node: str
1496 95ab4de9 David Knowles
    @param node: node to remove tags from
1497 95ab4de9 David Knowles
    @type tags: list of str
1498 95ab4de9 David Knowles
    @param tags: tags to remove from the node
1499 95ab4de9 David Knowles
    @type dry_run: bool
1500 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1501 95ab4de9 David Knowles

1502 98805538 Michael Hanselmann
    @rtype: string
1503 95ab4de9 David Knowles
    @return: job id
1504 95ab4de9 David Knowles

1505 95ab4de9 David Knowles
    """
1506 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
1507 95ab4de9 David Knowles
    if dry_run:
1508 95ab4de9 David Knowles
      query.append(("dry-run", 1))
1509 95ab4de9 David Knowles
1510 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE,
1511 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/tags" %
1512 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1513 a268af8d Adeodato Simo
1514 a268af8d Adeodato Simo
  def GetGroups(self, bulk=False):
1515 a268af8d Adeodato Simo
    """Gets all node groups in the cluster.
1516 a268af8d Adeodato Simo

1517 a268af8d Adeodato Simo
    @type bulk: bool
1518 a268af8d Adeodato Simo
    @param bulk: whether to return all information about the groups
1519 a268af8d Adeodato Simo

1520 a268af8d Adeodato Simo
    @rtype: list of dict or str
1521 a268af8d Adeodato Simo
    @return: if bulk is true, a list of dictionaries with info about all node
1522 a268af8d Adeodato Simo
        groups in the cluster, else a list of names of those node groups
1523 a268af8d Adeodato Simo

1524 a268af8d Adeodato Simo
    """
1525 a268af8d Adeodato Simo
    query = []
1526 a268af8d Adeodato Simo
    if bulk:
1527 a268af8d Adeodato Simo
      query.append(("bulk", 1))
1528 a268af8d Adeodato Simo
1529 a268af8d Adeodato Simo
    groups = self._SendRequest(HTTP_GET, "/%s/groups" % GANETI_RAPI_VERSION,
1530 a268af8d Adeodato Simo
                               query, None)
1531 a268af8d Adeodato Simo
    if bulk:
1532 a268af8d Adeodato Simo
      return groups
1533 a268af8d Adeodato Simo
    else:
1534 a268af8d Adeodato Simo
      return [g["name"] for g in groups]
1535 a268af8d Adeodato Simo
1536 a268af8d Adeodato Simo
  def GetGroup(self, group):
1537 a268af8d Adeodato Simo
    """Gets information about a node group.
1538 a268af8d Adeodato Simo

1539 a268af8d Adeodato Simo
    @type group: str
1540 a268af8d Adeodato Simo
    @param group: name of the node group whose info to return
1541 a268af8d Adeodato Simo

1542 a268af8d Adeodato Simo
    @rtype: dict
1543 a268af8d Adeodato Simo
    @return: info about the node group
1544 a268af8d Adeodato Simo

1545 a268af8d Adeodato Simo
    """
1546 a268af8d Adeodato Simo
    return self._SendRequest(HTTP_GET,
1547 a268af8d Adeodato Simo
                             "/%s/groups/%s" % (GANETI_RAPI_VERSION, group),
1548 a268af8d Adeodato Simo
                             None, None)
1549 a268af8d Adeodato Simo
1550 90e99856 Adeodato Simo
  def CreateGroup(self, name, alloc_policy=None, dry_run=False):
1551 a268af8d Adeodato Simo
    """Creates a new node group.
1552 a268af8d Adeodato Simo

1553 a268af8d Adeodato Simo
    @type name: str
1554 a268af8d Adeodato Simo
    @param name: the name of node group to create
1555 90e99856 Adeodato Simo
    @type alloc_policy: str
1556 90e99856 Adeodato Simo
    @param alloc_policy: the desired allocation policy for the group, if any
1557 a268af8d Adeodato Simo
    @type dry_run: bool
1558 a268af8d Adeodato Simo
    @param dry_run: whether to peform a dry run
1559 a268af8d Adeodato Simo

1560 98805538 Michael Hanselmann
    @rtype: string
1561 a268af8d Adeodato Simo
    @return: job id
1562 a268af8d Adeodato Simo

1563 a268af8d Adeodato Simo
    """
1564 a268af8d Adeodato Simo
    query = []
1565 a268af8d Adeodato Simo
    if dry_run:
1566 a268af8d Adeodato Simo
      query.append(("dry-run", 1))
1567 a268af8d Adeodato Simo
1568 a268af8d Adeodato Simo
    body = {
1569 a268af8d Adeodato Simo
      "name": name,
1570 90e99856 Adeodato Simo
      "alloc_policy": alloc_policy
1571 a268af8d Adeodato Simo
      }
1572 a268af8d Adeodato Simo
1573 a268af8d Adeodato Simo
    return self._SendRequest(HTTP_POST, "/%s/groups" % GANETI_RAPI_VERSION,
1574 a268af8d Adeodato Simo
                             query, body)
1575 a268af8d Adeodato Simo
1576 f18fab7d Adeodato Simo
  def ModifyGroup(self, group, **kwargs):
1577 f18fab7d Adeodato Simo
    """Modifies a node group.
1578 f18fab7d Adeodato Simo

1579 f18fab7d Adeodato Simo
    More details for parameters can be found in the RAPI documentation.
1580 f18fab7d Adeodato Simo

1581 f18fab7d Adeodato Simo
    @type group: string
1582 f18fab7d Adeodato Simo
    @param group: Node group name
1583 98805538 Michael Hanselmann
    @rtype: string
1584 f18fab7d Adeodato Simo
    @return: job id
1585 f18fab7d Adeodato Simo

1586 f18fab7d Adeodato Simo
    """
1587 f18fab7d Adeodato Simo
    return self._SendRequest(HTTP_PUT,
1588 f18fab7d Adeodato Simo
                             ("/%s/groups/%s/modify" %
1589 f18fab7d Adeodato Simo
                              (GANETI_RAPI_VERSION, group)), None, kwargs)
1590 f18fab7d Adeodato Simo
1591 a268af8d Adeodato Simo
  def DeleteGroup(self, group, dry_run=False):
1592 a268af8d Adeodato Simo
    """Deletes a node group.
1593 a268af8d Adeodato Simo

1594 a268af8d Adeodato Simo
    @type group: str
1595 a268af8d Adeodato Simo
    @param group: the node group to delete
1596 a268af8d Adeodato Simo
    @type dry_run: bool
1597 a268af8d Adeodato Simo
    @param dry_run: whether to peform a dry run
1598 a268af8d Adeodato Simo

1599 98805538 Michael Hanselmann
    @rtype: string
1600 a268af8d Adeodato Simo
    @return: job id
1601 a268af8d Adeodato Simo

1602 a268af8d Adeodato Simo
    """
1603 a268af8d Adeodato Simo
    query = []
1604 a268af8d Adeodato Simo
    if dry_run:
1605 a268af8d Adeodato Simo
      query.append(("dry-run", 1))
1606 a268af8d Adeodato Simo
1607 a268af8d Adeodato Simo
    return self._SendRequest(HTTP_DELETE,
1608 a268af8d Adeodato Simo
                             ("/%s/groups/%s" %
1609 a268af8d Adeodato Simo
                              (GANETI_RAPI_VERSION, group)), query, None)
1610 a268af8d Adeodato Simo
1611 a268af8d Adeodato Simo
  def RenameGroup(self, group, new_name):
1612 a268af8d Adeodato Simo
    """Changes the name of a node group.
1613 a268af8d Adeodato Simo

1614 a268af8d Adeodato Simo
    @type group: string
1615 a268af8d Adeodato Simo
    @param group: Node group name
1616 a268af8d Adeodato Simo
    @type new_name: string
1617 a268af8d Adeodato Simo
    @param new_name: New node group name
1618 a268af8d Adeodato Simo

1619 98805538 Michael Hanselmann
    @rtype: string
1620 a268af8d Adeodato Simo
    @return: job id
1621 a268af8d Adeodato Simo

1622 a268af8d Adeodato Simo
    """
1623 a268af8d Adeodato Simo
    body = {
1624 a268af8d Adeodato Simo
      "new_name": new_name,
1625 a268af8d Adeodato Simo
      }
1626 a268af8d Adeodato Simo
1627 a268af8d Adeodato Simo
    return self._SendRequest(HTTP_PUT,
1628 a268af8d Adeodato Simo
                             ("/%s/groups/%s/rename" %
1629 a268af8d Adeodato Simo
                              (GANETI_RAPI_VERSION, group)), None, body)
1630 4245446f Adeodato Simo
1631 4245446f Adeodato Simo
  def AssignGroupNodes(self, group, nodes, force=False, dry_run=False):
1632 4245446f Adeodato Simo
    """Assigns nodes to a group.
1633 4245446f Adeodato Simo

1634 4245446f Adeodato Simo
    @type group: string
1635 4245446f Adeodato Simo
    @param group: Node gropu name
1636 4245446f Adeodato Simo
    @type nodes: list of strings
1637 4245446f Adeodato Simo
    @param nodes: List of nodes to assign to the group
1638 4245446f Adeodato Simo

1639 98805538 Michael Hanselmann
    @rtype: string
1640 4245446f Adeodato Simo
    @return: job id
1641 4245446f Adeodato Simo

1642 4245446f Adeodato Simo
    """
1643 4245446f Adeodato Simo
    query = []
1644 4245446f Adeodato Simo
1645 4245446f Adeodato Simo
    if force:
1646 4245446f Adeodato Simo
      query.append(("force", 1))
1647 4245446f Adeodato Simo
1648 4245446f Adeodato Simo
    if dry_run:
1649 4245446f Adeodato Simo
      query.append(("dry-run", 1))
1650 4245446f Adeodato Simo
1651 4245446f Adeodato Simo
    body = {
1652 4245446f Adeodato Simo
      "nodes": nodes,
1653 4245446f Adeodato Simo
      }
1654 4245446f Adeodato Simo
1655 4245446f Adeodato Simo
    return self._SendRequest(HTTP_PUT,
1656 4245446f Adeodato Simo
                             ("/%s/groups/%s/assign-nodes" %
1657 4245446f Adeodato Simo
                             (GANETI_RAPI_VERSION, group)), query, body)
1658 208a6cff Michael Hanselmann
1659 414ebaf1 Michael Hanselmann
  def GetGroupTags(self, group):
1660 414ebaf1 Michael Hanselmann
    """Gets tags for a node group.
1661 414ebaf1 Michael Hanselmann

1662 414ebaf1 Michael Hanselmann
    @type group: string
1663 414ebaf1 Michael Hanselmann
    @param group: Node group whose tags to return
1664 414ebaf1 Michael Hanselmann

1665 414ebaf1 Michael Hanselmann
    @rtype: list of strings
1666 414ebaf1 Michael Hanselmann
    @return: tags for the group
1667 414ebaf1 Michael Hanselmann

1668 414ebaf1 Michael Hanselmann
    """
1669 414ebaf1 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1670 414ebaf1 Michael Hanselmann
                             ("/%s/groups/%s/tags" %
1671 414ebaf1 Michael Hanselmann
                              (GANETI_RAPI_VERSION, group)), None, None)
1672 414ebaf1 Michael Hanselmann
1673 414ebaf1 Michael Hanselmann
  def AddGroupTags(self, group, tags, dry_run=False):
1674 414ebaf1 Michael Hanselmann
    """Adds tags to a node group.
1675 414ebaf1 Michael Hanselmann

1676 414ebaf1 Michael Hanselmann
    @type group: str
1677 414ebaf1 Michael Hanselmann
    @param group: group to add tags to
1678 414ebaf1 Michael Hanselmann
    @type tags: list of string
1679 414ebaf1 Michael Hanselmann
    @param tags: tags to add to the group
1680 414ebaf1 Michael Hanselmann
    @type dry_run: bool
1681 414ebaf1 Michael Hanselmann
    @param dry_run: whether to perform a dry run
1682 414ebaf1 Michael Hanselmann

1683 414ebaf1 Michael Hanselmann
    @rtype: string
1684 414ebaf1 Michael Hanselmann
    @return: job id
1685 414ebaf1 Michael Hanselmann

1686 414ebaf1 Michael Hanselmann
    """
1687 414ebaf1 Michael Hanselmann
    query = [("tag", t) for t in tags]
1688 414ebaf1 Michael Hanselmann
    if dry_run:
1689 414ebaf1 Michael Hanselmann
      query.append(("dry-run", 1))
1690 414ebaf1 Michael Hanselmann
1691 414ebaf1 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1692 414ebaf1 Michael Hanselmann
                             ("/%s/groups/%s/tags" %
1693 414ebaf1 Michael Hanselmann
                              (GANETI_RAPI_VERSION, group)), query, None)
1694 414ebaf1 Michael Hanselmann
1695 414ebaf1 Michael Hanselmann
  def DeleteGroupTags(self, group, tags, dry_run=False):
1696 414ebaf1 Michael Hanselmann
    """Deletes tags from a node group.
1697 414ebaf1 Michael Hanselmann

1698 414ebaf1 Michael Hanselmann
    @type group: str
1699 414ebaf1 Michael Hanselmann
    @param group: group to delete tags from
1700 414ebaf1 Michael Hanselmann
    @type tags: list of string
1701 414ebaf1 Michael Hanselmann
    @param tags: tags to delete
1702 414ebaf1 Michael Hanselmann
    @type dry_run: bool
1703 414ebaf1 Michael Hanselmann
    @param dry_run: whether to perform a dry run
1704 414ebaf1 Michael Hanselmann
    @rtype: string
1705 414ebaf1 Michael Hanselmann
    @return: job id
1706 414ebaf1 Michael Hanselmann

1707 414ebaf1 Michael Hanselmann
    """
1708 414ebaf1 Michael Hanselmann
    query = [("tag", t) for t in tags]
1709 414ebaf1 Michael Hanselmann
    if dry_run:
1710 414ebaf1 Michael Hanselmann
      query.append(("dry-run", 1))
1711 414ebaf1 Michael Hanselmann
1712 414ebaf1 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE,
1713 414ebaf1 Michael Hanselmann
                             ("/%s/groups/%s/tags" %
1714 414ebaf1 Michael Hanselmann
                              (GANETI_RAPI_VERSION, group)), query, None)
1715 414ebaf1 Michael Hanselmann
1716 208a6cff Michael Hanselmann
  def Query(self, what, fields, filter_=None):
1717 208a6cff Michael Hanselmann
    """Retrieves information about resources.
1718 208a6cff Michael Hanselmann

1719 208a6cff Michael Hanselmann
    @type what: string
1720 208a6cff Michael Hanselmann
    @param what: Resource name, one of L{constants.QR_VIA_RAPI}
1721 208a6cff Michael Hanselmann
    @type fields: list of string
1722 208a6cff Michael Hanselmann
    @param fields: Requested fields
1723 208a6cff Michael Hanselmann
    @type filter_: None or list
1724 d914c76f Simeon Miteff
    @param filter_: Query filter
1725 208a6cff Michael Hanselmann

1726 208a6cff Michael Hanselmann
    @rtype: string
1727 208a6cff Michael Hanselmann
    @return: job id
1728 208a6cff Michael Hanselmann

1729 208a6cff Michael Hanselmann
    """
1730 208a6cff Michael Hanselmann
    body = {
1731 208a6cff Michael Hanselmann
      "fields": fields,
1732 208a6cff Michael Hanselmann
      }
1733 208a6cff Michael Hanselmann
1734 208a6cff Michael Hanselmann
    if filter_ is not None:
1735 208a6cff Michael Hanselmann
      body["filter"] = filter_
1736 208a6cff Michael Hanselmann
1737 208a6cff Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1738 208a6cff Michael Hanselmann
                             ("/%s/query/%s" %
1739 208a6cff Michael Hanselmann
                              (GANETI_RAPI_VERSION, what)), None, body)
1740 208a6cff Michael Hanselmann
1741 208a6cff Michael Hanselmann
  def QueryFields(self, what, fields=None):
1742 208a6cff Michael Hanselmann
    """Retrieves available fields for a resource.
1743 208a6cff Michael Hanselmann

1744 208a6cff Michael Hanselmann
    @type what: string
1745 208a6cff Michael Hanselmann
    @param what: Resource name, one of L{constants.QR_VIA_RAPI}
1746 208a6cff Michael Hanselmann
    @type fields: list of string
1747 208a6cff Michael Hanselmann
    @param fields: Requested fields
1748 208a6cff Michael Hanselmann

1749 208a6cff Michael Hanselmann
    @rtype: string
1750 208a6cff Michael Hanselmann
    @return: job id
1751 208a6cff Michael Hanselmann

1752 208a6cff Michael Hanselmann
    """
1753 208a6cff Michael Hanselmann
    query = []
1754 208a6cff Michael Hanselmann
1755 208a6cff Michael Hanselmann
    if fields is not None:
1756 208a6cff Michael Hanselmann
      query.append(("fields", ",".join(fields)))
1757 208a6cff Michael Hanselmann
1758 208a6cff Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1759 208a6cff Michael Hanselmann
                             ("/%s/query/%s/fields" %
1760 208a6cff Michael Hanselmann
                              (GANETI_RAPI_VERSION, what)), query, None)