Statistics
| Branch: | Tag: | Revision:

root / lib / rapi / client.py @ 4c864b55

History | View | Annotate | Download (54.1 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 8a47b447 Michael Hanselmann
_INST_CREATE_REQV1 = "instance-create-reqv1"
101 c744425f Michael Hanselmann
_INST_REINSTALL_REQV1 = "instance-reinstall-reqv1"
102 b7a1c816 Michael Hanselmann
_NODE_MIGRATE_REQV1 = "node-migrate-reqv1"
103 de40437a Michael Hanselmann
_NODE_EVAC_RES1 = "node-evac-res1"
104 c97fcab8 Guido Trotter
_INST_NIC_PARAMS = frozenset(["mac", "ip", "mode", "link"])
105 48436b97 Michael Hanselmann
_INST_CREATE_V0_DISK_PARAMS = frozenset(["size"])
106 48436b97 Michael Hanselmann
_INST_CREATE_V0_PARAMS = frozenset([
107 48436b97 Michael Hanselmann
  "os", "pnode", "snode", "iallocator", "start", "ip_check", "name_check",
108 48436b97 Michael Hanselmann
  "hypervisor", "file_storage_dir", "file_driver", "dry_run",
109 48436b97 Michael Hanselmann
  ])
110 48436b97 Michael Hanselmann
_INST_CREATE_V0_DPARAMS = frozenset(["beparams", "hvparams"])
111 4c864b55 Michael Hanselmann
_QPARAM_DRY_RUN = "dry-run"
112 4c864b55 Michael Hanselmann
_QPARAM_FORCE = "force"
113 8a47b447 Michael Hanselmann
114 2a7c3583 Michael Hanselmann
# Older pycURL versions don't have all error constants
115 2a7c3583 Michael Hanselmann
try:
116 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT = pycurl.E_SSL_CACERT
117 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT_BADFILE = pycurl.E_SSL_CACERT_BADFILE
118 2a7c3583 Michael Hanselmann
except AttributeError:
119 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT = 60
120 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT_BADFILE = 77
121 2a7c3583 Michael Hanselmann
122 2a7c3583 Michael Hanselmann
_CURL_SSL_CERT_ERRORS = frozenset([
123 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT,
124 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT_BADFILE,
125 2a7c3583 Michael Hanselmann
  ])
126 2a7c3583 Michael Hanselmann
127 95ab4de9 David Knowles
128 95ab4de9 David Knowles
class Error(Exception):
129 95ab4de9 David Knowles
  """Base error class for this module.
130 95ab4de9 David Knowles

131 95ab4de9 David Knowles
  """
132 95ab4de9 David Knowles
  pass
133 95ab4de9 David Knowles
134 95ab4de9 David Knowles
135 95ab4de9 David Knowles
class CertificateError(Error):
136 95ab4de9 David Knowles
  """Raised when a problem is found with the SSL certificate.
137 95ab4de9 David Knowles

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

145 95ab4de9 David Knowles
  """
146 8a019a03 Michael Hanselmann
  def __init__(self, msg, code=None):
147 8a019a03 Michael Hanselmann
    Error.__init__(self, msg)
148 8a019a03 Michael Hanselmann
    self.code = code
149 95ab4de9 David Knowles
150 95ab4de9 David Knowles
151 4c864b55 Michael Hanselmann
def _AppendIf(container, condition, value):
152 4c864b55 Michael Hanselmann
  """Appends to a list if a condition evaluates to truth.
153 4c864b55 Michael Hanselmann

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

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

171 4c864b55 Michael Hanselmann
  """
172 4c864b55 Michael Hanselmann
  return _AppendIf(container, condition, (_QPARAM_FORCE, 1))
173 4c864b55 Michael Hanselmann
174 4c864b55 Michael Hanselmann
175 2a7c3583 Michael Hanselmann
def UsesRapiClient(fn):
176 2a7c3583 Michael Hanselmann
  """Decorator for code using RAPI client to initialize pycURL.
177 9279e986 Michael Hanselmann

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

202 2a7c3583 Michael Hanselmann
  @type verbose: bool
203 2a7c3583 Michael Hanselmann
  @param verbose: Whether to set cURL to verbose mode
204 2a7c3583 Michael Hanselmann
  @type use_signal: bool
205 2a7c3583 Michael Hanselmann
  @param use_signal: Whether to allow cURL to use signals
206 2a7c3583 Michael Hanselmann
  @type use_curl_cabundle: bool
207 2a7c3583 Michael Hanselmann
  @param use_curl_cabundle: Whether to use cURL's default CA bundle
208 2a7c3583 Michael Hanselmann
  @type cafile: string
209 2a7c3583 Michael Hanselmann
  @param cafile: In which file we can find the certificates
210 2a7c3583 Michael Hanselmann
  @type capath: string
211 2a7c3583 Michael Hanselmann
  @param capath: In which directory we can find the certificates
212 2a7c3583 Michael Hanselmann
  @type proxy: string
213 2a7c3583 Michael Hanselmann
  @param proxy: Proxy to use, None for default behaviour and empty string for
214 2a7c3583 Michael Hanselmann
                disabling proxies (see curl_easy_setopt(3))
215 2a7c3583 Michael Hanselmann
  @type verify_hostname: bool
216 2a7c3583 Michael Hanselmann
  @param verify_hostname: Whether to verify the remote peer certificate's
217 2a7c3583 Michael Hanselmann
                          commonName
218 2a7c3583 Michael Hanselmann
  @type connect_timeout: number
219 2a7c3583 Michael Hanselmann
  @param connect_timeout: Timeout for establishing connection in seconds
220 2a7c3583 Michael Hanselmann
  @type timeout: number
221 2a7c3583 Michael Hanselmann
  @param timeout: Timeout for complete transfer in seconds (see
222 2a7c3583 Michael Hanselmann
                  curl_easy_setopt(3)).
223 9279e986 Michael Hanselmann

224 9279e986 Michael Hanselmann
  """
225 2a7c3583 Michael Hanselmann
  if use_curl_cabundle and (cafile or capath):
226 2a7c3583 Michael Hanselmann
    raise Error("Can not use default CA bundle when CA file or path is set")
227 9279e986 Michael Hanselmann
228 2a7c3583 Michael Hanselmann
  def _ConfigCurl(curl, logger):
229 2a7c3583 Michael Hanselmann
    """Configures a cURL object
230 9279e986 Michael Hanselmann

231 2a7c3583 Michael Hanselmann
    @type curl: pycurl.Curl
232 2a7c3583 Michael Hanselmann
    @param curl: cURL object
233 9279e986 Michael Hanselmann

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

297 95ab4de9 David Knowles
  """
298 95ab4de9 David Knowles
  USER_AGENT = "Ganeti RAPI Client"
299 d3844674 Michael Hanselmann
  _json_encoder = simplejson.JSONEncoder(sort_keys=True)
300 95ab4de9 David Knowles
301 9279e986 Michael Hanselmann
  def __init__(self, host, port=GANETI_RAPI_PORT,
302 2a7c3583 Michael Hanselmann
               username=None, password=None, logger=logging,
303 a5eba783 Michael Hanselmann
               curl_config_fn=None, curl_factory=None):
304 2a7c3583 Michael Hanselmann
    """Initializes this class.
305 95ab4de9 David Knowles

306 9279e986 Michael Hanselmann
    @type host: string
307 9279e986 Michael Hanselmann
    @param host: the ganeti cluster master to interact with
308 95ab4de9 David Knowles
    @type port: int
309 9279e986 Michael Hanselmann
    @param port: the port on which the RAPI is running (default is 5080)
310 9279e986 Michael Hanselmann
    @type username: string
311 95ab4de9 David Knowles
    @param username: the username to connect with
312 9279e986 Michael Hanselmann
    @type password: string
313 95ab4de9 David Knowles
    @param password: the password to connect with
314 2a7c3583 Michael Hanselmann
    @type curl_config_fn: callable
315 2a7c3583 Michael Hanselmann
    @param curl_config_fn: Function to configure C{pycurl.Curl} object
316 9279e986 Michael Hanselmann
    @param logger: Logging object
317 95ab4de9 David Knowles

318 95ab4de9 David Knowles
    """
319 a5eba783 Michael Hanselmann
    self._username = username
320 a5eba783 Michael Hanselmann
    self._password = password
321 9279e986 Michael Hanselmann
    self._logger = logger
322 a5eba783 Michael Hanselmann
    self._curl_config_fn = curl_config_fn
323 a5eba783 Michael Hanselmann
    self._curl_factory = curl_factory
324 95ab4de9 David Knowles
325 1a8337f2 Manuel Franceschini
    try:
326 1a8337f2 Manuel Franceschini
      socket.inet_pton(socket.AF_INET6, host)
327 1a8337f2 Manuel Franceschini
      address = "[%s]:%s" % (host, port)
328 1a8337f2 Manuel Franceschini
    except socket.error:
329 1a8337f2 Manuel Franceschini
      address = "%s:%s" % (host, port)
330 1a8337f2 Manuel Franceschini
331 1a8337f2 Manuel Franceschini
    self._base_url = "https://%s" % address
332 f2f88abf David Knowles
333 a5eba783 Michael Hanselmann
    if username is not None:
334 a5eba783 Michael Hanselmann
      if password is None:
335 a5eba783 Michael Hanselmann
        raise Error("Password not specified")
336 a5eba783 Michael Hanselmann
    elif password:
337 a5eba783 Michael Hanselmann
      raise Error("Specified password without username")
338 a5eba783 Michael Hanselmann
339 a5eba783 Michael Hanselmann
  def _CreateCurl(self):
340 a5eba783 Michael Hanselmann
    """Creates a cURL object.
341 a5eba783 Michael Hanselmann

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

381 10f5ab6c Michael Hanselmann
    @type query: list of two-tuples
382 10f5ab6c Michael Hanselmann
    @param query: Query arguments
383 10f5ab6c Michael Hanselmann
    @rtype: list
384 10f5ab6c Michael Hanselmann
    @return: Query list with encoded values
385 10f5ab6c Michael Hanselmann

386 10f5ab6c Michael Hanselmann
    """
387 10f5ab6c Michael Hanselmann
    result = []
388 10f5ab6c Michael Hanselmann
389 10f5ab6c Michael Hanselmann
    for name, value in query:
390 10f5ab6c Michael Hanselmann
      if value is None:
391 10f5ab6c Michael Hanselmann
        result.append((name, ""))
392 10f5ab6c Michael Hanselmann
393 10f5ab6c Michael Hanselmann
      elif isinstance(value, bool):
394 10f5ab6c Michael Hanselmann
        # Boolean values must be encoded as 0 or 1
395 10f5ab6c Michael Hanselmann
        result.append((name, int(value)))
396 10f5ab6c Michael Hanselmann
397 10f5ab6c Michael Hanselmann
      elif isinstance(value, (list, tuple, dict)):
398 10f5ab6c Michael Hanselmann
        raise ValueError("Invalid query data type %r" % type(value).__name__)
399 10f5ab6c Michael Hanselmann
400 10f5ab6c Michael Hanselmann
      else:
401 10f5ab6c Michael Hanselmann
        result.append((name, value))
402 10f5ab6c Michael Hanselmann
403 10f5ab6c Michael Hanselmann
    return result
404 10f5ab6c Michael Hanselmann
405 768747ed Michael Hanselmann
  def _SendRequest(self, method, path, query, content):
406 95ab4de9 David Knowles
    """Sends an HTTP request.
407 95ab4de9 David Knowles

408 95ab4de9 David Knowles
    This constructs a full URL, encodes and decodes HTTP bodies, and
409 95ab4de9 David Knowles
    handles invalid responses in a pythonic way.
410 95ab4de9 David Knowles

411 768747ed Michael Hanselmann
    @type method: string
412 95ab4de9 David Knowles
    @param method: HTTP method to use
413 768747ed Michael Hanselmann
    @type path: string
414 95ab4de9 David Knowles
    @param path: HTTP URL path
415 95ab4de9 David Knowles
    @type query: list of two-tuples
416 95ab4de9 David Knowles
    @param query: query arguments to pass to urllib.urlencode
417 95ab4de9 David Knowles
    @type content: str or None
418 95ab4de9 David Knowles
    @param content: HTTP body content
419 95ab4de9 David Knowles

420 95ab4de9 David Knowles
    @rtype: str
421 95ab4de9 David Knowles
    @return: JSON-Decoded response
422 95ab4de9 David Knowles

423 f2f88abf David Knowles
    @raises CertificateError: If an invalid SSL certificate is found
424 95ab4de9 David Knowles
    @raises GanetiApiError: If an invalid response is returned
425 95ab4de9 David Knowles

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

496 95ab4de9 David Knowles
    @rtype: int
497 f2f88abf David Knowles
    @return: Ganeti Remote API version
498 95ab4de9 David Knowles

499 95ab4de9 David Knowles
    """
500 768747ed Michael Hanselmann
    return self._SendRequest(HTTP_GET, "/version", None, None)
501 95ab4de9 David Knowles
502 7eac4a4d Michael Hanselmann
  def GetFeatures(self):
503 7eac4a4d Michael Hanselmann
    """Gets the list of optional features supported by RAPI server.
504 7eac4a4d Michael Hanselmann

505 7eac4a4d Michael Hanselmann
    @rtype: list
506 7eac4a4d Michael Hanselmann
    @return: List of optional features
507 7eac4a4d Michael Hanselmann

508 7eac4a4d Michael Hanselmann
    """
509 7eac4a4d Michael Hanselmann
    try:
510 7eac4a4d Michael Hanselmann
      return self._SendRequest(HTTP_GET, "/%s/features" % GANETI_RAPI_VERSION,
511 7eac4a4d Michael Hanselmann
                               None, None)
512 7eac4a4d Michael Hanselmann
    except GanetiApiError, err:
513 7eac4a4d Michael Hanselmann
      # Older RAPI servers don't support this resource
514 7eac4a4d Michael Hanselmann
      if err.code == HTTP_NOT_FOUND:
515 7eac4a4d Michael Hanselmann
        return []
516 7eac4a4d Michael Hanselmann
517 7eac4a4d Michael Hanselmann
      raise
518 7eac4a4d Michael Hanselmann
519 95ab4de9 David Knowles
  def GetOperatingSystems(self):
520 95ab4de9 David Knowles
    """Gets the Operating Systems running in the Ganeti cluster.
521 95ab4de9 David Knowles

522 95ab4de9 David Knowles
    @rtype: list of str
523 95ab4de9 David Knowles
    @return: operating systems
524 95ab4de9 David Knowles

525 95ab4de9 David Knowles
    """
526 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET, "/%s/os" % GANETI_RAPI_VERSION,
527 a198b2d9 Michael Hanselmann
                             None, None)
528 95ab4de9 David Knowles
529 95ab4de9 David Knowles
  def GetInfo(self):
530 95ab4de9 David Knowles
    """Gets info about the cluster.
531 95ab4de9 David Knowles

532 95ab4de9 David Knowles
    @rtype: dict
533 95ab4de9 David Knowles
    @return: information about the cluster
534 95ab4de9 David Knowles

535 95ab4de9 David Knowles
    """
536 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET, "/%s/info" % GANETI_RAPI_VERSION,
537 a198b2d9 Michael Hanselmann
                             None, None)
538 95ab4de9 David Knowles
539 54d4c13b Michael Hanselmann
  def RedistributeConfig(self):
540 54d4c13b Michael Hanselmann
    """Tells the cluster to redistribute its configuration files.
541 54d4c13b Michael Hanselmann

542 d914c76f Simeon Miteff
    @rtype: string
543 54d4c13b Michael Hanselmann
    @return: job id
544 54d4c13b Michael Hanselmann

545 54d4c13b Michael Hanselmann
    """
546 54d4c13b Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
547 54d4c13b Michael Hanselmann
                             "/%s/redistribute-config" % GANETI_RAPI_VERSION,
548 54d4c13b Michael Hanselmann
                             None, None)
549 54d4c13b Michael Hanselmann
550 62e999a5 Michael Hanselmann
  def ModifyCluster(self, **kwargs):
551 62e999a5 Michael Hanselmann
    """Modifies cluster parameters.
552 62e999a5 Michael Hanselmann

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

555 98805538 Michael Hanselmann
    @rtype: string
556 62e999a5 Michael Hanselmann
    @return: job id
557 62e999a5 Michael Hanselmann

558 62e999a5 Michael Hanselmann
    """
559 62e999a5 Michael Hanselmann
    body = kwargs
560 62e999a5 Michael Hanselmann
561 62e999a5 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
562 62e999a5 Michael Hanselmann
                             "/%s/modify" % GANETI_RAPI_VERSION, None, body)
563 62e999a5 Michael Hanselmann
564 95ab4de9 David Knowles
  def GetClusterTags(self):
565 95ab4de9 David Knowles
    """Gets the cluster tags.
566 95ab4de9 David Knowles

567 95ab4de9 David Knowles
    @rtype: list of str
568 95ab4de9 David Knowles
    @return: cluster tags
569 95ab4de9 David Knowles

570 95ab4de9 David Knowles
    """
571 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET, "/%s/tags" % GANETI_RAPI_VERSION,
572 a198b2d9 Michael Hanselmann
                             None, None)
573 95ab4de9 David Knowles
574 95ab4de9 David Knowles
  def AddClusterTags(self, tags, dry_run=False):
575 95ab4de9 David Knowles
    """Adds tags to the cluster.
576 95ab4de9 David Knowles

577 95ab4de9 David Knowles
    @type tags: list of str
578 95ab4de9 David Knowles
    @param tags: tags to add to the cluster
579 95ab4de9 David Knowles
    @type dry_run: bool
580 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
581 95ab4de9 David Knowles

582 98805538 Michael Hanselmann
    @rtype: string
583 95ab4de9 David Knowles
    @return: job id
584 95ab4de9 David Knowles

585 95ab4de9 David Knowles
    """
586 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
587 4c864b55 Michael Hanselmann
    _AppendDryRunIf(query, dry_run)
588 95ab4de9 David Knowles
589 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT, "/%s/tags" % GANETI_RAPI_VERSION,
590 a198b2d9 Michael Hanselmann
                             query, None)
591 95ab4de9 David Knowles
592 95ab4de9 David Knowles
  def DeleteClusterTags(self, tags, dry_run=False):
593 95ab4de9 David Knowles
    """Deletes tags from the cluster.
594 95ab4de9 David Knowles

595 95ab4de9 David Knowles
    @type tags: list of str
596 95ab4de9 David Knowles
    @param tags: tags to delete
597 95ab4de9 David Knowles
    @type dry_run: bool
598 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
599 d914c76f Simeon Miteff
    @rtype: string
600 d914c76f Simeon Miteff
    @return: job id
601 95ab4de9 David Knowles

602 95ab4de9 David Knowles
    """
603 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
604 4c864b55 Michael Hanselmann
    _AppendDryRunIf(query, dry_run)
605 95ab4de9 David Knowles
606 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE, "/%s/tags" % GANETI_RAPI_VERSION,
607 a198b2d9 Michael Hanselmann
                             query, None)
608 95ab4de9 David Knowles
609 95ab4de9 David Knowles
  def GetInstances(self, bulk=False):
610 95ab4de9 David Knowles
    """Gets information about instances on the cluster.
611 95ab4de9 David Knowles

612 95ab4de9 David Knowles
    @type bulk: bool
613 95ab4de9 David Knowles
    @param bulk: whether to return all information about all instances
614 95ab4de9 David Knowles

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

618 95ab4de9 David Knowles
    """
619 95ab4de9 David Knowles
    query = []
620 4c864b55 Michael Hanselmann
    _AppendIf(query, bulk, ("bulk", 1))
621 95ab4de9 David Knowles
622 a198b2d9 Michael Hanselmann
    instances = self._SendRequest(HTTP_GET,
623 a198b2d9 Michael Hanselmann
                                  "/%s/instances" % GANETI_RAPI_VERSION,
624 a198b2d9 Michael Hanselmann
                                  query, None)
625 95ab4de9 David Knowles
    if bulk:
626 95ab4de9 David Knowles
      return instances
627 95ab4de9 David Knowles
    else:
628 95ab4de9 David Knowles
      return [i["id"] for i in instances]
629 95ab4de9 David Knowles
630 591e5103 Michael Hanselmann
  def GetInstance(self, instance):
631 95ab4de9 David Knowles
    """Gets information about an instance.
632 95ab4de9 David Knowles

633 95ab4de9 David Knowles
    @type instance: str
634 95ab4de9 David Knowles
    @param instance: instance whose info to return
635 95ab4de9 David Knowles

636 95ab4de9 David Knowles
    @rtype: dict
637 95ab4de9 David Knowles
    @return: info about the instance
638 95ab4de9 David Knowles

639 95ab4de9 David Knowles
    """
640 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
641 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s" %
642 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, None)
643 95ab4de9 David Knowles
644 591e5103 Michael Hanselmann
  def GetInstanceInfo(self, instance, static=None):
645 591e5103 Michael Hanselmann
    """Gets information about an instance.
646 591e5103 Michael Hanselmann

647 591e5103 Michael Hanselmann
    @type instance: string
648 591e5103 Michael Hanselmann
    @param instance: Instance name
649 591e5103 Michael Hanselmann
    @rtype: string
650 591e5103 Michael Hanselmann
    @return: Job ID
651 591e5103 Michael Hanselmann

652 591e5103 Michael Hanselmann
    """
653 591e5103 Michael Hanselmann
    if static is not None:
654 591e5103 Michael Hanselmann
      query = [("static", static)]
655 591e5103 Michael Hanselmann
    else:
656 591e5103 Michael Hanselmann
      query = None
657 591e5103 Michael Hanselmann
658 591e5103 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
659 591e5103 Michael Hanselmann
                             ("/%s/instances/%s/info" %
660 591e5103 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
661 591e5103 Michael Hanselmann
662 8a47b447 Michael Hanselmann
  def CreateInstance(self, mode, name, disk_template, disks, nics,
663 8a47b447 Michael Hanselmann
                     **kwargs):
664 95ab4de9 David Knowles
    """Creates a new instance.
665 95ab4de9 David Knowles

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

668 8a47b447 Michael Hanselmann
    @type mode: string
669 8a47b447 Michael Hanselmann
    @param mode: Instance creation mode
670 8a47b447 Michael Hanselmann
    @type name: string
671 8a47b447 Michael Hanselmann
    @param name: Hostname of the instance to create
672 8a47b447 Michael Hanselmann
    @type disk_template: string
673 8a47b447 Michael Hanselmann
    @param disk_template: Disk template for instance (e.g. plain, diskless,
674 8a47b447 Michael Hanselmann
                          file, or drbd)
675 8a47b447 Michael Hanselmann
    @type disks: list of dicts
676 8a47b447 Michael Hanselmann
    @param disks: List of disk definitions
677 8a47b447 Michael Hanselmann
    @type nics: list of dicts
678 8a47b447 Michael Hanselmann
    @param nics: List of NIC definitions
679 95ab4de9 David Knowles
    @type dry_run: bool
680 8a47b447 Michael Hanselmann
    @keyword dry_run: whether to perform a dry run
681 95ab4de9 David Knowles

682 98805538 Michael Hanselmann
    @rtype: string
683 95ab4de9 David Knowles
    @return: job id
684 95ab4de9 David Knowles

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

718 95ab4de9 David Knowles
    @type instance: str
719 95ab4de9 David Knowles
    @param instance: the instance to delete
720 95ab4de9 David Knowles

721 98805538 Michael Hanselmann
    @rtype: string
722 cab667cc David Knowles
    @return: job id
723 cab667cc David Knowles

724 95ab4de9 David Knowles
    """
725 95ab4de9 David Knowles
    query = []
726 4c864b55 Michael Hanselmann
    _AppendDryRunIf(query, dry_run)
727 95ab4de9 David Knowles
728 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE,
729 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s" %
730 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
731 95ab4de9 David Knowles
732 3b7158ef Michael Hanselmann
  def ModifyInstance(self, instance, **kwargs):
733 3b7158ef Michael Hanselmann
    """Modifies an instance.
734 3b7158ef Michael Hanselmann

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

737 3b7158ef Michael Hanselmann
    @type instance: string
738 3b7158ef Michael Hanselmann
    @param instance: Instance name
739 98805538 Michael Hanselmann
    @rtype: string
740 3b7158ef Michael Hanselmann
    @return: job id
741 3b7158ef Michael Hanselmann

742 3b7158ef Michael Hanselmann
    """
743 3b7158ef Michael Hanselmann
    body = kwargs
744 3b7158ef Michael Hanselmann
745 3b7158ef Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
746 3b7158ef Michael Hanselmann
                             ("/%s/instances/%s/modify" %
747 3b7158ef Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, body)
748 3b7158ef Michael Hanselmann
749 b680c8be Michael Hanselmann
  def ActivateInstanceDisks(self, instance, ignore_size=None):
750 b680c8be Michael Hanselmann
    """Activates an instance's disks.
751 b680c8be Michael Hanselmann

752 b680c8be Michael Hanselmann
    @type instance: string
753 b680c8be Michael Hanselmann
    @param instance: Instance name
754 b680c8be Michael Hanselmann
    @type ignore_size: bool
755 b680c8be Michael Hanselmann
    @param ignore_size: Whether to ignore recorded size
756 d914c76f Simeon Miteff
    @rtype: string
757 b680c8be Michael Hanselmann
    @return: job id
758 b680c8be Michael Hanselmann

759 b680c8be Michael Hanselmann
    """
760 b680c8be Michael Hanselmann
    query = []
761 4c864b55 Michael Hanselmann
    _AppendIf(query, ignore_size, ("ignore_size", 1))
762 b680c8be Michael Hanselmann
763 b680c8be Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
764 b680c8be Michael Hanselmann
                             ("/%s/instances/%s/activate-disks" %
765 b680c8be Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
766 b680c8be Michael Hanselmann
767 b680c8be Michael Hanselmann
  def DeactivateInstanceDisks(self, instance):
768 b680c8be Michael Hanselmann
    """Deactivates an instance's disks.
769 b680c8be Michael Hanselmann

770 b680c8be Michael Hanselmann
    @type instance: string
771 b680c8be Michael Hanselmann
    @param instance: Instance name
772 d914c76f Simeon Miteff
    @rtype: string
773 b680c8be Michael Hanselmann
    @return: job id
774 b680c8be Michael Hanselmann

775 b680c8be Michael Hanselmann
    """
776 b680c8be Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
777 b680c8be Michael Hanselmann
                             ("/%s/instances/%s/deactivate-disks" %
778 b680c8be Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, None)
779 b680c8be Michael Hanselmann
780 a52978c7 Michael Hanselmann
  def RecreateInstanceDisks(self, instance, disks=None, nodes=None):
781 a52978c7 Michael Hanselmann
    """Recreate an instance's disks.
782 a52978c7 Michael Hanselmann

783 a52978c7 Michael Hanselmann
    @type instance: string
784 a52978c7 Michael Hanselmann
    @param instance: Instance name
785 a52978c7 Michael Hanselmann
    @type disks: list of int
786 a52978c7 Michael Hanselmann
    @param disks: List of disk indexes
787 a52978c7 Michael Hanselmann
    @type nodes: list of string
788 a52978c7 Michael Hanselmann
    @param nodes: New instance nodes, if relocation is desired
789 a52978c7 Michael Hanselmann
    @rtype: string
790 a52978c7 Michael Hanselmann
    @return: job id
791 a52978c7 Michael Hanselmann

792 a52978c7 Michael Hanselmann
    """
793 a52978c7 Michael Hanselmann
    body = {}
794 a52978c7 Michael Hanselmann
795 a52978c7 Michael Hanselmann
    if disks is not None:
796 a52978c7 Michael Hanselmann
      body["disks"] = disks
797 a52978c7 Michael Hanselmann
798 a52978c7 Michael Hanselmann
    if nodes is not None:
799 a52978c7 Michael Hanselmann
      body["nodes"] = nodes
800 a52978c7 Michael Hanselmann
801 a52978c7 Michael Hanselmann
    return self._SendRequest(HTTP_POST,
802 a52978c7 Michael Hanselmann
                             ("/%s/instances/%s/recreate-disks" %
803 a52978c7 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, body)
804 a52978c7 Michael Hanselmann
805 e23881ed Michael Hanselmann
  def GrowInstanceDisk(self, instance, disk, amount, wait_for_sync=None):
806 e23881ed Michael Hanselmann
    """Grows a disk of an instance.
807 e23881ed Michael Hanselmann

808 e23881ed Michael Hanselmann
    More details for parameters can be found in the RAPI documentation.
809 e23881ed Michael Hanselmann

810 e23881ed Michael Hanselmann
    @type instance: string
811 e23881ed Michael Hanselmann
    @param instance: Instance name
812 e23881ed Michael Hanselmann
    @type disk: integer
813 e23881ed Michael Hanselmann
    @param disk: Disk index
814 e23881ed Michael Hanselmann
    @type amount: integer
815 e23881ed Michael Hanselmann
    @param amount: Grow disk by this amount (MiB)
816 e23881ed Michael Hanselmann
    @type wait_for_sync: bool
817 e23881ed Michael Hanselmann
    @param wait_for_sync: Wait for disk to synchronize
818 98805538 Michael Hanselmann
    @rtype: string
819 e23881ed Michael Hanselmann
    @return: job id
820 e23881ed Michael Hanselmann

821 e23881ed Michael Hanselmann
    """
822 e23881ed Michael Hanselmann
    body = {
823 e23881ed Michael Hanselmann
      "amount": amount,
824 e23881ed Michael Hanselmann
      }
825 e23881ed Michael Hanselmann
826 e23881ed Michael Hanselmann
    if wait_for_sync is not None:
827 e23881ed Michael Hanselmann
      body["wait_for_sync"] = wait_for_sync
828 e23881ed Michael Hanselmann
829 e23881ed Michael Hanselmann
    return self._SendRequest(HTTP_POST,
830 e23881ed Michael Hanselmann
                             ("/%s/instances/%s/disk/%s/grow" %
831 e23881ed Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance, disk)),
832 e23881ed Michael Hanselmann
                             None, body)
833 e23881ed Michael Hanselmann
834 95ab4de9 David Knowles
  def GetInstanceTags(self, instance):
835 95ab4de9 David Knowles
    """Gets tags for an instance.
836 95ab4de9 David Knowles

837 95ab4de9 David Knowles
    @type instance: str
838 95ab4de9 David Knowles
    @param instance: instance whose tags to return
839 95ab4de9 David Knowles

840 95ab4de9 David Knowles
    @rtype: list of str
841 95ab4de9 David Knowles
    @return: tags for the instance
842 95ab4de9 David Knowles

843 95ab4de9 David Knowles
    """
844 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
845 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/tags" %
846 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, None)
847 95ab4de9 David Knowles
848 95ab4de9 David Knowles
  def AddInstanceTags(self, instance, tags, dry_run=False):
849 95ab4de9 David Knowles
    """Adds tags to an instance.
850 95ab4de9 David Knowles

851 95ab4de9 David Knowles
    @type instance: str
852 95ab4de9 David Knowles
    @param instance: instance to add tags to
853 95ab4de9 David Knowles
    @type tags: list of str
854 95ab4de9 David Knowles
    @param tags: tags to add to the instance
855 95ab4de9 David Knowles
    @type dry_run: bool
856 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
857 95ab4de9 David Knowles

858 98805538 Michael Hanselmann
    @rtype: string
859 95ab4de9 David Knowles
    @return: job id
860 95ab4de9 David Knowles

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

872 95ab4de9 David Knowles
    @type instance: str
873 95ab4de9 David Knowles
    @param instance: instance to delete tags from
874 95ab4de9 David Knowles
    @type tags: list of str
875 95ab4de9 David Knowles
    @param tags: tags to delete
876 95ab4de9 David Knowles
    @type dry_run: bool
877 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
878 d914c76f Simeon Miteff
    @rtype: string
879 d914c76f Simeon Miteff
    @return: job id
880 95ab4de9 David Knowles

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

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

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

919 95ab4de9 David Knowles
    @type instance: str
920 95ab4de9 David Knowles
    @param instance: the instance to shut down
921 95ab4de9 David Knowles
    @type dry_run: bool
922 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
923 2ba39b8f Iustin Pop
    @type no_remember: bool
924 2ba39b8f Iustin Pop
    @param no_remember: if true, will not record the state change
925 d914c76f Simeon Miteff
    @rtype: string
926 d914c76f Simeon Miteff
    @return: job id
927 95ab4de9 David Knowles

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

940 95ab4de9 David Knowles
    @type instance: str
941 95ab4de9 David Knowles
    @param instance: the instance to start up
942 95ab4de9 David Knowles
    @type dry_run: bool
943 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
944 2ba39b8f Iustin Pop
    @type no_remember: bool
945 2ba39b8f Iustin Pop
    @param no_remember: if true, will not record the state change
946 d914c76f Simeon Miteff
    @rtype: string
947 d914c76f Simeon Miteff
    @return: job id
948 95ab4de9 David Knowles

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

962 95ab4de9 David Knowles
    @type instance: str
963 fcee9675 David Knowles
    @param instance: The instance to reinstall
964 fcee9675 David Knowles
    @type os: str or None
965 fcee9675 David Knowles
    @param os: The operating system to reinstall. If None, the instance's
966 fcee9675 David Knowles
        current operating system will be installed again
967 95ab4de9 David Knowles
    @type no_startup: bool
968 fcee9675 David Knowles
    @param no_startup: Whether to start the instance automatically
969 d914c76f Simeon Miteff
    @rtype: string
970 d914c76f Simeon Miteff
    @return: job id
971 95ab4de9 David Knowles

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

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

1015 98805538 Michael Hanselmann
    @rtype: string
1016 95ab4de9 David Knowles
    @return: job id
1017 95ab4de9 David Knowles

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

1039 ebeb600f Michael Hanselmann
    @type instance: string
1040 ebeb600f Michael Hanselmann
    @param instance: Instance name
1041 ebeb600f Michael Hanselmann
    @type mode: string
1042 ebeb600f Michael Hanselmann
    @param mode: Export mode
1043 ebeb600f Michael Hanselmann
    @rtype: string
1044 ebeb600f Michael Hanselmann
    @return: Job ID
1045 ebeb600f Michael Hanselmann

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

1057 ebeb600f Michael Hanselmann
    @type instance: string
1058 ebeb600f Michael Hanselmann
    @param instance: Instance name
1059 ebeb600f Michael Hanselmann
    @type mode: string
1060 ebeb600f Michael Hanselmann
    @param mode: Export mode
1061 ebeb600f Michael Hanselmann
    @rtype: string
1062 ebeb600f Michael Hanselmann
    @return: Job ID
1063 ebeb600f Michael Hanselmann

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

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

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

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

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

1146 d654aae1 Michael Hanselmann
    @type instance: string
1147 d654aae1 Michael Hanselmann
    @param instance: Instance name
1148 d654aae1 Michael Hanselmann
    @type new_name: string
1149 d654aae1 Michael Hanselmann
    @param new_name: New instance name
1150 d654aae1 Michael Hanselmann
    @type ip_check: bool
1151 d654aae1 Michael Hanselmann
    @param ip_check: Whether to ensure instance's IP address is inactive
1152 d654aae1 Michael Hanselmann
    @type name_check: bool
1153 d654aae1 Michael Hanselmann
    @param name_check: Whether to ensure instance's name is resolvable
1154 d914c76f Simeon Miteff
    @rtype: string
1155 d914c76f Simeon Miteff
    @return: job id
1156 d654aae1 Michael Hanselmann

1157 d654aae1 Michael Hanselmann
    """
1158 d654aae1 Michael Hanselmann
    body = {
1159 d654aae1 Michael Hanselmann
      "new_name": new_name,
1160 d654aae1 Michael Hanselmann
      }
1161 d654aae1 Michael Hanselmann
1162 d654aae1 Michael Hanselmann
    if ip_check is not None:
1163 d654aae1 Michael Hanselmann
      body["ip_check"] = ip_check
1164 d654aae1 Michael Hanselmann
1165 d654aae1 Michael Hanselmann
    if name_check is not None:
1166 d654aae1 Michael Hanselmann
      body["name_check"] = name_check
1167 d654aae1 Michael Hanselmann
1168 d654aae1 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1169 d654aae1 Michael Hanselmann
                             ("/%s/instances/%s/rename" %
1170 d654aae1 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, body)
1171 d654aae1 Michael Hanselmann
1172 b82d4c5e Michael Hanselmann
  def GetInstanceConsole(self, instance):
1173 b82d4c5e Michael Hanselmann
    """Request information for connecting to instance's console.
1174 b82d4c5e Michael Hanselmann

1175 b82d4c5e Michael Hanselmann
    @type instance: string
1176 b82d4c5e Michael Hanselmann
    @param instance: Instance name
1177 d914c76f Simeon Miteff
    @rtype: dict
1178 d914c76f Simeon Miteff
    @return: dictionary containing information about instance's console
1179 b82d4c5e Michael Hanselmann

1180 b82d4c5e Michael Hanselmann
    """
1181 b82d4c5e Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1182 b82d4c5e Michael Hanselmann
                             ("/%s/instances/%s/console" %
1183 b82d4c5e Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, None)
1184 b82d4c5e Michael Hanselmann
1185 95ab4de9 David Knowles
  def GetJobs(self):
1186 95ab4de9 David Knowles
    """Gets all jobs for the cluster.
1187 95ab4de9 David Knowles

1188 95ab4de9 David Knowles
    @rtype: list of int
1189 95ab4de9 David Knowles
    @return: job ids for the cluster
1190 95ab4de9 David Knowles

1191 95ab4de9 David Knowles
    """
1192 768747ed Michael Hanselmann
    return [int(j["id"])
1193 a198b2d9 Michael Hanselmann
            for j in self._SendRequest(HTTP_GET,
1194 a198b2d9 Michael Hanselmann
                                       "/%s/jobs" % GANETI_RAPI_VERSION,
1195 a198b2d9 Michael Hanselmann
                                       None, None)]
1196 95ab4de9 David Knowles
1197 95ab4de9 David Knowles
  def GetJobStatus(self, job_id):
1198 95ab4de9 David Knowles
    """Gets the status of a job.
1199 95ab4de9 David Knowles

1200 98805538 Michael Hanselmann
    @type job_id: string
1201 95ab4de9 David Knowles
    @param job_id: job id whose status to query
1202 95ab4de9 David Knowles

1203 95ab4de9 David Knowles
    @rtype: dict
1204 95ab4de9 David Knowles
    @return: job status
1205 95ab4de9 David Knowles

1206 95ab4de9 David Knowles
    """
1207 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1208 a198b2d9 Michael Hanselmann
                             "/%s/jobs/%s" % (GANETI_RAPI_VERSION, job_id),
1209 a198b2d9 Michael Hanselmann
                             None, None)
1210 95ab4de9 David Knowles
1211 16c13387 Theo Van Dinter
  def WaitForJobCompletion(self, job_id, period=5, retries=-1):
1212 16c13387 Theo Van Dinter
    """Polls cluster for job status until completion.
1213 16c13387 Theo Van Dinter

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

1217 cfda0e48 Iustin Pop
    @type job_id: string
1218 16c13387 Theo Van Dinter
    @param job_id: job id to watch
1219 16c13387 Theo Van Dinter
    @type period: int
1220 16c13387 Theo Van Dinter
    @param period: how often to poll for status (optional, default 5s)
1221 16c13387 Theo Van Dinter
    @type retries: int
1222 16c13387 Theo Van Dinter
    @param retries: how many time to poll before giving up
1223 16c13387 Theo Van Dinter
                    (optional, default -1 means unlimited)
1224 16c13387 Theo Van Dinter

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

1231 16c13387 Theo Van Dinter
    """
1232 16c13387 Theo Van Dinter
    while retries != 0:
1233 16c13387 Theo Van Dinter
      job_result = self.GetJobStatus(job_id)
1234 dde0e97c Michael Hanselmann
1235 dde0e97c Michael Hanselmann
      if job_result and job_result["status"] == JOB_STATUS_SUCCESS:
1236 16c13387 Theo Van Dinter
        return True
1237 dde0e97c Michael Hanselmann
      elif not job_result or job_result["status"] in JOB_STATUS_FINALIZED:
1238 dde0e97c Michael Hanselmann
        return False
1239 dde0e97c Michael Hanselmann
1240 dde0e97c Michael Hanselmann
      if period:
1241 dde0e97c Michael Hanselmann
        time.sleep(period)
1242 dde0e97c Michael Hanselmann
1243 16c13387 Theo Van Dinter
      if retries > 0:
1244 16c13387 Theo Van Dinter
        retries -= 1
1245 dde0e97c Michael Hanselmann
1246 16c13387 Theo Van Dinter
    return False
1247 16c13387 Theo Van Dinter
1248 d9b67f70 Michael Hanselmann
  def WaitForJobChange(self, job_id, fields, prev_job_info, prev_log_serial):
1249 d9b67f70 Michael Hanselmann
    """Waits for job changes.
1250 d9b67f70 Michael Hanselmann

1251 98805538 Michael Hanselmann
    @type job_id: string
1252 d9b67f70 Michael Hanselmann
    @param job_id: Job ID for which to wait
1253 d914c76f Simeon Miteff
    @return: C{None} if no changes have been detected and a dict with two keys,
1254 d914c76f Simeon Miteff
      C{job_info} and C{log_entries} otherwise.
1255 d914c76f Simeon Miteff
    @rtype: dict
1256 d9b67f70 Michael Hanselmann

1257 d9b67f70 Michael Hanselmann
    """
1258 d9b67f70 Michael Hanselmann
    body = {
1259 d9b67f70 Michael Hanselmann
      "fields": fields,
1260 d9b67f70 Michael Hanselmann
      "previous_job_info": prev_job_info,
1261 d9b67f70 Michael Hanselmann
      "previous_log_serial": prev_log_serial,
1262 d9b67f70 Michael Hanselmann
      }
1263 d9b67f70 Michael Hanselmann
1264 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1265 a198b2d9 Michael Hanselmann
                             "/%s/jobs/%s/wait" % (GANETI_RAPI_VERSION, job_id),
1266 a198b2d9 Michael Hanselmann
                             None, body)
1267 d9b67f70 Michael Hanselmann
1268 cf9ada49 Michael Hanselmann
  def CancelJob(self, job_id, dry_run=False):
1269 cf9ada49 Michael Hanselmann
    """Cancels a job.
1270 95ab4de9 David Knowles

1271 98805538 Michael Hanselmann
    @type job_id: string
1272 95ab4de9 David Knowles
    @param job_id: id of the job to delete
1273 95ab4de9 David Knowles
    @type dry_run: bool
1274 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1275 d914c76f Simeon Miteff
    @rtype: tuple
1276 d914c76f Simeon Miteff
    @return: tuple containing the result, and a message (bool, string)
1277 95ab4de9 David Knowles

1278 95ab4de9 David Knowles
    """
1279 95ab4de9 David Knowles
    query = []
1280 4c864b55 Michael Hanselmann
    _AppendDryRunIf(query, dry_run)
1281 95ab4de9 David Knowles
1282 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE,
1283 a198b2d9 Michael Hanselmann
                             "/%s/jobs/%s" % (GANETI_RAPI_VERSION, job_id),
1284 a198b2d9 Michael Hanselmann
                             query, None)
1285 95ab4de9 David Knowles
1286 95ab4de9 David Knowles
  def GetNodes(self, bulk=False):
1287 95ab4de9 David Knowles
    """Gets all nodes in the cluster.
1288 95ab4de9 David Knowles

1289 95ab4de9 David Knowles
    @type bulk: bool
1290 95ab4de9 David Knowles
    @param bulk: whether to return all information about all instances
1291 95ab4de9 David Knowles

1292 95ab4de9 David Knowles
    @rtype: list of dict or str
1293 95ab4de9 David Knowles
    @return: if bulk is true, info about nodes in the cluster,
1294 95ab4de9 David Knowles
        else list of nodes in the cluster
1295 95ab4de9 David Knowles

1296 95ab4de9 David Knowles
    """
1297 95ab4de9 David Knowles
    query = []
1298 4c864b55 Michael Hanselmann
    _AppendIf(query, bulk, ("bulk", 1))
1299 95ab4de9 David Knowles
1300 a198b2d9 Michael Hanselmann
    nodes = self._SendRequest(HTTP_GET, "/%s/nodes" % GANETI_RAPI_VERSION,
1301 a198b2d9 Michael Hanselmann
                              query, None)
1302 95ab4de9 David Knowles
    if bulk:
1303 95ab4de9 David Knowles
      return nodes
1304 95ab4de9 David Knowles
    else:
1305 95ab4de9 David Knowles
      return [n["id"] for n in nodes]
1306 95ab4de9 David Knowles
1307 591e5103 Michael Hanselmann
  def GetNode(self, node):
1308 95ab4de9 David Knowles
    """Gets information about a node.
1309 95ab4de9 David Knowles

1310 95ab4de9 David Knowles
    @type node: str
1311 95ab4de9 David Knowles
    @param node: node whose info to return
1312 95ab4de9 David Knowles

1313 95ab4de9 David Knowles
    @rtype: dict
1314 95ab4de9 David Knowles
    @return: info about the node
1315 95ab4de9 David Knowles

1316 95ab4de9 David Knowles
    """
1317 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1318 a198b2d9 Michael Hanselmann
                             "/%s/nodes/%s" % (GANETI_RAPI_VERSION, node),
1319 a198b2d9 Michael Hanselmann
                             None, None)
1320 95ab4de9 David Knowles
1321 95ab4de9 David Knowles
  def EvacuateNode(self, node, iallocator=None, remote_node=None,
1322 de40437a Michael Hanselmann
                   dry_run=False, early_release=None,
1323 0b58db81 Michael Hanselmann
                   mode=None, accept_old=False):
1324 95ab4de9 David Knowles
    """Evacuates instances from a Ganeti node.
1325 95ab4de9 David Knowles

1326 95ab4de9 David Knowles
    @type node: str
1327 95ab4de9 David Knowles
    @param node: node to evacuate
1328 95ab4de9 David Knowles
    @type iallocator: str or None
1329 95ab4de9 David Knowles
    @param iallocator: instance allocator to use
1330 95ab4de9 David Knowles
    @type remote_node: str
1331 95ab4de9 David Knowles
    @param remote_node: node to evaucate to
1332 95ab4de9 David Knowles
    @type dry_run: bool
1333 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1334 941b9309 Iustin Pop
    @type early_release: bool
1335 941b9309 Iustin Pop
    @param early_release: whether to enable parallelization
1336 0b58db81 Michael Hanselmann
    @type mode: string
1337 0b58db81 Michael Hanselmann
    @param mode: Node evacuation mode
1338 de40437a Michael Hanselmann
    @type accept_old: bool
1339 de40437a Michael Hanselmann
    @param accept_old: Whether caller is ready to accept old-style (pre-2.5)
1340 de40437a Michael Hanselmann
        results
1341 de40437a Michael Hanselmann

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

1348 941b9309 Iustin Pop
    @raises GanetiApiError: if an iallocator and remote_node are both
1349 941b9309 Iustin Pop
        specified
1350 95ab4de9 David Knowles

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

1395 95ab4de9 David Knowles
    @type node: str
1396 95ab4de9 David Knowles
    @param node: node to migrate
1397 1f334d96 Iustin Pop
    @type mode: string
1398 1f334d96 Iustin Pop
    @param mode: if passed, it will overwrite the live migration type,
1399 1f334d96 Iustin Pop
        otherwise the hypervisor default will be used
1400 95ab4de9 David Knowles
    @type dry_run: bool
1401 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1402 b7a1c816 Michael Hanselmann
    @type iallocator: string
1403 b7a1c816 Michael Hanselmann
    @param iallocator: instance allocator to use
1404 b7a1c816 Michael Hanselmann
    @type target_node: string
1405 b7a1c816 Michael Hanselmann
    @param target_node: Target node for shared-storage instances
1406 95ab4de9 David Knowles

1407 98805538 Michael Hanselmann
    @rtype: string
1408 95ab4de9 David Knowles
    @return: job id
1409 95ab4de9 David Knowles

1410 95ab4de9 David Knowles
    """
1411 95ab4de9 David Knowles
    query = []
1412 4c864b55 Michael Hanselmann
    _AppendDryRunIf(query, dry_run)
1413 95ab4de9 David Knowles
1414 b7a1c816 Michael Hanselmann
    if _NODE_MIGRATE_REQV1 in self.GetFeatures():
1415 b7a1c816 Michael Hanselmann
      body = {}
1416 b7a1c816 Michael Hanselmann
1417 b7a1c816 Michael Hanselmann
      if mode is not None:
1418 b7a1c816 Michael Hanselmann
        body["mode"] = mode
1419 b7a1c816 Michael Hanselmann
      if iallocator is not None:
1420 b7a1c816 Michael Hanselmann
        body["iallocator"] = iallocator
1421 b7a1c816 Michael Hanselmann
      if target_node is not None:
1422 b7a1c816 Michael Hanselmann
        body["target_node"] = target_node
1423 b7a1c816 Michael Hanselmann
1424 b7a1c816 Michael Hanselmann
      assert len(query) <= 1
1425 b7a1c816 Michael Hanselmann
1426 b7a1c816 Michael Hanselmann
      return self._SendRequest(HTTP_POST,
1427 b7a1c816 Michael Hanselmann
                               ("/%s/nodes/%s/migrate" %
1428 b7a1c816 Michael Hanselmann
                                (GANETI_RAPI_VERSION, node)), query, body)
1429 b7a1c816 Michael Hanselmann
    else:
1430 b7a1c816 Michael Hanselmann
      # Use old request format
1431 b7a1c816 Michael Hanselmann
      if target_node is not None:
1432 b7a1c816 Michael Hanselmann
        raise GanetiApiError("Server does not support specifying target node"
1433 b7a1c816 Michael Hanselmann
                             " for node migration")
1434 b7a1c816 Michael Hanselmann
1435 4c864b55 Michael Hanselmann
      _AppendIf(query, mode is not None, ("mode", mode))
1436 b7a1c816 Michael Hanselmann
1437 b7a1c816 Michael Hanselmann
      return self._SendRequest(HTTP_POST,
1438 b7a1c816 Michael Hanselmann
                               ("/%s/nodes/%s/migrate" %
1439 b7a1c816 Michael Hanselmann
                                (GANETI_RAPI_VERSION, node)), query, None)
1440 95ab4de9 David Knowles
1441 95ab4de9 David Knowles
  def GetNodeRole(self, node):
1442 95ab4de9 David Knowles
    """Gets the current role for a node.
1443 95ab4de9 David Knowles

1444 95ab4de9 David Knowles
    @type node: str
1445 95ab4de9 David Knowles
    @param node: node whose role to return
1446 95ab4de9 David Knowles

1447 95ab4de9 David Knowles
    @rtype: str
1448 95ab4de9 David Knowles
    @return: the current role for a node
1449 95ab4de9 David Knowles

1450 95ab4de9 David Knowles
    """
1451 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1452 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/role" %
1453 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), None, None)
1454 95ab4de9 David Knowles
1455 8de8e68d Michael Hanselmann
  def SetNodeRole(self, node, role, force=False, auto_promote=None):
1456 95ab4de9 David Knowles
    """Sets the role for a node.
1457 95ab4de9 David Knowles

1458 95ab4de9 David Knowles
    @type node: str
1459 95ab4de9 David Knowles
    @param node: the node whose role to set
1460 95ab4de9 David Knowles
    @type role: str
1461 95ab4de9 David Knowles
    @param role: the role to set for the node
1462 95ab4de9 David Knowles
    @type force: bool
1463 95ab4de9 David Knowles
    @param force: whether to force the role change
1464 8de8e68d Michael Hanselmann
    @type auto_promote: bool
1465 8de8e68d Michael Hanselmann
    @param auto_promote: Whether node(s) should be promoted to master candidate
1466 8de8e68d Michael Hanselmann
                         if necessary
1467 95ab4de9 David Knowles

1468 98805538 Michael Hanselmann
    @rtype: string
1469 95ab4de9 David Knowles
    @return: job id
1470 95ab4de9 David Knowles

1471 95ab4de9 David Knowles
    """
1472 4c864b55 Michael Hanselmann
    query = []
1473 4c864b55 Michael Hanselmann
    _AppendForceIf(query, force)
1474 4c864b55 Michael Hanselmann
    _AppendIf(query, auto_promote is not None, ("auto-promote", auto_promote))
1475 8de8e68d Michael Hanselmann
1476 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1477 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/role" %
1478 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, role)
1479 95ab4de9 David Knowles
1480 42d4d8b9 Michael Hanselmann
  def PowercycleNode(self, node, force=False):
1481 42d4d8b9 Michael Hanselmann
    """Powercycles a node.
1482 42d4d8b9 Michael Hanselmann

1483 42d4d8b9 Michael Hanselmann
    @type node: string
1484 42d4d8b9 Michael Hanselmann
    @param node: Node name
1485 42d4d8b9 Michael Hanselmann
    @type force: bool
1486 42d4d8b9 Michael Hanselmann
    @param force: Whether to force the operation
1487 42d4d8b9 Michael Hanselmann
    @rtype: string
1488 42d4d8b9 Michael Hanselmann
    @return: job id
1489 42d4d8b9 Michael Hanselmann

1490 42d4d8b9 Michael Hanselmann
    """
1491 4c864b55 Michael Hanselmann
    query = []
1492 4c864b55 Michael Hanselmann
    _AppendForceIf(query, force)
1493 42d4d8b9 Michael Hanselmann
1494 42d4d8b9 Michael Hanselmann
    return self._SendRequest(HTTP_POST,
1495 42d4d8b9 Michael Hanselmann
                             ("/%s/nodes/%s/powercycle" %
1496 42d4d8b9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1497 42d4d8b9 Michael Hanselmann
1498 370f2042 Guido Trotter
  def ModifyNode(self, node, **kwargs):
1499 94497dd1 Michael Hanselmann
    """Modifies a node.
1500 94497dd1 Michael Hanselmann

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

1503 370f2042 Guido Trotter
    @type node: string
1504 370f2042 Guido Trotter
    @param node: Node name
1505 94497dd1 Michael Hanselmann
    @rtype: string
1506 94497dd1 Michael Hanselmann
    @return: job id
1507 94497dd1 Michael Hanselmann

1508 94497dd1 Michael Hanselmann
    """
1509 e366273a Guido Trotter
    return self._SendRequest(HTTP_POST,
1510 94497dd1 Michael Hanselmann
                             ("/%s/nodes/%s/modify" %
1511 370f2042 Guido Trotter
                              (GANETI_RAPI_VERSION, node)), None, kwargs)
1512 94497dd1 Michael Hanselmann
1513 95ab4de9 David Knowles
  def GetNodeStorageUnits(self, node, storage_type, output_fields):
1514 95ab4de9 David Knowles
    """Gets the storage units for a node.
1515 95ab4de9 David Knowles

1516 95ab4de9 David Knowles
    @type node: str
1517 95ab4de9 David Knowles
    @param node: the node whose storage units to return
1518 95ab4de9 David Knowles
    @type storage_type: str
1519 95ab4de9 David Knowles
    @param storage_type: storage type whose units to return
1520 95ab4de9 David Knowles
    @type output_fields: str
1521 95ab4de9 David Knowles
    @param output_fields: storage type fields to return
1522 95ab4de9 David Knowles

1523 98805538 Michael Hanselmann
    @rtype: string
1524 95ab4de9 David Knowles
    @return: job id where results can be retrieved
1525 95ab4de9 David Knowles

1526 95ab4de9 David Knowles
    """
1527 cfc03c54 Michael Hanselmann
    query = [
1528 cfc03c54 Michael Hanselmann
      ("storage_type", storage_type),
1529 cfc03c54 Michael Hanselmann
      ("output_fields", output_fields),
1530 cfc03c54 Michael Hanselmann
      ]
1531 95ab4de9 David Knowles
1532 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1533 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/storage" %
1534 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1535 95ab4de9 David Knowles
1536 fde28316 Michael Hanselmann
  def ModifyNodeStorageUnits(self, node, storage_type, name, allocatable=None):
1537 95ab4de9 David Knowles
    """Modifies parameters of storage units on the node.
1538 95ab4de9 David Knowles

1539 95ab4de9 David Knowles
    @type node: str
1540 95ab4de9 David Knowles
    @param node: node whose storage units to modify
1541 95ab4de9 David Knowles
    @type storage_type: str
1542 95ab4de9 David Knowles
    @param storage_type: storage type whose units to modify
1543 95ab4de9 David Knowles
    @type name: str
1544 95ab4de9 David Knowles
    @param name: name of the storage unit
1545 fde28316 Michael Hanselmann
    @type allocatable: bool or None
1546 fde28316 Michael Hanselmann
    @param allocatable: Whether to set the "allocatable" flag on the storage
1547 fde28316 Michael Hanselmann
                        unit (None=no modification, True=set, False=unset)
1548 95ab4de9 David Knowles

1549 98805538 Michael Hanselmann
    @rtype: string
1550 95ab4de9 David Knowles
    @return: job id
1551 95ab4de9 David Knowles

1552 95ab4de9 David Knowles
    """
1553 95ab4de9 David Knowles
    query = [
1554 cfc03c54 Michael Hanselmann
      ("storage_type", storage_type),
1555 cfc03c54 Michael Hanselmann
      ("name", name),
1556 cfc03c54 Michael Hanselmann
      ]
1557 cfc03c54 Michael Hanselmann
1558 4c864b55 Michael Hanselmann
    _AppendIf(query, allocatable is not None, ("allocatable", allocatable))
1559 fde28316 Michael Hanselmann
1560 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1561 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/storage/modify" %
1562 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1563 95ab4de9 David Knowles
1564 95ab4de9 David Knowles
  def RepairNodeStorageUnits(self, node, storage_type, name):
1565 95ab4de9 David Knowles
    """Repairs a storage unit on the node.
1566 95ab4de9 David Knowles

1567 95ab4de9 David Knowles
    @type node: str
1568 95ab4de9 David Knowles
    @param node: node whose storage units to repair
1569 95ab4de9 David Knowles
    @type storage_type: str
1570 95ab4de9 David Knowles
    @param storage_type: storage type to repair
1571 95ab4de9 David Knowles
    @type name: str
1572 95ab4de9 David Knowles
    @param name: name of the storage unit to repair
1573 95ab4de9 David Knowles

1574 98805538 Michael Hanselmann
    @rtype: string
1575 95ab4de9 David Knowles
    @return: job id
1576 95ab4de9 David Knowles

1577 95ab4de9 David Knowles
    """
1578 cfc03c54 Michael Hanselmann
    query = [
1579 cfc03c54 Michael Hanselmann
      ("storage_type", storage_type),
1580 cfc03c54 Michael Hanselmann
      ("name", name),
1581 cfc03c54 Michael Hanselmann
      ]
1582 95ab4de9 David Knowles
1583 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1584 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/storage/repair" %
1585 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1586 95ab4de9 David Knowles
1587 95ab4de9 David Knowles
  def GetNodeTags(self, node):
1588 95ab4de9 David Knowles
    """Gets the tags for a node.
1589 95ab4de9 David Knowles

1590 95ab4de9 David Knowles
    @type node: str
1591 95ab4de9 David Knowles
    @param node: node whose tags to return
1592 95ab4de9 David Knowles

1593 95ab4de9 David Knowles
    @rtype: list of str
1594 95ab4de9 David Knowles
    @return: tags for the node
1595 95ab4de9 David Knowles

1596 95ab4de9 David Knowles
    """
1597 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1598 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/tags" %
1599 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), None, None)
1600 95ab4de9 David Knowles
1601 95ab4de9 David Knowles
  def AddNodeTags(self, node, tags, dry_run=False):
1602 95ab4de9 David Knowles
    """Adds tags to a node.
1603 95ab4de9 David Knowles

1604 95ab4de9 David Knowles
    @type node: str
1605 95ab4de9 David Knowles
    @param node: node to add tags to
1606 95ab4de9 David Knowles
    @type tags: list of str
1607 95ab4de9 David Knowles
    @param tags: tags to add to the node
1608 95ab4de9 David Knowles
    @type dry_run: bool
1609 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1610 95ab4de9 David Knowles

1611 98805538 Michael Hanselmann
    @rtype: string
1612 95ab4de9 David Knowles
    @return: job id
1613 95ab4de9 David Knowles

1614 95ab4de9 David Knowles
    """
1615 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
1616 4c864b55 Michael Hanselmann
    _AppendDryRunIf(query, dry_run)
1617 95ab4de9 David Knowles
1618 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1619 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/tags" %
1620 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, tags)
1621 95ab4de9 David Knowles
1622 95ab4de9 David Knowles
  def DeleteNodeTags(self, node, tags, dry_run=False):
1623 95ab4de9 David Knowles
    """Delete tags from a node.
1624 95ab4de9 David Knowles

1625 95ab4de9 David Knowles
    @type node: str
1626 95ab4de9 David Knowles
    @param node: node to remove tags from
1627 95ab4de9 David Knowles
    @type tags: list of str
1628 95ab4de9 David Knowles
    @param tags: tags to remove from the node
1629 95ab4de9 David Knowles
    @type dry_run: bool
1630 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1631 95ab4de9 David Knowles

1632 98805538 Michael Hanselmann
    @rtype: string
1633 95ab4de9 David Knowles
    @return: job id
1634 95ab4de9 David Knowles

1635 95ab4de9 David Knowles
    """
1636 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
1637 4c864b55 Michael Hanselmann
    _AppendDryRunIf(query, dry_run)
1638 95ab4de9 David Knowles
1639 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE,
1640 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/tags" %
1641 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1642 a268af8d Adeodato Simo
1643 a268af8d Adeodato Simo
  def GetGroups(self, bulk=False):
1644 a268af8d Adeodato Simo
    """Gets all node groups in the cluster.
1645 a268af8d Adeodato Simo

1646 a268af8d Adeodato Simo
    @type bulk: bool
1647 a268af8d Adeodato Simo
    @param bulk: whether to return all information about the groups
1648 a268af8d Adeodato Simo

1649 a268af8d Adeodato Simo
    @rtype: list of dict or str
1650 a268af8d Adeodato Simo
    @return: if bulk is true, a list of dictionaries with info about all node
1651 a268af8d Adeodato Simo
        groups in the cluster, else a list of names of those node groups
1652 a268af8d Adeodato Simo

1653 a268af8d Adeodato Simo
    """
1654 a268af8d Adeodato Simo
    query = []
1655 4c864b55 Michael Hanselmann
    _AppendIf(query, bulk, ("bulk", 1))
1656 a268af8d Adeodato Simo
1657 a268af8d Adeodato Simo
    groups = self._SendRequest(HTTP_GET, "/%s/groups" % GANETI_RAPI_VERSION,
1658 a268af8d Adeodato Simo
                               query, None)
1659 a268af8d Adeodato Simo
    if bulk:
1660 a268af8d Adeodato Simo
      return groups
1661 a268af8d Adeodato Simo
    else:
1662 a268af8d Adeodato Simo
      return [g["name"] for g in groups]
1663 a268af8d Adeodato Simo
1664 a268af8d Adeodato Simo
  def GetGroup(self, group):
1665 a268af8d Adeodato Simo
    """Gets information about a node group.
1666 a268af8d Adeodato Simo

1667 a268af8d Adeodato Simo
    @type group: str
1668 a268af8d Adeodato Simo
    @param group: name of the node group whose info to return
1669 a268af8d Adeodato Simo

1670 a268af8d Adeodato Simo
    @rtype: dict
1671 a268af8d Adeodato Simo
    @return: info about the node group
1672 a268af8d Adeodato Simo

1673 a268af8d Adeodato Simo
    """
1674 a268af8d Adeodato Simo
    return self._SendRequest(HTTP_GET,
1675 a268af8d Adeodato Simo
                             "/%s/groups/%s" % (GANETI_RAPI_VERSION, group),
1676 a268af8d Adeodato Simo
                             None, None)
1677 a268af8d Adeodato Simo
1678 90e99856 Adeodato Simo
  def CreateGroup(self, name, alloc_policy=None, dry_run=False):
1679 a268af8d Adeodato Simo
    """Creates a new node group.
1680 a268af8d Adeodato Simo

1681 a268af8d Adeodato Simo
    @type name: str
1682 a268af8d Adeodato Simo
    @param name: the name of node group to create
1683 90e99856 Adeodato Simo
    @type alloc_policy: str
1684 90e99856 Adeodato Simo
    @param alloc_policy: the desired allocation policy for the group, if any
1685 a268af8d Adeodato Simo
    @type dry_run: bool
1686 a268af8d Adeodato Simo
    @param dry_run: whether to peform a dry run
1687 a268af8d Adeodato Simo

1688 98805538 Michael Hanselmann
    @rtype: string
1689 a268af8d Adeodato Simo
    @return: job id
1690 a268af8d Adeodato Simo

1691 a268af8d Adeodato Simo
    """
1692 a268af8d Adeodato Simo
    query = []
1693 4c864b55 Michael Hanselmann
    _AppendDryRunIf(query, dry_run)
1694 a268af8d Adeodato Simo
1695 a268af8d Adeodato Simo
    body = {
1696 a268af8d Adeodato Simo
      "name": name,
1697 90e99856 Adeodato Simo
      "alloc_policy": alloc_policy
1698 a268af8d Adeodato Simo
      }
1699 a268af8d Adeodato Simo
1700 a268af8d Adeodato Simo
    return self._SendRequest(HTTP_POST, "/%s/groups" % GANETI_RAPI_VERSION,
1701 a268af8d Adeodato Simo
                             query, body)
1702 a268af8d Adeodato Simo
1703 f18fab7d Adeodato Simo
  def ModifyGroup(self, group, **kwargs):
1704 f18fab7d Adeodato Simo
    """Modifies a node group.
1705 f18fab7d Adeodato Simo

1706 f18fab7d Adeodato Simo
    More details for parameters can be found in the RAPI documentation.
1707 f18fab7d Adeodato Simo

1708 f18fab7d Adeodato Simo
    @type group: string
1709 f18fab7d Adeodato Simo
    @param group: Node group name
1710 98805538 Michael Hanselmann
    @rtype: string
1711 f18fab7d Adeodato Simo
    @return: job id
1712 f18fab7d Adeodato Simo

1713 f18fab7d Adeodato Simo
    """
1714 f18fab7d Adeodato Simo
    return self._SendRequest(HTTP_PUT,
1715 f18fab7d Adeodato Simo
                             ("/%s/groups/%s/modify" %
1716 f18fab7d Adeodato Simo
                              (GANETI_RAPI_VERSION, group)), None, kwargs)
1717 f18fab7d Adeodato Simo
1718 a268af8d Adeodato Simo
  def DeleteGroup(self, group, dry_run=False):
1719 a268af8d Adeodato Simo
    """Deletes a node group.
1720 a268af8d Adeodato Simo

1721 a268af8d Adeodato Simo
    @type group: str
1722 a268af8d Adeodato Simo
    @param group: the node group to delete
1723 a268af8d Adeodato Simo
    @type dry_run: bool
1724 a268af8d Adeodato Simo
    @param dry_run: whether to peform a dry run
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
    query = []
1731 4c864b55 Michael Hanselmann
    _AppendDryRunIf(query, dry_run)
1732 a268af8d Adeodato Simo
1733 a268af8d Adeodato Simo
    return self._SendRequest(HTTP_DELETE,
1734 a268af8d Adeodato Simo
                             ("/%s/groups/%s" %
1735 a268af8d Adeodato Simo
                              (GANETI_RAPI_VERSION, group)), query, None)
1736 a268af8d Adeodato Simo
1737 a268af8d Adeodato Simo
  def RenameGroup(self, group, new_name):
1738 a268af8d Adeodato Simo
    """Changes the name of a node group.
1739 a268af8d Adeodato Simo

1740 a268af8d Adeodato Simo
    @type group: string
1741 a268af8d Adeodato Simo
    @param group: Node group name
1742 a268af8d Adeodato Simo
    @type new_name: string
1743 a268af8d Adeodato Simo
    @param new_name: New node group name
1744 a268af8d Adeodato Simo

1745 98805538 Michael Hanselmann
    @rtype: string
1746 a268af8d Adeodato Simo
    @return: job id
1747 a268af8d Adeodato Simo

1748 a268af8d Adeodato Simo
    """
1749 a268af8d Adeodato Simo
    body = {
1750 a268af8d Adeodato Simo
      "new_name": new_name,
1751 a268af8d Adeodato Simo
      }
1752 a268af8d Adeodato Simo
1753 a268af8d Adeodato Simo
    return self._SendRequest(HTTP_PUT,
1754 a268af8d Adeodato Simo
                             ("/%s/groups/%s/rename" %
1755 a268af8d Adeodato Simo
                              (GANETI_RAPI_VERSION, group)), None, body)
1756 4245446f Adeodato Simo
1757 4245446f Adeodato Simo
  def AssignGroupNodes(self, group, nodes, force=False, dry_run=False):
1758 4245446f Adeodato Simo
    """Assigns nodes to a group.
1759 4245446f Adeodato Simo

1760 4245446f Adeodato Simo
    @type group: string
1761 4245446f Adeodato Simo
    @param group: Node gropu name
1762 4245446f Adeodato Simo
    @type nodes: list of strings
1763 4245446f Adeodato Simo
    @param nodes: List of nodes to assign to the group
1764 4245446f Adeodato Simo

1765 98805538 Michael Hanselmann
    @rtype: string
1766 4245446f Adeodato Simo
    @return: job id
1767 4245446f Adeodato Simo

1768 4245446f Adeodato Simo
    """
1769 4245446f Adeodato Simo
    query = []
1770 4c864b55 Michael Hanselmann
    _AppendForceIf(query, force)
1771 4c864b55 Michael Hanselmann
    _AppendDryRunIf(query, dry_run)
1772 4245446f Adeodato Simo
1773 4245446f Adeodato Simo
    body = {
1774 4245446f Adeodato Simo
      "nodes": nodes,
1775 4245446f Adeodato Simo
      }
1776 4245446f Adeodato Simo
1777 4245446f Adeodato Simo
    return self._SendRequest(HTTP_PUT,
1778 4245446f Adeodato Simo
                             ("/%s/groups/%s/assign-nodes" %
1779 4245446f Adeodato Simo
                             (GANETI_RAPI_VERSION, group)), query, body)
1780 208a6cff Michael Hanselmann
1781 414ebaf1 Michael Hanselmann
  def GetGroupTags(self, group):
1782 414ebaf1 Michael Hanselmann
    """Gets tags for a node group.
1783 414ebaf1 Michael Hanselmann

1784 414ebaf1 Michael Hanselmann
    @type group: string
1785 414ebaf1 Michael Hanselmann
    @param group: Node group whose tags to return
1786 414ebaf1 Michael Hanselmann

1787 414ebaf1 Michael Hanselmann
    @rtype: list of strings
1788 414ebaf1 Michael Hanselmann
    @return: tags for the group
1789 414ebaf1 Michael Hanselmann

1790 414ebaf1 Michael Hanselmann
    """
1791 414ebaf1 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1792 414ebaf1 Michael Hanselmann
                             ("/%s/groups/%s/tags" %
1793 414ebaf1 Michael Hanselmann
                              (GANETI_RAPI_VERSION, group)), None, None)
1794 414ebaf1 Michael Hanselmann
1795 414ebaf1 Michael Hanselmann
  def AddGroupTags(self, group, tags, dry_run=False):
1796 414ebaf1 Michael Hanselmann
    """Adds tags to a node group.
1797 414ebaf1 Michael Hanselmann

1798 414ebaf1 Michael Hanselmann
    @type group: str
1799 414ebaf1 Michael Hanselmann
    @param group: group to add tags to
1800 414ebaf1 Michael Hanselmann
    @type tags: list of string
1801 414ebaf1 Michael Hanselmann
    @param tags: tags to add to the group
1802 414ebaf1 Michael Hanselmann
    @type dry_run: bool
1803 414ebaf1 Michael Hanselmann
    @param dry_run: whether to perform a dry run
1804 414ebaf1 Michael Hanselmann

1805 414ebaf1 Michael Hanselmann
    @rtype: string
1806 414ebaf1 Michael Hanselmann
    @return: job id
1807 414ebaf1 Michael Hanselmann

1808 414ebaf1 Michael Hanselmann
    """
1809 414ebaf1 Michael Hanselmann
    query = [("tag", t) for t in tags]
1810 4c864b55 Michael Hanselmann
    _AppendDryRunIf(query, dry_run)
1811 414ebaf1 Michael Hanselmann
1812 414ebaf1 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1813 414ebaf1 Michael Hanselmann
                             ("/%s/groups/%s/tags" %
1814 414ebaf1 Michael Hanselmann
                              (GANETI_RAPI_VERSION, group)), query, None)
1815 414ebaf1 Michael Hanselmann
1816 414ebaf1 Michael Hanselmann
  def DeleteGroupTags(self, group, tags, dry_run=False):
1817 414ebaf1 Michael Hanselmann
    """Deletes tags from a node group.
1818 414ebaf1 Michael Hanselmann

1819 414ebaf1 Michael Hanselmann
    @type group: str
1820 414ebaf1 Michael Hanselmann
    @param group: group to delete tags from
1821 414ebaf1 Michael Hanselmann
    @type tags: list of string
1822 414ebaf1 Michael Hanselmann
    @param tags: tags to delete
1823 414ebaf1 Michael Hanselmann
    @type dry_run: bool
1824 414ebaf1 Michael Hanselmann
    @param dry_run: whether to perform a dry run
1825 414ebaf1 Michael Hanselmann
    @rtype: string
1826 414ebaf1 Michael Hanselmann
    @return: job id
1827 414ebaf1 Michael Hanselmann

1828 414ebaf1 Michael Hanselmann
    """
1829 414ebaf1 Michael Hanselmann
    query = [("tag", t) for t in tags]
1830 4c864b55 Michael Hanselmann
    _AppendDryRunIf(query, dry_run)
1831 414ebaf1 Michael Hanselmann
1832 414ebaf1 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE,
1833 414ebaf1 Michael Hanselmann
                             ("/%s/groups/%s/tags" %
1834 414ebaf1 Michael Hanselmann
                              (GANETI_RAPI_VERSION, group)), query, None)
1835 414ebaf1 Michael Hanselmann
1836 2e5c33db Iustin Pop
  def Query(self, what, fields, qfilter=None):
1837 208a6cff Michael Hanselmann
    """Retrieves information about resources.
1838 208a6cff Michael Hanselmann

1839 208a6cff Michael Hanselmann
    @type what: string
1840 208a6cff Michael Hanselmann
    @param what: Resource name, one of L{constants.QR_VIA_RAPI}
1841 208a6cff Michael Hanselmann
    @type fields: list of string
1842 208a6cff Michael Hanselmann
    @param fields: Requested fields
1843 2e5c33db Iustin Pop
    @type qfilter: None or list
1844 2e5c33db Iustin Pop
    @param qfilter: Query filter
1845 208a6cff Michael Hanselmann

1846 208a6cff Michael Hanselmann
    @rtype: string
1847 208a6cff Michael Hanselmann
    @return: job id
1848 208a6cff Michael Hanselmann

1849 208a6cff Michael Hanselmann
    """
1850 208a6cff Michael Hanselmann
    body = {
1851 208a6cff Michael Hanselmann
      "fields": fields,
1852 208a6cff Michael Hanselmann
      }
1853 208a6cff Michael Hanselmann
1854 2e5c33db Iustin Pop
    if qfilter is not None:
1855 2e5c33db Iustin Pop
      body["qfilter"] = qfilter
1856 2e5c33db Iustin Pop
      # TODO: remove this after 2.7
1857 2e5c33db Iustin Pop
      body["filter"] = qfilter
1858 208a6cff Michael Hanselmann
1859 208a6cff Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1860 208a6cff Michael Hanselmann
                             ("/%s/query/%s" %
1861 208a6cff Michael Hanselmann
                              (GANETI_RAPI_VERSION, what)), None, body)
1862 208a6cff Michael Hanselmann
1863 208a6cff Michael Hanselmann
  def QueryFields(self, what, fields=None):
1864 208a6cff Michael Hanselmann
    """Retrieves available fields for a resource.
1865 208a6cff Michael Hanselmann

1866 208a6cff Michael Hanselmann
    @type what: string
1867 208a6cff Michael Hanselmann
    @param what: Resource name, one of L{constants.QR_VIA_RAPI}
1868 208a6cff Michael Hanselmann
    @type fields: list of string
1869 208a6cff Michael Hanselmann
    @param fields: Requested fields
1870 208a6cff Michael Hanselmann

1871 208a6cff Michael Hanselmann
    @rtype: string
1872 208a6cff Michael Hanselmann
    @return: job id
1873 208a6cff Michael Hanselmann

1874 208a6cff Michael Hanselmann
    """
1875 208a6cff Michael Hanselmann
    query = []
1876 208a6cff Michael Hanselmann
1877 208a6cff Michael Hanselmann
    if fields is not None:
1878 4c864b55 Michael Hanselmann
      _AppendIf(query, True, ("fields", ",".join(fields)))
1879 208a6cff Michael Hanselmann
1880 208a6cff Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1881 208a6cff Michael Hanselmann
                             ("/%s/query/%s/fields" %
1882 208a6cff Michael Hanselmann
                              (GANETI_RAPI_VERSION, what)), query, None)