Statistics
| Branch: | Tag: | Revision:

root / lib / rapi / client.py @ fa6dd6bb

History | View | Annotate | Download (54.3 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 CertificateError(Error):
137 95ab4de9 David Knowles
  """Raised when a problem is found with the SSL certificate.
138 95ab4de9 David Knowles

139 95ab4de9 David Knowles
  """
140 95ab4de9 David Knowles
  pass
141 95ab4de9 David Knowles
142 95ab4de9 David Knowles
143 95ab4de9 David Knowles
class GanetiApiError(Error):
144 95ab4de9 David Knowles
  """Generic error raised from Ganeti API.
145 95ab4de9 David Knowles

146 95ab4de9 David Knowles
  """
147 8a019a03 Michael Hanselmann
  def __init__(self, msg, code=None):
148 8a019a03 Michael Hanselmann
    Error.__init__(self, msg)
149 8a019a03 Michael Hanselmann
    self.code = code
150 95ab4de9 David Knowles
151 95ab4de9 David Knowles
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 2a7c3583 Michael Hanselmann
    elif lcsslver.startswith("gnutls/"):
260 2a7c3583 Michael Hanselmann
      if capath:
261 2a7c3583 Michael Hanselmann
        raise Error("cURL linked against GnuTLS has no support for a"
262 2a7c3583 Michael Hanselmann
                    " CA path (%s)" % (pycurl.version, ))
263 9279e986 Michael Hanselmann
    else:
264 2a7c3583 Michael Hanselmann
      raise NotImplementedError("cURL uses unsupported SSL version '%s'" %
265 2a7c3583 Michael Hanselmann
                                sslver)
266 2a7c3583 Michael Hanselmann
267 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.VERBOSE, verbose)
268 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.NOSIGNAL, not use_signal)
269 2a7c3583 Michael Hanselmann
270 2a7c3583 Michael Hanselmann
    # Whether to verify remote peer's CN
271 2a7c3583 Michael Hanselmann
    if verify_hostname:
272 2a7c3583 Michael Hanselmann
      # curl_easy_setopt(3): "When CURLOPT_SSL_VERIFYHOST is 2, that
273 2a7c3583 Michael Hanselmann
      # certificate must indicate that the server is the server to which you
274 2a7c3583 Michael Hanselmann
      # meant to connect, or the connection fails. [...] When the value is 1,
275 2a7c3583 Michael Hanselmann
      # the certificate must contain a Common Name field, but it doesn't matter
276 2a7c3583 Michael Hanselmann
      # what name it says. [...]"
277 2a7c3583 Michael Hanselmann
      curl.setopt(pycurl.SSL_VERIFYHOST, 2)
278 beba56ae Michael Hanselmann
    else:
279 2a7c3583 Michael Hanselmann
      curl.setopt(pycurl.SSL_VERIFYHOST, 0)
280 2a7c3583 Michael Hanselmann
281 2a7c3583 Michael Hanselmann
    if cafile or capath or use_curl_cabundle:
282 2a7c3583 Michael Hanselmann
      # Require certificates to be checked
283 2a7c3583 Michael Hanselmann
      curl.setopt(pycurl.SSL_VERIFYPEER, True)
284 2a7c3583 Michael Hanselmann
      if cafile:
285 2a7c3583 Michael Hanselmann
        curl.setopt(pycurl.CAINFO, str(cafile))
286 2a7c3583 Michael Hanselmann
      if capath:
287 2a7c3583 Michael Hanselmann
        curl.setopt(pycurl.CAPATH, str(capath))
288 2a7c3583 Michael Hanselmann
      # Not changing anything for using default CA bundle
289 2a7c3583 Michael Hanselmann
    else:
290 2a7c3583 Michael Hanselmann
      # Disable SSL certificate verification
291 2a7c3583 Michael Hanselmann
      curl.setopt(pycurl.SSL_VERIFYPEER, False)
292 9279e986 Michael Hanselmann
293 2a7c3583 Michael Hanselmann
    if proxy is not None:
294 2a7c3583 Michael Hanselmann
      curl.setopt(pycurl.PROXY, str(proxy))
295 9279e986 Michael Hanselmann
296 2a7c3583 Michael Hanselmann
    # Timeouts
297 2a7c3583 Michael Hanselmann
    if connect_timeout is not None:
298 2a7c3583 Michael Hanselmann
      curl.setopt(pycurl.CONNECTTIMEOUT, connect_timeout)
299 2a7c3583 Michael Hanselmann
    if timeout is not None:
300 2a7c3583 Michael Hanselmann
      curl.setopt(pycurl.TIMEOUT, timeout)
301 9279e986 Michael Hanselmann
302 2a7c3583 Michael Hanselmann
  return _ConfigCurl
303 9279e986 Michael Hanselmann
304 9279e986 Michael Hanselmann
305 b459a848 Andrea Spadaccini
class GanetiRapiClient(object): # pylint: disable=R0904
306 95ab4de9 David Knowles
  """Ganeti RAPI client.
307 95ab4de9 David Knowles

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

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

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

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

392 10f5ab6c Michael Hanselmann
    @type query: list of two-tuples
393 10f5ab6c Michael Hanselmann
    @param query: Query arguments
394 10f5ab6c Michael Hanselmann
    @rtype: list
395 10f5ab6c Michael Hanselmann
    @return: Query list with encoded values
396 10f5ab6c Michael Hanselmann

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

419 95ab4de9 David Knowles
    This constructs a full URL, encodes and decodes HTTP bodies, and
420 95ab4de9 David Knowles
    handles invalid responses in a pythonic way.
421 95ab4de9 David Knowles

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

431 95ab4de9 David Knowles
    @rtype: str
432 95ab4de9 David Knowles
    @return: JSON-Decoded response
433 95ab4de9 David Knowles

434 f2f88abf David Knowles
    @raises CertificateError: If an invalid SSL certificate is found
435 95ab4de9 David Knowles
    @raises GanetiApiError: If an invalid response is returned
436 95ab4de9 David Knowles

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

507 95ab4de9 David Knowles
    @rtype: int
508 f2f88abf David Knowles
    @return: Ganeti Remote API version
509 95ab4de9 David Knowles

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

516 7eac4a4d Michael Hanselmann
    @rtype: list
517 7eac4a4d Michael Hanselmann
    @return: List of optional features
518 7eac4a4d Michael Hanselmann

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

533 95ab4de9 David Knowles
    @rtype: list of str
534 95ab4de9 David Knowles
    @return: operating systems
535 95ab4de9 David Knowles

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

543 95ab4de9 David Knowles
    @rtype: dict
544 95ab4de9 David Knowles
    @return: information about the cluster
545 95ab4de9 David Knowles

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

553 d914c76f Simeon Miteff
    @rtype: string
554 54d4c13b Michael Hanselmann
    @return: job id
555 54d4c13b Michael Hanselmann

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

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

566 98805538 Michael Hanselmann
    @rtype: string
567 62e999a5 Michael Hanselmann
    @return: job id
568 62e999a5 Michael Hanselmann

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

578 95ab4de9 David Knowles
    @rtype: list of str
579 95ab4de9 David Knowles
    @return: cluster tags
580 95ab4de9 David Knowles

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

588 95ab4de9 David Knowles
    @type tags: list of str
589 95ab4de9 David Knowles
    @param tags: tags to add to the cluster
590 95ab4de9 David Knowles
    @type dry_run: bool
591 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
592 95ab4de9 David Knowles

593 98805538 Michael Hanselmann
    @rtype: string
594 95ab4de9 David Knowles
    @return: job id
595 95ab4de9 David Knowles

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

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

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

623 95ab4de9 David Knowles
    @type bulk: bool
624 95ab4de9 David Knowles
    @param bulk: whether to return all information about all instances
625 95ab4de9 David Knowles

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

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

644 95ab4de9 David Knowles
    @type instance: str
645 95ab4de9 David Knowles
    @param instance: instance whose info to return
646 95ab4de9 David Knowles

647 95ab4de9 David Knowles
    @rtype: dict
648 95ab4de9 David Knowles
    @return: info about the instance
649 95ab4de9 David Knowles

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

658 591e5103 Michael Hanselmann
    @type instance: string
659 591e5103 Michael Hanselmann
    @param instance: Instance name
660 591e5103 Michael Hanselmann
    @rtype: string
661 591e5103 Michael Hanselmann
    @return: Job ID
662 591e5103 Michael Hanselmann

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

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

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

693 98805538 Michael Hanselmann
    @rtype: string
694 95ab4de9 David Knowles
    @return: job id
695 95ab4de9 David Knowles

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

729 95ab4de9 David Knowles
    @type instance: str
730 95ab4de9 David Knowles
    @param instance: the instance to delete
731 95ab4de9 David Knowles

732 98805538 Michael Hanselmann
    @rtype: string
733 cab667cc David Knowles
    @return: job id
734 cab667cc David Knowles

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

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

748 3b7158ef Michael Hanselmann
    @type instance: string
749 3b7158ef Michael Hanselmann
    @param instance: Instance name
750 98805538 Michael Hanselmann
    @rtype: string
751 3b7158ef Michael Hanselmann
    @return: job id
752 3b7158ef Michael Hanselmann

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

763 b680c8be Michael Hanselmann
    @type instance: string
764 b680c8be Michael Hanselmann
    @param instance: Instance name
765 b680c8be Michael Hanselmann
    @type ignore_size: bool
766 b680c8be Michael Hanselmann
    @param ignore_size: Whether to ignore recorded size
767 d914c76f Simeon Miteff
    @rtype: string
768 b680c8be Michael Hanselmann
    @return: job id
769 b680c8be Michael Hanselmann

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

781 b680c8be Michael Hanselmann
    @type instance: string
782 b680c8be Michael Hanselmann
    @param instance: Instance name
783 d914c76f Simeon Miteff
    @rtype: string
784 b680c8be Michael Hanselmann
    @return: job id
785 b680c8be Michael Hanselmann

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

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

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

815 e23881ed Michael Hanselmann
    More details for parameters can be found in the RAPI documentation.
816 e23881ed Michael Hanselmann

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

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

843 95ab4de9 David Knowles
    @type instance: str
844 95ab4de9 David Knowles
    @param instance: instance whose tags to return
845 95ab4de9 David Knowles

846 95ab4de9 David Knowles
    @rtype: list of str
847 95ab4de9 David Knowles
    @return: tags for the instance
848 95ab4de9 David Knowles

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

857 95ab4de9 David Knowles
    @type instance: str
858 95ab4de9 David Knowles
    @param instance: instance to add tags to
859 95ab4de9 David Knowles
    @type tags: list of str
860 95ab4de9 David Knowles
    @param tags: tags to add to the instance
861 95ab4de9 David Knowles
    @type dry_run: bool
862 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
863 95ab4de9 David Knowles

864 98805538 Michael Hanselmann
    @rtype: string
865 95ab4de9 David Knowles
    @return: job id
866 95ab4de9 David Knowles

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

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

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

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

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

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

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

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

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

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

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

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

1019 98805538 Michael Hanselmann
    @rtype: string
1020 95ab4de9 David Knowles
    @return: job id
1021 95ab4de9 David Knowles

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

1043 ebeb600f Michael Hanselmann
    @type instance: string
1044 ebeb600f Michael Hanselmann
    @param instance: Instance name
1045 ebeb600f Michael Hanselmann
    @type mode: string
1046 ebeb600f Michael Hanselmann
    @param mode: Export mode
1047 ebeb600f Michael Hanselmann
    @rtype: string
1048 ebeb600f Michael Hanselmann
    @return: Job ID
1049 ebeb600f Michael Hanselmann

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

1061 ebeb600f Michael Hanselmann
    @type instance: string
1062 ebeb600f Michael Hanselmann
    @param instance: Instance name
1063 ebeb600f Michael Hanselmann
    @type mode: string
1064 ebeb600f Michael Hanselmann
    @param mode: Export mode
1065 ebeb600f Michael Hanselmann
    @rtype: string
1066 ebeb600f Michael Hanselmann
    @return: Job ID
1067 ebeb600f Michael Hanselmann

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

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

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

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

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

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

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

1162 b82d4c5e Michael Hanselmann
    @type instance: string
1163 b82d4c5e Michael Hanselmann
    @param instance: Instance name
1164 d914c76f Simeon Miteff
    @rtype: dict
1165 d914c76f Simeon Miteff
    @return: dictionary containing information about instance's console
1166 b82d4c5e Michael Hanselmann

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

1175 95ab4de9 David Knowles
    @rtype: list of int
1176 95ab4de9 David Knowles
    @return: job ids for the cluster
1177 95ab4de9 David Knowles

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

1187 98805538 Michael Hanselmann
    @type job_id: string
1188 95ab4de9 David Knowles
    @param job_id: job id whose status to query
1189 95ab4de9 David Knowles

1190 95ab4de9 David Knowles
    @rtype: dict
1191 95ab4de9 David Knowles
    @return: job status
1192 95ab4de9 David Knowles

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

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

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

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

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

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

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

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

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

1276 95ab4de9 David Knowles
    @type bulk: bool
1277 95ab4de9 David Knowles
    @param bulk: whether to return all information about all instances
1278 95ab4de9 David Knowles

1279 95ab4de9 David Knowles
    @rtype: list of dict or str
1280 95ab4de9 David Knowles
    @return: if bulk is true, info about nodes in the cluster,
1281 95ab4de9 David Knowles
        else list of nodes in the cluster
1282 95ab4de9 David Knowles

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

1297 95ab4de9 David Knowles
    @type node: str
1298 95ab4de9 David Knowles
    @param node: node whose info to return
1299 95ab4de9 David Knowles

1300 95ab4de9 David Knowles
    @rtype: dict
1301 95ab4de9 David Knowles
    @return: info about the node
1302 95ab4de9 David Knowles

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

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

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

1335 941b9309 Iustin Pop
    @raises GanetiApiError: if an iallocator and remote_node are both
1336 941b9309 Iustin Pop
        specified
1337 95ab4de9 David Knowles

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

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

1391 98805538 Michael Hanselmann
    @rtype: string
1392 95ab4de9 David Knowles
    @return: job id
1393 95ab4de9 David Knowles

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

1425 95ab4de9 David Knowles
    @type node: str
1426 95ab4de9 David Knowles
    @param node: node whose role to return
1427 95ab4de9 David Knowles

1428 95ab4de9 David Knowles
    @rtype: str
1429 95ab4de9 David Knowles
    @return: the current role for a node
1430 95ab4de9 David Knowles

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

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

1449 98805538 Michael Hanselmann
    @rtype: string
1450 95ab4de9 David Knowles
    @return: job id
1451 95ab4de9 David Knowles

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

1464 42d4d8b9 Michael Hanselmann
    @type node: string
1465 42d4d8b9 Michael Hanselmann
    @param node: Node name
1466 42d4d8b9 Michael Hanselmann
    @type force: bool
1467 42d4d8b9 Michael Hanselmann
    @param force: Whether to force the operation
1468 42d4d8b9 Michael Hanselmann
    @rtype: string
1469 42d4d8b9 Michael Hanselmann
    @return: job id
1470 42d4d8b9 Michael Hanselmann

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

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

1484 370f2042 Guido Trotter
    @type node: string
1485 370f2042 Guido Trotter
    @param node: Node name
1486 94497dd1 Michael Hanselmann
    @rtype: string
1487 94497dd1 Michael Hanselmann
    @return: job id
1488 94497dd1 Michael Hanselmann

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

1497 95ab4de9 David Knowles
    @type node: str
1498 95ab4de9 David Knowles
    @param node: the node whose storage units to return
1499 95ab4de9 David Knowles
    @type storage_type: str
1500 95ab4de9 David Knowles
    @param storage_type: storage type whose units to return
1501 95ab4de9 David Knowles
    @type output_fields: str
1502 95ab4de9 David Knowles
    @param output_fields: storage type fields to return
1503 95ab4de9 David Knowles

1504 98805538 Michael Hanselmann
    @rtype: string
1505 95ab4de9 David Knowles
    @return: job id where results can be retrieved
1506 95ab4de9 David Knowles

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

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

1530 98805538 Michael Hanselmann
    @rtype: string
1531 95ab4de9 David Knowles
    @return: job id
1532 95ab4de9 David Knowles

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

1548 95ab4de9 David Knowles
    @type node: str
1549 95ab4de9 David Knowles
    @param node: node whose storage units to repair
1550 95ab4de9 David Knowles
    @type storage_type: str
1551 95ab4de9 David Knowles
    @param storage_type: storage type to repair
1552 95ab4de9 David Knowles
    @type name: str
1553 95ab4de9 David Knowles
    @param name: name of the storage unit to repair
1554 95ab4de9 David Knowles

1555 98805538 Michael Hanselmann
    @rtype: string
1556 95ab4de9 David Knowles
    @return: job id
1557 95ab4de9 David Knowles

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

1571 95ab4de9 David Knowles
    @type node: str
1572 95ab4de9 David Knowles
    @param node: node whose tags to return
1573 95ab4de9 David Knowles

1574 95ab4de9 David Knowles
    @rtype: list of str
1575 95ab4de9 David Knowles
    @return: tags for the node
1576 95ab4de9 David Knowles

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

1585 95ab4de9 David Knowles
    @type node: str
1586 95ab4de9 David Knowles
    @param node: node to add tags to
1587 95ab4de9 David Knowles
    @type tags: list of str
1588 95ab4de9 David Knowles
    @param tags: tags to add to the node
1589 95ab4de9 David Knowles
    @type dry_run: bool
1590 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1591 95ab4de9 David Knowles

1592 98805538 Michael Hanselmann
    @rtype: string
1593 95ab4de9 David Knowles
    @return: job id
1594 95ab4de9 David Knowles

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

1606 95ab4de9 David Knowles
    @type node: str
1607 95ab4de9 David Knowles
    @param node: node to remove tags from
1608 95ab4de9 David Knowles
    @type tags: list of str
1609 95ab4de9 David Knowles
    @param tags: tags to remove from the node
1610 95ab4de9 David Knowles
    @type dry_run: bool
1611 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1612 95ab4de9 David Knowles

1613 98805538 Michael Hanselmann
    @rtype: string
1614 95ab4de9 David Knowles
    @return: job id
1615 95ab4de9 David Knowles

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

1627 a268af8d Adeodato Simo
    @type bulk: bool
1628 a268af8d Adeodato Simo
    @param bulk: whether to return all information about the groups
1629 a268af8d Adeodato Simo

1630 a268af8d Adeodato Simo
    @rtype: list of dict or str
1631 a268af8d Adeodato Simo
    @return: if bulk is true, a list of dictionaries with info about all node
1632 a268af8d Adeodato Simo
        groups in the cluster, else a list of names of those node groups
1633 a268af8d Adeodato Simo

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

1648 a268af8d Adeodato Simo
    @type group: str
1649 a268af8d Adeodato Simo
    @param group: name of the node group whose info to return
1650 a268af8d Adeodato Simo

1651 a268af8d Adeodato Simo
    @rtype: dict
1652 a268af8d Adeodato Simo
    @return: info about the node group
1653 a268af8d Adeodato Simo

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

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

1669 98805538 Michael Hanselmann
    @rtype: string
1670 a268af8d Adeodato Simo
    @return: job id
1671 a268af8d Adeodato Simo

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

1687 f18fab7d Adeodato Simo
    More details for parameters can be found in the RAPI documentation.
1688 f18fab7d Adeodato Simo

1689 f18fab7d Adeodato Simo
    @type group: string
1690 f18fab7d Adeodato Simo
    @param group: Node group name
1691 98805538 Michael Hanselmann
    @rtype: string
1692 f18fab7d Adeodato Simo
    @return: job id
1693 f18fab7d Adeodato Simo

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

1702 a268af8d Adeodato Simo
    @type group: str
1703 a268af8d Adeodato Simo
    @param group: the node group to delete
1704 a268af8d Adeodato Simo
    @type dry_run: bool
1705 a268af8d Adeodato Simo
    @param dry_run: whether to peform a dry run
1706 a268af8d Adeodato Simo

1707 98805538 Michael Hanselmann
    @rtype: string
1708 a268af8d Adeodato Simo
    @return: job id
1709 a268af8d Adeodato Simo

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

1721 a268af8d Adeodato Simo
    @type group: string
1722 a268af8d Adeodato Simo
    @param group: Node group name
1723 a268af8d Adeodato Simo
    @type new_name: string
1724 a268af8d Adeodato Simo
    @param new_name: New node group name
1725 a268af8d Adeodato Simo

1726 98805538 Michael Hanselmann
    @rtype: string
1727 a268af8d Adeodato Simo
    @return: job id
1728 a268af8d Adeodato Simo

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

1741 4245446f Adeodato Simo
    @type group: string
1742 4245446f Adeodato Simo
    @param group: Node gropu name
1743 4245446f Adeodato Simo
    @type nodes: list of strings
1744 4245446f Adeodato Simo
    @param nodes: List of nodes to assign to the group
1745 4245446f Adeodato Simo

1746 98805538 Michael Hanselmann
    @rtype: string
1747 4245446f Adeodato Simo
    @return: job id
1748 4245446f Adeodato Simo

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

1765 414ebaf1 Michael Hanselmann
    @type group: string
1766 414ebaf1 Michael Hanselmann
    @param group: Node group whose tags to return
1767 414ebaf1 Michael Hanselmann

1768 414ebaf1 Michael Hanselmann
    @rtype: list of strings
1769 414ebaf1 Michael Hanselmann
    @return: tags for the group
1770 414ebaf1 Michael Hanselmann

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

1779 414ebaf1 Michael Hanselmann
    @type group: str
1780 414ebaf1 Michael Hanselmann
    @param group: group to add tags to
1781 414ebaf1 Michael Hanselmann
    @type tags: list of string
1782 414ebaf1 Michael Hanselmann
    @param tags: tags to add to the group
1783 414ebaf1 Michael Hanselmann
    @type dry_run: bool
1784 414ebaf1 Michael Hanselmann
    @param dry_run: whether to perform a dry run
1785 414ebaf1 Michael Hanselmann

1786 414ebaf1 Michael Hanselmann
    @rtype: string
1787 414ebaf1 Michael Hanselmann
    @return: job id
1788 414ebaf1 Michael Hanselmann

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

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

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

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

1827 208a6cff Michael Hanselmann
    @rtype: string
1828 208a6cff Michael Hanselmann
    @return: job id
1829 208a6cff Michael Hanselmann

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

1846 208a6cff Michael Hanselmann
    @type what: string
1847 208a6cff Michael Hanselmann
    @param what: Resource name, one of L{constants.QR_VIA_RAPI}
1848 208a6cff Michael Hanselmann
    @type fields: list of string
1849 208a6cff Michael Hanselmann
    @param fields: Requested fields
1850 208a6cff Michael Hanselmann

1851 208a6cff Michael Hanselmann
    @rtype: string
1852 208a6cff Michael Hanselmann
    @return: job id
1853 208a6cff Michael Hanselmann

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