Statistics
| Branch: | Tag: | Revision:

root / lib / rapi / client.py @ 47099cd1

History | View | Annotate | Download (51.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 1068639f Michael Hanselmann
NODE_ROLE_DRAINED = "drained"
67 1068639f Michael Hanselmann
NODE_ROLE_MASTER_CANDIATE = "master-candidate"
68 1068639f Michael Hanselmann
NODE_ROLE_MASTER = "master"
69 1068639f Michael Hanselmann
NODE_ROLE_OFFLINE = "offline"
70 1068639f Michael Hanselmann
NODE_ROLE_REGULAR = "regular"
71 95ab4de9 David Knowles
72 63d5eb8a Michael Hanselmann
JOB_STATUS_QUEUED = "queued"
73 47099cd1 Michael Hanselmann
JOB_STATUS_WAITING = "waiting"
74 63d5eb8a Michael Hanselmann
JOB_STATUS_CANCELING = "canceling"
75 63d5eb8a Michael Hanselmann
JOB_STATUS_RUNNING = "running"
76 63d5eb8a Michael Hanselmann
JOB_STATUS_CANCELED = "canceled"
77 63d5eb8a Michael Hanselmann
JOB_STATUS_SUCCESS = "success"
78 63d5eb8a Michael Hanselmann
JOB_STATUS_ERROR = "error"
79 63d5eb8a Michael Hanselmann
JOB_STATUS_FINALIZED = frozenset([
80 63d5eb8a Michael Hanselmann
  JOB_STATUS_CANCELED,
81 63d5eb8a Michael Hanselmann
  JOB_STATUS_SUCCESS,
82 63d5eb8a Michael Hanselmann
  JOB_STATUS_ERROR,
83 63d5eb8a Michael Hanselmann
  ])
84 63d5eb8a Michael Hanselmann
JOB_STATUS_ALL = frozenset([
85 63d5eb8a Michael Hanselmann
  JOB_STATUS_QUEUED,
86 47099cd1 Michael Hanselmann
  JOB_STATUS_WAITING,
87 63d5eb8a Michael Hanselmann
  JOB_STATUS_CANCELING,
88 63d5eb8a Michael Hanselmann
  JOB_STATUS_RUNNING,
89 63d5eb8a Michael Hanselmann
  ]) | JOB_STATUS_FINALIZED
90 63d5eb8a Michael Hanselmann
91 47099cd1 Michael Hanselmann
# Legacy name
92 47099cd1 Michael Hanselmann
JOB_STATUS_WAITLOCK = JOB_STATUS_WAITING
93 47099cd1 Michael Hanselmann
94 8a47b447 Michael Hanselmann
# Internal constants
95 8a47b447 Michael Hanselmann
_REQ_DATA_VERSION_FIELD = "__version__"
96 8a47b447 Michael Hanselmann
_INST_CREATE_REQV1 = "instance-create-reqv1"
97 c744425f Michael Hanselmann
_INST_REINSTALL_REQV1 = "instance-reinstall-reqv1"
98 b7a1c816 Michael Hanselmann
_NODE_MIGRATE_REQV1 = "node-migrate-reqv1"
99 de40437a Michael Hanselmann
_NODE_EVAC_RES1 = "node-evac-res1"
100 c97fcab8 Guido Trotter
_INST_NIC_PARAMS = frozenset(["mac", "ip", "mode", "link"])
101 48436b97 Michael Hanselmann
_INST_CREATE_V0_DISK_PARAMS = frozenset(["size"])
102 48436b97 Michael Hanselmann
_INST_CREATE_V0_PARAMS = frozenset([
103 48436b97 Michael Hanselmann
  "os", "pnode", "snode", "iallocator", "start", "ip_check", "name_check",
104 48436b97 Michael Hanselmann
  "hypervisor", "file_storage_dir", "file_driver", "dry_run",
105 48436b97 Michael Hanselmann
  ])
106 48436b97 Michael Hanselmann
_INST_CREATE_V0_DPARAMS = frozenset(["beparams", "hvparams"])
107 8a47b447 Michael Hanselmann
108 2a7c3583 Michael Hanselmann
# Older pycURL versions don't have all error constants
109 2a7c3583 Michael Hanselmann
try:
110 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT = pycurl.E_SSL_CACERT
111 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT_BADFILE = pycurl.E_SSL_CACERT_BADFILE
112 2a7c3583 Michael Hanselmann
except AttributeError:
113 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT = 60
114 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT_BADFILE = 77
115 2a7c3583 Michael Hanselmann
116 2a7c3583 Michael Hanselmann
_CURL_SSL_CERT_ERRORS = frozenset([
117 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT,
118 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT_BADFILE,
119 2a7c3583 Michael Hanselmann
  ])
120 2a7c3583 Michael Hanselmann
121 95ab4de9 David Knowles
122 95ab4de9 David Knowles
class Error(Exception):
123 95ab4de9 David Knowles
  """Base error class for this module.
124 95ab4de9 David Knowles

125 95ab4de9 David Knowles
  """
126 95ab4de9 David Knowles
  pass
127 95ab4de9 David Knowles
128 95ab4de9 David Knowles
129 95ab4de9 David Knowles
class CertificateError(Error):
130 95ab4de9 David Knowles
  """Raised when a problem is found with the SSL certificate.
131 95ab4de9 David Knowles

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

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

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

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

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

201 2a7c3583 Michael Hanselmann
    @type curl: pycurl.Curl
202 2a7c3583 Michael Hanselmann
    @param curl: cURL object
203 9279e986 Michael Hanselmann

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

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

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

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

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

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

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

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

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

390 95ab4de9 David Knowles
    @rtype: str
391 95ab4de9 David Knowles
    @return: JSON-Decoded response
392 95ab4de9 David Knowles

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

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

466 95ab4de9 David Knowles
    @rtype: int
467 f2f88abf David Knowles
    @return: Ganeti Remote API version
468 95ab4de9 David Knowles

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

475 7eac4a4d Michael Hanselmann
    @rtype: list
476 7eac4a4d Michael Hanselmann
    @return: List of optional features
477 7eac4a4d Michael Hanselmann

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

492 95ab4de9 David Knowles
    @rtype: list of str
493 95ab4de9 David Knowles
    @return: operating systems
494 95ab4de9 David Knowles

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

502 95ab4de9 David Knowles
    @rtype: dict
503 95ab4de9 David Knowles
    @return: information about the cluster
504 95ab4de9 David Knowles

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

512 d914c76f Simeon Miteff
    @rtype: string
513 54d4c13b Michael Hanselmann
    @return: job id
514 54d4c13b Michael Hanselmann

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

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

525 98805538 Michael Hanselmann
    @rtype: string
526 62e999a5 Michael Hanselmann
    @return: job id
527 62e999a5 Michael Hanselmann

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

537 95ab4de9 David Knowles
    @rtype: list of str
538 95ab4de9 David Knowles
    @return: cluster tags
539 95ab4de9 David Knowles

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

547 95ab4de9 David Knowles
    @type tags: list of str
548 95ab4de9 David Knowles
    @param tags: tags to add to the cluster
549 95ab4de9 David Knowles
    @type dry_run: bool
550 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
551 95ab4de9 David Knowles

552 98805538 Michael Hanselmann
    @rtype: string
553 95ab4de9 David Knowles
    @return: job id
554 95ab4de9 David Knowles

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

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

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

584 95ab4de9 David Knowles
    @type bulk: bool
585 95ab4de9 David Knowles
    @param bulk: whether to return all information about all instances
586 95ab4de9 David Knowles

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

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

606 95ab4de9 David Knowles
    @type instance: str
607 95ab4de9 David Knowles
    @param instance: instance whose info to return
608 95ab4de9 David Knowles

609 95ab4de9 David Knowles
    @rtype: dict
610 95ab4de9 David Knowles
    @return: info about the instance
611 95ab4de9 David Knowles

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

620 591e5103 Michael Hanselmann
    @type instance: string
621 591e5103 Michael Hanselmann
    @param instance: Instance name
622 591e5103 Michael Hanselmann
    @rtype: string
623 591e5103 Michael Hanselmann
    @return: Job ID
624 591e5103 Michael Hanselmann

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

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

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

655 98805538 Michael Hanselmann
    @rtype: string
656 95ab4de9 David Knowles
    @return: job id
657 95ab4de9 David Knowles

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

692 95ab4de9 David Knowles
    @type instance: str
693 95ab4de9 David Knowles
    @param instance: the instance to delete
694 95ab4de9 David Knowles

695 98805538 Michael Hanselmann
    @rtype: string
696 cab667cc David Knowles
    @return: job id
697 cab667cc David Knowles

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

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

712 3b7158ef Michael Hanselmann
    @type instance: string
713 3b7158ef Michael Hanselmann
    @param instance: Instance name
714 98805538 Michael Hanselmann
    @rtype: string
715 3b7158ef Michael Hanselmann
    @return: job id
716 3b7158ef Michael Hanselmann

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

727 b680c8be Michael Hanselmann
    @type instance: string
728 b680c8be Michael Hanselmann
    @param instance: Instance name
729 b680c8be Michael Hanselmann
    @type ignore_size: bool
730 b680c8be Michael Hanselmann
    @param ignore_size: Whether to ignore recorded size
731 d914c76f Simeon Miteff
    @rtype: string
732 b680c8be Michael Hanselmann
    @return: job id
733 b680c8be Michael Hanselmann

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

746 b680c8be Michael Hanselmann
    @type instance: string
747 b680c8be Michael Hanselmann
    @param instance: Instance name
748 d914c76f Simeon Miteff
    @rtype: string
749 b680c8be Michael Hanselmann
    @return: job id
750 b680c8be Michael Hanselmann

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

759 e23881ed Michael Hanselmann
    More details for parameters can be found in the RAPI documentation.
760 e23881ed Michael Hanselmann

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

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

788 95ab4de9 David Knowles
    @type instance: str
789 95ab4de9 David Knowles
    @param instance: instance whose tags to return
790 95ab4de9 David Knowles

791 95ab4de9 David Knowles
    @rtype: list of str
792 95ab4de9 David Knowles
    @return: tags for the instance
793 95ab4de9 David Knowles

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

802 95ab4de9 David Knowles
    @type instance: str
803 95ab4de9 David Knowles
    @param instance: instance to add tags to
804 95ab4de9 David Knowles
    @type tags: list of str
805 95ab4de9 David Knowles
    @param tags: tags to add to the instance
806 95ab4de9 David Knowles
    @type dry_run: bool
807 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
808 95ab4de9 David Knowles

809 98805538 Michael Hanselmann
    @rtype: string
810 95ab4de9 David Knowles
    @return: job id
811 95ab4de9 David Knowles

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

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

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

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

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

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

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

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

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

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

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

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

977 98805538 Michael Hanselmann
    @rtype: string
978 95ab4de9 David Knowles
    @return: job id
979 95ab4de9 David Knowles

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

1004 ebeb600f Michael Hanselmann
    @type instance: string
1005 ebeb600f Michael Hanselmann
    @param instance: Instance name
1006 ebeb600f Michael Hanselmann
    @type mode: string
1007 ebeb600f Michael Hanselmann
    @param mode: Export mode
1008 ebeb600f Michael Hanselmann
    @rtype: string
1009 ebeb600f Michael Hanselmann
    @return: Job ID
1010 ebeb600f Michael Hanselmann

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

1022 ebeb600f Michael Hanselmann
    @type instance: string
1023 ebeb600f Michael Hanselmann
    @param instance: Instance name
1024 ebeb600f Michael Hanselmann
    @type mode: string
1025 ebeb600f Michael Hanselmann
    @param mode: Export mode
1026 ebeb600f Michael Hanselmann
    @rtype: string
1027 ebeb600f Michael Hanselmann
    @return: Job ID
1028 ebeb600f Michael Hanselmann

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

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

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

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

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

1108 b82d4c5e Michael Hanselmann
    @type instance: string
1109 b82d4c5e Michael Hanselmann
    @param instance: Instance name
1110 d914c76f Simeon Miteff
    @rtype: dict
1111 d914c76f Simeon Miteff
    @return: dictionary containing information about instance's console
1112 b82d4c5e Michael Hanselmann

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

1121 95ab4de9 David Knowles
    @rtype: list of int
1122 95ab4de9 David Knowles
    @return: job ids for the cluster
1123 95ab4de9 David Knowles

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

1133 98805538 Michael Hanselmann
    @type job_id: string
1134 95ab4de9 David Knowles
    @param job_id: job id whose status to query
1135 95ab4de9 David Knowles

1136 95ab4de9 David Knowles
    @rtype: dict
1137 95ab4de9 David Knowles
    @return: job status
1138 95ab4de9 David Knowles

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

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

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

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

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

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

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

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

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

1223 95ab4de9 David Knowles
    @type bulk: bool
1224 95ab4de9 David Knowles
    @param bulk: whether to return all information about all instances
1225 95ab4de9 David Knowles

1226 95ab4de9 David Knowles
    @rtype: list of dict or str
1227 95ab4de9 David Knowles
    @return: if bulk is true, info about nodes in the cluster,
1228 95ab4de9 David Knowles
        else list of nodes in the cluster
1229 95ab4de9 David Knowles

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

1245 95ab4de9 David Knowles
    @type node: str
1246 95ab4de9 David Knowles
    @param node: node whose info to return
1247 95ab4de9 David Knowles

1248 95ab4de9 David Knowles
    @rtype: dict
1249 95ab4de9 David Knowles
    @return: info about the node
1250 95ab4de9 David Knowles

1251 95ab4de9 David Knowles
    """
1252 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1253 a198b2d9 Michael Hanselmann
                             "/%s/nodes/%s" % (GANETI_RAPI_VERSION, node),
1254 a198b2d9 Michael Hanselmann
                             None, None)
1255 95ab4de9 David Knowles
1256 95ab4de9 David Knowles
  def EvacuateNode(self, node, iallocator=None, remote_node=None,
1257 de40437a Michael Hanselmann
                   dry_run=False, early_release=None,
1258 de40437a Michael Hanselmann
                   primary=None, secondary=None, accept_old=False):
1259 95ab4de9 David Knowles
    """Evacuates instances from a Ganeti node.
1260 95ab4de9 David Knowles

1261 95ab4de9 David Knowles
    @type node: str
1262 95ab4de9 David Knowles
    @param node: node to evacuate
1263 95ab4de9 David Knowles
    @type iallocator: str or None
1264 95ab4de9 David Knowles
    @param iallocator: instance allocator to use
1265 95ab4de9 David Knowles
    @type remote_node: str
1266 95ab4de9 David Knowles
    @param remote_node: node to evaucate to
1267 95ab4de9 David Knowles
    @type dry_run: bool
1268 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1269 941b9309 Iustin Pop
    @type early_release: bool
1270 941b9309 Iustin Pop
    @param early_release: whether to enable parallelization
1271 de40437a Michael Hanselmann
    @type primary: bool
1272 de40437a Michael Hanselmann
    @param primary: Whether to evacuate primary instances
1273 de40437a Michael Hanselmann
    @type secondary: bool
1274 de40437a Michael Hanselmann
    @param secondary: Whether to evacuate secondary instances
1275 de40437a Michael Hanselmann
    @type accept_old: bool
1276 de40437a Michael Hanselmann
    @param accept_old: Whether caller is ready to accept old-style (pre-2.5)
1277 de40437a Michael Hanselmann
        results
1278 de40437a Michael Hanselmann

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

1285 941b9309 Iustin Pop
    @raises GanetiApiError: if an iallocator and remote_node are both
1286 941b9309 Iustin Pop
        specified
1287 95ab4de9 David Knowles

1288 95ab4de9 David Knowles
    """
1289 95ab4de9 David Knowles
    if iallocator and remote_node:
1290 cfc03c54 Michael Hanselmann
      raise GanetiApiError("Only one of iallocator or remote_node can be used")
1291 95ab4de9 David Knowles
1292 cfc03c54 Michael Hanselmann
    query = []
1293 95ab4de9 David Knowles
    if dry_run:
1294 95ab4de9 David Knowles
      query.append(("dry-run", 1))
1295 de40437a Michael Hanselmann
1296 de40437a Michael Hanselmann
    if _NODE_EVAC_RES1 in self.GetFeatures():
1297 de40437a Michael Hanselmann
      body = {}
1298 de40437a Michael Hanselmann
1299 de40437a Michael Hanselmann
      if iallocator is not None:
1300 de40437a Michael Hanselmann
        body["iallocator"] = iallocator
1301 de40437a Michael Hanselmann
      if remote_node is not None:
1302 de40437a Michael Hanselmann
        body["remote_node"] = remote_node
1303 de40437a Michael Hanselmann
      if early_release is not None:
1304 de40437a Michael Hanselmann
        body["early_release"] = early_release
1305 de40437a Michael Hanselmann
      if primary is not None:
1306 de40437a Michael Hanselmann
        body["primary"] = primary
1307 de40437a Michael Hanselmann
      if secondary is not None:
1308 de40437a Michael Hanselmann
        body["secondary"] = secondary
1309 de40437a Michael Hanselmann
    else:
1310 de40437a Michael Hanselmann
      # Pre-2.5 request format
1311 de40437a Michael Hanselmann
      body = None
1312 de40437a Michael Hanselmann
1313 de40437a Michael Hanselmann
      if not accept_old:
1314 de40437a Michael Hanselmann
        raise GanetiApiError("Server is version 2.4 or earlier and caller does"
1315 de40437a Michael Hanselmann
                             " not accept old-style results (parameter"
1316 de40437a Michael Hanselmann
                             " accept_old)")
1317 de40437a Michael Hanselmann
1318 de40437a Michael Hanselmann
      if primary or primary is None or not (secondary is None or secondary):
1319 de40437a Michael Hanselmann
        raise GanetiApiError("Server can only evacuate secondary instances")
1320 de40437a Michael Hanselmann
1321 de40437a Michael Hanselmann
      if iallocator:
1322 de40437a Michael Hanselmann
        query.append(("iallocator", iallocator))
1323 de40437a Michael Hanselmann
      if remote_node:
1324 de40437a Michael Hanselmann
        query.append(("remote_node", remote_node))
1325 de40437a Michael Hanselmann
      if early_release:
1326 de40437a Michael Hanselmann
        query.append(("early_release", 1))
1327 95ab4de9 David Knowles
1328 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_POST,
1329 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/evacuate" %
1330 de40437a Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, body)
1331 95ab4de9 David Knowles
1332 b7a1c816 Michael Hanselmann
  def MigrateNode(self, node, mode=None, dry_run=False, iallocator=None,
1333 b7a1c816 Michael Hanselmann
                  target_node=None):
1334 95ab4de9 David Knowles
    """Migrates all primary instances from a node.
1335 95ab4de9 David Knowles

1336 95ab4de9 David Knowles
    @type node: str
1337 95ab4de9 David Knowles
    @param node: node to migrate
1338 1f334d96 Iustin Pop
    @type mode: string
1339 1f334d96 Iustin Pop
    @param mode: if passed, it will overwrite the live migration type,
1340 1f334d96 Iustin Pop
        otherwise the hypervisor default will be used
1341 95ab4de9 David Knowles
    @type dry_run: bool
1342 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1343 b7a1c816 Michael Hanselmann
    @type iallocator: string
1344 b7a1c816 Michael Hanselmann
    @param iallocator: instance allocator to use
1345 b7a1c816 Michael Hanselmann
    @type target_node: string
1346 b7a1c816 Michael Hanselmann
    @param target_node: Target node for shared-storage instances
1347 95ab4de9 David Knowles

1348 98805538 Michael Hanselmann
    @rtype: string
1349 95ab4de9 David Knowles
    @return: job id
1350 95ab4de9 David Knowles

1351 95ab4de9 David Knowles
    """
1352 95ab4de9 David Knowles
    query = []
1353 95ab4de9 David Knowles
    if dry_run:
1354 95ab4de9 David Knowles
      query.append(("dry-run", 1))
1355 95ab4de9 David Knowles
1356 b7a1c816 Michael Hanselmann
    if _NODE_MIGRATE_REQV1 in self.GetFeatures():
1357 b7a1c816 Michael Hanselmann
      body = {}
1358 b7a1c816 Michael Hanselmann
1359 b7a1c816 Michael Hanselmann
      if mode is not None:
1360 b7a1c816 Michael Hanselmann
        body["mode"] = mode
1361 b7a1c816 Michael Hanselmann
      if iallocator is not None:
1362 b7a1c816 Michael Hanselmann
        body["iallocator"] = iallocator
1363 b7a1c816 Michael Hanselmann
      if target_node is not None:
1364 b7a1c816 Michael Hanselmann
        body["target_node"] = target_node
1365 b7a1c816 Michael Hanselmann
1366 b7a1c816 Michael Hanselmann
      assert len(query) <= 1
1367 b7a1c816 Michael Hanselmann
1368 b7a1c816 Michael Hanselmann
      return self._SendRequest(HTTP_POST,
1369 b7a1c816 Michael Hanselmann
                               ("/%s/nodes/%s/migrate" %
1370 b7a1c816 Michael Hanselmann
                                (GANETI_RAPI_VERSION, node)), query, body)
1371 b7a1c816 Michael Hanselmann
    else:
1372 b7a1c816 Michael Hanselmann
      # Use old request format
1373 b7a1c816 Michael Hanselmann
      if target_node is not None:
1374 b7a1c816 Michael Hanselmann
        raise GanetiApiError("Server does not support specifying target node"
1375 b7a1c816 Michael Hanselmann
                             " for node migration")
1376 b7a1c816 Michael Hanselmann
1377 b7a1c816 Michael Hanselmann
      if mode is not None:
1378 b7a1c816 Michael Hanselmann
        query.append(("mode", mode))
1379 b7a1c816 Michael Hanselmann
1380 b7a1c816 Michael Hanselmann
      return self._SendRequest(HTTP_POST,
1381 b7a1c816 Michael Hanselmann
                               ("/%s/nodes/%s/migrate" %
1382 b7a1c816 Michael Hanselmann
                                (GANETI_RAPI_VERSION, node)), query, None)
1383 95ab4de9 David Knowles
1384 95ab4de9 David Knowles
  def GetNodeRole(self, node):
1385 95ab4de9 David Knowles
    """Gets the current role for a node.
1386 95ab4de9 David Knowles

1387 95ab4de9 David Knowles
    @type node: str
1388 95ab4de9 David Knowles
    @param node: node whose role to return
1389 95ab4de9 David Knowles

1390 95ab4de9 David Knowles
    @rtype: str
1391 95ab4de9 David Knowles
    @return: the current role for a node
1392 95ab4de9 David Knowles

1393 95ab4de9 David Knowles
    """
1394 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1395 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/role" %
1396 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), None, None)
1397 95ab4de9 David Knowles
1398 95ab4de9 David Knowles
  def SetNodeRole(self, node, role, force=False):
1399 95ab4de9 David Knowles
    """Sets the role for a node.
1400 95ab4de9 David Knowles

1401 95ab4de9 David Knowles
    @type node: str
1402 95ab4de9 David Knowles
    @param node: the node whose role to set
1403 95ab4de9 David Knowles
    @type role: str
1404 95ab4de9 David Knowles
    @param role: the role to set for the node
1405 95ab4de9 David Knowles
    @type force: bool
1406 95ab4de9 David Knowles
    @param force: whether to force the role change
1407 95ab4de9 David Knowles

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

1411 95ab4de9 David Knowles
    """
1412 1068639f Michael Hanselmann
    query = [
1413 1068639f Michael Hanselmann
      ("force", force),
1414 1068639f Michael Hanselmann
      ]
1415 cfc03c54 Michael Hanselmann
1416 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1417 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/role" %
1418 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, role)
1419 95ab4de9 David Knowles
1420 95ab4de9 David Knowles
  def GetNodeStorageUnits(self, node, storage_type, output_fields):
1421 95ab4de9 David Knowles
    """Gets the storage units for a node.
1422 95ab4de9 David Knowles

1423 95ab4de9 David Knowles
    @type node: str
1424 95ab4de9 David Knowles
    @param node: the node whose storage units to return
1425 95ab4de9 David Knowles
    @type storage_type: str
1426 95ab4de9 David Knowles
    @param storage_type: storage type whose units to return
1427 95ab4de9 David Knowles
    @type output_fields: str
1428 95ab4de9 David Knowles
    @param output_fields: storage type fields to return
1429 95ab4de9 David Knowles

1430 98805538 Michael Hanselmann
    @rtype: string
1431 95ab4de9 David Knowles
    @return: job id where results can be retrieved
1432 95ab4de9 David Knowles

1433 95ab4de9 David Knowles
    """
1434 cfc03c54 Michael Hanselmann
    query = [
1435 cfc03c54 Michael Hanselmann
      ("storage_type", storage_type),
1436 cfc03c54 Michael Hanselmann
      ("output_fields", output_fields),
1437 cfc03c54 Michael Hanselmann
      ]
1438 95ab4de9 David Knowles
1439 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1440 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/storage" %
1441 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1442 95ab4de9 David Knowles
1443 fde28316 Michael Hanselmann
  def ModifyNodeStorageUnits(self, node, storage_type, name, allocatable=None):
1444 95ab4de9 David Knowles
    """Modifies parameters of storage units on the node.
1445 95ab4de9 David Knowles

1446 95ab4de9 David Knowles
    @type node: str
1447 95ab4de9 David Knowles
    @param node: node whose storage units to modify
1448 95ab4de9 David Knowles
    @type storage_type: str
1449 95ab4de9 David Knowles
    @param storage_type: storage type whose units to modify
1450 95ab4de9 David Knowles
    @type name: str
1451 95ab4de9 David Knowles
    @param name: name of the storage unit
1452 fde28316 Michael Hanselmann
    @type allocatable: bool or None
1453 fde28316 Michael Hanselmann
    @param allocatable: Whether to set the "allocatable" flag on the storage
1454 fde28316 Michael Hanselmann
                        unit (None=no modification, True=set, False=unset)
1455 95ab4de9 David Knowles

1456 98805538 Michael Hanselmann
    @rtype: string
1457 95ab4de9 David Knowles
    @return: job id
1458 95ab4de9 David Knowles

1459 95ab4de9 David Knowles
    """
1460 95ab4de9 David Knowles
    query = [
1461 cfc03c54 Michael Hanselmann
      ("storage_type", storage_type),
1462 cfc03c54 Michael Hanselmann
      ("name", name),
1463 cfc03c54 Michael Hanselmann
      ]
1464 cfc03c54 Michael Hanselmann
1465 fde28316 Michael Hanselmann
    if allocatable is not None:
1466 fde28316 Michael Hanselmann
      query.append(("allocatable", allocatable))
1467 fde28316 Michael Hanselmann
1468 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1469 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/storage/modify" %
1470 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1471 95ab4de9 David Knowles
1472 95ab4de9 David Knowles
  def RepairNodeStorageUnits(self, node, storage_type, name):
1473 95ab4de9 David Knowles
    """Repairs a storage unit on the node.
1474 95ab4de9 David Knowles

1475 95ab4de9 David Knowles
    @type node: str
1476 95ab4de9 David Knowles
    @param node: node whose storage units to repair
1477 95ab4de9 David Knowles
    @type storage_type: str
1478 95ab4de9 David Knowles
    @param storage_type: storage type to repair
1479 95ab4de9 David Knowles
    @type name: str
1480 95ab4de9 David Knowles
    @param name: name of the storage unit to repair
1481 95ab4de9 David Knowles

1482 98805538 Michael Hanselmann
    @rtype: string
1483 95ab4de9 David Knowles
    @return: job id
1484 95ab4de9 David Knowles

1485 95ab4de9 David Knowles
    """
1486 cfc03c54 Michael Hanselmann
    query = [
1487 cfc03c54 Michael Hanselmann
      ("storage_type", storage_type),
1488 cfc03c54 Michael Hanselmann
      ("name", name),
1489 cfc03c54 Michael Hanselmann
      ]
1490 95ab4de9 David Knowles
1491 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1492 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/storage/repair" %
1493 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1494 95ab4de9 David Knowles
1495 95ab4de9 David Knowles
  def GetNodeTags(self, node):
1496 95ab4de9 David Knowles
    """Gets the tags for a node.
1497 95ab4de9 David Knowles

1498 95ab4de9 David Knowles
    @type node: str
1499 95ab4de9 David Knowles
    @param node: node whose tags to return
1500 95ab4de9 David Knowles

1501 95ab4de9 David Knowles
    @rtype: list of str
1502 95ab4de9 David Knowles
    @return: tags for the node
1503 95ab4de9 David Knowles

1504 95ab4de9 David Knowles
    """
1505 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1506 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/tags" %
1507 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), None, None)
1508 95ab4de9 David Knowles
1509 95ab4de9 David Knowles
  def AddNodeTags(self, node, tags, dry_run=False):
1510 95ab4de9 David Knowles
    """Adds tags to a node.
1511 95ab4de9 David Knowles

1512 95ab4de9 David Knowles
    @type node: str
1513 95ab4de9 David Knowles
    @param node: node to add tags to
1514 95ab4de9 David Knowles
    @type tags: list of str
1515 95ab4de9 David Knowles
    @param tags: tags to add to the node
1516 95ab4de9 David Knowles
    @type dry_run: bool
1517 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1518 95ab4de9 David Knowles

1519 98805538 Michael Hanselmann
    @rtype: string
1520 95ab4de9 David Knowles
    @return: job id
1521 95ab4de9 David Knowles

1522 95ab4de9 David Knowles
    """
1523 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
1524 95ab4de9 David Knowles
    if dry_run:
1525 95ab4de9 David Knowles
      query.append(("dry-run", 1))
1526 95ab4de9 David Knowles
1527 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1528 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/tags" %
1529 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, tags)
1530 95ab4de9 David Knowles
1531 95ab4de9 David Knowles
  def DeleteNodeTags(self, node, tags, dry_run=False):
1532 95ab4de9 David Knowles
    """Delete tags from a node.
1533 95ab4de9 David Knowles

1534 95ab4de9 David Knowles
    @type node: str
1535 95ab4de9 David Knowles
    @param node: node to remove tags from
1536 95ab4de9 David Knowles
    @type tags: list of str
1537 95ab4de9 David Knowles
    @param tags: tags to remove from the node
1538 95ab4de9 David Knowles
    @type dry_run: bool
1539 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1540 95ab4de9 David Knowles

1541 98805538 Michael Hanselmann
    @rtype: string
1542 95ab4de9 David Knowles
    @return: job id
1543 95ab4de9 David Knowles

1544 95ab4de9 David Knowles
    """
1545 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
1546 95ab4de9 David Knowles
    if dry_run:
1547 95ab4de9 David Knowles
      query.append(("dry-run", 1))
1548 95ab4de9 David Knowles
1549 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE,
1550 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/tags" %
1551 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1552 a268af8d Adeodato Simo
1553 a268af8d Adeodato Simo
  def GetGroups(self, bulk=False):
1554 a268af8d Adeodato Simo
    """Gets all node groups in the cluster.
1555 a268af8d Adeodato Simo

1556 a268af8d Adeodato Simo
    @type bulk: bool
1557 a268af8d Adeodato Simo
    @param bulk: whether to return all information about the groups
1558 a268af8d Adeodato Simo

1559 a268af8d Adeodato Simo
    @rtype: list of dict or str
1560 a268af8d Adeodato Simo
    @return: if bulk is true, a list of dictionaries with info about all node
1561 a268af8d Adeodato Simo
        groups in the cluster, else a list of names of those node groups
1562 a268af8d Adeodato Simo

1563 a268af8d Adeodato Simo
    """
1564 a268af8d Adeodato Simo
    query = []
1565 a268af8d Adeodato Simo
    if bulk:
1566 a268af8d Adeodato Simo
      query.append(("bulk", 1))
1567 a268af8d Adeodato Simo
1568 a268af8d Adeodato Simo
    groups = self._SendRequest(HTTP_GET, "/%s/groups" % GANETI_RAPI_VERSION,
1569 a268af8d Adeodato Simo
                               query, None)
1570 a268af8d Adeodato Simo
    if bulk:
1571 a268af8d Adeodato Simo
      return groups
1572 a268af8d Adeodato Simo
    else:
1573 a268af8d Adeodato Simo
      return [g["name"] for g in groups]
1574 a268af8d Adeodato Simo
1575 a268af8d Adeodato Simo
  def GetGroup(self, group):
1576 a268af8d Adeodato Simo
    """Gets information about a node group.
1577 a268af8d Adeodato Simo

1578 a268af8d Adeodato Simo
    @type group: str
1579 a268af8d Adeodato Simo
    @param group: name of the node group whose info to return
1580 a268af8d Adeodato Simo

1581 a268af8d Adeodato Simo
    @rtype: dict
1582 a268af8d Adeodato Simo
    @return: info about the node group
1583 a268af8d Adeodato Simo

1584 a268af8d Adeodato Simo
    """
1585 a268af8d Adeodato Simo
    return self._SendRequest(HTTP_GET,
1586 a268af8d Adeodato Simo
                             "/%s/groups/%s" % (GANETI_RAPI_VERSION, group),
1587 a268af8d Adeodato Simo
                             None, None)
1588 a268af8d Adeodato Simo
1589 90e99856 Adeodato Simo
  def CreateGroup(self, name, alloc_policy=None, dry_run=False):
1590 a268af8d Adeodato Simo
    """Creates a new node group.
1591 a268af8d Adeodato Simo

1592 a268af8d Adeodato Simo
    @type name: str
1593 a268af8d Adeodato Simo
    @param name: the name of node group to create
1594 90e99856 Adeodato Simo
    @type alloc_policy: str
1595 90e99856 Adeodato Simo
    @param alloc_policy: the desired allocation policy for the group, if any
1596 a268af8d Adeodato Simo
    @type dry_run: bool
1597 a268af8d Adeodato Simo
    @param dry_run: whether to peform a dry run
1598 a268af8d Adeodato Simo

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

1602 a268af8d Adeodato Simo
    """
1603 a268af8d Adeodato Simo
    query = []
1604 a268af8d Adeodato Simo
    if dry_run:
1605 a268af8d Adeodato Simo
      query.append(("dry-run", 1))
1606 a268af8d Adeodato Simo
1607 a268af8d Adeodato Simo
    body = {
1608 a268af8d Adeodato Simo
      "name": name,
1609 90e99856 Adeodato Simo
      "alloc_policy": alloc_policy
1610 a268af8d Adeodato Simo
      }
1611 a268af8d Adeodato Simo
1612 a268af8d Adeodato Simo
    return self._SendRequest(HTTP_POST, "/%s/groups" % GANETI_RAPI_VERSION,
1613 a268af8d Adeodato Simo
                             query, body)
1614 a268af8d Adeodato Simo
1615 f18fab7d Adeodato Simo
  def ModifyGroup(self, group, **kwargs):
1616 f18fab7d Adeodato Simo
    """Modifies a node group.
1617 f18fab7d Adeodato Simo

1618 f18fab7d Adeodato Simo
    More details for parameters can be found in the RAPI documentation.
1619 f18fab7d Adeodato Simo

1620 f18fab7d Adeodato Simo
    @type group: string
1621 f18fab7d Adeodato Simo
    @param group: Node group name
1622 98805538 Michael Hanselmann
    @rtype: string
1623 f18fab7d Adeodato Simo
    @return: job id
1624 f18fab7d Adeodato Simo

1625 f18fab7d Adeodato Simo
    """
1626 f18fab7d Adeodato Simo
    return self._SendRequest(HTTP_PUT,
1627 f18fab7d Adeodato Simo
                             ("/%s/groups/%s/modify" %
1628 f18fab7d Adeodato Simo
                              (GANETI_RAPI_VERSION, group)), None, kwargs)
1629 f18fab7d Adeodato Simo
1630 a268af8d Adeodato Simo
  def DeleteGroup(self, group, dry_run=False):
1631 a268af8d Adeodato Simo
    """Deletes a node group.
1632 a268af8d Adeodato Simo

1633 a268af8d Adeodato Simo
    @type group: str
1634 a268af8d Adeodato Simo
    @param group: the node group to delete
1635 a268af8d Adeodato Simo
    @type dry_run: bool
1636 a268af8d Adeodato Simo
    @param dry_run: whether to peform a dry run
1637 a268af8d Adeodato Simo

1638 98805538 Michael Hanselmann
    @rtype: string
1639 a268af8d Adeodato Simo
    @return: job id
1640 a268af8d Adeodato Simo

1641 a268af8d Adeodato Simo
    """
1642 a268af8d Adeodato Simo
    query = []
1643 a268af8d Adeodato Simo
    if dry_run:
1644 a268af8d Adeodato Simo
      query.append(("dry-run", 1))
1645 a268af8d Adeodato Simo
1646 a268af8d Adeodato Simo
    return self._SendRequest(HTTP_DELETE,
1647 a268af8d Adeodato Simo
                             ("/%s/groups/%s" %
1648 a268af8d Adeodato Simo
                              (GANETI_RAPI_VERSION, group)), query, None)
1649 a268af8d Adeodato Simo
1650 a268af8d Adeodato Simo
  def RenameGroup(self, group, new_name):
1651 a268af8d Adeodato Simo
    """Changes the name of a node group.
1652 a268af8d Adeodato Simo

1653 a268af8d Adeodato Simo
    @type group: string
1654 a268af8d Adeodato Simo
    @param group: Node group name
1655 a268af8d Adeodato Simo
    @type new_name: string
1656 a268af8d Adeodato Simo
    @param new_name: New node group name
1657 a268af8d Adeodato Simo

1658 98805538 Michael Hanselmann
    @rtype: string
1659 a268af8d Adeodato Simo
    @return: job id
1660 a268af8d Adeodato Simo

1661 a268af8d Adeodato Simo
    """
1662 a268af8d Adeodato Simo
    body = {
1663 a268af8d Adeodato Simo
      "new_name": new_name,
1664 a268af8d Adeodato Simo
      }
1665 a268af8d Adeodato Simo
1666 a268af8d Adeodato Simo
    return self._SendRequest(HTTP_PUT,
1667 a268af8d Adeodato Simo
                             ("/%s/groups/%s/rename" %
1668 a268af8d Adeodato Simo
                              (GANETI_RAPI_VERSION, group)), None, body)
1669 4245446f Adeodato Simo
1670 4245446f Adeodato Simo
  def AssignGroupNodes(self, group, nodes, force=False, dry_run=False):
1671 4245446f Adeodato Simo
    """Assigns nodes to a group.
1672 4245446f Adeodato Simo

1673 4245446f Adeodato Simo
    @type group: string
1674 4245446f Adeodato Simo
    @param group: Node gropu name
1675 4245446f Adeodato Simo
    @type nodes: list of strings
1676 4245446f Adeodato Simo
    @param nodes: List of nodes to assign to the group
1677 4245446f Adeodato Simo

1678 98805538 Michael Hanselmann
    @rtype: string
1679 4245446f Adeodato Simo
    @return: job id
1680 4245446f Adeodato Simo

1681 4245446f Adeodato Simo
    """
1682 4245446f Adeodato Simo
    query = []
1683 4245446f Adeodato Simo
1684 4245446f Adeodato Simo
    if force:
1685 4245446f Adeodato Simo
      query.append(("force", 1))
1686 4245446f Adeodato Simo
1687 4245446f Adeodato Simo
    if dry_run:
1688 4245446f Adeodato Simo
      query.append(("dry-run", 1))
1689 4245446f Adeodato Simo
1690 4245446f Adeodato Simo
    body = {
1691 4245446f Adeodato Simo
      "nodes": nodes,
1692 4245446f Adeodato Simo
      }
1693 4245446f Adeodato Simo
1694 4245446f Adeodato Simo
    return self._SendRequest(HTTP_PUT,
1695 4245446f Adeodato Simo
                             ("/%s/groups/%s/assign-nodes" %
1696 4245446f Adeodato Simo
                             (GANETI_RAPI_VERSION, group)), query, body)
1697 208a6cff Michael Hanselmann
1698 414ebaf1 Michael Hanselmann
  def GetGroupTags(self, group):
1699 414ebaf1 Michael Hanselmann
    """Gets tags for a node group.
1700 414ebaf1 Michael Hanselmann

1701 414ebaf1 Michael Hanselmann
    @type group: string
1702 414ebaf1 Michael Hanselmann
    @param group: Node group whose tags to return
1703 414ebaf1 Michael Hanselmann

1704 414ebaf1 Michael Hanselmann
    @rtype: list of strings
1705 414ebaf1 Michael Hanselmann
    @return: tags for the group
1706 414ebaf1 Michael Hanselmann

1707 414ebaf1 Michael Hanselmann
    """
1708 414ebaf1 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1709 414ebaf1 Michael Hanselmann
                             ("/%s/groups/%s/tags" %
1710 414ebaf1 Michael Hanselmann
                              (GANETI_RAPI_VERSION, group)), None, None)
1711 414ebaf1 Michael Hanselmann
1712 414ebaf1 Michael Hanselmann
  def AddGroupTags(self, group, tags, dry_run=False):
1713 414ebaf1 Michael Hanselmann
    """Adds tags to a node group.
1714 414ebaf1 Michael Hanselmann

1715 414ebaf1 Michael Hanselmann
    @type group: str
1716 414ebaf1 Michael Hanselmann
    @param group: group to add tags to
1717 414ebaf1 Michael Hanselmann
    @type tags: list of string
1718 414ebaf1 Michael Hanselmann
    @param tags: tags to add to the group
1719 414ebaf1 Michael Hanselmann
    @type dry_run: bool
1720 414ebaf1 Michael Hanselmann
    @param dry_run: whether to perform a dry run
1721 414ebaf1 Michael Hanselmann

1722 414ebaf1 Michael Hanselmann
    @rtype: string
1723 414ebaf1 Michael Hanselmann
    @return: job id
1724 414ebaf1 Michael Hanselmann

1725 414ebaf1 Michael Hanselmann
    """
1726 414ebaf1 Michael Hanselmann
    query = [("tag", t) for t in tags]
1727 414ebaf1 Michael Hanselmann
    if dry_run:
1728 414ebaf1 Michael Hanselmann
      query.append(("dry-run", 1))
1729 414ebaf1 Michael Hanselmann
1730 414ebaf1 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1731 414ebaf1 Michael Hanselmann
                             ("/%s/groups/%s/tags" %
1732 414ebaf1 Michael Hanselmann
                              (GANETI_RAPI_VERSION, group)), query, None)
1733 414ebaf1 Michael Hanselmann
1734 414ebaf1 Michael Hanselmann
  def DeleteGroupTags(self, group, tags, dry_run=False):
1735 414ebaf1 Michael Hanselmann
    """Deletes tags from a node group.
1736 414ebaf1 Michael Hanselmann

1737 414ebaf1 Michael Hanselmann
    @type group: str
1738 414ebaf1 Michael Hanselmann
    @param group: group to delete tags from
1739 414ebaf1 Michael Hanselmann
    @type tags: list of string
1740 414ebaf1 Michael Hanselmann
    @param tags: tags to delete
1741 414ebaf1 Michael Hanselmann
    @type dry_run: bool
1742 414ebaf1 Michael Hanselmann
    @param dry_run: whether to perform a dry run
1743 414ebaf1 Michael Hanselmann
    @rtype: string
1744 414ebaf1 Michael Hanselmann
    @return: job id
1745 414ebaf1 Michael Hanselmann

1746 414ebaf1 Michael Hanselmann
    """
1747 414ebaf1 Michael Hanselmann
    query = [("tag", t) for t in tags]
1748 414ebaf1 Michael Hanselmann
    if dry_run:
1749 414ebaf1 Michael Hanselmann
      query.append(("dry-run", 1))
1750 414ebaf1 Michael Hanselmann
1751 414ebaf1 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE,
1752 414ebaf1 Michael Hanselmann
                             ("/%s/groups/%s/tags" %
1753 414ebaf1 Michael Hanselmann
                              (GANETI_RAPI_VERSION, group)), query, None)
1754 414ebaf1 Michael Hanselmann
1755 208a6cff Michael Hanselmann
  def Query(self, what, fields, filter_=None):
1756 208a6cff Michael Hanselmann
    """Retrieves information about resources.
1757 208a6cff Michael Hanselmann

1758 208a6cff Michael Hanselmann
    @type what: string
1759 208a6cff Michael Hanselmann
    @param what: Resource name, one of L{constants.QR_VIA_RAPI}
1760 208a6cff Michael Hanselmann
    @type fields: list of string
1761 208a6cff Michael Hanselmann
    @param fields: Requested fields
1762 208a6cff Michael Hanselmann
    @type filter_: None or list
1763 d914c76f Simeon Miteff
    @param filter_: Query filter
1764 208a6cff Michael Hanselmann

1765 208a6cff Michael Hanselmann
    @rtype: string
1766 208a6cff Michael Hanselmann
    @return: job id
1767 208a6cff Michael Hanselmann

1768 208a6cff Michael Hanselmann
    """
1769 208a6cff Michael Hanselmann
    body = {
1770 208a6cff Michael Hanselmann
      "fields": fields,
1771 208a6cff Michael Hanselmann
      }
1772 208a6cff Michael Hanselmann
1773 208a6cff Michael Hanselmann
    if filter_ is not None:
1774 208a6cff Michael Hanselmann
      body["filter"] = filter_
1775 208a6cff Michael Hanselmann
1776 208a6cff Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1777 208a6cff Michael Hanselmann
                             ("/%s/query/%s" %
1778 208a6cff Michael Hanselmann
                              (GANETI_RAPI_VERSION, what)), None, body)
1779 208a6cff Michael Hanselmann
1780 208a6cff Michael Hanselmann
  def QueryFields(self, what, fields=None):
1781 208a6cff Michael Hanselmann
    """Retrieves available fields for a resource.
1782 208a6cff Michael Hanselmann

1783 208a6cff Michael Hanselmann
    @type what: string
1784 208a6cff Michael Hanselmann
    @param what: Resource name, one of L{constants.QR_VIA_RAPI}
1785 208a6cff Michael Hanselmann
    @type fields: list of string
1786 208a6cff Michael Hanselmann
    @param fields: Requested fields
1787 208a6cff Michael Hanselmann

1788 208a6cff Michael Hanselmann
    @rtype: string
1789 208a6cff Michael Hanselmann
    @return: job id
1790 208a6cff Michael Hanselmann

1791 208a6cff Michael Hanselmann
    """
1792 208a6cff Michael Hanselmann
    query = []
1793 208a6cff Michael Hanselmann
1794 208a6cff Michael Hanselmann
    if fields is not None:
1795 208a6cff Michael Hanselmann
      query.append(("fields", ",".join(fields)))
1796 208a6cff Michael Hanselmann
1797 208a6cff Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1798 208a6cff Michael Hanselmann
                             ("/%s/query/%s/fields" %
1799 208a6cff Michael Hanselmann
                              (GANETI_RAPI_VERSION, what)), query, None)