Statistics
| Branch: | Tag: | Revision:

root / lib / rapi / client.py @ 592eb0df

History | View | Annotate | Download (54.5 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 0b58db81 Michael Hanselmann
NODE_EVAC_PRI = "primary-only"
67 0b58db81 Michael Hanselmann
NODE_EVAC_SEC = "secondary-only"
68 0b58db81 Michael Hanselmann
NODE_EVAC_ALL = "all"
69 0b58db81 Michael Hanselmann
70 1068639f Michael Hanselmann
NODE_ROLE_DRAINED = "drained"
71 1068639f Michael Hanselmann
NODE_ROLE_MASTER_CANDIATE = "master-candidate"
72 1068639f Michael Hanselmann
NODE_ROLE_MASTER = "master"
73 1068639f Michael Hanselmann
NODE_ROLE_OFFLINE = "offline"
74 1068639f Michael Hanselmann
NODE_ROLE_REGULAR = "regular"
75 95ab4de9 David Knowles
76 63d5eb8a Michael Hanselmann
JOB_STATUS_QUEUED = "queued"
77 47099cd1 Michael Hanselmann
JOB_STATUS_WAITING = "waiting"
78 63d5eb8a Michael Hanselmann
JOB_STATUS_CANCELING = "canceling"
79 63d5eb8a Michael Hanselmann
JOB_STATUS_RUNNING = "running"
80 63d5eb8a Michael Hanselmann
JOB_STATUS_CANCELED = "canceled"
81 63d5eb8a Michael Hanselmann
JOB_STATUS_SUCCESS = "success"
82 63d5eb8a Michael Hanselmann
JOB_STATUS_ERROR = "error"
83 63d5eb8a Michael Hanselmann
JOB_STATUS_FINALIZED = frozenset([
84 63d5eb8a Michael Hanselmann
  JOB_STATUS_CANCELED,
85 63d5eb8a Michael Hanselmann
  JOB_STATUS_SUCCESS,
86 63d5eb8a Michael Hanselmann
  JOB_STATUS_ERROR,
87 63d5eb8a Michael Hanselmann
  ])
88 63d5eb8a Michael Hanselmann
JOB_STATUS_ALL = frozenset([
89 63d5eb8a Michael Hanselmann
  JOB_STATUS_QUEUED,
90 47099cd1 Michael Hanselmann
  JOB_STATUS_WAITING,
91 63d5eb8a Michael Hanselmann
  JOB_STATUS_CANCELING,
92 63d5eb8a Michael Hanselmann
  JOB_STATUS_RUNNING,
93 63d5eb8a Michael Hanselmann
  ]) | JOB_STATUS_FINALIZED
94 63d5eb8a Michael Hanselmann
95 47099cd1 Michael Hanselmann
# Legacy name
96 47099cd1 Michael Hanselmann
JOB_STATUS_WAITLOCK = JOB_STATUS_WAITING
97 47099cd1 Michael Hanselmann
98 8a47b447 Michael Hanselmann
# Internal constants
99 8a47b447 Michael Hanselmann
_REQ_DATA_VERSION_FIELD = "__version__"
100 4c864b55 Michael Hanselmann
_QPARAM_DRY_RUN = "dry-run"
101 4c864b55 Michael Hanselmann
_QPARAM_FORCE = "force"
102 8a47b447 Michael Hanselmann
103 6396dc04 Michael Hanselmann
# Feature strings
104 6396dc04 Michael Hanselmann
INST_CREATE_REQV1 = "instance-create-reqv1"
105 6396dc04 Michael Hanselmann
INST_REINSTALL_REQV1 = "instance-reinstall-reqv1"
106 6396dc04 Michael Hanselmann
NODE_MIGRATE_REQV1 = "node-migrate-reqv1"
107 6396dc04 Michael Hanselmann
NODE_EVAC_RES1 = "node-evac-res1"
108 6396dc04 Michael Hanselmann
109 6396dc04 Michael Hanselmann
# Old feature constant names in case they're references by users of this module
110 6396dc04 Michael Hanselmann
_INST_CREATE_REQV1 = INST_CREATE_REQV1
111 6396dc04 Michael Hanselmann
_INST_REINSTALL_REQV1 = INST_REINSTALL_REQV1
112 6396dc04 Michael Hanselmann
_NODE_MIGRATE_REQV1 = NODE_MIGRATE_REQV1
113 6396dc04 Michael Hanselmann
_NODE_EVAC_RES1 = NODE_EVAC_RES1
114 6396dc04 Michael Hanselmann
115 2a7c3583 Michael Hanselmann
# Older pycURL versions don't have all error constants
116 2a7c3583 Michael Hanselmann
try:
117 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT = pycurl.E_SSL_CACERT
118 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT_BADFILE = pycurl.E_SSL_CACERT_BADFILE
119 2a7c3583 Michael Hanselmann
except AttributeError:
120 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT = 60
121 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT_BADFILE = 77
122 2a7c3583 Michael Hanselmann
123 2a7c3583 Michael Hanselmann
_CURL_SSL_CERT_ERRORS = frozenset([
124 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT,
125 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT_BADFILE,
126 2a7c3583 Michael Hanselmann
  ])
127 2a7c3583 Michael Hanselmann
128 95ab4de9 David Knowles
129 95ab4de9 David Knowles
class Error(Exception):
130 95ab4de9 David Knowles
  """Base error class for this module.
131 95ab4de9 David Knowles

132 95ab4de9 David Knowles
  """
133 95ab4de9 David Knowles
  pass
134 95ab4de9 David Knowles
135 95ab4de9 David Knowles
136 95ab4de9 David Knowles
class GanetiApiError(Error):
137 95ab4de9 David Knowles
  """Generic error raised from Ganeti API.
138 95ab4de9 David Knowles

139 95ab4de9 David Knowles
  """
140 8a019a03 Michael Hanselmann
  def __init__(self, msg, code=None):
141 8a019a03 Michael Hanselmann
    Error.__init__(self, msg)
142 8a019a03 Michael Hanselmann
    self.code = code
143 95ab4de9 David Knowles
144 95ab4de9 David Knowles
145 4b129313 Chris Schrier
class CertificateError(GanetiApiError):
146 4b129313 Chris Schrier
  """Raised when a problem is found with the SSL certificate.
147 4b129313 Chris Schrier

148 4b129313 Chris Schrier
  """
149 4b129313 Chris Schrier
  pass
150 4b129313 Chris Schrier
151 4b129313 Chris Schrier
152 4c864b55 Michael Hanselmann
def _AppendIf(container, condition, value):
153 4c864b55 Michael Hanselmann
  """Appends to a list if a condition evaluates to truth.
154 4c864b55 Michael Hanselmann

155 4c864b55 Michael Hanselmann
  """
156 4c864b55 Michael Hanselmann
  if condition:
157 4c864b55 Michael Hanselmann
    container.append(value)
158 4c864b55 Michael Hanselmann
159 4c864b55 Michael Hanselmann
  return condition
160 4c864b55 Michael Hanselmann
161 4c864b55 Michael Hanselmann
162 4c864b55 Michael Hanselmann
def _AppendDryRunIf(container, condition):
163 4c864b55 Michael Hanselmann
  """Appends a "dry-run" parameter if a condition evaluates to truth.
164 4c864b55 Michael Hanselmann

165 4c864b55 Michael Hanselmann
  """
166 4c864b55 Michael Hanselmann
  return _AppendIf(container, condition, (_QPARAM_DRY_RUN, 1))
167 4c864b55 Michael Hanselmann
168 4c864b55 Michael Hanselmann
169 4c864b55 Michael Hanselmann
def _AppendForceIf(container, condition):
170 4c864b55 Michael Hanselmann
  """Appends a "force" parameter if a condition evaluates to truth.
171 4c864b55 Michael Hanselmann

172 4c864b55 Michael Hanselmann
  """
173 4c864b55 Michael Hanselmann
  return _AppendIf(container, condition, (_QPARAM_FORCE, 1))
174 4c864b55 Michael Hanselmann
175 4c864b55 Michael Hanselmann
176 57d8e228 Michael Hanselmann
def _SetItemIf(container, condition, item, value):
177 57d8e228 Michael Hanselmann
  """Sets an item if a condition evaluates to truth.
178 57d8e228 Michael Hanselmann

179 57d8e228 Michael Hanselmann
  """
180 57d8e228 Michael Hanselmann
  if condition:
181 57d8e228 Michael Hanselmann
    container[item] = value
182 57d8e228 Michael Hanselmann
183 57d8e228 Michael Hanselmann
  return condition
184 57d8e228 Michael Hanselmann
185 57d8e228 Michael Hanselmann
186 2a7c3583 Michael Hanselmann
def UsesRapiClient(fn):
187 2a7c3583 Michael Hanselmann
  """Decorator for code using RAPI client to initialize pycURL.
188 9279e986 Michael Hanselmann

189 9279e986 Michael Hanselmann
  """
190 2a7c3583 Michael Hanselmann
  def wrapper(*args, **kwargs):
191 2a7c3583 Michael Hanselmann
    # curl_global_init(3) and curl_global_cleanup(3) must be called with only
192 2a7c3583 Michael Hanselmann
    # one thread running. This check is just a safety measure -- it doesn't
193 2a7c3583 Michael Hanselmann
    # cover all cases.
194 2a7c3583 Michael Hanselmann
    assert threading.activeCount() == 1, \
195 2a7c3583 Michael Hanselmann
           "Found active threads when initializing pycURL"
196 2a7c3583 Michael Hanselmann
197 2a7c3583 Michael Hanselmann
    pycurl.global_init(pycurl.GLOBAL_ALL)
198 2a7c3583 Michael Hanselmann
    try:
199 2a7c3583 Michael Hanselmann
      return fn(*args, **kwargs)
200 2a7c3583 Michael Hanselmann
    finally:
201 2a7c3583 Michael Hanselmann
      pycurl.global_cleanup()
202 2a7c3583 Michael Hanselmann
203 2a7c3583 Michael Hanselmann
  return wrapper
204 2a7c3583 Michael Hanselmann
205 2a7c3583 Michael Hanselmann
206 2a7c3583 Michael Hanselmann
def GenericCurlConfig(verbose=False, use_signal=False,
207 2a7c3583 Michael Hanselmann
                      use_curl_cabundle=False, cafile=None, capath=None,
208 2a7c3583 Michael Hanselmann
                      proxy=None, verify_hostname=False,
209 2a7c3583 Michael Hanselmann
                      connect_timeout=None, timeout=None,
210 2a7c3583 Michael Hanselmann
                      _pycurl_version_fn=pycurl.version_info):
211 2a7c3583 Michael Hanselmann
  """Curl configuration function generator.
212 2a7c3583 Michael Hanselmann

213 2a7c3583 Michael Hanselmann
  @type verbose: bool
214 2a7c3583 Michael Hanselmann
  @param verbose: Whether to set cURL to verbose mode
215 2a7c3583 Michael Hanselmann
  @type use_signal: bool
216 2a7c3583 Michael Hanselmann
  @param use_signal: Whether to allow cURL to use signals
217 2a7c3583 Michael Hanselmann
  @type use_curl_cabundle: bool
218 2a7c3583 Michael Hanselmann
  @param use_curl_cabundle: Whether to use cURL's default CA bundle
219 2a7c3583 Michael Hanselmann
  @type cafile: string
220 2a7c3583 Michael Hanselmann
  @param cafile: In which file we can find the certificates
221 2a7c3583 Michael Hanselmann
  @type capath: string
222 2a7c3583 Michael Hanselmann
  @param capath: In which directory we can find the certificates
223 2a7c3583 Michael Hanselmann
  @type proxy: string
224 2a7c3583 Michael Hanselmann
  @param proxy: Proxy to use, None for default behaviour and empty string for
225 2a7c3583 Michael Hanselmann
                disabling proxies (see curl_easy_setopt(3))
226 2a7c3583 Michael Hanselmann
  @type verify_hostname: bool
227 2a7c3583 Michael Hanselmann
  @param verify_hostname: Whether to verify the remote peer certificate's
228 2a7c3583 Michael Hanselmann
                          commonName
229 2a7c3583 Michael Hanselmann
  @type connect_timeout: number
230 2a7c3583 Michael Hanselmann
  @param connect_timeout: Timeout for establishing connection in seconds
231 2a7c3583 Michael Hanselmann
  @type timeout: number
232 2a7c3583 Michael Hanselmann
  @param timeout: Timeout for complete transfer in seconds (see
233 2a7c3583 Michael Hanselmann
                  curl_easy_setopt(3)).
234 9279e986 Michael Hanselmann

235 9279e986 Michael Hanselmann
  """
236 2a7c3583 Michael Hanselmann
  if use_curl_cabundle and (cafile or capath):
237 2a7c3583 Michael Hanselmann
    raise Error("Can not use default CA bundle when CA file or path is set")
238 9279e986 Michael Hanselmann
239 2a7c3583 Michael Hanselmann
  def _ConfigCurl(curl, logger):
240 2a7c3583 Michael Hanselmann
    """Configures a cURL object
241 9279e986 Michael Hanselmann

242 2a7c3583 Michael Hanselmann
    @type curl: pycurl.Curl
243 2a7c3583 Michael Hanselmann
    @param curl: cURL object
244 9279e986 Michael Hanselmann

245 9279e986 Michael Hanselmann
    """
246 2a7c3583 Michael Hanselmann
    logger.debug("Using cURL version %s", pycurl.version)
247 2a7c3583 Michael Hanselmann
248 2a7c3583 Michael Hanselmann
    # pycurl.version_info returns a tuple with information about the used
249 2a7c3583 Michael Hanselmann
    # version of libcurl. Item 5 is the SSL library linked to it.
250 2a7c3583 Michael Hanselmann
    # e.g.: (3, '7.18.0', 463360, 'x86_64-pc-linux-gnu', 1581, 'GnuTLS/2.0.4',
251 2a7c3583 Michael Hanselmann
    # 0, '1.2.3.3', ...)
252 2a7c3583 Michael Hanselmann
    sslver = _pycurl_version_fn()[5]
253 2a7c3583 Michael Hanselmann
    if not sslver:
254 2a7c3583 Michael Hanselmann
      raise Error("No SSL support in cURL")
255 2a7c3583 Michael Hanselmann
256 2a7c3583 Michael Hanselmann
    lcsslver = sslver.lower()
257 2a7c3583 Michael Hanselmann
    if lcsslver.startswith("openssl/"):
258 2a7c3583 Michael Hanselmann
      pass
259 592eb0df René Nussbaumer
    elif lcsslver.startswith("nss/"):
260 592eb0df René Nussbaumer
      # TODO: investigate compatibility beyond a simple test
261 592eb0df René Nussbaumer
      pass
262 2a7c3583 Michael Hanselmann
    elif lcsslver.startswith("gnutls/"):
263 2a7c3583 Michael Hanselmann
      if capath:
264 2a7c3583 Michael Hanselmann
        raise Error("cURL linked against GnuTLS has no support for a"
265 2a7c3583 Michael Hanselmann
                    " CA path (%s)" % (pycurl.version, ))
266 9279e986 Michael Hanselmann
    else:
267 2a7c3583 Michael Hanselmann
      raise NotImplementedError("cURL uses unsupported SSL version '%s'" %
268 2a7c3583 Michael Hanselmann
                                sslver)
269 2a7c3583 Michael Hanselmann
270 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.VERBOSE, verbose)
271 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.NOSIGNAL, not use_signal)
272 2a7c3583 Michael Hanselmann
273 2a7c3583 Michael Hanselmann
    # Whether to verify remote peer's CN
274 2a7c3583 Michael Hanselmann
    if verify_hostname:
275 2a7c3583 Michael Hanselmann
      # curl_easy_setopt(3): "When CURLOPT_SSL_VERIFYHOST is 2, that
276 2a7c3583 Michael Hanselmann
      # certificate must indicate that the server is the server to which you
277 2a7c3583 Michael Hanselmann
      # meant to connect, or the connection fails. [...] When the value is 1,
278 2a7c3583 Michael Hanselmann
      # the certificate must contain a Common Name field, but it doesn't matter
279 2a7c3583 Michael Hanselmann
      # what name it says. [...]"
280 2a7c3583 Michael Hanselmann
      curl.setopt(pycurl.SSL_VERIFYHOST, 2)
281 beba56ae Michael Hanselmann
    else:
282 2a7c3583 Michael Hanselmann
      curl.setopt(pycurl.SSL_VERIFYHOST, 0)
283 2a7c3583 Michael Hanselmann
284 2a7c3583 Michael Hanselmann
    if cafile or capath or use_curl_cabundle:
285 2a7c3583 Michael Hanselmann
      # Require certificates to be checked
286 2a7c3583 Michael Hanselmann
      curl.setopt(pycurl.SSL_VERIFYPEER, True)
287 2a7c3583 Michael Hanselmann
      if cafile:
288 2a7c3583 Michael Hanselmann
        curl.setopt(pycurl.CAINFO, str(cafile))
289 2a7c3583 Michael Hanselmann
      if capath:
290 2a7c3583 Michael Hanselmann
        curl.setopt(pycurl.CAPATH, str(capath))
291 2a7c3583 Michael Hanselmann
      # Not changing anything for using default CA bundle
292 2a7c3583 Michael Hanselmann
    else:
293 2a7c3583 Michael Hanselmann
      # Disable SSL certificate verification
294 2a7c3583 Michael Hanselmann
      curl.setopt(pycurl.SSL_VERIFYPEER, False)
295 9279e986 Michael Hanselmann
296 2a7c3583 Michael Hanselmann
    if proxy is not None:
297 2a7c3583 Michael Hanselmann
      curl.setopt(pycurl.PROXY, str(proxy))
298 9279e986 Michael Hanselmann
299 2a7c3583 Michael Hanselmann
    # Timeouts
300 2a7c3583 Michael Hanselmann
    if connect_timeout is not None:
301 2a7c3583 Michael Hanselmann
      curl.setopt(pycurl.CONNECTTIMEOUT, connect_timeout)
302 2a7c3583 Michael Hanselmann
    if timeout is not None:
303 2a7c3583 Michael Hanselmann
      curl.setopt(pycurl.TIMEOUT, timeout)
304 9279e986 Michael Hanselmann
305 2a7c3583 Michael Hanselmann
  return _ConfigCurl
306 9279e986 Michael Hanselmann
307 9279e986 Michael Hanselmann
308 b459a848 Andrea Spadaccini
class GanetiRapiClient(object): # pylint: disable=R0904
309 95ab4de9 David Knowles
  """Ganeti RAPI client.
310 95ab4de9 David Knowles

311 95ab4de9 David Knowles
  """
312 95ab4de9 David Knowles
  USER_AGENT = "Ganeti RAPI Client"
313 d3844674 Michael Hanselmann
  _json_encoder = simplejson.JSONEncoder(sort_keys=True)
314 95ab4de9 David Knowles
315 9279e986 Michael Hanselmann
  def __init__(self, host, port=GANETI_RAPI_PORT,
316 2a7c3583 Michael Hanselmann
               username=None, password=None, logger=logging,
317 a5eba783 Michael Hanselmann
               curl_config_fn=None, curl_factory=None):
318 2a7c3583 Michael Hanselmann
    """Initializes this class.
319 95ab4de9 David Knowles

320 9279e986 Michael Hanselmann
    @type host: string
321 9279e986 Michael Hanselmann
    @param host: the ganeti cluster master to interact with
322 95ab4de9 David Knowles
    @type port: int
323 9279e986 Michael Hanselmann
    @param port: the port on which the RAPI is running (default is 5080)
324 9279e986 Michael Hanselmann
    @type username: string
325 95ab4de9 David Knowles
    @param username: the username to connect with
326 9279e986 Michael Hanselmann
    @type password: string
327 95ab4de9 David Knowles
    @param password: the password to connect with
328 2a7c3583 Michael Hanselmann
    @type curl_config_fn: callable
329 2a7c3583 Michael Hanselmann
    @param curl_config_fn: Function to configure C{pycurl.Curl} object
330 9279e986 Michael Hanselmann
    @param logger: Logging object
331 95ab4de9 David Knowles

332 95ab4de9 David Knowles
    """
333 a5eba783 Michael Hanselmann
    self._username = username
334 a5eba783 Michael Hanselmann
    self._password = password
335 9279e986 Michael Hanselmann
    self._logger = logger
336 a5eba783 Michael Hanselmann
    self._curl_config_fn = curl_config_fn
337 a5eba783 Michael Hanselmann
    self._curl_factory = curl_factory
338 95ab4de9 David Knowles
339 1a8337f2 Manuel Franceschini
    try:
340 1a8337f2 Manuel Franceschini
      socket.inet_pton(socket.AF_INET6, host)
341 1a8337f2 Manuel Franceschini
      address = "[%s]:%s" % (host, port)
342 1a8337f2 Manuel Franceschini
    except socket.error:
343 1a8337f2 Manuel Franceschini
      address = "%s:%s" % (host, port)
344 1a8337f2 Manuel Franceschini
345 1a8337f2 Manuel Franceschini
    self._base_url = "https://%s" % address
346 f2f88abf David Knowles
347 a5eba783 Michael Hanselmann
    if username is not None:
348 a5eba783 Michael Hanselmann
      if password is None:
349 a5eba783 Michael Hanselmann
        raise Error("Password not specified")
350 a5eba783 Michael Hanselmann
    elif password:
351 a5eba783 Michael Hanselmann
      raise Error("Specified password without username")
352 a5eba783 Michael Hanselmann
353 a5eba783 Michael Hanselmann
  def _CreateCurl(self):
354 a5eba783 Michael Hanselmann
    """Creates a cURL object.
355 a5eba783 Michael Hanselmann

356 a5eba783 Michael Hanselmann
    """
357 a5eba783 Michael Hanselmann
    # Create pycURL object if no factory is provided
358 a5eba783 Michael Hanselmann
    if self._curl_factory:
359 a5eba783 Michael Hanselmann
      curl = self._curl_factory()
360 a5eba783 Michael Hanselmann
    else:
361 2a7c3583 Michael Hanselmann
      curl = pycurl.Curl()
362 2a7c3583 Michael Hanselmann
363 2a7c3583 Michael Hanselmann
    # Default cURL settings
364 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.VERBOSE, False)
365 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.FOLLOWLOCATION, False)
366 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.MAXREDIRS, 5)
367 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.NOSIGNAL, True)
368 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.USERAGENT, self.USER_AGENT)
369 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.SSL_VERIFYHOST, 0)
370 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.SSL_VERIFYPEER, False)
371 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.HTTPHEADER, [
372 2a7c3583 Michael Hanselmann
      "Accept: %s" % HTTP_APP_JSON,
373 2a7c3583 Michael Hanselmann
      "Content-type: %s" % HTTP_APP_JSON,
374 2a7c3583 Michael Hanselmann
      ])
375 2a7c3583 Michael Hanselmann
376 a5eba783 Michael Hanselmann
    assert ((self._username is None and self._password is None) ^
377 a5eba783 Michael Hanselmann
            (self._username is not None and self._password is not None))
378 a5eba783 Michael Hanselmann
379 a5eba783 Michael Hanselmann
    if self._username:
380 a5eba783 Michael Hanselmann
      # Setup authentication
381 2a7c3583 Michael Hanselmann
      curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_BASIC)
382 a5eba783 Michael Hanselmann
      curl.setopt(pycurl.USERPWD,
383 a5eba783 Michael Hanselmann
                  str("%s:%s" % (self._username, self._password)))
384 9279e986 Michael Hanselmann
385 2a7c3583 Michael Hanselmann
    # Call external configuration function
386 a5eba783 Michael Hanselmann
    if self._curl_config_fn:
387 a5eba783 Michael Hanselmann
      self._curl_config_fn(curl, self._logger)
388 f2f88abf David Knowles
389 a5eba783 Michael Hanselmann
    return curl
390 95ab4de9 David Knowles
391 10f5ab6c Michael Hanselmann
  @staticmethod
392 10f5ab6c Michael Hanselmann
  def _EncodeQuery(query):
393 10f5ab6c Michael Hanselmann
    """Encode query values for RAPI URL.
394 10f5ab6c Michael Hanselmann

395 10f5ab6c Michael Hanselmann
    @type query: list of two-tuples
396 10f5ab6c Michael Hanselmann
    @param query: Query arguments
397 10f5ab6c Michael Hanselmann
    @rtype: list
398 10f5ab6c Michael Hanselmann
    @return: Query list with encoded values
399 10f5ab6c Michael Hanselmann

400 10f5ab6c Michael Hanselmann
    """
401 10f5ab6c Michael Hanselmann
    result = []
402 10f5ab6c Michael Hanselmann
403 10f5ab6c Michael Hanselmann
    for name, value in query:
404 10f5ab6c Michael Hanselmann
      if value is None:
405 10f5ab6c Michael Hanselmann
        result.append((name, ""))
406 10f5ab6c Michael Hanselmann
407 10f5ab6c Michael Hanselmann
      elif isinstance(value, bool):
408 10f5ab6c Michael Hanselmann
        # Boolean values must be encoded as 0 or 1
409 10f5ab6c Michael Hanselmann
        result.append((name, int(value)))
410 10f5ab6c Michael Hanselmann
411 10f5ab6c Michael Hanselmann
      elif isinstance(value, (list, tuple, dict)):
412 10f5ab6c Michael Hanselmann
        raise ValueError("Invalid query data type %r" % type(value).__name__)
413 10f5ab6c Michael Hanselmann
414 10f5ab6c Michael Hanselmann
      else:
415 10f5ab6c Michael Hanselmann
        result.append((name, value))
416 10f5ab6c Michael Hanselmann
417 10f5ab6c Michael Hanselmann
    return result
418 10f5ab6c Michael Hanselmann
419 768747ed Michael Hanselmann
  def _SendRequest(self, method, path, query, content):
420 95ab4de9 David Knowles
    """Sends an HTTP request.
421 95ab4de9 David Knowles

422 95ab4de9 David Knowles
    This constructs a full URL, encodes and decodes HTTP bodies, and
423 95ab4de9 David Knowles
    handles invalid responses in a pythonic way.
424 95ab4de9 David Knowles

425 768747ed Michael Hanselmann
    @type method: string
426 95ab4de9 David Knowles
    @param method: HTTP method to use
427 768747ed Michael Hanselmann
    @type path: string
428 95ab4de9 David Knowles
    @param path: HTTP URL path
429 95ab4de9 David Knowles
    @type query: list of two-tuples
430 95ab4de9 David Knowles
    @param query: query arguments to pass to urllib.urlencode
431 95ab4de9 David Knowles
    @type content: str or None
432 95ab4de9 David Knowles
    @param content: HTTP body content
433 95ab4de9 David Knowles

434 95ab4de9 David Knowles
    @rtype: str
435 95ab4de9 David Knowles
    @return: JSON-Decoded response
436 95ab4de9 David Knowles

437 f2f88abf David Knowles
    @raises CertificateError: If an invalid SSL certificate is found
438 95ab4de9 David Knowles
    @raises GanetiApiError: If an invalid response is returned
439 95ab4de9 David Knowles

440 95ab4de9 David Knowles
    """
441 ccd6b542 Michael Hanselmann
    assert path.startswith("/")
442 ccd6b542 Michael Hanselmann
443 a5eba783 Michael Hanselmann
    curl = self._CreateCurl()
444 2a7c3583 Michael Hanselmann
445 8306e0e4 Michael Hanselmann
    if content is not None:
446 d3844674 Michael Hanselmann
      encoded_content = self._json_encoder.encode(content)
447 d3844674 Michael Hanselmann
    else:
448 2a7c3583 Michael Hanselmann
      encoded_content = ""
449 95ab4de9 David Knowles
450 ccd6b542 Michael Hanselmann
    # Build URL
451 f961e2ba Michael Hanselmann
    urlparts = [self._base_url, path]
452 ccd6b542 Michael Hanselmann
    if query:
453 f961e2ba Michael Hanselmann
      urlparts.append("?")
454 f961e2ba Michael Hanselmann
      urlparts.append(urllib.urlencode(self._EncodeQuery(query)))
455 9279e986 Michael Hanselmann
456 f961e2ba Michael Hanselmann
    url = "".join(urlparts)
457 f961e2ba Michael Hanselmann
458 a5eba783 Michael Hanselmann
    self._logger.debug("Sending request %s %s (content=%r)",
459 a5eba783 Michael Hanselmann
                       method, url, encoded_content)
460 2a7c3583 Michael Hanselmann
461 2a7c3583 Michael Hanselmann
    # Buffer for response
462 2a7c3583 Michael Hanselmann
    encoded_resp_body = StringIO()
463 f961e2ba Michael Hanselmann
464 2a7c3583 Michael Hanselmann
    # Configure cURL
465 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.CUSTOMREQUEST, str(method))
466 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.URL, str(url))
467 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.POSTFIELDS, str(encoded_content))
468 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.WRITEFUNCTION, encoded_resp_body.write)
469 9279e986 Michael Hanselmann
470 f2f88abf David Knowles
    try:
471 2a7c3583 Michael Hanselmann
      # Send request and wait for response
472 2a7c3583 Michael Hanselmann
      try:
473 2a7c3583 Michael Hanselmann
        curl.perform()
474 2a7c3583 Michael Hanselmann
      except pycurl.error, err:
475 2a7c3583 Michael Hanselmann
        if err.args[0] in _CURL_SSL_CERT_ERRORS:
476 4b129313 Chris Schrier
          raise CertificateError("SSL certificate error %s" % err,
477 4b129313 Chris Schrier
                                 code=err.args[0])
478 2a7c3583 Michael Hanselmann
479 b5800ee9 Chris Schrier
        raise GanetiApiError(str(err), code=err.args[0])
480 2a7c3583 Michael Hanselmann
    finally:
481 2a7c3583 Michael Hanselmann
      # Reset settings to not keep references to large objects in memory
482 2a7c3583 Michael Hanselmann
      # between requests
483 2a7c3583 Michael Hanselmann
      curl.setopt(pycurl.POSTFIELDS, "")
484 2a7c3583 Michael Hanselmann
      curl.setopt(pycurl.WRITEFUNCTION, lambda _: None)
485 2a7c3583 Michael Hanselmann
486 2a7c3583 Michael Hanselmann
    # Get HTTP response code
487 2a7c3583 Michael Hanselmann
    http_code = curl.getinfo(pycurl.RESPONSE_CODE)
488 2a7c3583 Michael Hanselmann
489 2a7c3583 Michael Hanselmann
    # Was anything written to the response buffer?
490 2a7c3583 Michael Hanselmann
    if encoded_resp_body.tell():
491 2a7c3583 Michael Hanselmann
      response_content = simplejson.loads(encoded_resp_body.getvalue())
492 d3844674 Michael Hanselmann
    else:
493 d3844674 Michael Hanselmann
      response_content = None
494 95ab4de9 David Knowles
495 2a7c3583 Michael Hanselmann
    if http_code != HTTP_OK:
496 d3844674 Michael Hanselmann
      if isinstance(response_content, dict):
497 95ab4de9 David Knowles
        msg = ("%s %s: %s" %
498 d3844674 Michael Hanselmann
               (response_content["code"],
499 d3844674 Michael Hanselmann
                response_content["message"],
500 d3844674 Michael Hanselmann
                response_content["explain"]))
501 95ab4de9 David Knowles
      else:
502 d3844674 Michael Hanselmann
        msg = str(response_content)
503 d3844674 Michael Hanselmann
504 2a7c3583 Michael Hanselmann
      raise GanetiApiError(msg, code=http_code)
505 95ab4de9 David Knowles
506 d3844674 Michael Hanselmann
    return response_content
507 95ab4de9 David Knowles
508 95ab4de9 David Knowles
  def GetVersion(self):
509 cab667cc David Knowles
    """Gets the Remote API version running on the cluster.
510 95ab4de9 David Knowles

511 95ab4de9 David Knowles
    @rtype: int
512 f2f88abf David Knowles
    @return: Ganeti Remote API version
513 95ab4de9 David Knowles

514 95ab4de9 David Knowles
    """
515 768747ed Michael Hanselmann
    return self._SendRequest(HTTP_GET, "/version", None, None)
516 95ab4de9 David Knowles
517 7eac4a4d Michael Hanselmann
  def GetFeatures(self):
518 7eac4a4d Michael Hanselmann
    """Gets the list of optional features supported by RAPI server.
519 7eac4a4d Michael Hanselmann

520 7eac4a4d Michael Hanselmann
    @rtype: list
521 7eac4a4d Michael Hanselmann
    @return: List of optional features
522 7eac4a4d Michael Hanselmann

523 7eac4a4d Michael Hanselmann
    """
524 7eac4a4d Michael Hanselmann
    try:
525 7eac4a4d Michael Hanselmann
      return self._SendRequest(HTTP_GET, "/%s/features" % GANETI_RAPI_VERSION,
526 7eac4a4d Michael Hanselmann
                               None, None)
527 7eac4a4d Michael Hanselmann
    except GanetiApiError, err:
528 7eac4a4d Michael Hanselmann
      # Older RAPI servers don't support this resource
529 7eac4a4d Michael Hanselmann
      if err.code == HTTP_NOT_FOUND:
530 7eac4a4d Michael Hanselmann
        return []
531 7eac4a4d Michael Hanselmann
532 7eac4a4d Michael Hanselmann
      raise
533 7eac4a4d Michael Hanselmann
534 95ab4de9 David Knowles
  def GetOperatingSystems(self):
535 95ab4de9 David Knowles
    """Gets the Operating Systems running in the Ganeti cluster.
536 95ab4de9 David Knowles

537 95ab4de9 David Knowles
    @rtype: list of str
538 95ab4de9 David Knowles
    @return: operating systems
539 95ab4de9 David Knowles

540 95ab4de9 David Knowles
    """
541 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET, "/%s/os" % GANETI_RAPI_VERSION,
542 a198b2d9 Michael Hanselmann
                             None, None)
543 95ab4de9 David Knowles
544 95ab4de9 David Knowles
  def GetInfo(self):
545 95ab4de9 David Knowles
    """Gets info about the cluster.
546 95ab4de9 David Knowles

547 95ab4de9 David Knowles
    @rtype: dict
548 95ab4de9 David Knowles
    @return: information about the cluster
549 95ab4de9 David Knowles

550 95ab4de9 David Knowles
    """
551 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET, "/%s/info" % GANETI_RAPI_VERSION,
552 a198b2d9 Michael Hanselmann
                             None, None)
553 95ab4de9 David Knowles
554 54d4c13b Michael Hanselmann
  def RedistributeConfig(self):
555 54d4c13b Michael Hanselmann
    """Tells the cluster to redistribute its configuration files.
556 54d4c13b Michael Hanselmann

557 d914c76f Simeon Miteff
    @rtype: string
558 54d4c13b Michael Hanselmann
    @return: job id
559 54d4c13b Michael Hanselmann

560 54d4c13b Michael Hanselmann
    """
561 54d4c13b Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
562 54d4c13b Michael Hanselmann
                             "/%s/redistribute-config" % GANETI_RAPI_VERSION,
563 54d4c13b Michael Hanselmann
                             None, None)
564 54d4c13b Michael Hanselmann
565 62e999a5 Michael Hanselmann
  def ModifyCluster(self, **kwargs):
566 62e999a5 Michael Hanselmann
    """Modifies cluster parameters.
567 62e999a5 Michael Hanselmann

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

570 98805538 Michael Hanselmann
    @rtype: string
571 62e999a5 Michael Hanselmann
    @return: job id
572 62e999a5 Michael Hanselmann

573 62e999a5 Michael Hanselmann
    """
574 62e999a5 Michael Hanselmann
    body = kwargs
575 62e999a5 Michael Hanselmann
576 62e999a5 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
577 62e999a5 Michael Hanselmann
                             "/%s/modify" % GANETI_RAPI_VERSION, None, body)
578 62e999a5 Michael Hanselmann
579 95ab4de9 David Knowles
  def GetClusterTags(self):
580 95ab4de9 David Knowles
    """Gets the cluster tags.
581 95ab4de9 David Knowles

582 95ab4de9 David Knowles
    @rtype: list of str
583 95ab4de9 David Knowles
    @return: cluster tags
584 95ab4de9 David Knowles

585 95ab4de9 David Knowles
    """
586 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET, "/%s/tags" % GANETI_RAPI_VERSION,
587 a198b2d9 Michael Hanselmann
                             None, None)
588 95ab4de9 David Knowles
589 95ab4de9 David Knowles
  def AddClusterTags(self, tags, dry_run=False):
590 95ab4de9 David Knowles
    """Adds tags to the cluster.
591 95ab4de9 David Knowles

592 95ab4de9 David Knowles
    @type tags: list of str
593 95ab4de9 David Knowles
    @param tags: tags to add to the cluster
594 95ab4de9 David Knowles
    @type dry_run: bool
595 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
596 95ab4de9 David Knowles

597 98805538 Michael Hanselmann
    @rtype: string
598 95ab4de9 David Knowles
    @return: job id
599 95ab4de9 David Knowles

600 95ab4de9 David Knowles
    """
601 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
602 4c864b55 Michael Hanselmann
    _AppendDryRunIf(query, dry_run)
603 95ab4de9 David Knowles
604 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT, "/%s/tags" % GANETI_RAPI_VERSION,
605 a198b2d9 Michael Hanselmann
                             query, None)
606 95ab4de9 David Knowles
607 95ab4de9 David Knowles
  def DeleteClusterTags(self, tags, dry_run=False):
608 95ab4de9 David Knowles
    """Deletes tags from the cluster.
609 95ab4de9 David Knowles

610 95ab4de9 David Knowles
    @type tags: list of str
611 95ab4de9 David Knowles
    @param tags: tags to delete
612 95ab4de9 David Knowles
    @type dry_run: bool
613 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
614 d914c76f Simeon Miteff
    @rtype: string
615 d914c76f Simeon Miteff
    @return: job id
616 95ab4de9 David Knowles

617 95ab4de9 David Knowles
    """
618 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
619 4c864b55 Michael Hanselmann
    _AppendDryRunIf(query, dry_run)
620 95ab4de9 David Knowles
621 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE, "/%s/tags" % GANETI_RAPI_VERSION,
622 a198b2d9 Michael Hanselmann
                             query, None)
623 95ab4de9 David Knowles
624 95ab4de9 David Knowles
  def GetInstances(self, bulk=False):
625 95ab4de9 David Knowles
    """Gets information about instances on the cluster.
626 95ab4de9 David Knowles

627 95ab4de9 David Knowles
    @type bulk: bool
628 95ab4de9 David Knowles
    @param bulk: whether to return all information about all instances
629 95ab4de9 David Knowles

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

633 95ab4de9 David Knowles
    """
634 95ab4de9 David Knowles
    query = []
635 4c864b55 Michael Hanselmann
    _AppendIf(query, bulk, ("bulk", 1))
636 95ab4de9 David Knowles
637 a198b2d9 Michael Hanselmann
    instances = self._SendRequest(HTTP_GET,
638 a198b2d9 Michael Hanselmann
                                  "/%s/instances" % GANETI_RAPI_VERSION,
639 a198b2d9 Michael Hanselmann
                                  query, None)
640 95ab4de9 David Knowles
    if bulk:
641 95ab4de9 David Knowles
      return instances
642 95ab4de9 David Knowles
    else:
643 95ab4de9 David Knowles
      return [i["id"] for i in instances]
644 95ab4de9 David Knowles
645 591e5103 Michael Hanselmann
  def GetInstance(self, instance):
646 95ab4de9 David Knowles
    """Gets information about an instance.
647 95ab4de9 David Knowles

648 95ab4de9 David Knowles
    @type instance: str
649 95ab4de9 David Knowles
    @param instance: instance whose info to return
650 95ab4de9 David Knowles

651 95ab4de9 David Knowles
    @rtype: dict
652 95ab4de9 David Knowles
    @return: info about the instance
653 95ab4de9 David Knowles

654 95ab4de9 David Knowles
    """
655 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
656 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s" %
657 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, None)
658 95ab4de9 David Knowles
659 591e5103 Michael Hanselmann
  def GetInstanceInfo(self, instance, static=None):
660 591e5103 Michael Hanselmann
    """Gets information about an instance.
661 591e5103 Michael Hanselmann

662 591e5103 Michael Hanselmann
    @type instance: string
663 591e5103 Michael Hanselmann
    @param instance: Instance name
664 591e5103 Michael Hanselmann
    @rtype: string
665 591e5103 Michael Hanselmann
    @return: Job ID
666 591e5103 Michael Hanselmann

667 591e5103 Michael Hanselmann
    """
668 591e5103 Michael Hanselmann
    if static is not None:
669 591e5103 Michael Hanselmann
      query = [("static", static)]
670 591e5103 Michael Hanselmann
    else:
671 591e5103 Michael Hanselmann
      query = None
672 591e5103 Michael Hanselmann
673 591e5103 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
674 591e5103 Michael Hanselmann
                             ("/%s/instances/%s/info" %
675 591e5103 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
676 591e5103 Michael Hanselmann
677 8a47b447 Michael Hanselmann
  def CreateInstance(self, mode, name, disk_template, disks, nics,
678 8a47b447 Michael Hanselmann
                     **kwargs):
679 95ab4de9 David Knowles
    """Creates a new instance.
680 95ab4de9 David Knowles

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

683 8a47b447 Michael Hanselmann
    @type mode: string
684 8a47b447 Michael Hanselmann
    @param mode: Instance creation mode
685 8a47b447 Michael Hanselmann
    @type name: string
686 8a47b447 Michael Hanselmann
    @param name: Hostname of the instance to create
687 8a47b447 Michael Hanselmann
    @type disk_template: string
688 8a47b447 Michael Hanselmann
    @param disk_template: Disk template for instance (e.g. plain, diskless,
689 8a47b447 Michael Hanselmann
                          file, or drbd)
690 8a47b447 Michael Hanselmann
    @type disks: list of dicts
691 8a47b447 Michael Hanselmann
    @param disks: List of disk definitions
692 8a47b447 Michael Hanselmann
    @type nics: list of dicts
693 8a47b447 Michael Hanselmann
    @param nics: List of NIC definitions
694 95ab4de9 David Knowles
    @type dry_run: bool
695 8a47b447 Michael Hanselmann
    @keyword dry_run: whether to perform a dry run
696 95ab4de9 David Knowles

697 98805538 Michael Hanselmann
    @rtype: string
698 95ab4de9 David Knowles
    @return: job id
699 95ab4de9 David Knowles

700 95ab4de9 David Knowles
    """
701 95ab4de9 David Knowles
    query = []
702 8a47b447 Michael Hanselmann
703 4c864b55 Michael Hanselmann
    _AppendDryRunIf(query, kwargs.get("dry_run"))
704 95ab4de9 David Knowles
705 8a47b447 Michael Hanselmann
    if _INST_CREATE_REQV1 in self.GetFeatures():
706 8a47b447 Michael Hanselmann
      # All required fields for request data version 1
707 8a47b447 Michael Hanselmann
      body = {
708 8a47b447 Michael Hanselmann
        _REQ_DATA_VERSION_FIELD: 1,
709 8a47b447 Michael Hanselmann
        "mode": mode,
710 8a47b447 Michael Hanselmann
        "name": name,
711 8a47b447 Michael Hanselmann
        "disk_template": disk_template,
712 8a47b447 Michael Hanselmann
        "disks": disks,
713 8a47b447 Michael Hanselmann
        "nics": nics,
714 8a47b447 Michael Hanselmann
        }
715 8a47b447 Michael Hanselmann
716 8a47b447 Michael Hanselmann
      conflicts = set(kwargs.iterkeys()) & set(body.iterkeys())
717 8a47b447 Michael Hanselmann
      if conflicts:
718 8a47b447 Michael Hanselmann
        raise GanetiApiError("Required fields can not be specified as"
719 8a47b447 Michael Hanselmann
                             " keywords: %s" % ", ".join(conflicts))
720 8a47b447 Michael Hanselmann
721 8a47b447 Michael Hanselmann
      body.update((key, value) for key, value in kwargs.iteritems()
722 8a47b447 Michael Hanselmann
                  if key != "dry_run")
723 8a47b447 Michael Hanselmann
    else:
724 9a8ae794 Michael Hanselmann
      raise GanetiApiError("Server does not support new-style (version 1)"
725 9a8ae794 Michael Hanselmann
                           " instance creation requests")
726 8a47b447 Michael Hanselmann
727 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_POST, "/%s/instances" % GANETI_RAPI_VERSION,
728 8a47b447 Michael Hanselmann
                             query, body)
729 95ab4de9 David Knowles
730 95ab4de9 David Knowles
  def DeleteInstance(self, instance, dry_run=False):
731 95ab4de9 David Knowles
    """Deletes an instance.
732 95ab4de9 David Knowles

733 95ab4de9 David Knowles
    @type instance: str
734 95ab4de9 David Knowles
    @param instance: the instance to delete
735 95ab4de9 David Knowles

736 98805538 Michael Hanselmann
    @rtype: string
737 cab667cc David Knowles
    @return: job id
738 cab667cc David Knowles

739 95ab4de9 David Knowles
    """
740 95ab4de9 David Knowles
    query = []
741 4c864b55 Michael Hanselmann
    _AppendDryRunIf(query, dry_run)
742 95ab4de9 David Knowles
743 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE,
744 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s" %
745 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
746 95ab4de9 David Knowles
747 3b7158ef Michael Hanselmann
  def ModifyInstance(self, instance, **kwargs):
748 3b7158ef Michael Hanselmann
    """Modifies an instance.
749 3b7158ef Michael Hanselmann

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

752 3b7158ef Michael Hanselmann
    @type instance: string
753 3b7158ef Michael Hanselmann
    @param instance: Instance name
754 98805538 Michael Hanselmann
    @rtype: string
755 3b7158ef Michael Hanselmann
    @return: job id
756 3b7158ef Michael Hanselmann

757 3b7158ef Michael Hanselmann
    """
758 3b7158ef Michael Hanselmann
    body = kwargs
759 3b7158ef Michael Hanselmann
760 3b7158ef Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
761 3b7158ef Michael Hanselmann
                             ("/%s/instances/%s/modify" %
762 3b7158ef Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, body)
763 3b7158ef Michael Hanselmann
764 b680c8be Michael Hanselmann
  def ActivateInstanceDisks(self, instance, ignore_size=None):
765 b680c8be Michael Hanselmann
    """Activates an instance's disks.
766 b680c8be Michael Hanselmann

767 b680c8be Michael Hanselmann
    @type instance: string
768 b680c8be Michael Hanselmann
    @param instance: Instance name
769 b680c8be Michael Hanselmann
    @type ignore_size: bool
770 b680c8be Michael Hanselmann
    @param ignore_size: Whether to ignore recorded size
771 d914c76f Simeon Miteff
    @rtype: string
772 b680c8be Michael Hanselmann
    @return: job id
773 b680c8be Michael Hanselmann

774 b680c8be Michael Hanselmann
    """
775 b680c8be Michael Hanselmann
    query = []
776 4c864b55 Michael Hanselmann
    _AppendIf(query, ignore_size, ("ignore_size", 1))
777 b680c8be Michael Hanselmann
778 b680c8be Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
779 b680c8be Michael Hanselmann
                             ("/%s/instances/%s/activate-disks" %
780 b680c8be Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
781 b680c8be Michael Hanselmann
782 b680c8be Michael Hanselmann
  def DeactivateInstanceDisks(self, instance):
783 b680c8be Michael Hanselmann
    """Deactivates an instance's disks.
784 b680c8be Michael Hanselmann

785 b680c8be Michael Hanselmann
    @type instance: string
786 b680c8be Michael Hanselmann
    @param instance: Instance name
787 d914c76f Simeon Miteff
    @rtype: string
788 b680c8be Michael Hanselmann
    @return: job id
789 b680c8be Michael Hanselmann

790 b680c8be Michael Hanselmann
    """
791 b680c8be Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
792 b680c8be Michael Hanselmann
                             ("/%s/instances/%s/deactivate-disks" %
793 b680c8be Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, None)
794 b680c8be Michael Hanselmann
795 a52978c7 Michael Hanselmann
  def RecreateInstanceDisks(self, instance, disks=None, nodes=None):
796 a52978c7 Michael Hanselmann
    """Recreate an instance's disks.
797 a52978c7 Michael Hanselmann

798 a52978c7 Michael Hanselmann
    @type instance: string
799 a52978c7 Michael Hanselmann
    @param instance: Instance name
800 a52978c7 Michael Hanselmann
    @type disks: list of int
801 a52978c7 Michael Hanselmann
    @param disks: List of disk indexes
802 a52978c7 Michael Hanselmann
    @type nodes: list of string
803 a52978c7 Michael Hanselmann
    @param nodes: New instance nodes, if relocation is desired
804 a52978c7 Michael Hanselmann
    @rtype: string
805 a52978c7 Michael Hanselmann
    @return: job id
806 a52978c7 Michael Hanselmann

807 a52978c7 Michael Hanselmann
    """
808 a52978c7 Michael Hanselmann
    body = {}
809 57d8e228 Michael Hanselmann
    _SetItemIf(body, disks is not None, "disks", disks)
810 57d8e228 Michael Hanselmann
    _SetItemIf(body, nodes is not None, "nodes", nodes)
811 a52978c7 Michael Hanselmann
812 a52978c7 Michael Hanselmann
    return self._SendRequest(HTTP_POST,
813 a52978c7 Michael Hanselmann
                             ("/%s/instances/%s/recreate-disks" %
814 a52978c7 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, body)
815 a52978c7 Michael Hanselmann
816 e23881ed Michael Hanselmann
  def GrowInstanceDisk(self, instance, disk, amount, wait_for_sync=None):
817 e23881ed Michael Hanselmann
    """Grows a disk of an instance.
818 e23881ed Michael Hanselmann

819 e23881ed Michael Hanselmann
    More details for parameters can be found in the RAPI documentation.
820 e23881ed Michael Hanselmann

821 e23881ed Michael Hanselmann
    @type instance: string
822 e23881ed Michael Hanselmann
    @param instance: Instance name
823 e23881ed Michael Hanselmann
    @type disk: integer
824 e23881ed Michael Hanselmann
    @param disk: Disk index
825 e23881ed Michael Hanselmann
    @type amount: integer
826 e23881ed Michael Hanselmann
    @param amount: Grow disk by this amount (MiB)
827 e23881ed Michael Hanselmann
    @type wait_for_sync: bool
828 e23881ed Michael Hanselmann
    @param wait_for_sync: Wait for disk to synchronize
829 98805538 Michael Hanselmann
    @rtype: string
830 e23881ed Michael Hanselmann
    @return: job id
831 e23881ed Michael Hanselmann

832 e23881ed Michael Hanselmann
    """
833 e23881ed Michael Hanselmann
    body = {
834 e23881ed Michael Hanselmann
      "amount": amount,
835 e23881ed Michael Hanselmann
      }
836 e23881ed Michael Hanselmann
837 57d8e228 Michael Hanselmann
    _SetItemIf(body, wait_for_sync is not None, "wait_for_sync", wait_for_sync)
838 e23881ed Michael Hanselmann
839 e23881ed Michael Hanselmann
    return self._SendRequest(HTTP_POST,
840 e23881ed Michael Hanselmann
                             ("/%s/instances/%s/disk/%s/grow" %
841 e23881ed Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance, disk)),
842 e23881ed Michael Hanselmann
                             None, body)
843 e23881ed Michael Hanselmann
844 95ab4de9 David Knowles
  def GetInstanceTags(self, instance):
845 95ab4de9 David Knowles
    """Gets tags for an instance.
846 95ab4de9 David Knowles

847 95ab4de9 David Knowles
    @type instance: str
848 95ab4de9 David Knowles
    @param instance: instance whose tags to return
849 95ab4de9 David Knowles

850 95ab4de9 David Knowles
    @rtype: list of str
851 95ab4de9 David Knowles
    @return: tags for the instance
852 95ab4de9 David Knowles

853 95ab4de9 David Knowles
    """
854 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
855 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/tags" %
856 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, None)
857 95ab4de9 David Knowles
858 95ab4de9 David Knowles
  def AddInstanceTags(self, instance, tags, dry_run=False):
859 95ab4de9 David Knowles
    """Adds tags to an instance.
860 95ab4de9 David Knowles

861 95ab4de9 David Knowles
    @type instance: str
862 95ab4de9 David Knowles
    @param instance: instance to add tags to
863 95ab4de9 David Knowles
    @type tags: list of str
864 95ab4de9 David Knowles
    @param tags: tags to add to the instance
865 95ab4de9 David Knowles
    @type dry_run: bool
866 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
867 95ab4de9 David Knowles

868 98805538 Michael Hanselmann
    @rtype: string
869 95ab4de9 David Knowles
    @return: job id
870 95ab4de9 David Knowles

871 95ab4de9 David Knowles
    """
872 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
873 4c864b55 Michael Hanselmann
    _AppendDryRunIf(query, dry_run)
874 95ab4de9 David Knowles
875 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
876 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/tags" %
877 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
878 95ab4de9 David Knowles
879 95ab4de9 David Knowles
  def DeleteInstanceTags(self, instance, tags, dry_run=False):
880 95ab4de9 David Knowles
    """Deletes tags from an instance.
881 95ab4de9 David Knowles

882 95ab4de9 David Knowles
    @type instance: str
883 95ab4de9 David Knowles
    @param instance: instance to delete tags from
884 95ab4de9 David Knowles
    @type tags: list of str
885 95ab4de9 David Knowles
    @param tags: tags to delete
886 95ab4de9 David Knowles
    @type dry_run: bool
887 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
888 d914c76f Simeon Miteff
    @rtype: string
889 d914c76f Simeon Miteff
    @return: job id
890 95ab4de9 David Knowles

891 95ab4de9 David Knowles
    """
892 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
893 4c864b55 Michael Hanselmann
    _AppendDryRunIf(query, dry_run)
894 95ab4de9 David Knowles
895 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE,
896 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/tags" %
897 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
898 95ab4de9 David Knowles
899 95ab4de9 David Knowles
  def RebootInstance(self, instance, reboot_type=None, ignore_secondaries=None,
900 95ab4de9 David Knowles
                     dry_run=False):
901 95ab4de9 David Knowles
    """Reboots an instance.
902 95ab4de9 David Knowles

903 95ab4de9 David Knowles
    @type instance: str
904 95ab4de9 David Knowles
    @param instance: instance to rebot
905 95ab4de9 David Knowles
    @type reboot_type: str
906 95ab4de9 David Knowles
    @param reboot_type: one of: hard, soft, full
907 95ab4de9 David Knowles
    @type ignore_secondaries: bool
908 95ab4de9 David Knowles
    @param ignore_secondaries: if True, ignores errors for the secondary node
909 95ab4de9 David Knowles
        while re-assembling disks (in hard-reboot mode only)
910 95ab4de9 David Knowles
    @type dry_run: bool
911 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
912 d914c76f Simeon Miteff
    @rtype: string
913 d914c76f Simeon Miteff
    @return: job id
914 95ab4de9 David Knowles

915 95ab4de9 David Knowles
    """
916 95ab4de9 David Knowles
    query = []
917 4c864b55 Michael Hanselmann
    _AppendDryRunIf(query, dry_run)
918 4c864b55 Michael Hanselmann
    _AppendIf(query, reboot_type, ("type", reboot_type))
919 4c864b55 Michael Hanselmann
    _AppendIf(query, ignore_secondaries is not None,
920 4c864b55 Michael Hanselmann
              ("ignore_secondaries", ignore_secondaries))
921 95ab4de9 David Knowles
922 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_POST,
923 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/reboot" %
924 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
925 95ab4de9 David Knowles
926 2ba39b8f Iustin Pop
  def ShutdownInstance(self, instance, dry_run=False, no_remember=False):
927 95ab4de9 David Knowles
    """Shuts down an instance.
928 95ab4de9 David Knowles

929 95ab4de9 David Knowles
    @type instance: str
930 95ab4de9 David Knowles
    @param instance: the instance to shut down
931 95ab4de9 David Knowles
    @type dry_run: bool
932 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
933 2ba39b8f Iustin Pop
    @type no_remember: bool
934 2ba39b8f Iustin Pop
    @param no_remember: if true, will not record the state change
935 d914c76f Simeon Miteff
    @rtype: string
936 d914c76f Simeon Miteff
    @return: job id
937 95ab4de9 David Knowles

938 95ab4de9 David Knowles
    """
939 95ab4de9 David Knowles
    query = []
940 4c864b55 Michael Hanselmann
    _AppendDryRunIf(query, dry_run)
941 4c864b55 Michael Hanselmann
    _AppendIf(query, no_remember, ("no-remember", 1))
942 95ab4de9 David Knowles
943 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
944 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/shutdown" %
945 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
946 95ab4de9 David Knowles
947 2ba39b8f Iustin Pop
  def StartupInstance(self, instance, dry_run=False, no_remember=False):
948 95ab4de9 David Knowles
    """Starts up an instance.
949 95ab4de9 David Knowles

950 95ab4de9 David Knowles
    @type instance: str
951 95ab4de9 David Knowles
    @param instance: the instance to start up
952 95ab4de9 David Knowles
    @type dry_run: bool
953 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
954 2ba39b8f Iustin Pop
    @type no_remember: bool
955 2ba39b8f Iustin Pop
    @param no_remember: if true, will not record the state change
956 d914c76f Simeon Miteff
    @rtype: string
957 d914c76f Simeon Miteff
    @return: job id
958 95ab4de9 David Knowles

959 95ab4de9 David Knowles
    """
960 95ab4de9 David Knowles
    query = []
961 4c864b55 Michael Hanselmann
    _AppendDryRunIf(query, dry_run)
962 4c864b55 Michael Hanselmann
    _AppendIf(query, no_remember, ("no-remember", 1))
963 95ab4de9 David Knowles
964 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
965 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/startup" %
966 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
967 95ab4de9 David Knowles
968 c744425f Michael Hanselmann
  def ReinstallInstance(self, instance, os=None, no_startup=False,
969 c744425f Michael Hanselmann
                        osparams=None):
970 95ab4de9 David Knowles
    """Reinstalls an instance.
971 95ab4de9 David Knowles

972 95ab4de9 David Knowles
    @type instance: str
973 fcee9675 David Knowles
    @param instance: The instance to reinstall
974 fcee9675 David Knowles
    @type os: str or None
975 fcee9675 David Knowles
    @param os: The operating system to reinstall. If None, the instance's
976 fcee9675 David Knowles
        current operating system will be installed again
977 95ab4de9 David Knowles
    @type no_startup: bool
978 fcee9675 David Knowles
    @param no_startup: Whether to start the instance automatically
979 d914c76f Simeon Miteff
    @rtype: string
980 d914c76f Simeon Miteff
    @return: job id
981 95ab4de9 David Knowles

982 95ab4de9 David Knowles
    """
983 c744425f Michael Hanselmann
    if _INST_REINSTALL_REQV1 in self.GetFeatures():
984 c744425f Michael Hanselmann
      body = {
985 c744425f Michael Hanselmann
        "start": not no_startup,
986 c744425f Michael Hanselmann
        }
987 57d8e228 Michael Hanselmann
      _SetItemIf(body, os is not None, "os", os)
988 57d8e228 Michael Hanselmann
      _SetItemIf(body, osparams is not None, "osparams", osparams)
989 c744425f Michael Hanselmann
      return self._SendRequest(HTTP_POST,
990 c744425f Michael Hanselmann
                               ("/%s/instances/%s/reinstall" %
991 c744425f Michael Hanselmann
                                (GANETI_RAPI_VERSION, instance)), None, body)
992 c744425f Michael Hanselmann
993 c744425f Michael Hanselmann
    # Use old request format
994 c744425f Michael Hanselmann
    if osparams:
995 c744425f Michael Hanselmann
      raise GanetiApiError("Server does not support specifying OS parameters"
996 c744425f Michael Hanselmann
                           " for instance reinstallation")
997 c744425f Michael Hanselmann
998 fcee9675 David Knowles
    query = []
999 4c864b55 Michael Hanselmann
    _AppendIf(query, os, ("os", os))
1000 4c864b55 Michael Hanselmann
    _AppendIf(query, no_startup, ("nostartup", 1))
1001 4c864b55 Michael Hanselmann
1002 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_POST,
1003 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/reinstall" %
1004 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
1005 95ab4de9 David Knowles
1006 bfc2002f Michael Hanselmann
  def ReplaceInstanceDisks(self, instance, disks=None, mode=REPLACE_DISK_AUTO,
1007 539d65ba Michael Hanselmann
                           remote_node=None, iallocator=None):
1008 95ab4de9 David Knowles
    """Replaces disks on an instance.
1009 95ab4de9 David Knowles

1010 95ab4de9 David Knowles
    @type instance: str
1011 95ab4de9 David Knowles
    @param instance: instance whose disks to replace
1012 bfc2002f Michael Hanselmann
    @type disks: list of ints
1013 bfc2002f Michael Hanselmann
    @param disks: Indexes of disks to replace
1014 95ab4de9 David Knowles
    @type mode: str
1015 cfc03c54 Michael Hanselmann
    @param mode: replacement mode to use (defaults to replace_auto)
1016 95ab4de9 David Knowles
    @type remote_node: str or None
1017 95ab4de9 David Knowles
    @param remote_node: new secondary node to use (for use with
1018 cfc03c54 Michael Hanselmann
        replace_new_secondary mode)
1019 95ab4de9 David Knowles
    @type iallocator: str or None
1020 95ab4de9 David Knowles
    @param iallocator: instance allocator plugin to use (for use with
1021 cfc03c54 Michael Hanselmann
                       replace_auto mode)
1022 95ab4de9 David Knowles

1023 98805538 Michael Hanselmann
    @rtype: string
1024 95ab4de9 David Knowles
    @return: job id
1025 95ab4de9 David Knowles

1026 95ab4de9 David Knowles
    """
1027 cfc03c54 Michael Hanselmann
    query = [
1028 cfc03c54 Michael Hanselmann
      ("mode", mode),
1029 cfc03c54 Michael Hanselmann
      ]
1030 95ab4de9 David Knowles
1031 539d65ba Michael Hanselmann
    # TODO: Convert to body parameters
1032 539d65ba Michael Hanselmann
1033 539d65ba Michael Hanselmann
    if disks is not None:
1034 4c864b55 Michael Hanselmann
      _AppendIf(query, True,
1035 4c864b55 Michael Hanselmann
                ("disks", ",".join(str(idx) for idx in disks)))
1036 bfc2002f Michael Hanselmann
1037 4c864b55 Michael Hanselmann
    _AppendIf(query, remote_node is not None, ("remote_node", remote_node))
1038 4c864b55 Michael Hanselmann
    _AppendIf(query, iallocator is not None, ("iallocator", iallocator))
1039 bfc2002f Michael Hanselmann
1040 95ab4de9 David Knowles
    return self._SendRequest(HTTP_POST,
1041 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/replace-disks" %
1042 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
1043 95ab4de9 David Knowles
1044 ebeb600f Michael Hanselmann
  def PrepareExport(self, instance, mode):
1045 ebeb600f Michael Hanselmann
    """Prepares an instance for an export.
1046 ebeb600f Michael Hanselmann

1047 ebeb600f Michael Hanselmann
    @type instance: string
1048 ebeb600f Michael Hanselmann
    @param instance: Instance name
1049 ebeb600f Michael Hanselmann
    @type mode: string
1050 ebeb600f Michael Hanselmann
    @param mode: Export mode
1051 ebeb600f Michael Hanselmann
    @rtype: string
1052 ebeb600f Michael Hanselmann
    @return: Job ID
1053 ebeb600f Michael Hanselmann

1054 ebeb600f Michael Hanselmann
    """
1055 ebeb600f Michael Hanselmann
    query = [("mode", mode)]
1056 ebeb600f Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1057 ebeb600f Michael Hanselmann
                             ("/%s/instances/%s/prepare-export" %
1058 ebeb600f Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
1059 ebeb600f Michael Hanselmann
1060 ebeb600f Michael Hanselmann
  def ExportInstance(self, instance, mode, destination, shutdown=None,
1061 ebeb600f Michael Hanselmann
                     remove_instance=None,
1062 ebeb600f Michael Hanselmann
                     x509_key_name=None, destination_x509_ca=None):
1063 ebeb600f Michael Hanselmann
    """Exports an instance.
1064 ebeb600f Michael Hanselmann

1065 ebeb600f Michael Hanselmann
    @type instance: string
1066 ebeb600f Michael Hanselmann
    @param instance: Instance name
1067 ebeb600f Michael Hanselmann
    @type mode: string
1068 ebeb600f Michael Hanselmann
    @param mode: Export mode
1069 ebeb600f Michael Hanselmann
    @rtype: string
1070 ebeb600f Michael Hanselmann
    @return: Job ID
1071 ebeb600f Michael Hanselmann

1072 ebeb600f Michael Hanselmann
    """
1073 ebeb600f Michael Hanselmann
    body = {
1074 ebeb600f Michael Hanselmann
      "destination": destination,
1075 ebeb600f Michael Hanselmann
      "mode": mode,
1076 ebeb600f Michael Hanselmann
      }
1077 ebeb600f Michael Hanselmann
1078 57d8e228 Michael Hanselmann
    _SetItemIf(body, shutdown is not None, "shutdown", shutdown)
1079 57d8e228 Michael Hanselmann
    _SetItemIf(body, remove_instance is not None,
1080 57d8e228 Michael Hanselmann
               "remove_instance", remove_instance)
1081 57d8e228 Michael Hanselmann
    _SetItemIf(body, x509_key_name is not None, "x509_key_name", x509_key_name)
1082 57d8e228 Michael Hanselmann
    _SetItemIf(body, destination_x509_ca is not None,
1083 57d8e228 Michael Hanselmann
               "destination_x509_ca", destination_x509_ca)
1084 ebeb600f Michael Hanselmann
1085 ebeb600f Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1086 ebeb600f Michael Hanselmann
                             ("/%s/instances/%s/export" %
1087 ebeb600f Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, body)
1088 ebeb600f Michael Hanselmann
1089 e0ac6ce6 Michael Hanselmann
  def MigrateInstance(self, instance, mode=None, cleanup=None):
1090 c63eb9c0 Michael Hanselmann
    """Migrates an instance.
1091 e0ac6ce6 Michael Hanselmann

1092 e0ac6ce6 Michael Hanselmann
    @type instance: string
1093 e0ac6ce6 Michael Hanselmann
    @param instance: Instance name
1094 e0ac6ce6 Michael Hanselmann
    @type mode: string
1095 e0ac6ce6 Michael Hanselmann
    @param mode: Migration mode
1096 e0ac6ce6 Michael Hanselmann
    @type cleanup: bool
1097 e0ac6ce6 Michael Hanselmann
    @param cleanup: Whether to clean up a previously failed migration
1098 d914c76f Simeon Miteff
    @rtype: string
1099 d914c76f Simeon Miteff
    @return: job id
1100 e0ac6ce6 Michael Hanselmann

1101 e0ac6ce6 Michael Hanselmann
    """
1102 e0ac6ce6 Michael Hanselmann
    body = {}
1103 57d8e228 Michael Hanselmann
    _SetItemIf(body, mode is not None, "mode", mode)
1104 57d8e228 Michael Hanselmann
    _SetItemIf(body, cleanup is not None, "cleanup", cleanup)
1105 e0ac6ce6 Michael Hanselmann
1106 e0ac6ce6 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1107 e0ac6ce6 Michael Hanselmann
                             ("/%s/instances/%s/migrate" %
1108 e0ac6ce6 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, body)
1109 e0ac6ce6 Michael Hanselmann
1110 c0a146a1 Michael Hanselmann
  def FailoverInstance(self, instance, iallocator=None,
1111 c0a146a1 Michael Hanselmann
                       ignore_consistency=None, target_node=None):
1112 c0a146a1 Michael Hanselmann
    """Does a failover of an instance.
1113 c0a146a1 Michael Hanselmann

1114 c0a146a1 Michael Hanselmann
    @type instance: string
1115 c0a146a1 Michael Hanselmann
    @param instance: Instance name
1116 c0a146a1 Michael Hanselmann
    @type iallocator: string
1117 c0a146a1 Michael Hanselmann
    @param iallocator: Iallocator for deciding the target node for
1118 c0a146a1 Michael Hanselmann
      shared-storage instances
1119 c0a146a1 Michael Hanselmann
    @type ignore_consistency: bool
1120 c0a146a1 Michael Hanselmann
    @param ignore_consistency: Whether to ignore disk consistency
1121 c0a146a1 Michael Hanselmann
    @type target_node: string
1122 c0a146a1 Michael Hanselmann
    @param target_node: Target node for shared-storage instances
1123 c0a146a1 Michael Hanselmann
    @rtype: string
1124 c0a146a1 Michael Hanselmann
    @return: job id
1125 c0a146a1 Michael Hanselmann

1126 c0a146a1 Michael Hanselmann
    """
1127 c0a146a1 Michael Hanselmann
    body = {}
1128 57d8e228 Michael Hanselmann
    _SetItemIf(body, iallocator is not None, "iallocator", iallocator)
1129 57d8e228 Michael Hanselmann
    _SetItemIf(body, ignore_consistency is not None,
1130 57d8e228 Michael Hanselmann
               "ignore_consistency", ignore_consistency)
1131 57d8e228 Michael Hanselmann
    _SetItemIf(body, target_node is not None, "target_node", target_node)
1132 c0a146a1 Michael Hanselmann
1133 c0a146a1 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1134 c0a146a1 Michael Hanselmann
                             ("/%s/instances/%s/failover" %
1135 c0a146a1 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, body)
1136 c0a146a1 Michael Hanselmann
1137 d654aae1 Michael Hanselmann
  def RenameInstance(self, instance, new_name, ip_check=None, name_check=None):
1138 d654aae1 Michael Hanselmann
    """Changes the name of an instance.
1139 d654aae1 Michael Hanselmann

1140 d654aae1 Michael Hanselmann
    @type instance: string
1141 d654aae1 Michael Hanselmann
    @param instance: Instance name
1142 d654aae1 Michael Hanselmann
    @type new_name: string
1143 d654aae1 Michael Hanselmann
    @param new_name: New instance name
1144 d654aae1 Michael Hanselmann
    @type ip_check: bool
1145 d654aae1 Michael Hanselmann
    @param ip_check: Whether to ensure instance's IP address is inactive
1146 d654aae1 Michael Hanselmann
    @type name_check: bool
1147 d654aae1 Michael Hanselmann
    @param name_check: Whether to ensure instance's name is resolvable
1148 d914c76f Simeon Miteff
    @rtype: string
1149 d914c76f Simeon Miteff
    @return: job id
1150 d654aae1 Michael Hanselmann

1151 d654aae1 Michael Hanselmann
    """
1152 d654aae1 Michael Hanselmann
    body = {
1153 d654aae1 Michael Hanselmann
      "new_name": new_name,
1154 d654aae1 Michael Hanselmann
      }
1155 d654aae1 Michael Hanselmann
1156 57d8e228 Michael Hanselmann
    _SetItemIf(body, ip_check is not None, "ip_check", ip_check)
1157 57d8e228 Michael Hanselmann
    _SetItemIf(body, name_check is not None, "name_check", name_check)
1158 d654aae1 Michael Hanselmann
1159 d654aae1 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1160 d654aae1 Michael Hanselmann
                             ("/%s/instances/%s/rename" %
1161 d654aae1 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, body)
1162 d654aae1 Michael Hanselmann
1163 b82d4c5e Michael Hanselmann
  def GetInstanceConsole(self, instance):
1164 b82d4c5e Michael Hanselmann
    """Request information for connecting to instance's console.
1165 b82d4c5e Michael Hanselmann

1166 b82d4c5e Michael Hanselmann
    @type instance: string
1167 b82d4c5e Michael Hanselmann
    @param instance: Instance name
1168 d914c76f Simeon Miteff
    @rtype: dict
1169 d914c76f Simeon Miteff
    @return: dictionary containing information about instance's console
1170 b82d4c5e Michael Hanselmann

1171 b82d4c5e Michael Hanselmann
    """
1172 b82d4c5e Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1173 b82d4c5e Michael Hanselmann
                             ("/%s/instances/%s/console" %
1174 b82d4c5e Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, None)
1175 b82d4c5e Michael Hanselmann
1176 95ab4de9 David Knowles
  def GetJobs(self):
1177 95ab4de9 David Knowles
    """Gets all jobs for the cluster.
1178 95ab4de9 David Knowles

1179 95ab4de9 David Knowles
    @rtype: list of int
1180 95ab4de9 David Knowles
    @return: job ids for the cluster
1181 95ab4de9 David Knowles

1182 95ab4de9 David Knowles
    """
1183 768747ed Michael Hanselmann
    return [int(j["id"])
1184 a198b2d9 Michael Hanselmann
            for j in self._SendRequest(HTTP_GET,
1185 a198b2d9 Michael Hanselmann
                                       "/%s/jobs" % GANETI_RAPI_VERSION,
1186 a198b2d9 Michael Hanselmann
                                       None, None)]
1187 95ab4de9 David Knowles
1188 95ab4de9 David Knowles
  def GetJobStatus(self, job_id):
1189 95ab4de9 David Knowles
    """Gets the status of a job.
1190 95ab4de9 David Knowles

1191 98805538 Michael Hanselmann
    @type job_id: string
1192 95ab4de9 David Knowles
    @param job_id: job id whose status to query
1193 95ab4de9 David Knowles

1194 95ab4de9 David Knowles
    @rtype: dict
1195 95ab4de9 David Knowles
    @return: job status
1196 95ab4de9 David Knowles

1197 95ab4de9 David Knowles
    """
1198 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1199 a198b2d9 Michael Hanselmann
                             "/%s/jobs/%s" % (GANETI_RAPI_VERSION, job_id),
1200 a198b2d9 Michael Hanselmann
                             None, None)
1201 95ab4de9 David Knowles
1202 16c13387 Theo Van Dinter
  def WaitForJobCompletion(self, job_id, period=5, retries=-1):
1203 16c13387 Theo Van Dinter
    """Polls cluster for job status until completion.
1204 16c13387 Theo Van Dinter

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

1208 cfda0e48 Iustin Pop
    @type job_id: string
1209 16c13387 Theo Van Dinter
    @param job_id: job id to watch
1210 16c13387 Theo Van Dinter
    @type period: int
1211 16c13387 Theo Van Dinter
    @param period: how often to poll for status (optional, default 5s)
1212 16c13387 Theo Van Dinter
    @type retries: int
1213 16c13387 Theo Van Dinter
    @param retries: how many time to poll before giving up
1214 16c13387 Theo Van Dinter
                    (optional, default -1 means unlimited)
1215 16c13387 Theo Van Dinter

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

1222 16c13387 Theo Van Dinter
    """
1223 16c13387 Theo Van Dinter
    while retries != 0:
1224 16c13387 Theo Van Dinter
      job_result = self.GetJobStatus(job_id)
1225 dde0e97c Michael Hanselmann
1226 dde0e97c Michael Hanselmann
      if job_result and job_result["status"] == JOB_STATUS_SUCCESS:
1227 16c13387 Theo Van Dinter
        return True
1228 dde0e97c Michael Hanselmann
      elif not job_result or job_result["status"] in JOB_STATUS_FINALIZED:
1229 dde0e97c Michael Hanselmann
        return False
1230 dde0e97c Michael Hanselmann
1231 dde0e97c Michael Hanselmann
      if period:
1232 dde0e97c Michael Hanselmann
        time.sleep(period)
1233 dde0e97c Michael Hanselmann
1234 16c13387 Theo Van Dinter
      if retries > 0:
1235 16c13387 Theo Van Dinter
        retries -= 1
1236 dde0e97c Michael Hanselmann
1237 16c13387 Theo Van Dinter
    return False
1238 16c13387 Theo Van Dinter
1239 d9b67f70 Michael Hanselmann
  def WaitForJobChange(self, job_id, fields, prev_job_info, prev_log_serial):
1240 d9b67f70 Michael Hanselmann
    """Waits for job changes.
1241 d9b67f70 Michael Hanselmann

1242 98805538 Michael Hanselmann
    @type job_id: string
1243 d9b67f70 Michael Hanselmann
    @param job_id: Job ID for which to wait
1244 d914c76f Simeon Miteff
    @return: C{None} if no changes have been detected and a dict with two keys,
1245 d914c76f Simeon Miteff
      C{job_info} and C{log_entries} otherwise.
1246 d914c76f Simeon Miteff
    @rtype: dict
1247 d9b67f70 Michael Hanselmann

1248 d9b67f70 Michael Hanselmann
    """
1249 d9b67f70 Michael Hanselmann
    body = {
1250 d9b67f70 Michael Hanselmann
      "fields": fields,
1251 d9b67f70 Michael Hanselmann
      "previous_job_info": prev_job_info,
1252 d9b67f70 Michael Hanselmann
      "previous_log_serial": prev_log_serial,
1253 d9b67f70 Michael Hanselmann
      }
1254 d9b67f70 Michael Hanselmann
1255 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1256 a198b2d9 Michael Hanselmann
                             "/%s/jobs/%s/wait" % (GANETI_RAPI_VERSION, job_id),
1257 a198b2d9 Michael Hanselmann
                             None, body)
1258 d9b67f70 Michael Hanselmann
1259 cf9ada49 Michael Hanselmann
  def CancelJob(self, job_id, dry_run=False):
1260 cf9ada49 Michael Hanselmann
    """Cancels a job.
1261 95ab4de9 David Knowles

1262 98805538 Michael Hanselmann
    @type job_id: string
1263 95ab4de9 David Knowles
    @param job_id: id of the job to delete
1264 95ab4de9 David Knowles
    @type dry_run: bool
1265 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1266 d914c76f Simeon Miteff
    @rtype: tuple
1267 d914c76f Simeon Miteff
    @return: tuple containing the result, and a message (bool, string)
1268 95ab4de9 David Knowles

1269 95ab4de9 David Knowles
    """
1270 95ab4de9 David Knowles
    query = []
1271 4c864b55 Michael Hanselmann
    _AppendDryRunIf(query, dry_run)
1272 95ab4de9 David Knowles
1273 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE,
1274 a198b2d9 Michael Hanselmann
                             "/%s/jobs/%s" % (GANETI_RAPI_VERSION, job_id),
1275 a198b2d9 Michael Hanselmann
                             query, None)
1276 95ab4de9 David Knowles
1277 95ab4de9 David Knowles
  def GetNodes(self, bulk=False):
1278 95ab4de9 David Knowles
    """Gets all nodes in the cluster.
1279 95ab4de9 David Knowles

1280 95ab4de9 David Knowles
    @type bulk: bool
1281 95ab4de9 David Knowles
    @param bulk: whether to return all information about all instances
1282 95ab4de9 David Knowles

1283 95ab4de9 David Knowles
    @rtype: list of dict or str
1284 95ab4de9 David Knowles
    @return: if bulk is true, info about nodes in the cluster,
1285 95ab4de9 David Knowles
        else list of nodes in the cluster
1286 95ab4de9 David Knowles

1287 95ab4de9 David Knowles
    """
1288 95ab4de9 David Knowles
    query = []
1289 4c864b55 Michael Hanselmann
    _AppendIf(query, bulk, ("bulk", 1))
1290 95ab4de9 David Knowles
1291 a198b2d9 Michael Hanselmann
    nodes = self._SendRequest(HTTP_GET, "/%s/nodes" % GANETI_RAPI_VERSION,
1292 a198b2d9 Michael Hanselmann
                              query, None)
1293 95ab4de9 David Knowles
    if bulk:
1294 95ab4de9 David Knowles
      return nodes
1295 95ab4de9 David Knowles
    else:
1296 95ab4de9 David Knowles
      return [n["id"] for n in nodes]
1297 95ab4de9 David Knowles
1298 591e5103 Michael Hanselmann
  def GetNode(self, node):
1299 95ab4de9 David Knowles
    """Gets information about a node.
1300 95ab4de9 David Knowles

1301 95ab4de9 David Knowles
    @type node: str
1302 95ab4de9 David Knowles
    @param node: node whose info to return
1303 95ab4de9 David Knowles

1304 95ab4de9 David Knowles
    @rtype: dict
1305 95ab4de9 David Knowles
    @return: info about the node
1306 95ab4de9 David Knowles

1307 95ab4de9 David Knowles
    """
1308 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1309 a198b2d9 Michael Hanselmann
                             "/%s/nodes/%s" % (GANETI_RAPI_VERSION, node),
1310 a198b2d9 Michael Hanselmann
                             None, None)
1311 95ab4de9 David Knowles
1312 95ab4de9 David Knowles
  def EvacuateNode(self, node, iallocator=None, remote_node=None,
1313 de40437a Michael Hanselmann
                   dry_run=False, early_release=None,
1314 0b58db81 Michael Hanselmann
                   mode=None, accept_old=False):
1315 95ab4de9 David Knowles
    """Evacuates instances from a Ganeti node.
1316 95ab4de9 David Knowles

1317 95ab4de9 David Knowles
    @type node: str
1318 95ab4de9 David Knowles
    @param node: node to evacuate
1319 95ab4de9 David Knowles
    @type iallocator: str or None
1320 95ab4de9 David Knowles
    @param iallocator: instance allocator to use
1321 95ab4de9 David Knowles
    @type remote_node: str
1322 95ab4de9 David Knowles
    @param remote_node: node to evaucate to
1323 95ab4de9 David Knowles
    @type dry_run: bool
1324 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1325 941b9309 Iustin Pop
    @type early_release: bool
1326 941b9309 Iustin Pop
    @param early_release: whether to enable parallelization
1327 0b58db81 Michael Hanselmann
    @type mode: string
1328 0b58db81 Michael Hanselmann
    @param mode: Node evacuation mode
1329 de40437a Michael Hanselmann
    @type accept_old: bool
1330 de40437a Michael Hanselmann
    @param accept_old: Whether caller is ready to accept old-style (pre-2.5)
1331 de40437a Michael Hanselmann
        results
1332 de40437a Michael Hanselmann

1333 de40437a Michael Hanselmann
    @rtype: string, or a list for pre-2.5 results
1334 de40437a Michael Hanselmann
    @return: Job ID or, if C{accept_old} is set and server is pre-2.5,
1335 de40437a Michael Hanselmann
      list of (job ID, instance name, new secondary node); if dry_run was
1336 de40437a Michael Hanselmann
      specified, then the actual move jobs were not submitted and the job IDs
1337 de40437a Michael Hanselmann
      will be C{None}
1338 95ab4de9 David Knowles

1339 941b9309 Iustin Pop
    @raises GanetiApiError: if an iallocator and remote_node are both
1340 941b9309 Iustin Pop
        specified
1341 95ab4de9 David Knowles

1342 95ab4de9 David Knowles
    """
1343 95ab4de9 David Knowles
    if iallocator and remote_node:
1344 cfc03c54 Michael Hanselmann
      raise GanetiApiError("Only one of iallocator or remote_node can be used")
1345 95ab4de9 David Knowles
1346 cfc03c54 Michael Hanselmann
    query = []
1347 4c864b55 Michael Hanselmann
    _AppendDryRunIf(query, dry_run)
1348 de40437a Michael Hanselmann
1349 de40437a Michael Hanselmann
    if _NODE_EVAC_RES1 in self.GetFeatures():
1350 0b58db81 Michael Hanselmann
      # Server supports body parameters
1351 de40437a Michael Hanselmann
      body = {}
1352 de40437a Michael Hanselmann
1353 57d8e228 Michael Hanselmann
      _SetItemIf(body, iallocator is not None, "iallocator", iallocator)
1354 57d8e228 Michael Hanselmann
      _SetItemIf(body, remote_node is not None, "remote_node", remote_node)
1355 57d8e228 Michael Hanselmann
      _SetItemIf(body, early_release is not None,
1356 57d8e228 Michael Hanselmann
                 "early_release", early_release)
1357 57d8e228 Michael Hanselmann
      _SetItemIf(body, mode is not None, "mode", mode)
1358 de40437a Michael Hanselmann
    else:
1359 de40437a Michael Hanselmann
      # Pre-2.5 request format
1360 de40437a Michael Hanselmann
      body = None
1361 de40437a Michael Hanselmann
1362 de40437a Michael Hanselmann
      if not accept_old:
1363 de40437a Michael Hanselmann
        raise GanetiApiError("Server is version 2.4 or earlier and caller does"
1364 de40437a Michael Hanselmann
                             " not accept old-style results (parameter"
1365 de40437a Michael Hanselmann
                             " accept_old)")
1366 de40437a Michael Hanselmann
1367 0b58db81 Michael Hanselmann
      # Pre-2.5 servers can only evacuate secondaries
1368 0b58db81 Michael Hanselmann
      if mode is not None and mode != NODE_EVAC_SEC:
1369 de40437a Michael Hanselmann
        raise GanetiApiError("Server can only evacuate secondary instances")
1370 de40437a Michael Hanselmann
1371 4c864b55 Michael Hanselmann
      _AppendIf(query, iallocator, ("iallocator", iallocator))
1372 4c864b55 Michael Hanselmann
      _AppendIf(query, remote_node, ("remote_node", remote_node))
1373 4c864b55 Michael Hanselmann
      _AppendIf(query, early_release, ("early_release", 1))
1374 95ab4de9 David Knowles
1375 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_POST,
1376 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/evacuate" %
1377 de40437a Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, body)
1378 95ab4de9 David Knowles
1379 b7a1c816 Michael Hanselmann
  def MigrateNode(self, node, mode=None, dry_run=False, iallocator=None,
1380 b7a1c816 Michael Hanselmann
                  target_node=None):
1381 95ab4de9 David Knowles
    """Migrates all primary instances from a node.
1382 95ab4de9 David Knowles

1383 95ab4de9 David Knowles
    @type node: str
1384 95ab4de9 David Knowles
    @param node: node to migrate
1385 1f334d96 Iustin Pop
    @type mode: string
1386 1f334d96 Iustin Pop
    @param mode: if passed, it will overwrite the live migration type,
1387 1f334d96 Iustin Pop
        otherwise the hypervisor default will be used
1388 95ab4de9 David Knowles
    @type dry_run: bool
1389 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1390 b7a1c816 Michael Hanselmann
    @type iallocator: string
1391 b7a1c816 Michael Hanselmann
    @param iallocator: instance allocator to use
1392 b7a1c816 Michael Hanselmann
    @type target_node: string
1393 b7a1c816 Michael Hanselmann
    @param target_node: Target node for shared-storage instances
1394 95ab4de9 David Knowles

1395 98805538 Michael Hanselmann
    @rtype: string
1396 95ab4de9 David Knowles
    @return: job id
1397 95ab4de9 David Knowles

1398 95ab4de9 David Knowles
    """
1399 95ab4de9 David Knowles
    query = []
1400 4c864b55 Michael Hanselmann
    _AppendDryRunIf(query, dry_run)
1401 95ab4de9 David Knowles
1402 b7a1c816 Michael Hanselmann
    if _NODE_MIGRATE_REQV1 in self.GetFeatures():
1403 b7a1c816 Michael Hanselmann
      body = {}
1404 b7a1c816 Michael Hanselmann
1405 57d8e228 Michael Hanselmann
      _SetItemIf(body, mode is not None, "mode", mode)
1406 57d8e228 Michael Hanselmann
      _SetItemIf(body, iallocator is not None, "iallocator", iallocator)
1407 57d8e228 Michael Hanselmann
      _SetItemIf(body, target_node is not None, "target_node", target_node)
1408 b7a1c816 Michael Hanselmann
1409 b7a1c816 Michael Hanselmann
      assert len(query) <= 1
1410 b7a1c816 Michael Hanselmann
1411 b7a1c816 Michael Hanselmann
      return self._SendRequest(HTTP_POST,
1412 b7a1c816 Michael Hanselmann
                               ("/%s/nodes/%s/migrate" %
1413 b7a1c816 Michael Hanselmann
                                (GANETI_RAPI_VERSION, node)), query, body)
1414 b7a1c816 Michael Hanselmann
    else:
1415 b7a1c816 Michael Hanselmann
      # Use old request format
1416 b7a1c816 Michael Hanselmann
      if target_node is not None:
1417 b7a1c816 Michael Hanselmann
        raise GanetiApiError("Server does not support specifying target node"
1418 b7a1c816 Michael Hanselmann
                             " for node migration")
1419 b7a1c816 Michael Hanselmann
1420 4c864b55 Michael Hanselmann
      _AppendIf(query, mode is not None, ("mode", mode))
1421 b7a1c816 Michael Hanselmann
1422 b7a1c816 Michael Hanselmann
      return self._SendRequest(HTTP_POST,
1423 b7a1c816 Michael Hanselmann
                               ("/%s/nodes/%s/migrate" %
1424 b7a1c816 Michael Hanselmann
                                (GANETI_RAPI_VERSION, node)), query, None)
1425 95ab4de9 David Knowles
1426 95ab4de9 David Knowles
  def GetNodeRole(self, node):
1427 95ab4de9 David Knowles
    """Gets the current role for a node.
1428 95ab4de9 David Knowles

1429 95ab4de9 David Knowles
    @type node: str
1430 95ab4de9 David Knowles
    @param node: node whose role to return
1431 95ab4de9 David Knowles

1432 95ab4de9 David Knowles
    @rtype: str
1433 95ab4de9 David Knowles
    @return: the current role for a node
1434 95ab4de9 David Knowles

1435 95ab4de9 David Knowles
    """
1436 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1437 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/role" %
1438 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), None, None)
1439 95ab4de9 David Knowles
1440 8de8e68d Michael Hanselmann
  def SetNodeRole(self, node, role, force=False, auto_promote=None):
1441 95ab4de9 David Knowles
    """Sets the role for a node.
1442 95ab4de9 David Knowles

1443 95ab4de9 David Knowles
    @type node: str
1444 95ab4de9 David Knowles
    @param node: the node whose role to set
1445 95ab4de9 David Knowles
    @type role: str
1446 95ab4de9 David Knowles
    @param role: the role to set for the node
1447 95ab4de9 David Knowles
    @type force: bool
1448 95ab4de9 David Knowles
    @param force: whether to force the role change
1449 8de8e68d Michael Hanselmann
    @type auto_promote: bool
1450 8de8e68d Michael Hanselmann
    @param auto_promote: Whether node(s) should be promoted to master candidate
1451 8de8e68d Michael Hanselmann
                         if necessary
1452 95ab4de9 David Knowles

1453 98805538 Michael Hanselmann
    @rtype: string
1454 95ab4de9 David Knowles
    @return: job id
1455 95ab4de9 David Knowles

1456 95ab4de9 David Knowles
    """
1457 4c864b55 Michael Hanselmann
    query = []
1458 4c864b55 Michael Hanselmann
    _AppendForceIf(query, force)
1459 4c864b55 Michael Hanselmann
    _AppendIf(query, auto_promote is not None, ("auto-promote", auto_promote))
1460 8de8e68d Michael Hanselmann
1461 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1462 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/role" %
1463 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, role)
1464 95ab4de9 David Knowles
1465 42d4d8b9 Michael Hanselmann
  def PowercycleNode(self, node, force=False):
1466 42d4d8b9 Michael Hanselmann
    """Powercycles a node.
1467 42d4d8b9 Michael Hanselmann

1468 42d4d8b9 Michael Hanselmann
    @type node: string
1469 42d4d8b9 Michael Hanselmann
    @param node: Node name
1470 42d4d8b9 Michael Hanselmann
    @type force: bool
1471 42d4d8b9 Michael Hanselmann
    @param force: Whether to force the operation
1472 42d4d8b9 Michael Hanselmann
    @rtype: string
1473 42d4d8b9 Michael Hanselmann
    @return: job id
1474 42d4d8b9 Michael Hanselmann

1475 42d4d8b9 Michael Hanselmann
    """
1476 4c864b55 Michael Hanselmann
    query = []
1477 4c864b55 Michael Hanselmann
    _AppendForceIf(query, force)
1478 42d4d8b9 Michael Hanselmann
1479 42d4d8b9 Michael Hanselmann
    return self._SendRequest(HTTP_POST,
1480 42d4d8b9 Michael Hanselmann
                             ("/%s/nodes/%s/powercycle" %
1481 42d4d8b9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1482 42d4d8b9 Michael Hanselmann
1483 370f2042 Guido Trotter
  def ModifyNode(self, node, **kwargs):
1484 94497dd1 Michael Hanselmann
    """Modifies a node.
1485 94497dd1 Michael Hanselmann

1486 94497dd1 Michael Hanselmann
    More details for parameters can be found in the RAPI documentation.
1487 94497dd1 Michael Hanselmann

1488 370f2042 Guido Trotter
    @type node: string
1489 370f2042 Guido Trotter
    @param node: Node name
1490 94497dd1 Michael Hanselmann
    @rtype: string
1491 94497dd1 Michael Hanselmann
    @return: job id
1492 94497dd1 Michael Hanselmann

1493 94497dd1 Michael Hanselmann
    """
1494 e366273a Guido Trotter
    return self._SendRequest(HTTP_POST,
1495 94497dd1 Michael Hanselmann
                             ("/%s/nodes/%s/modify" %
1496 370f2042 Guido Trotter
                              (GANETI_RAPI_VERSION, node)), None, kwargs)
1497 94497dd1 Michael Hanselmann
1498 95ab4de9 David Knowles
  def GetNodeStorageUnits(self, node, storage_type, output_fields):
1499 95ab4de9 David Knowles
    """Gets the storage units for a node.
1500 95ab4de9 David Knowles

1501 95ab4de9 David Knowles
    @type node: str
1502 95ab4de9 David Knowles
    @param node: the node whose storage units to return
1503 95ab4de9 David Knowles
    @type storage_type: str
1504 95ab4de9 David Knowles
    @param storage_type: storage type whose units to return
1505 95ab4de9 David Knowles
    @type output_fields: str
1506 95ab4de9 David Knowles
    @param output_fields: storage type fields to return
1507 95ab4de9 David Knowles

1508 98805538 Michael Hanselmann
    @rtype: string
1509 95ab4de9 David Knowles
    @return: job id where results can be retrieved
1510 95ab4de9 David Knowles

1511 95ab4de9 David Knowles
    """
1512 cfc03c54 Michael Hanselmann
    query = [
1513 cfc03c54 Michael Hanselmann
      ("storage_type", storage_type),
1514 cfc03c54 Michael Hanselmann
      ("output_fields", output_fields),
1515 cfc03c54 Michael Hanselmann
      ]
1516 95ab4de9 David Knowles
1517 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1518 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/storage" %
1519 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1520 95ab4de9 David Knowles
1521 fde28316 Michael Hanselmann
  def ModifyNodeStorageUnits(self, node, storage_type, name, allocatable=None):
1522 95ab4de9 David Knowles
    """Modifies parameters of storage units on the node.
1523 95ab4de9 David Knowles

1524 95ab4de9 David Knowles
    @type node: str
1525 95ab4de9 David Knowles
    @param node: node whose storage units to modify
1526 95ab4de9 David Knowles
    @type storage_type: str
1527 95ab4de9 David Knowles
    @param storage_type: storage type whose units to modify
1528 95ab4de9 David Knowles
    @type name: str
1529 95ab4de9 David Knowles
    @param name: name of the storage unit
1530 fde28316 Michael Hanselmann
    @type allocatable: bool or None
1531 fde28316 Michael Hanselmann
    @param allocatable: Whether to set the "allocatable" flag on the storage
1532 fde28316 Michael Hanselmann
                        unit (None=no modification, True=set, False=unset)
1533 95ab4de9 David Knowles

1534 98805538 Michael Hanselmann
    @rtype: string
1535 95ab4de9 David Knowles
    @return: job id
1536 95ab4de9 David Knowles

1537 95ab4de9 David Knowles
    """
1538 95ab4de9 David Knowles
    query = [
1539 cfc03c54 Michael Hanselmann
      ("storage_type", storage_type),
1540 cfc03c54 Michael Hanselmann
      ("name", name),
1541 cfc03c54 Michael Hanselmann
      ]
1542 cfc03c54 Michael Hanselmann
1543 4c864b55 Michael Hanselmann
    _AppendIf(query, allocatable is not None, ("allocatable", allocatable))
1544 fde28316 Michael Hanselmann
1545 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1546 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/storage/modify" %
1547 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1548 95ab4de9 David Knowles
1549 95ab4de9 David Knowles
  def RepairNodeStorageUnits(self, node, storage_type, name):
1550 95ab4de9 David Knowles
    """Repairs a storage unit on the node.
1551 95ab4de9 David Knowles

1552 95ab4de9 David Knowles
    @type node: str
1553 95ab4de9 David Knowles
    @param node: node whose storage units to repair
1554 95ab4de9 David Knowles
    @type storage_type: str
1555 95ab4de9 David Knowles
    @param storage_type: storage type to repair
1556 95ab4de9 David Knowles
    @type name: str
1557 95ab4de9 David Knowles
    @param name: name of the storage unit to repair
1558 95ab4de9 David Knowles

1559 98805538 Michael Hanselmann
    @rtype: string
1560 95ab4de9 David Knowles
    @return: job id
1561 95ab4de9 David Knowles

1562 95ab4de9 David Knowles
    """
1563 cfc03c54 Michael Hanselmann
    query = [
1564 cfc03c54 Michael Hanselmann
      ("storage_type", storage_type),
1565 cfc03c54 Michael Hanselmann
      ("name", name),
1566 cfc03c54 Michael Hanselmann
      ]
1567 95ab4de9 David Knowles
1568 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1569 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/storage/repair" %
1570 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1571 95ab4de9 David Knowles
1572 95ab4de9 David Knowles
  def GetNodeTags(self, node):
1573 95ab4de9 David Knowles
    """Gets the tags for a node.
1574 95ab4de9 David Knowles

1575 95ab4de9 David Knowles
    @type node: str
1576 95ab4de9 David Knowles
    @param node: node whose tags to return
1577 95ab4de9 David Knowles

1578 95ab4de9 David Knowles
    @rtype: list of str
1579 95ab4de9 David Knowles
    @return: tags for the node
1580 95ab4de9 David Knowles

1581 95ab4de9 David Knowles
    """
1582 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1583 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/tags" %
1584 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), None, None)
1585 95ab4de9 David Knowles
1586 95ab4de9 David Knowles
  def AddNodeTags(self, node, tags, dry_run=False):
1587 95ab4de9 David Knowles
    """Adds tags to a node.
1588 95ab4de9 David Knowles

1589 95ab4de9 David Knowles
    @type node: str
1590 95ab4de9 David Knowles
    @param node: node to add tags to
1591 95ab4de9 David Knowles
    @type tags: list of str
1592 95ab4de9 David Knowles
    @param tags: tags to add to the node
1593 95ab4de9 David Knowles
    @type dry_run: bool
1594 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1595 95ab4de9 David Knowles

1596 98805538 Michael Hanselmann
    @rtype: string
1597 95ab4de9 David Knowles
    @return: job id
1598 95ab4de9 David Knowles

1599 95ab4de9 David Knowles
    """
1600 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
1601 4c864b55 Michael Hanselmann
    _AppendDryRunIf(query, dry_run)
1602 95ab4de9 David Knowles
1603 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1604 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/tags" %
1605 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, tags)
1606 95ab4de9 David Knowles
1607 95ab4de9 David Knowles
  def DeleteNodeTags(self, node, tags, dry_run=False):
1608 95ab4de9 David Knowles
    """Delete tags from a node.
1609 95ab4de9 David Knowles

1610 95ab4de9 David Knowles
    @type node: str
1611 95ab4de9 David Knowles
    @param node: node to remove tags from
1612 95ab4de9 David Knowles
    @type tags: list of str
1613 95ab4de9 David Knowles
    @param tags: tags to remove from the node
1614 95ab4de9 David Knowles
    @type dry_run: bool
1615 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1616 95ab4de9 David Knowles

1617 98805538 Michael Hanselmann
    @rtype: string
1618 95ab4de9 David Knowles
    @return: job id
1619 95ab4de9 David Knowles

1620 95ab4de9 David Knowles
    """
1621 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
1622 4c864b55 Michael Hanselmann
    _AppendDryRunIf(query, dry_run)
1623 95ab4de9 David Knowles
1624 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE,
1625 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/tags" %
1626 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1627 a268af8d Adeodato Simo
1628 a268af8d Adeodato Simo
  def GetGroups(self, bulk=False):
1629 a268af8d Adeodato Simo
    """Gets all node groups in the cluster.
1630 a268af8d Adeodato Simo

1631 a268af8d Adeodato Simo
    @type bulk: bool
1632 a268af8d Adeodato Simo
    @param bulk: whether to return all information about the groups
1633 a268af8d Adeodato Simo

1634 a268af8d Adeodato Simo
    @rtype: list of dict or str
1635 a268af8d Adeodato Simo
    @return: if bulk is true, a list of dictionaries with info about all node
1636 a268af8d Adeodato Simo
        groups in the cluster, else a list of names of those node groups
1637 a268af8d Adeodato Simo

1638 a268af8d Adeodato Simo
    """
1639 a268af8d Adeodato Simo
    query = []
1640 4c864b55 Michael Hanselmann
    _AppendIf(query, bulk, ("bulk", 1))
1641 a268af8d Adeodato Simo
1642 a268af8d Adeodato Simo
    groups = self._SendRequest(HTTP_GET, "/%s/groups" % GANETI_RAPI_VERSION,
1643 a268af8d Adeodato Simo
                               query, None)
1644 a268af8d Adeodato Simo
    if bulk:
1645 a268af8d Adeodato Simo
      return groups
1646 a268af8d Adeodato Simo
    else:
1647 a268af8d Adeodato Simo
      return [g["name"] for g in groups]
1648 a268af8d Adeodato Simo
1649 a268af8d Adeodato Simo
  def GetGroup(self, group):
1650 a268af8d Adeodato Simo
    """Gets information about a node group.
1651 a268af8d Adeodato Simo

1652 a268af8d Adeodato Simo
    @type group: str
1653 a268af8d Adeodato Simo
    @param group: name of the node group whose info to return
1654 a268af8d Adeodato Simo

1655 a268af8d Adeodato Simo
    @rtype: dict
1656 a268af8d Adeodato Simo
    @return: info about the node group
1657 a268af8d Adeodato Simo

1658 a268af8d Adeodato Simo
    """
1659 a268af8d Adeodato Simo
    return self._SendRequest(HTTP_GET,
1660 a268af8d Adeodato Simo
                             "/%s/groups/%s" % (GANETI_RAPI_VERSION, group),
1661 a268af8d Adeodato Simo
                             None, None)
1662 a268af8d Adeodato Simo
1663 90e99856 Adeodato Simo
  def CreateGroup(self, name, alloc_policy=None, dry_run=False):
1664 a268af8d Adeodato Simo
    """Creates a new node group.
1665 a268af8d Adeodato Simo

1666 a268af8d Adeodato Simo
    @type name: str
1667 a268af8d Adeodato Simo
    @param name: the name of node group to create
1668 90e99856 Adeodato Simo
    @type alloc_policy: str
1669 90e99856 Adeodato Simo
    @param alloc_policy: the desired allocation policy for the group, if any
1670 a268af8d Adeodato Simo
    @type dry_run: bool
1671 a268af8d Adeodato Simo
    @param dry_run: whether to peform a dry run
1672 a268af8d Adeodato Simo

1673 98805538 Michael Hanselmann
    @rtype: string
1674 a268af8d Adeodato Simo
    @return: job id
1675 a268af8d Adeodato Simo

1676 a268af8d Adeodato Simo
    """
1677 a268af8d Adeodato Simo
    query = []
1678 4c864b55 Michael Hanselmann
    _AppendDryRunIf(query, dry_run)
1679 a268af8d Adeodato Simo
1680 a268af8d Adeodato Simo
    body = {
1681 a268af8d Adeodato Simo
      "name": name,
1682 90e99856 Adeodato Simo
      "alloc_policy": alloc_policy
1683 a268af8d Adeodato Simo
      }
1684 a268af8d Adeodato Simo
1685 a268af8d Adeodato Simo
    return self._SendRequest(HTTP_POST, "/%s/groups" % GANETI_RAPI_VERSION,
1686 a268af8d Adeodato Simo
                             query, body)
1687 a268af8d Adeodato Simo
1688 f18fab7d Adeodato Simo
  def ModifyGroup(self, group, **kwargs):
1689 f18fab7d Adeodato Simo
    """Modifies a node group.
1690 f18fab7d Adeodato Simo

1691 f18fab7d Adeodato Simo
    More details for parameters can be found in the RAPI documentation.
1692 f18fab7d Adeodato Simo

1693 f18fab7d Adeodato Simo
    @type group: string
1694 f18fab7d Adeodato Simo
    @param group: Node group name
1695 98805538 Michael Hanselmann
    @rtype: string
1696 f18fab7d Adeodato Simo
    @return: job id
1697 f18fab7d Adeodato Simo

1698 f18fab7d Adeodato Simo
    """
1699 f18fab7d Adeodato Simo
    return self._SendRequest(HTTP_PUT,
1700 f18fab7d Adeodato Simo
                             ("/%s/groups/%s/modify" %
1701 f18fab7d Adeodato Simo
                              (GANETI_RAPI_VERSION, group)), None, kwargs)
1702 f18fab7d Adeodato Simo
1703 a268af8d Adeodato Simo
  def DeleteGroup(self, group, dry_run=False):
1704 a268af8d Adeodato Simo
    """Deletes a node group.
1705 a268af8d Adeodato Simo

1706 a268af8d Adeodato Simo
    @type group: str
1707 a268af8d Adeodato Simo
    @param group: the node group to delete
1708 a268af8d Adeodato Simo
    @type dry_run: bool
1709 a268af8d Adeodato Simo
    @param dry_run: whether to peform a dry run
1710 a268af8d Adeodato Simo

1711 98805538 Michael Hanselmann
    @rtype: string
1712 a268af8d Adeodato Simo
    @return: job id
1713 a268af8d Adeodato Simo

1714 a268af8d Adeodato Simo
    """
1715 a268af8d Adeodato Simo
    query = []
1716 4c864b55 Michael Hanselmann
    _AppendDryRunIf(query, dry_run)
1717 a268af8d Adeodato Simo
1718 a268af8d Adeodato Simo
    return self._SendRequest(HTTP_DELETE,
1719 a268af8d Adeodato Simo
                             ("/%s/groups/%s" %
1720 a268af8d Adeodato Simo
                              (GANETI_RAPI_VERSION, group)), query, None)
1721 a268af8d Adeodato Simo
1722 a268af8d Adeodato Simo
  def RenameGroup(self, group, new_name):
1723 a268af8d Adeodato Simo
    """Changes the name of a node group.
1724 a268af8d Adeodato Simo

1725 a268af8d Adeodato Simo
    @type group: string
1726 a268af8d Adeodato Simo
    @param group: Node group name
1727 a268af8d Adeodato Simo
    @type new_name: string
1728 a268af8d Adeodato Simo
    @param new_name: New node group name
1729 a268af8d Adeodato Simo

1730 98805538 Michael Hanselmann
    @rtype: string
1731 a268af8d Adeodato Simo
    @return: job id
1732 a268af8d Adeodato Simo

1733 a268af8d Adeodato Simo
    """
1734 a268af8d Adeodato Simo
    body = {
1735 a268af8d Adeodato Simo
      "new_name": new_name,
1736 a268af8d Adeodato Simo
      }
1737 a268af8d Adeodato Simo
1738 a268af8d Adeodato Simo
    return self._SendRequest(HTTP_PUT,
1739 a268af8d Adeodato Simo
                             ("/%s/groups/%s/rename" %
1740 a268af8d Adeodato Simo
                              (GANETI_RAPI_VERSION, group)), None, body)
1741 4245446f Adeodato Simo
1742 4245446f Adeodato Simo
  def AssignGroupNodes(self, group, nodes, force=False, dry_run=False):
1743 4245446f Adeodato Simo
    """Assigns nodes to a group.
1744 4245446f Adeodato Simo

1745 4245446f Adeodato Simo
    @type group: string
1746 4245446f Adeodato Simo
    @param group: Node gropu name
1747 4245446f Adeodato Simo
    @type nodes: list of strings
1748 4245446f Adeodato Simo
    @param nodes: List of nodes to assign to the group
1749 4245446f Adeodato Simo

1750 98805538 Michael Hanselmann
    @rtype: string
1751 4245446f Adeodato Simo
    @return: job id
1752 4245446f Adeodato Simo

1753 4245446f Adeodato Simo
    """
1754 4245446f Adeodato Simo
    query = []
1755 4c864b55 Michael Hanselmann
    _AppendForceIf(query, force)
1756 4c864b55 Michael Hanselmann
    _AppendDryRunIf(query, dry_run)
1757 4245446f Adeodato Simo
1758 4245446f Adeodato Simo
    body = {
1759 4245446f Adeodato Simo
      "nodes": nodes,
1760 4245446f Adeodato Simo
      }
1761 4245446f Adeodato Simo
1762 4245446f Adeodato Simo
    return self._SendRequest(HTTP_PUT,
1763 4245446f Adeodato Simo
                             ("/%s/groups/%s/assign-nodes" %
1764 4245446f Adeodato Simo
                             (GANETI_RAPI_VERSION, group)), query, body)
1765 208a6cff Michael Hanselmann
1766 414ebaf1 Michael Hanselmann
  def GetGroupTags(self, group):
1767 414ebaf1 Michael Hanselmann
    """Gets tags for a node group.
1768 414ebaf1 Michael Hanselmann

1769 414ebaf1 Michael Hanselmann
    @type group: string
1770 414ebaf1 Michael Hanselmann
    @param group: Node group whose tags to return
1771 414ebaf1 Michael Hanselmann

1772 414ebaf1 Michael Hanselmann
    @rtype: list of strings
1773 414ebaf1 Michael Hanselmann
    @return: tags for the group
1774 414ebaf1 Michael Hanselmann

1775 414ebaf1 Michael Hanselmann
    """
1776 414ebaf1 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1777 414ebaf1 Michael Hanselmann
                             ("/%s/groups/%s/tags" %
1778 414ebaf1 Michael Hanselmann
                              (GANETI_RAPI_VERSION, group)), None, None)
1779 414ebaf1 Michael Hanselmann
1780 414ebaf1 Michael Hanselmann
  def AddGroupTags(self, group, tags, dry_run=False):
1781 414ebaf1 Michael Hanselmann
    """Adds tags to a node group.
1782 414ebaf1 Michael Hanselmann

1783 414ebaf1 Michael Hanselmann
    @type group: str
1784 414ebaf1 Michael Hanselmann
    @param group: group to add tags to
1785 414ebaf1 Michael Hanselmann
    @type tags: list of string
1786 414ebaf1 Michael Hanselmann
    @param tags: tags to add to the group
1787 414ebaf1 Michael Hanselmann
    @type dry_run: bool
1788 414ebaf1 Michael Hanselmann
    @param dry_run: whether to perform a dry run
1789 414ebaf1 Michael Hanselmann

1790 414ebaf1 Michael Hanselmann
    @rtype: string
1791 414ebaf1 Michael Hanselmann
    @return: job id
1792 414ebaf1 Michael Hanselmann

1793 414ebaf1 Michael Hanselmann
    """
1794 414ebaf1 Michael Hanselmann
    query = [("tag", t) for t in tags]
1795 4c864b55 Michael Hanselmann
    _AppendDryRunIf(query, dry_run)
1796 414ebaf1 Michael Hanselmann
1797 414ebaf1 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1798 414ebaf1 Michael Hanselmann
                             ("/%s/groups/%s/tags" %
1799 414ebaf1 Michael Hanselmann
                              (GANETI_RAPI_VERSION, group)), query, None)
1800 414ebaf1 Michael Hanselmann
1801 414ebaf1 Michael Hanselmann
  def DeleteGroupTags(self, group, tags, dry_run=False):
1802 414ebaf1 Michael Hanselmann
    """Deletes tags from a node group.
1803 414ebaf1 Michael Hanselmann

1804 414ebaf1 Michael Hanselmann
    @type group: str
1805 414ebaf1 Michael Hanselmann
    @param group: group to delete tags from
1806 414ebaf1 Michael Hanselmann
    @type tags: list of string
1807 414ebaf1 Michael Hanselmann
    @param tags: tags to delete
1808 414ebaf1 Michael Hanselmann
    @type dry_run: bool
1809 414ebaf1 Michael Hanselmann
    @param dry_run: whether to perform a dry run
1810 414ebaf1 Michael Hanselmann
    @rtype: string
1811 414ebaf1 Michael Hanselmann
    @return: job id
1812 414ebaf1 Michael Hanselmann

1813 414ebaf1 Michael Hanselmann
    """
1814 414ebaf1 Michael Hanselmann
    query = [("tag", t) for t in tags]
1815 4c864b55 Michael Hanselmann
    _AppendDryRunIf(query, dry_run)
1816 414ebaf1 Michael Hanselmann
1817 414ebaf1 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE,
1818 414ebaf1 Michael Hanselmann
                             ("/%s/groups/%s/tags" %
1819 414ebaf1 Michael Hanselmann
                              (GANETI_RAPI_VERSION, group)), query, None)
1820 414ebaf1 Michael Hanselmann
1821 2e5c33db Iustin Pop
  def Query(self, what, fields, qfilter=None):
1822 208a6cff Michael Hanselmann
    """Retrieves information about resources.
1823 208a6cff Michael Hanselmann

1824 208a6cff Michael Hanselmann
    @type what: string
1825 208a6cff Michael Hanselmann
    @param what: Resource name, one of L{constants.QR_VIA_RAPI}
1826 208a6cff Michael Hanselmann
    @type fields: list of string
1827 208a6cff Michael Hanselmann
    @param fields: Requested fields
1828 2e5c33db Iustin Pop
    @type qfilter: None or list
1829 2e5c33db Iustin Pop
    @param qfilter: Query filter
1830 208a6cff Michael Hanselmann

1831 208a6cff Michael Hanselmann
    @rtype: string
1832 208a6cff Michael Hanselmann
    @return: job id
1833 208a6cff Michael Hanselmann

1834 208a6cff Michael Hanselmann
    """
1835 208a6cff Michael Hanselmann
    body = {
1836 208a6cff Michael Hanselmann
      "fields": fields,
1837 208a6cff Michael Hanselmann
      }
1838 208a6cff Michael Hanselmann
1839 57d8e228 Michael Hanselmann
    _SetItemIf(body, qfilter is not None, "qfilter", qfilter)
1840 57d8e228 Michael Hanselmann
    # TODO: remove "filter" after 2.7
1841 57d8e228 Michael Hanselmann
    _SetItemIf(body, qfilter is not None, "filter", qfilter)
1842 208a6cff Michael Hanselmann
1843 208a6cff Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1844 208a6cff Michael Hanselmann
                             ("/%s/query/%s" %
1845 208a6cff Michael Hanselmann
                              (GANETI_RAPI_VERSION, what)), None, body)
1846 208a6cff Michael Hanselmann
1847 208a6cff Michael Hanselmann
  def QueryFields(self, what, fields=None):
1848 208a6cff Michael Hanselmann
    """Retrieves available fields for a resource.
1849 208a6cff Michael Hanselmann

1850 208a6cff Michael Hanselmann
    @type what: string
1851 208a6cff Michael Hanselmann
    @param what: Resource name, one of L{constants.QR_VIA_RAPI}
1852 208a6cff Michael Hanselmann
    @type fields: list of string
1853 208a6cff Michael Hanselmann
    @param fields: Requested fields
1854 208a6cff Michael Hanselmann

1855 208a6cff Michael Hanselmann
    @rtype: string
1856 208a6cff Michael Hanselmann
    @return: job id
1857 208a6cff Michael Hanselmann

1858 208a6cff Michael Hanselmann
    """
1859 208a6cff Michael Hanselmann
    query = []
1860 208a6cff Michael Hanselmann
1861 208a6cff Michael Hanselmann
    if fields is not None:
1862 4c864b55 Michael Hanselmann
      _AppendIf(query, True, ("fields", ",".join(fields)))
1863 208a6cff Michael Hanselmann
1864 208a6cff Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1865 208a6cff Michael Hanselmann
                             ("/%s/query/%s/fields" %
1866 208a6cff Michael Hanselmann
                              (GANETI_RAPI_VERSION, what)), query, None)