Statistics
| Branch: | Tag: | Revision:

root / lib / rapi / client.py @ f3c1a70c

History | View | Annotate | Download (52.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 c0a146a1 Michael Hanselmann
  def FailoverInstance(self, instance, iallocator=None,
1077 c0a146a1 Michael Hanselmann
                       ignore_consistency=None, target_node=None):
1078 c0a146a1 Michael Hanselmann
    """Does a failover of an instance.
1079 c0a146a1 Michael Hanselmann

1080 c0a146a1 Michael Hanselmann
    @type instance: string
1081 c0a146a1 Michael Hanselmann
    @param instance: Instance name
1082 c0a146a1 Michael Hanselmann
    @type iallocator: string
1083 c0a146a1 Michael Hanselmann
    @param iallocator: Iallocator for deciding the target node for
1084 c0a146a1 Michael Hanselmann
      shared-storage instances
1085 c0a146a1 Michael Hanselmann
    @type ignore_consistency: bool
1086 c0a146a1 Michael Hanselmann
    @param ignore_consistency: Whether to ignore disk consistency
1087 c0a146a1 Michael Hanselmann
    @type target_node: string
1088 c0a146a1 Michael Hanselmann
    @param target_node: Target node for shared-storage instances
1089 c0a146a1 Michael Hanselmann
    @rtype: string
1090 c0a146a1 Michael Hanselmann
    @return: job id
1091 c0a146a1 Michael Hanselmann

1092 c0a146a1 Michael Hanselmann
    """
1093 c0a146a1 Michael Hanselmann
    body = {}
1094 c0a146a1 Michael Hanselmann
1095 c0a146a1 Michael Hanselmann
    if iallocator is not None:
1096 c0a146a1 Michael Hanselmann
      body["iallocator"] = iallocator
1097 c0a146a1 Michael Hanselmann
1098 c0a146a1 Michael Hanselmann
    if ignore_consistency is not None:
1099 c0a146a1 Michael Hanselmann
      body["ignore_consistency"] = ignore_consistency
1100 c0a146a1 Michael Hanselmann
1101 c0a146a1 Michael Hanselmann
    if target_node is not None:
1102 c0a146a1 Michael Hanselmann
      body["target_node"] = target_node
1103 c0a146a1 Michael Hanselmann
1104 c0a146a1 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1105 c0a146a1 Michael Hanselmann
                             ("/%s/instances/%s/failover" %
1106 c0a146a1 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, body)
1107 c0a146a1 Michael Hanselmann
1108 d654aae1 Michael Hanselmann
  def RenameInstance(self, instance, new_name, ip_check=None, name_check=None):
1109 d654aae1 Michael Hanselmann
    """Changes the name of an instance.
1110 d654aae1 Michael Hanselmann

1111 d654aae1 Michael Hanselmann
    @type instance: string
1112 d654aae1 Michael Hanselmann
    @param instance: Instance name
1113 d654aae1 Michael Hanselmann
    @type new_name: string
1114 d654aae1 Michael Hanselmann
    @param new_name: New instance name
1115 d654aae1 Michael Hanselmann
    @type ip_check: bool
1116 d654aae1 Michael Hanselmann
    @param ip_check: Whether to ensure instance's IP address is inactive
1117 d654aae1 Michael Hanselmann
    @type name_check: bool
1118 d654aae1 Michael Hanselmann
    @param name_check: Whether to ensure instance's name is resolvable
1119 d914c76f Simeon Miteff
    @rtype: string
1120 d914c76f Simeon Miteff
    @return: job id
1121 d654aae1 Michael Hanselmann

1122 d654aae1 Michael Hanselmann
    """
1123 d654aae1 Michael Hanselmann
    body = {
1124 d654aae1 Michael Hanselmann
      "new_name": new_name,
1125 d654aae1 Michael Hanselmann
      }
1126 d654aae1 Michael Hanselmann
1127 d654aae1 Michael Hanselmann
    if ip_check is not None:
1128 d654aae1 Michael Hanselmann
      body["ip_check"] = ip_check
1129 d654aae1 Michael Hanselmann
1130 d654aae1 Michael Hanselmann
    if name_check is not None:
1131 d654aae1 Michael Hanselmann
      body["name_check"] = name_check
1132 d654aae1 Michael Hanselmann
1133 d654aae1 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1134 d654aae1 Michael Hanselmann
                             ("/%s/instances/%s/rename" %
1135 d654aae1 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, body)
1136 d654aae1 Michael Hanselmann
1137 b82d4c5e Michael Hanselmann
  def GetInstanceConsole(self, instance):
1138 b82d4c5e Michael Hanselmann
    """Request information for connecting to instance's console.
1139 b82d4c5e Michael Hanselmann

1140 b82d4c5e Michael Hanselmann
    @type instance: string
1141 b82d4c5e Michael Hanselmann
    @param instance: Instance name
1142 d914c76f Simeon Miteff
    @rtype: dict
1143 d914c76f Simeon Miteff
    @return: dictionary containing information about instance's console
1144 b82d4c5e Michael Hanselmann

1145 b82d4c5e Michael Hanselmann
    """
1146 b82d4c5e Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1147 b82d4c5e Michael Hanselmann
                             ("/%s/instances/%s/console" %
1148 b82d4c5e Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, None)
1149 b82d4c5e Michael Hanselmann
1150 95ab4de9 David Knowles
  def GetJobs(self):
1151 95ab4de9 David Knowles
    """Gets all jobs for the cluster.
1152 95ab4de9 David Knowles

1153 95ab4de9 David Knowles
    @rtype: list of int
1154 95ab4de9 David Knowles
    @return: job ids for the cluster
1155 95ab4de9 David Knowles

1156 95ab4de9 David Knowles
    """
1157 768747ed Michael Hanselmann
    return [int(j["id"])
1158 a198b2d9 Michael Hanselmann
            for j in self._SendRequest(HTTP_GET,
1159 a198b2d9 Michael Hanselmann
                                       "/%s/jobs" % GANETI_RAPI_VERSION,
1160 a198b2d9 Michael Hanselmann
                                       None, None)]
1161 95ab4de9 David Knowles
1162 95ab4de9 David Knowles
  def GetJobStatus(self, job_id):
1163 95ab4de9 David Knowles
    """Gets the status of a job.
1164 95ab4de9 David Knowles

1165 98805538 Michael Hanselmann
    @type job_id: string
1166 95ab4de9 David Knowles
    @param job_id: job id whose status to query
1167 95ab4de9 David Knowles

1168 95ab4de9 David Knowles
    @rtype: dict
1169 95ab4de9 David Knowles
    @return: job status
1170 95ab4de9 David Knowles

1171 95ab4de9 David Knowles
    """
1172 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1173 a198b2d9 Michael Hanselmann
                             "/%s/jobs/%s" % (GANETI_RAPI_VERSION, job_id),
1174 a198b2d9 Michael Hanselmann
                             None, None)
1175 95ab4de9 David Knowles
1176 16c13387 Theo Van Dinter
  def WaitForJobCompletion(self, job_id, period=5, retries=-1):
1177 16c13387 Theo Van Dinter
    """Polls cluster for job status until completion.
1178 16c13387 Theo Van Dinter

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

1182 cfda0e48 Iustin Pop
    @type job_id: string
1183 16c13387 Theo Van Dinter
    @param job_id: job id to watch
1184 16c13387 Theo Van Dinter
    @type period: int
1185 16c13387 Theo Van Dinter
    @param period: how often to poll for status (optional, default 5s)
1186 16c13387 Theo Van Dinter
    @type retries: int
1187 16c13387 Theo Van Dinter
    @param retries: how many time to poll before giving up
1188 16c13387 Theo Van Dinter
                    (optional, default -1 means unlimited)
1189 16c13387 Theo Van Dinter

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

1196 16c13387 Theo Van Dinter
    """
1197 16c13387 Theo Van Dinter
    while retries != 0:
1198 16c13387 Theo Van Dinter
      job_result = self.GetJobStatus(job_id)
1199 dde0e97c Michael Hanselmann
1200 dde0e97c Michael Hanselmann
      if job_result and job_result["status"] == JOB_STATUS_SUCCESS:
1201 16c13387 Theo Van Dinter
        return True
1202 dde0e97c Michael Hanselmann
      elif not job_result or job_result["status"] in JOB_STATUS_FINALIZED:
1203 dde0e97c Michael Hanselmann
        return False
1204 dde0e97c Michael Hanselmann
1205 dde0e97c Michael Hanselmann
      if period:
1206 dde0e97c Michael Hanselmann
        time.sleep(period)
1207 dde0e97c Michael Hanselmann
1208 16c13387 Theo Van Dinter
      if retries > 0:
1209 16c13387 Theo Van Dinter
        retries -= 1
1210 dde0e97c Michael Hanselmann
1211 16c13387 Theo Van Dinter
    return False
1212 16c13387 Theo Van Dinter
1213 d9b67f70 Michael Hanselmann
  def WaitForJobChange(self, job_id, fields, prev_job_info, prev_log_serial):
1214 d9b67f70 Michael Hanselmann
    """Waits for job changes.
1215 d9b67f70 Michael Hanselmann

1216 98805538 Michael Hanselmann
    @type job_id: string
1217 d9b67f70 Michael Hanselmann
    @param job_id: Job ID for which to wait
1218 d914c76f Simeon Miteff
    @return: C{None} if no changes have been detected and a dict with two keys,
1219 d914c76f Simeon Miteff
      C{job_info} and C{log_entries} otherwise.
1220 d914c76f Simeon Miteff
    @rtype: dict
1221 d9b67f70 Michael Hanselmann

1222 d9b67f70 Michael Hanselmann
    """
1223 d9b67f70 Michael Hanselmann
    body = {
1224 d9b67f70 Michael Hanselmann
      "fields": fields,
1225 d9b67f70 Michael Hanselmann
      "previous_job_info": prev_job_info,
1226 d9b67f70 Michael Hanselmann
      "previous_log_serial": prev_log_serial,
1227 d9b67f70 Michael Hanselmann
      }
1228 d9b67f70 Michael Hanselmann
1229 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1230 a198b2d9 Michael Hanselmann
                             "/%s/jobs/%s/wait" % (GANETI_RAPI_VERSION, job_id),
1231 a198b2d9 Michael Hanselmann
                             None, body)
1232 d9b67f70 Michael Hanselmann
1233 cf9ada49 Michael Hanselmann
  def CancelJob(self, job_id, dry_run=False):
1234 cf9ada49 Michael Hanselmann
    """Cancels a job.
1235 95ab4de9 David Knowles

1236 98805538 Michael Hanselmann
    @type job_id: string
1237 95ab4de9 David Knowles
    @param job_id: id of the job to delete
1238 95ab4de9 David Knowles
    @type dry_run: bool
1239 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1240 d914c76f Simeon Miteff
    @rtype: tuple
1241 d914c76f Simeon Miteff
    @return: tuple containing the result, and a message (bool, string)
1242 95ab4de9 David Knowles

1243 95ab4de9 David Knowles
    """
1244 95ab4de9 David Knowles
    query = []
1245 95ab4de9 David Knowles
    if dry_run:
1246 95ab4de9 David Knowles
      query.append(("dry-run", 1))
1247 95ab4de9 David Knowles
1248 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE,
1249 a198b2d9 Michael Hanselmann
                             "/%s/jobs/%s" % (GANETI_RAPI_VERSION, job_id),
1250 a198b2d9 Michael Hanselmann
                             query, None)
1251 95ab4de9 David Knowles
1252 95ab4de9 David Knowles
  def GetNodes(self, bulk=False):
1253 95ab4de9 David Knowles
    """Gets all nodes in the cluster.
1254 95ab4de9 David Knowles

1255 95ab4de9 David Knowles
    @type bulk: bool
1256 95ab4de9 David Knowles
    @param bulk: whether to return all information about all instances
1257 95ab4de9 David Knowles

1258 95ab4de9 David Knowles
    @rtype: list of dict or str
1259 95ab4de9 David Knowles
    @return: if bulk is true, info about nodes in the cluster,
1260 95ab4de9 David Knowles
        else list of nodes in the cluster
1261 95ab4de9 David Knowles

1262 95ab4de9 David Knowles
    """
1263 95ab4de9 David Knowles
    query = []
1264 95ab4de9 David Knowles
    if bulk:
1265 95ab4de9 David Knowles
      query.append(("bulk", 1))
1266 95ab4de9 David Knowles
1267 a198b2d9 Michael Hanselmann
    nodes = self._SendRequest(HTTP_GET, "/%s/nodes" % GANETI_RAPI_VERSION,
1268 a198b2d9 Michael Hanselmann
                              query, None)
1269 95ab4de9 David Knowles
    if bulk:
1270 95ab4de9 David Knowles
      return nodes
1271 95ab4de9 David Knowles
    else:
1272 95ab4de9 David Knowles
      return [n["id"] for n in nodes]
1273 95ab4de9 David Knowles
1274 591e5103 Michael Hanselmann
  def GetNode(self, node):
1275 95ab4de9 David Knowles
    """Gets information about a node.
1276 95ab4de9 David Knowles

1277 95ab4de9 David Knowles
    @type node: str
1278 95ab4de9 David Knowles
    @param node: node whose info to return
1279 95ab4de9 David Knowles

1280 95ab4de9 David Knowles
    @rtype: dict
1281 95ab4de9 David Knowles
    @return: info about the node
1282 95ab4de9 David Knowles

1283 95ab4de9 David Knowles
    """
1284 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1285 a198b2d9 Michael Hanselmann
                             "/%s/nodes/%s" % (GANETI_RAPI_VERSION, node),
1286 a198b2d9 Michael Hanselmann
                             None, None)
1287 95ab4de9 David Knowles
1288 95ab4de9 David Knowles
  def EvacuateNode(self, node, iallocator=None, remote_node=None,
1289 de40437a Michael Hanselmann
                   dry_run=False, early_release=None,
1290 de40437a Michael Hanselmann
                   primary=None, secondary=None, accept_old=False):
1291 95ab4de9 David Knowles
    """Evacuates instances from a Ganeti node.
1292 95ab4de9 David Knowles

1293 95ab4de9 David Knowles
    @type node: str
1294 95ab4de9 David Knowles
    @param node: node to evacuate
1295 95ab4de9 David Knowles
    @type iallocator: str or None
1296 95ab4de9 David Knowles
    @param iallocator: instance allocator to use
1297 95ab4de9 David Knowles
    @type remote_node: str
1298 95ab4de9 David Knowles
    @param remote_node: node to evaucate to
1299 95ab4de9 David Knowles
    @type dry_run: bool
1300 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1301 941b9309 Iustin Pop
    @type early_release: bool
1302 941b9309 Iustin Pop
    @param early_release: whether to enable parallelization
1303 de40437a Michael Hanselmann
    @type primary: bool
1304 de40437a Michael Hanselmann
    @param primary: Whether to evacuate primary instances
1305 de40437a Michael Hanselmann
    @type secondary: bool
1306 de40437a Michael Hanselmann
    @param secondary: Whether to evacuate secondary instances
1307 de40437a Michael Hanselmann
    @type accept_old: bool
1308 de40437a Michael Hanselmann
    @param accept_old: Whether caller is ready to accept old-style (pre-2.5)
1309 de40437a Michael Hanselmann
        results
1310 de40437a Michael Hanselmann

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

1317 941b9309 Iustin Pop
    @raises GanetiApiError: if an iallocator and remote_node are both
1318 941b9309 Iustin Pop
        specified
1319 95ab4de9 David Knowles

1320 95ab4de9 David Knowles
    """
1321 95ab4de9 David Knowles
    if iallocator and remote_node:
1322 cfc03c54 Michael Hanselmann
      raise GanetiApiError("Only one of iallocator or remote_node can be used")
1323 95ab4de9 David Knowles
1324 cfc03c54 Michael Hanselmann
    query = []
1325 95ab4de9 David Knowles
    if dry_run:
1326 95ab4de9 David Knowles
      query.append(("dry-run", 1))
1327 de40437a Michael Hanselmann
1328 de40437a Michael Hanselmann
    if _NODE_EVAC_RES1 in self.GetFeatures():
1329 de40437a Michael Hanselmann
      body = {}
1330 de40437a Michael Hanselmann
1331 de40437a Michael Hanselmann
      if iallocator is not None:
1332 de40437a Michael Hanselmann
        body["iallocator"] = iallocator
1333 de40437a Michael Hanselmann
      if remote_node is not None:
1334 de40437a Michael Hanselmann
        body["remote_node"] = remote_node
1335 de40437a Michael Hanselmann
      if early_release is not None:
1336 de40437a Michael Hanselmann
        body["early_release"] = early_release
1337 de40437a Michael Hanselmann
      if primary is not None:
1338 de40437a Michael Hanselmann
        body["primary"] = primary
1339 de40437a Michael Hanselmann
      if secondary is not None:
1340 de40437a Michael Hanselmann
        body["secondary"] = secondary
1341 de40437a Michael Hanselmann
    else:
1342 de40437a Michael Hanselmann
      # Pre-2.5 request format
1343 de40437a Michael Hanselmann
      body = None
1344 de40437a Michael Hanselmann
1345 de40437a Michael Hanselmann
      if not accept_old:
1346 de40437a Michael Hanselmann
        raise GanetiApiError("Server is version 2.4 or earlier and caller does"
1347 de40437a Michael Hanselmann
                             " not accept old-style results (parameter"
1348 de40437a Michael Hanselmann
                             " accept_old)")
1349 de40437a Michael Hanselmann
1350 de40437a Michael Hanselmann
      if primary or primary is None or not (secondary is None or secondary):
1351 de40437a Michael Hanselmann
        raise GanetiApiError("Server can only evacuate secondary instances")
1352 de40437a Michael Hanselmann
1353 de40437a Michael Hanselmann
      if iallocator:
1354 de40437a Michael Hanselmann
        query.append(("iallocator", iallocator))
1355 de40437a Michael Hanselmann
      if remote_node:
1356 de40437a Michael Hanselmann
        query.append(("remote_node", remote_node))
1357 de40437a Michael Hanselmann
      if early_release:
1358 de40437a Michael Hanselmann
        query.append(("early_release", 1))
1359 95ab4de9 David Knowles
1360 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_POST,
1361 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/evacuate" %
1362 de40437a Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, body)
1363 95ab4de9 David Knowles
1364 b7a1c816 Michael Hanselmann
  def MigrateNode(self, node, mode=None, dry_run=False, iallocator=None,
1365 b7a1c816 Michael Hanselmann
                  target_node=None):
1366 95ab4de9 David Knowles
    """Migrates all primary instances from a node.
1367 95ab4de9 David Knowles

1368 95ab4de9 David Knowles
    @type node: str
1369 95ab4de9 David Knowles
    @param node: node to migrate
1370 1f334d96 Iustin Pop
    @type mode: string
1371 1f334d96 Iustin Pop
    @param mode: if passed, it will overwrite the live migration type,
1372 1f334d96 Iustin Pop
        otherwise the hypervisor default will be used
1373 95ab4de9 David Knowles
    @type dry_run: bool
1374 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1375 b7a1c816 Michael Hanselmann
    @type iallocator: string
1376 b7a1c816 Michael Hanselmann
    @param iallocator: instance allocator to use
1377 b7a1c816 Michael Hanselmann
    @type target_node: string
1378 b7a1c816 Michael Hanselmann
    @param target_node: Target node for shared-storage instances
1379 95ab4de9 David Knowles

1380 98805538 Michael Hanselmann
    @rtype: string
1381 95ab4de9 David Knowles
    @return: job id
1382 95ab4de9 David Knowles

1383 95ab4de9 David Knowles
    """
1384 95ab4de9 David Knowles
    query = []
1385 95ab4de9 David Knowles
    if dry_run:
1386 95ab4de9 David Knowles
      query.append(("dry-run", 1))
1387 95ab4de9 David Knowles
1388 b7a1c816 Michael Hanselmann
    if _NODE_MIGRATE_REQV1 in self.GetFeatures():
1389 b7a1c816 Michael Hanselmann
      body = {}
1390 b7a1c816 Michael Hanselmann
1391 b7a1c816 Michael Hanselmann
      if mode is not None:
1392 b7a1c816 Michael Hanselmann
        body["mode"] = mode
1393 b7a1c816 Michael Hanselmann
      if iallocator is not None:
1394 b7a1c816 Michael Hanselmann
        body["iallocator"] = iallocator
1395 b7a1c816 Michael Hanselmann
      if target_node is not None:
1396 b7a1c816 Michael Hanselmann
        body["target_node"] = target_node
1397 b7a1c816 Michael Hanselmann
1398 b7a1c816 Michael Hanselmann
      assert len(query) <= 1
1399 b7a1c816 Michael Hanselmann
1400 b7a1c816 Michael Hanselmann
      return self._SendRequest(HTTP_POST,
1401 b7a1c816 Michael Hanselmann
                               ("/%s/nodes/%s/migrate" %
1402 b7a1c816 Michael Hanselmann
                                (GANETI_RAPI_VERSION, node)), query, body)
1403 b7a1c816 Michael Hanselmann
    else:
1404 b7a1c816 Michael Hanselmann
      # Use old request format
1405 b7a1c816 Michael Hanselmann
      if target_node is not None:
1406 b7a1c816 Michael Hanselmann
        raise GanetiApiError("Server does not support specifying target node"
1407 b7a1c816 Michael Hanselmann
                             " for node migration")
1408 b7a1c816 Michael Hanselmann
1409 b7a1c816 Michael Hanselmann
      if mode is not None:
1410 b7a1c816 Michael Hanselmann
        query.append(("mode", mode))
1411 b7a1c816 Michael Hanselmann
1412 b7a1c816 Michael Hanselmann
      return self._SendRequest(HTTP_POST,
1413 b7a1c816 Michael Hanselmann
                               ("/%s/nodes/%s/migrate" %
1414 b7a1c816 Michael Hanselmann
                                (GANETI_RAPI_VERSION, node)), query, None)
1415 95ab4de9 David Knowles
1416 95ab4de9 David Knowles
  def GetNodeRole(self, node):
1417 95ab4de9 David Knowles
    """Gets the current role for a node.
1418 95ab4de9 David Knowles

1419 95ab4de9 David Knowles
    @type node: str
1420 95ab4de9 David Knowles
    @param node: node whose role to return
1421 95ab4de9 David Knowles

1422 95ab4de9 David Knowles
    @rtype: str
1423 95ab4de9 David Knowles
    @return: the current role for a node
1424 95ab4de9 David Knowles

1425 95ab4de9 David Knowles
    """
1426 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1427 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/role" %
1428 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), None, None)
1429 95ab4de9 David Knowles
1430 95ab4de9 David Knowles
  def SetNodeRole(self, node, role, force=False):
1431 95ab4de9 David Knowles
    """Sets the role for a node.
1432 95ab4de9 David Knowles

1433 95ab4de9 David Knowles
    @type node: str
1434 95ab4de9 David Knowles
    @param node: the node whose role to set
1435 95ab4de9 David Knowles
    @type role: str
1436 95ab4de9 David Knowles
    @param role: the role to set for the node
1437 95ab4de9 David Knowles
    @type force: bool
1438 95ab4de9 David Knowles
    @param force: whether to force the role change
1439 95ab4de9 David Knowles

1440 98805538 Michael Hanselmann
    @rtype: string
1441 95ab4de9 David Knowles
    @return: job id
1442 95ab4de9 David Knowles

1443 95ab4de9 David Knowles
    """
1444 1068639f Michael Hanselmann
    query = [
1445 1068639f Michael Hanselmann
      ("force", force),
1446 1068639f Michael Hanselmann
      ]
1447 cfc03c54 Michael Hanselmann
1448 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1449 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/role" %
1450 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, role)
1451 95ab4de9 David Knowles
1452 95ab4de9 David Knowles
  def GetNodeStorageUnits(self, node, storage_type, output_fields):
1453 95ab4de9 David Knowles
    """Gets the storage units for a node.
1454 95ab4de9 David Knowles

1455 95ab4de9 David Knowles
    @type node: str
1456 95ab4de9 David Knowles
    @param node: the node whose storage units to return
1457 95ab4de9 David Knowles
    @type storage_type: str
1458 95ab4de9 David Knowles
    @param storage_type: storage type whose units to return
1459 95ab4de9 David Knowles
    @type output_fields: str
1460 95ab4de9 David Knowles
    @param output_fields: storage type fields to return
1461 95ab4de9 David Knowles

1462 98805538 Michael Hanselmann
    @rtype: string
1463 95ab4de9 David Knowles
    @return: job id where results can be retrieved
1464 95ab4de9 David Knowles

1465 95ab4de9 David Knowles
    """
1466 cfc03c54 Michael Hanselmann
    query = [
1467 cfc03c54 Michael Hanselmann
      ("storage_type", storage_type),
1468 cfc03c54 Michael Hanselmann
      ("output_fields", output_fields),
1469 cfc03c54 Michael Hanselmann
      ]
1470 95ab4de9 David Knowles
1471 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1472 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/storage" %
1473 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1474 95ab4de9 David Knowles
1475 fde28316 Michael Hanselmann
  def ModifyNodeStorageUnits(self, node, storage_type, name, allocatable=None):
1476 95ab4de9 David Knowles
    """Modifies parameters of storage units on the node.
1477 95ab4de9 David Knowles

1478 95ab4de9 David Knowles
    @type node: str
1479 95ab4de9 David Knowles
    @param node: node whose storage units to modify
1480 95ab4de9 David Knowles
    @type storage_type: str
1481 95ab4de9 David Knowles
    @param storage_type: storage type whose units to modify
1482 95ab4de9 David Knowles
    @type name: str
1483 95ab4de9 David Knowles
    @param name: name of the storage unit
1484 fde28316 Michael Hanselmann
    @type allocatable: bool or None
1485 fde28316 Michael Hanselmann
    @param allocatable: Whether to set the "allocatable" flag on the storage
1486 fde28316 Michael Hanselmann
                        unit (None=no modification, True=set, False=unset)
1487 95ab4de9 David Knowles

1488 98805538 Michael Hanselmann
    @rtype: string
1489 95ab4de9 David Knowles
    @return: job id
1490 95ab4de9 David Knowles

1491 95ab4de9 David Knowles
    """
1492 95ab4de9 David Knowles
    query = [
1493 cfc03c54 Michael Hanselmann
      ("storage_type", storage_type),
1494 cfc03c54 Michael Hanselmann
      ("name", name),
1495 cfc03c54 Michael Hanselmann
      ]
1496 cfc03c54 Michael Hanselmann
1497 fde28316 Michael Hanselmann
    if allocatable is not None:
1498 fde28316 Michael Hanselmann
      query.append(("allocatable", allocatable))
1499 fde28316 Michael Hanselmann
1500 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1501 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/storage/modify" %
1502 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1503 95ab4de9 David Knowles
1504 95ab4de9 David Knowles
  def RepairNodeStorageUnits(self, node, storage_type, name):
1505 95ab4de9 David Knowles
    """Repairs a storage unit on the node.
1506 95ab4de9 David Knowles

1507 95ab4de9 David Knowles
    @type node: str
1508 95ab4de9 David Knowles
    @param node: node whose storage units to repair
1509 95ab4de9 David Knowles
    @type storage_type: str
1510 95ab4de9 David Knowles
    @param storage_type: storage type to repair
1511 95ab4de9 David Knowles
    @type name: str
1512 95ab4de9 David Knowles
    @param name: name of the storage unit to repair
1513 95ab4de9 David Knowles

1514 98805538 Michael Hanselmann
    @rtype: string
1515 95ab4de9 David Knowles
    @return: job id
1516 95ab4de9 David Knowles

1517 95ab4de9 David Knowles
    """
1518 cfc03c54 Michael Hanselmann
    query = [
1519 cfc03c54 Michael Hanselmann
      ("storage_type", storage_type),
1520 cfc03c54 Michael Hanselmann
      ("name", name),
1521 cfc03c54 Michael Hanselmann
      ]
1522 95ab4de9 David Knowles
1523 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1524 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/storage/repair" %
1525 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1526 95ab4de9 David Knowles
1527 95ab4de9 David Knowles
  def GetNodeTags(self, node):
1528 95ab4de9 David Knowles
    """Gets the tags for a node.
1529 95ab4de9 David Knowles

1530 95ab4de9 David Knowles
    @type node: str
1531 95ab4de9 David Knowles
    @param node: node whose tags to return
1532 95ab4de9 David Knowles

1533 95ab4de9 David Knowles
    @rtype: list of str
1534 95ab4de9 David Knowles
    @return: tags for the node
1535 95ab4de9 David Knowles

1536 95ab4de9 David Knowles
    """
1537 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1538 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/tags" %
1539 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), None, None)
1540 95ab4de9 David Knowles
1541 95ab4de9 David Knowles
  def AddNodeTags(self, node, tags, dry_run=False):
1542 95ab4de9 David Knowles
    """Adds tags to a node.
1543 95ab4de9 David Knowles

1544 95ab4de9 David Knowles
    @type node: str
1545 95ab4de9 David Knowles
    @param node: node to add tags to
1546 95ab4de9 David Knowles
    @type tags: list of str
1547 95ab4de9 David Knowles
    @param tags: tags to add to the node
1548 95ab4de9 David Knowles
    @type dry_run: bool
1549 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1550 95ab4de9 David Knowles

1551 98805538 Michael Hanselmann
    @rtype: string
1552 95ab4de9 David Knowles
    @return: job id
1553 95ab4de9 David Knowles

1554 95ab4de9 David Knowles
    """
1555 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
1556 95ab4de9 David Knowles
    if dry_run:
1557 95ab4de9 David Knowles
      query.append(("dry-run", 1))
1558 95ab4de9 David Knowles
1559 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1560 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/tags" %
1561 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, tags)
1562 95ab4de9 David Knowles
1563 95ab4de9 David Knowles
  def DeleteNodeTags(self, node, tags, dry_run=False):
1564 95ab4de9 David Knowles
    """Delete tags from a node.
1565 95ab4de9 David Knowles

1566 95ab4de9 David Knowles
    @type node: str
1567 95ab4de9 David Knowles
    @param node: node to remove tags from
1568 95ab4de9 David Knowles
    @type tags: list of str
1569 95ab4de9 David Knowles
    @param tags: tags to remove from the node
1570 95ab4de9 David Knowles
    @type dry_run: bool
1571 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1572 95ab4de9 David Knowles

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

1576 95ab4de9 David Knowles
    """
1577 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
1578 95ab4de9 David Knowles
    if dry_run:
1579 95ab4de9 David Knowles
      query.append(("dry-run", 1))
1580 95ab4de9 David Knowles
1581 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE,
1582 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/tags" %
1583 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1584 a268af8d Adeodato Simo
1585 a268af8d Adeodato Simo
  def GetGroups(self, bulk=False):
1586 a268af8d Adeodato Simo
    """Gets all node groups in the cluster.
1587 a268af8d Adeodato Simo

1588 a268af8d Adeodato Simo
    @type bulk: bool
1589 a268af8d Adeodato Simo
    @param bulk: whether to return all information about the groups
1590 a268af8d Adeodato Simo

1591 a268af8d Adeodato Simo
    @rtype: list of dict or str
1592 a268af8d Adeodato Simo
    @return: if bulk is true, a list of dictionaries with info about all node
1593 a268af8d Adeodato Simo
        groups in the cluster, else a list of names of those node groups
1594 a268af8d Adeodato Simo

1595 a268af8d Adeodato Simo
    """
1596 a268af8d Adeodato Simo
    query = []
1597 a268af8d Adeodato Simo
    if bulk:
1598 a268af8d Adeodato Simo
      query.append(("bulk", 1))
1599 a268af8d Adeodato Simo
1600 a268af8d Adeodato Simo
    groups = self._SendRequest(HTTP_GET, "/%s/groups" % GANETI_RAPI_VERSION,
1601 a268af8d Adeodato Simo
                               query, None)
1602 a268af8d Adeodato Simo
    if bulk:
1603 a268af8d Adeodato Simo
      return groups
1604 a268af8d Adeodato Simo
    else:
1605 a268af8d Adeodato Simo
      return [g["name"] for g in groups]
1606 a268af8d Adeodato Simo
1607 a268af8d Adeodato Simo
  def GetGroup(self, group):
1608 a268af8d Adeodato Simo
    """Gets information about a node group.
1609 a268af8d Adeodato Simo

1610 a268af8d Adeodato Simo
    @type group: str
1611 a268af8d Adeodato Simo
    @param group: name of the node group whose info to return
1612 a268af8d Adeodato Simo

1613 a268af8d Adeodato Simo
    @rtype: dict
1614 a268af8d Adeodato Simo
    @return: info about the node group
1615 a268af8d Adeodato Simo

1616 a268af8d Adeodato Simo
    """
1617 a268af8d Adeodato Simo
    return self._SendRequest(HTTP_GET,
1618 a268af8d Adeodato Simo
                             "/%s/groups/%s" % (GANETI_RAPI_VERSION, group),
1619 a268af8d Adeodato Simo
                             None, None)
1620 a268af8d Adeodato Simo
1621 90e99856 Adeodato Simo
  def CreateGroup(self, name, alloc_policy=None, dry_run=False):
1622 a268af8d Adeodato Simo
    """Creates a new node group.
1623 a268af8d Adeodato Simo

1624 a268af8d Adeodato Simo
    @type name: str
1625 a268af8d Adeodato Simo
    @param name: the name of node group to create
1626 90e99856 Adeodato Simo
    @type alloc_policy: str
1627 90e99856 Adeodato Simo
    @param alloc_policy: the desired allocation policy for the group, if any
1628 a268af8d Adeodato Simo
    @type dry_run: bool
1629 a268af8d Adeodato Simo
    @param dry_run: whether to peform a dry run
1630 a268af8d Adeodato Simo

1631 98805538 Michael Hanselmann
    @rtype: string
1632 a268af8d Adeodato Simo
    @return: job id
1633 a268af8d Adeodato Simo

1634 a268af8d Adeodato Simo
    """
1635 a268af8d Adeodato Simo
    query = []
1636 a268af8d Adeodato Simo
    if dry_run:
1637 a268af8d Adeodato Simo
      query.append(("dry-run", 1))
1638 a268af8d Adeodato Simo
1639 a268af8d Adeodato Simo
    body = {
1640 a268af8d Adeodato Simo
      "name": name,
1641 90e99856 Adeodato Simo
      "alloc_policy": alloc_policy
1642 a268af8d Adeodato Simo
      }
1643 a268af8d Adeodato Simo
1644 a268af8d Adeodato Simo
    return self._SendRequest(HTTP_POST, "/%s/groups" % GANETI_RAPI_VERSION,
1645 a268af8d Adeodato Simo
                             query, body)
1646 a268af8d Adeodato Simo
1647 f18fab7d Adeodato Simo
  def ModifyGroup(self, group, **kwargs):
1648 f18fab7d Adeodato Simo
    """Modifies a node group.
1649 f18fab7d Adeodato Simo

1650 f18fab7d Adeodato Simo
    More details for parameters can be found in the RAPI documentation.
1651 f18fab7d Adeodato Simo

1652 f18fab7d Adeodato Simo
    @type group: string
1653 f18fab7d Adeodato Simo
    @param group: Node group name
1654 98805538 Michael Hanselmann
    @rtype: string
1655 f18fab7d Adeodato Simo
    @return: job id
1656 f18fab7d Adeodato Simo

1657 f18fab7d Adeodato Simo
    """
1658 f18fab7d Adeodato Simo
    return self._SendRequest(HTTP_PUT,
1659 f18fab7d Adeodato Simo
                             ("/%s/groups/%s/modify" %
1660 f18fab7d Adeodato Simo
                              (GANETI_RAPI_VERSION, group)), None, kwargs)
1661 f18fab7d Adeodato Simo
1662 a268af8d Adeodato Simo
  def DeleteGroup(self, group, dry_run=False):
1663 a268af8d Adeodato Simo
    """Deletes a node group.
1664 a268af8d Adeodato Simo

1665 a268af8d Adeodato Simo
    @type group: str
1666 a268af8d Adeodato Simo
    @param group: the node group to delete
1667 a268af8d Adeodato Simo
    @type dry_run: bool
1668 a268af8d Adeodato Simo
    @param dry_run: whether to peform a dry run
1669 a268af8d Adeodato Simo

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

1673 a268af8d Adeodato Simo
    """
1674 a268af8d Adeodato Simo
    query = []
1675 a268af8d Adeodato Simo
    if dry_run:
1676 a268af8d Adeodato Simo
      query.append(("dry-run", 1))
1677 a268af8d Adeodato Simo
1678 a268af8d Adeodato Simo
    return self._SendRequest(HTTP_DELETE,
1679 a268af8d Adeodato Simo
                             ("/%s/groups/%s" %
1680 a268af8d Adeodato Simo
                              (GANETI_RAPI_VERSION, group)), query, None)
1681 a268af8d Adeodato Simo
1682 a268af8d Adeodato Simo
  def RenameGroup(self, group, new_name):
1683 a268af8d Adeodato Simo
    """Changes the name of a node group.
1684 a268af8d Adeodato Simo

1685 a268af8d Adeodato Simo
    @type group: string
1686 a268af8d Adeodato Simo
    @param group: Node group name
1687 a268af8d Adeodato Simo
    @type new_name: string
1688 a268af8d Adeodato Simo
    @param new_name: New node group name
1689 a268af8d Adeodato Simo

1690 98805538 Michael Hanselmann
    @rtype: string
1691 a268af8d Adeodato Simo
    @return: job id
1692 a268af8d Adeodato Simo

1693 a268af8d Adeodato Simo
    """
1694 a268af8d Adeodato Simo
    body = {
1695 a268af8d Adeodato Simo
      "new_name": new_name,
1696 a268af8d Adeodato Simo
      }
1697 a268af8d Adeodato Simo
1698 a268af8d Adeodato Simo
    return self._SendRequest(HTTP_PUT,
1699 a268af8d Adeodato Simo
                             ("/%s/groups/%s/rename" %
1700 a268af8d Adeodato Simo
                              (GANETI_RAPI_VERSION, group)), None, body)
1701 4245446f Adeodato Simo
1702 4245446f Adeodato Simo
  def AssignGroupNodes(self, group, nodes, force=False, dry_run=False):
1703 4245446f Adeodato Simo
    """Assigns nodes to a group.
1704 4245446f Adeodato Simo

1705 4245446f Adeodato Simo
    @type group: string
1706 4245446f Adeodato Simo
    @param group: Node gropu name
1707 4245446f Adeodato Simo
    @type nodes: list of strings
1708 4245446f Adeodato Simo
    @param nodes: List of nodes to assign to the group
1709 4245446f Adeodato Simo

1710 98805538 Michael Hanselmann
    @rtype: string
1711 4245446f Adeodato Simo
    @return: job id
1712 4245446f Adeodato Simo

1713 4245446f Adeodato Simo
    """
1714 4245446f Adeodato Simo
    query = []
1715 4245446f Adeodato Simo
1716 4245446f Adeodato Simo
    if force:
1717 4245446f Adeodato Simo
      query.append(("force", 1))
1718 4245446f Adeodato Simo
1719 4245446f Adeodato Simo
    if dry_run:
1720 4245446f Adeodato Simo
      query.append(("dry-run", 1))
1721 4245446f Adeodato Simo
1722 4245446f Adeodato Simo
    body = {
1723 4245446f Adeodato Simo
      "nodes": nodes,
1724 4245446f Adeodato Simo
      }
1725 4245446f Adeodato Simo
1726 4245446f Adeodato Simo
    return self._SendRequest(HTTP_PUT,
1727 4245446f Adeodato Simo
                             ("/%s/groups/%s/assign-nodes" %
1728 4245446f Adeodato Simo
                             (GANETI_RAPI_VERSION, group)), query, body)
1729 208a6cff Michael Hanselmann
1730 414ebaf1 Michael Hanselmann
  def GetGroupTags(self, group):
1731 414ebaf1 Michael Hanselmann
    """Gets tags for a node group.
1732 414ebaf1 Michael Hanselmann

1733 414ebaf1 Michael Hanselmann
    @type group: string
1734 414ebaf1 Michael Hanselmann
    @param group: Node group whose tags to return
1735 414ebaf1 Michael Hanselmann

1736 414ebaf1 Michael Hanselmann
    @rtype: list of strings
1737 414ebaf1 Michael Hanselmann
    @return: tags for the group
1738 414ebaf1 Michael Hanselmann

1739 414ebaf1 Michael Hanselmann
    """
1740 414ebaf1 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1741 414ebaf1 Michael Hanselmann
                             ("/%s/groups/%s/tags" %
1742 414ebaf1 Michael Hanselmann
                              (GANETI_RAPI_VERSION, group)), None, None)
1743 414ebaf1 Michael Hanselmann
1744 414ebaf1 Michael Hanselmann
  def AddGroupTags(self, group, tags, dry_run=False):
1745 414ebaf1 Michael Hanselmann
    """Adds tags to a node group.
1746 414ebaf1 Michael Hanselmann

1747 414ebaf1 Michael Hanselmann
    @type group: str
1748 414ebaf1 Michael Hanselmann
    @param group: group to add tags to
1749 414ebaf1 Michael Hanselmann
    @type tags: list of string
1750 414ebaf1 Michael Hanselmann
    @param tags: tags to add to the group
1751 414ebaf1 Michael Hanselmann
    @type dry_run: bool
1752 414ebaf1 Michael Hanselmann
    @param dry_run: whether to perform a dry run
1753 414ebaf1 Michael Hanselmann

1754 414ebaf1 Michael Hanselmann
    @rtype: string
1755 414ebaf1 Michael Hanselmann
    @return: job id
1756 414ebaf1 Michael Hanselmann

1757 414ebaf1 Michael Hanselmann
    """
1758 414ebaf1 Michael Hanselmann
    query = [("tag", t) for t in tags]
1759 414ebaf1 Michael Hanselmann
    if dry_run:
1760 414ebaf1 Michael Hanselmann
      query.append(("dry-run", 1))
1761 414ebaf1 Michael Hanselmann
1762 414ebaf1 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1763 414ebaf1 Michael Hanselmann
                             ("/%s/groups/%s/tags" %
1764 414ebaf1 Michael Hanselmann
                              (GANETI_RAPI_VERSION, group)), query, None)
1765 414ebaf1 Michael Hanselmann
1766 414ebaf1 Michael Hanselmann
  def DeleteGroupTags(self, group, tags, dry_run=False):
1767 414ebaf1 Michael Hanselmann
    """Deletes tags from a node group.
1768 414ebaf1 Michael Hanselmann

1769 414ebaf1 Michael Hanselmann
    @type group: str
1770 414ebaf1 Michael Hanselmann
    @param group: group to delete tags from
1771 414ebaf1 Michael Hanselmann
    @type tags: list of string
1772 414ebaf1 Michael Hanselmann
    @param tags: tags to delete
1773 414ebaf1 Michael Hanselmann
    @type dry_run: bool
1774 414ebaf1 Michael Hanselmann
    @param dry_run: whether to perform a dry run
1775 414ebaf1 Michael Hanselmann
    @rtype: string
1776 414ebaf1 Michael Hanselmann
    @return: job id
1777 414ebaf1 Michael Hanselmann

1778 414ebaf1 Michael Hanselmann
    """
1779 414ebaf1 Michael Hanselmann
    query = [("tag", t) for t in tags]
1780 414ebaf1 Michael Hanselmann
    if dry_run:
1781 414ebaf1 Michael Hanselmann
      query.append(("dry-run", 1))
1782 414ebaf1 Michael Hanselmann
1783 414ebaf1 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE,
1784 414ebaf1 Michael Hanselmann
                             ("/%s/groups/%s/tags" %
1785 414ebaf1 Michael Hanselmann
                              (GANETI_RAPI_VERSION, group)), query, None)
1786 414ebaf1 Michael Hanselmann
1787 208a6cff Michael Hanselmann
  def Query(self, what, fields, filter_=None):
1788 208a6cff Michael Hanselmann
    """Retrieves information about resources.
1789 208a6cff Michael Hanselmann

1790 208a6cff Michael Hanselmann
    @type what: string
1791 208a6cff Michael Hanselmann
    @param what: Resource name, one of L{constants.QR_VIA_RAPI}
1792 208a6cff Michael Hanselmann
    @type fields: list of string
1793 208a6cff Michael Hanselmann
    @param fields: Requested fields
1794 208a6cff Michael Hanselmann
    @type filter_: None or list
1795 d914c76f Simeon Miteff
    @param filter_: Query filter
1796 208a6cff Michael Hanselmann

1797 208a6cff Michael Hanselmann
    @rtype: string
1798 208a6cff Michael Hanselmann
    @return: job id
1799 208a6cff Michael Hanselmann

1800 208a6cff Michael Hanselmann
    """
1801 208a6cff Michael Hanselmann
    body = {
1802 208a6cff Michael Hanselmann
      "fields": fields,
1803 208a6cff Michael Hanselmann
      }
1804 208a6cff Michael Hanselmann
1805 208a6cff Michael Hanselmann
    if filter_ is not None:
1806 208a6cff Michael Hanselmann
      body["filter"] = filter_
1807 208a6cff Michael Hanselmann
1808 208a6cff Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1809 208a6cff Michael Hanselmann
                             ("/%s/query/%s" %
1810 208a6cff Michael Hanselmann
                              (GANETI_RAPI_VERSION, what)), None, body)
1811 208a6cff Michael Hanselmann
1812 208a6cff Michael Hanselmann
  def QueryFields(self, what, fields=None):
1813 208a6cff Michael Hanselmann
    """Retrieves available fields for a resource.
1814 208a6cff Michael Hanselmann

1815 208a6cff Michael Hanselmann
    @type what: string
1816 208a6cff Michael Hanselmann
    @param what: Resource name, one of L{constants.QR_VIA_RAPI}
1817 208a6cff Michael Hanselmann
    @type fields: list of string
1818 208a6cff Michael Hanselmann
    @param fields: Requested fields
1819 208a6cff Michael Hanselmann

1820 208a6cff Michael Hanselmann
    @rtype: string
1821 208a6cff Michael Hanselmann
    @return: job id
1822 208a6cff Michael Hanselmann

1823 208a6cff Michael Hanselmann
    """
1824 208a6cff Michael Hanselmann
    query = []
1825 208a6cff Michael Hanselmann
1826 208a6cff Michael Hanselmann
    if fields is not None:
1827 208a6cff Michael Hanselmann
      query.append(("fields", ",".join(fields)))
1828 208a6cff Michael Hanselmann
1829 208a6cff Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1830 208a6cff Michael Hanselmann
                             ("/%s/query/%s/fields" %
1831 208a6cff Michael Hanselmann
                              (GANETI_RAPI_VERSION, what)), query, None)