Statistics
| Branch: | Tag: | Revision:

root / lib / rapi / client.py @ cb4d3314

History | View | Annotate | Download (53.9 kB)

1 95ab4de9 David Knowles
#
2 95ab4de9 David Knowles
#
3 95ab4de9 David Knowles
4 16c13387 Theo Van Dinter
# Copyright (C) 2010, 2011 Google Inc.
5 95ab4de9 David Knowles
#
6 95ab4de9 David Knowles
# This program is free software; you can redistribute it and/or modify
7 95ab4de9 David Knowles
# it under the terms of the GNU General Public License as published by
8 95ab4de9 David Knowles
# the Free Software Foundation; either version 2 of the License, or
9 95ab4de9 David Knowles
# (at your option) any later version.
10 95ab4de9 David Knowles
#
11 95ab4de9 David Knowles
# This program is distributed in the hope that it will be useful, but
12 95ab4de9 David Knowles
# WITHOUT ANY WARRANTY; without even the implied warranty of
13 95ab4de9 David Knowles
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 95ab4de9 David Knowles
# General Public License for more details.
15 95ab4de9 David Knowles
#
16 95ab4de9 David Knowles
# You should have received a copy of the GNU General Public License
17 95ab4de9 David Knowles
# along with this program; if not, write to the Free Software
18 95ab4de9 David Knowles
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 95ab4de9 David Knowles
# 02110-1301, USA.
20 95ab4de9 David Knowles
21 95ab4de9 David Knowles
22 2a7c3583 Michael Hanselmann
"""Ganeti RAPI client.
23 2a7c3583 Michael Hanselmann

24 2a7c3583 Michael Hanselmann
@attention: To use the RAPI client, the application B{must} call
25 2a7c3583 Michael Hanselmann
            C{pycurl.global_init} during initialization and
26 2a7c3583 Michael Hanselmann
            C{pycurl.global_cleanup} before exiting the process. This is very
27 2a7c3583 Michael Hanselmann
            important in multi-threaded programs. See curl_global_init(3) and
28 2a7c3583 Michael Hanselmann
            curl_global_cleanup(3) for details. The decorator L{UsesRapiClient}
29 2a7c3583 Michael Hanselmann
            can be used.
30 2a7c3583 Michael Hanselmann

31 2a7c3583 Michael Hanselmann
"""
32 95ab4de9 David Knowles
33 5ef5cfea Michael Hanselmann
# No Ganeti-specific modules should be imported. The RAPI client is supposed to
34 5ef5cfea Michael Hanselmann
# be standalone.
35 5ef5cfea Michael Hanselmann
36 9279e986 Michael Hanselmann
import logging
37 95ab4de9 David Knowles
import simplejson
38 1a8337f2 Manuel Franceschini
import socket
39 95ab4de9 David Knowles
import urllib
40 2a7c3583 Michael Hanselmann
import threading
41 2a7c3583 Michael Hanselmann
import pycurl
42 16c13387 Theo Van Dinter
import time
43 2a7c3583 Michael Hanselmann
44 2a7c3583 Michael Hanselmann
try:
45 2a7c3583 Michael Hanselmann
  from cStringIO import StringIO
46 2a7c3583 Michael Hanselmann
except ImportError:
47 2a7c3583 Michael Hanselmann
  from StringIO import StringIO
48 95ab4de9 David Knowles
49 95ab4de9 David Knowles
50 9279e986 Michael Hanselmann
GANETI_RAPI_PORT = 5080
51 a198b2d9 Michael Hanselmann
GANETI_RAPI_VERSION = 2
52 9279e986 Michael Hanselmann
53 95ab4de9 David Knowles
HTTP_DELETE = "DELETE"
54 95ab4de9 David Knowles
HTTP_GET = "GET"
55 95ab4de9 David Knowles
HTTP_PUT = "PUT"
56 95ab4de9 David Knowles
HTTP_POST = "POST"
57 9279e986 Michael Hanselmann
HTTP_OK = 200
58 7eac4a4d Michael Hanselmann
HTTP_NOT_FOUND = 404
59 9279e986 Michael Hanselmann
HTTP_APP_JSON = "application/json"
60 9279e986 Michael Hanselmann
61 95ab4de9 David Knowles
REPLACE_DISK_PRI = "replace_on_primary"
62 95ab4de9 David Knowles
REPLACE_DISK_SECONDARY = "replace_on_secondary"
63 95ab4de9 David Knowles
REPLACE_DISK_CHG = "replace_new_secondary"
64 95ab4de9 David Knowles
REPLACE_DISK_AUTO = "replace_auto"
65 1068639f Michael Hanselmann
66 0b58db81 Michael Hanselmann
NODE_EVAC_PRI = "primary-only"
67 0b58db81 Michael Hanselmann
NODE_EVAC_SEC = "secondary-only"
68 0b58db81 Michael Hanselmann
NODE_EVAC_ALL = "all"
69 0b58db81 Michael Hanselmann
70 1068639f Michael Hanselmann
NODE_ROLE_DRAINED = "drained"
71 1068639f Michael Hanselmann
NODE_ROLE_MASTER_CANDIATE = "master-candidate"
72 1068639f Michael Hanselmann
NODE_ROLE_MASTER = "master"
73 1068639f Michael Hanselmann
NODE_ROLE_OFFLINE = "offline"
74 1068639f Michael Hanselmann
NODE_ROLE_REGULAR = "regular"
75 95ab4de9 David Knowles
76 63d5eb8a Michael Hanselmann
JOB_STATUS_QUEUED = "queued"
77 47099cd1 Michael Hanselmann
JOB_STATUS_WAITING = "waiting"
78 63d5eb8a Michael Hanselmann
JOB_STATUS_CANCELING = "canceling"
79 63d5eb8a Michael Hanselmann
JOB_STATUS_RUNNING = "running"
80 63d5eb8a Michael Hanselmann
JOB_STATUS_CANCELED = "canceled"
81 63d5eb8a Michael Hanselmann
JOB_STATUS_SUCCESS = "success"
82 63d5eb8a Michael Hanselmann
JOB_STATUS_ERROR = "error"
83 63d5eb8a Michael Hanselmann
JOB_STATUS_FINALIZED = frozenset([
84 63d5eb8a Michael Hanselmann
  JOB_STATUS_CANCELED,
85 63d5eb8a Michael Hanselmann
  JOB_STATUS_SUCCESS,
86 63d5eb8a Michael Hanselmann
  JOB_STATUS_ERROR,
87 63d5eb8a Michael Hanselmann
  ])
88 63d5eb8a Michael Hanselmann
JOB_STATUS_ALL = frozenset([
89 63d5eb8a Michael Hanselmann
  JOB_STATUS_QUEUED,
90 47099cd1 Michael Hanselmann
  JOB_STATUS_WAITING,
91 63d5eb8a Michael Hanselmann
  JOB_STATUS_CANCELING,
92 63d5eb8a Michael Hanselmann
  JOB_STATUS_RUNNING,
93 63d5eb8a Michael Hanselmann
  ]) | JOB_STATUS_FINALIZED
94 63d5eb8a Michael Hanselmann
95 47099cd1 Michael Hanselmann
# Legacy name
96 47099cd1 Michael Hanselmann
JOB_STATUS_WAITLOCK = JOB_STATUS_WAITING
97 47099cd1 Michael Hanselmann
98 8a47b447 Michael Hanselmann
# Internal constants
99 8a47b447 Michael Hanselmann
_REQ_DATA_VERSION_FIELD = "__version__"
100 8a47b447 Michael Hanselmann
_INST_CREATE_REQV1 = "instance-create-reqv1"
101 c744425f Michael Hanselmann
_INST_REINSTALL_REQV1 = "instance-reinstall-reqv1"
102 b7a1c816 Michael Hanselmann
_NODE_MIGRATE_REQV1 = "node-migrate-reqv1"
103 de40437a Michael Hanselmann
_NODE_EVAC_RES1 = "node-evac-res1"
104 c97fcab8 Guido Trotter
_INST_NIC_PARAMS = frozenset(["mac", "ip", "mode", "link"])
105 48436b97 Michael Hanselmann
_INST_CREATE_V0_DISK_PARAMS = frozenset(["size"])
106 48436b97 Michael Hanselmann
_INST_CREATE_V0_PARAMS = frozenset([
107 48436b97 Michael Hanselmann
  "os", "pnode", "snode", "iallocator", "start", "ip_check", "name_check",
108 48436b97 Michael Hanselmann
  "hypervisor", "file_storage_dir", "file_driver", "dry_run",
109 48436b97 Michael Hanselmann
  ])
110 48436b97 Michael Hanselmann
_INST_CREATE_V0_DPARAMS = frozenset(["beparams", "hvparams"])
111 8a47b447 Michael Hanselmann
112 2a7c3583 Michael Hanselmann
# Older pycURL versions don't have all error constants
113 2a7c3583 Michael Hanselmann
try:
114 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT = pycurl.E_SSL_CACERT
115 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT_BADFILE = pycurl.E_SSL_CACERT_BADFILE
116 2a7c3583 Michael Hanselmann
except AttributeError:
117 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT = 60
118 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT_BADFILE = 77
119 2a7c3583 Michael Hanselmann
120 2a7c3583 Michael Hanselmann
_CURL_SSL_CERT_ERRORS = frozenset([
121 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT,
122 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT_BADFILE,
123 2a7c3583 Michael Hanselmann
  ])
124 2a7c3583 Michael Hanselmann
125 95ab4de9 David Knowles
126 95ab4de9 David Knowles
class Error(Exception):
127 95ab4de9 David Knowles
  """Base error class for this module.
128 95ab4de9 David Knowles

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

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

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

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

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

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

205 2a7c3583 Michael Hanselmann
    @type curl: pycurl.Curl
206 2a7c3583 Michael Hanselmann
    @param curl: cURL object
207 9279e986 Michael Hanselmann

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

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

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

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

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

355 10f5ab6c Michael Hanselmann
    @type query: list of two-tuples
356 10f5ab6c Michael Hanselmann
    @param query: Query arguments
357 10f5ab6c Michael Hanselmann
    @rtype: list
358 10f5ab6c Michael Hanselmann
    @return: Query list with encoded values
359 10f5ab6c Michael Hanselmann

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

382 95ab4de9 David Knowles
    This constructs a full URL, encodes and decodes HTTP bodies, and
383 95ab4de9 David Knowles
    handles invalid responses in a pythonic way.
384 95ab4de9 David Knowles

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

394 95ab4de9 David Knowles
    @rtype: str
395 95ab4de9 David Knowles
    @return: JSON-Decoded response
396 95ab4de9 David Knowles

397 f2f88abf David Knowles
    @raises CertificateError: If an invalid SSL certificate is found
398 95ab4de9 David Knowles
    @raises GanetiApiError: If an invalid response is returned
399 95ab4de9 David Knowles

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

470 95ab4de9 David Knowles
    @rtype: int
471 f2f88abf David Knowles
    @return: Ganeti Remote API version
472 95ab4de9 David Knowles

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

479 7eac4a4d Michael Hanselmann
    @rtype: list
480 7eac4a4d Michael Hanselmann
    @return: List of optional features
481 7eac4a4d Michael Hanselmann

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

496 95ab4de9 David Knowles
    @rtype: list of str
497 95ab4de9 David Knowles
    @return: operating systems
498 95ab4de9 David Knowles

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

506 95ab4de9 David Knowles
    @rtype: dict
507 95ab4de9 David Knowles
    @return: information about the cluster
508 95ab4de9 David Knowles

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

516 d914c76f Simeon Miteff
    @rtype: string
517 54d4c13b Michael Hanselmann
    @return: job id
518 54d4c13b Michael Hanselmann

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

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

529 98805538 Michael Hanselmann
    @rtype: string
530 62e999a5 Michael Hanselmann
    @return: job id
531 62e999a5 Michael Hanselmann

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

541 95ab4de9 David Knowles
    @rtype: list of str
542 95ab4de9 David Knowles
    @return: cluster tags
543 95ab4de9 David Knowles

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

551 95ab4de9 David Knowles
    @type tags: list of str
552 95ab4de9 David Knowles
    @param tags: tags to add to the cluster
553 95ab4de9 David Knowles
    @type dry_run: bool
554 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
555 95ab4de9 David Knowles

556 98805538 Michael Hanselmann
    @rtype: string
557 95ab4de9 David Knowles
    @return: job id
558 95ab4de9 David Knowles

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

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

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

588 95ab4de9 David Knowles
    @type bulk: bool
589 95ab4de9 David Knowles
    @param bulk: whether to return all information about all instances
590 95ab4de9 David Knowles

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

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

610 95ab4de9 David Knowles
    @type instance: str
611 95ab4de9 David Knowles
    @param instance: instance whose info to return
612 95ab4de9 David Knowles

613 95ab4de9 David Knowles
    @rtype: dict
614 95ab4de9 David Knowles
    @return: info about the instance
615 95ab4de9 David Knowles

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

624 591e5103 Michael Hanselmann
    @type instance: string
625 591e5103 Michael Hanselmann
    @param instance: Instance name
626 591e5103 Michael Hanselmann
    @rtype: string
627 591e5103 Michael Hanselmann
    @return: Job ID
628 591e5103 Michael Hanselmann

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

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

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

659 98805538 Michael Hanselmann
    @rtype: string
660 95ab4de9 David Knowles
    @return: job id
661 95ab4de9 David Knowles

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

696 95ab4de9 David Knowles
    @type instance: str
697 95ab4de9 David Knowles
    @param instance: the instance to delete
698 95ab4de9 David Knowles

699 98805538 Michael Hanselmann
    @rtype: string
700 cab667cc David Knowles
    @return: job id
701 cab667cc David Knowles

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

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

716 3b7158ef Michael Hanselmann
    @type instance: string
717 3b7158ef Michael Hanselmann
    @param instance: Instance name
718 98805538 Michael Hanselmann
    @rtype: string
719 3b7158ef Michael Hanselmann
    @return: job id
720 3b7158ef Michael Hanselmann

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

731 b680c8be Michael Hanselmann
    @type instance: string
732 b680c8be Michael Hanselmann
    @param instance: Instance name
733 b680c8be Michael Hanselmann
    @type ignore_size: bool
734 b680c8be Michael Hanselmann
    @param ignore_size: Whether to ignore recorded size
735 d914c76f Simeon Miteff
    @rtype: string
736 b680c8be Michael Hanselmann
    @return: job id
737 b680c8be Michael Hanselmann

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

750 b680c8be Michael Hanselmann
    @type instance: string
751 b680c8be Michael Hanselmann
    @param instance: Instance name
752 d914c76f Simeon Miteff
    @rtype: string
753 b680c8be Michael Hanselmann
    @return: job id
754 b680c8be Michael Hanselmann

755 b680c8be Michael Hanselmann
    """
756 b680c8be Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
757 b680c8be Michael Hanselmann
                             ("/%s/instances/%s/deactivate-disks" %
758 b680c8be Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, None)
759 b680c8be Michael Hanselmann
760 a52978c7 Michael Hanselmann
  def RecreateInstanceDisks(self, instance, disks=None, nodes=None):
761 a52978c7 Michael Hanselmann
    """Recreate an instance's disks.
762 a52978c7 Michael Hanselmann

763 a52978c7 Michael Hanselmann
    @type instance: string
764 a52978c7 Michael Hanselmann
    @param instance: Instance name
765 a52978c7 Michael Hanselmann
    @type disks: list of int
766 a52978c7 Michael Hanselmann
    @param disks: List of disk indexes
767 a52978c7 Michael Hanselmann
    @type nodes: list of string
768 a52978c7 Michael Hanselmann
    @param nodes: New instance nodes, if relocation is desired
769 a52978c7 Michael Hanselmann
    @rtype: string
770 a52978c7 Michael Hanselmann
    @return: job id
771 a52978c7 Michael Hanselmann

772 a52978c7 Michael Hanselmann
    """
773 a52978c7 Michael Hanselmann
    body = {}
774 a52978c7 Michael Hanselmann
775 a52978c7 Michael Hanselmann
    if disks is not None:
776 a52978c7 Michael Hanselmann
      body["disks"] = disks
777 a52978c7 Michael Hanselmann
778 a52978c7 Michael Hanselmann
    if nodes is not None:
779 a52978c7 Michael Hanselmann
      body["nodes"] = nodes
780 a52978c7 Michael Hanselmann
781 a52978c7 Michael Hanselmann
    return self._SendRequest(HTTP_POST,
782 a52978c7 Michael Hanselmann
                             ("/%s/instances/%s/recreate-disks" %
783 a52978c7 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, body)
784 a52978c7 Michael Hanselmann
785 e23881ed Michael Hanselmann
  def GrowInstanceDisk(self, instance, disk, amount, wait_for_sync=None):
786 e23881ed Michael Hanselmann
    """Grows a disk of an instance.
787 e23881ed Michael Hanselmann

788 e23881ed Michael Hanselmann
    More details for parameters can be found in the RAPI documentation.
789 e23881ed Michael Hanselmann

790 e23881ed Michael Hanselmann
    @type instance: string
791 e23881ed Michael Hanselmann
    @param instance: Instance name
792 e23881ed Michael Hanselmann
    @type disk: integer
793 e23881ed Michael Hanselmann
    @param disk: Disk index
794 e23881ed Michael Hanselmann
    @type amount: integer
795 e23881ed Michael Hanselmann
    @param amount: Grow disk by this amount (MiB)
796 e23881ed Michael Hanselmann
    @type wait_for_sync: bool
797 e23881ed Michael Hanselmann
    @param wait_for_sync: Wait for disk to synchronize
798 98805538 Michael Hanselmann
    @rtype: string
799 e23881ed Michael Hanselmann
    @return: job id
800 e23881ed Michael Hanselmann

801 e23881ed Michael Hanselmann
    """
802 e23881ed Michael Hanselmann
    body = {
803 e23881ed Michael Hanselmann
      "amount": amount,
804 e23881ed Michael Hanselmann
      }
805 e23881ed Michael Hanselmann
806 e23881ed Michael Hanselmann
    if wait_for_sync is not None:
807 e23881ed Michael Hanselmann
      body["wait_for_sync"] = wait_for_sync
808 e23881ed Michael Hanselmann
809 e23881ed Michael Hanselmann
    return self._SendRequest(HTTP_POST,
810 e23881ed Michael Hanselmann
                             ("/%s/instances/%s/disk/%s/grow" %
811 e23881ed Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance, disk)),
812 e23881ed Michael Hanselmann
                             None, body)
813 e23881ed Michael Hanselmann
814 95ab4de9 David Knowles
  def GetInstanceTags(self, instance):
815 95ab4de9 David Knowles
    """Gets tags for an instance.
816 95ab4de9 David Knowles

817 95ab4de9 David Knowles
    @type instance: str
818 95ab4de9 David Knowles
    @param instance: instance whose tags to return
819 95ab4de9 David Knowles

820 95ab4de9 David Knowles
    @rtype: list of str
821 95ab4de9 David Knowles
    @return: tags for the instance
822 95ab4de9 David Knowles

823 95ab4de9 David Knowles
    """
824 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
825 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/tags" %
826 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, None)
827 95ab4de9 David Knowles
828 95ab4de9 David Knowles
  def AddInstanceTags(self, instance, tags, dry_run=False):
829 95ab4de9 David Knowles
    """Adds tags to an instance.
830 95ab4de9 David Knowles

831 95ab4de9 David Knowles
    @type instance: str
832 95ab4de9 David Knowles
    @param instance: instance to add tags to
833 95ab4de9 David Knowles
    @type tags: list of str
834 95ab4de9 David Knowles
    @param tags: tags to add to the instance
835 95ab4de9 David Knowles
    @type dry_run: bool
836 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
837 95ab4de9 David Knowles

838 98805538 Michael Hanselmann
    @rtype: string
839 95ab4de9 David Knowles
    @return: job id
840 95ab4de9 David Knowles

841 95ab4de9 David Knowles
    """
842 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
843 95ab4de9 David Knowles
    if dry_run:
844 95ab4de9 David Knowles
      query.append(("dry-run", 1))
845 95ab4de9 David Knowles
846 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
847 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/tags" %
848 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
849 95ab4de9 David Knowles
850 95ab4de9 David Knowles
  def DeleteInstanceTags(self, instance, tags, dry_run=False):
851 95ab4de9 David Knowles
    """Deletes tags from an instance.
852 95ab4de9 David Knowles

853 95ab4de9 David Knowles
    @type instance: str
854 95ab4de9 David Knowles
    @param instance: instance to delete tags from
855 95ab4de9 David Knowles
    @type tags: list of str
856 95ab4de9 David Knowles
    @param tags: tags to delete
857 95ab4de9 David Knowles
    @type dry_run: bool
858 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
859 d914c76f Simeon Miteff
    @rtype: string
860 d914c76f Simeon Miteff
    @return: job id
861 95ab4de9 David Knowles

862 95ab4de9 David Knowles
    """
863 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
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_DELETE,
868 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/tags" %
869 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
870 95ab4de9 David Knowles
871 95ab4de9 David Knowles
  def RebootInstance(self, instance, reboot_type=None, ignore_secondaries=None,
872 95ab4de9 David Knowles
                     dry_run=False):
873 95ab4de9 David Knowles
    """Reboots an instance.
874 95ab4de9 David Knowles

875 95ab4de9 David Knowles
    @type instance: str
876 95ab4de9 David Knowles
    @param instance: instance to rebot
877 95ab4de9 David Knowles
    @type reboot_type: str
878 95ab4de9 David Knowles
    @param reboot_type: one of: hard, soft, full
879 95ab4de9 David Knowles
    @type ignore_secondaries: bool
880 95ab4de9 David Knowles
    @param ignore_secondaries: if True, ignores errors for the secondary node
881 95ab4de9 David Knowles
        while re-assembling disks (in hard-reboot mode only)
882 95ab4de9 David Knowles
    @type dry_run: bool
883 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
884 d914c76f Simeon Miteff
    @rtype: string
885 d914c76f Simeon Miteff
    @return: job id
886 95ab4de9 David Knowles

887 95ab4de9 David Knowles
    """
888 95ab4de9 David Knowles
    query = []
889 95ab4de9 David Knowles
    if reboot_type:
890 95ab4de9 David Knowles
      query.append(("type", reboot_type))
891 95ab4de9 David Knowles
    if ignore_secondaries is not None:
892 95ab4de9 David Knowles
      query.append(("ignore_secondaries", ignore_secondaries))
893 95ab4de9 David Knowles
    if dry_run:
894 95ab4de9 David Knowles
      query.append(("dry-run", 1))
895 95ab4de9 David Knowles
896 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_POST,
897 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/reboot" %
898 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
899 95ab4de9 David Knowles
900 2ba39b8f Iustin Pop
  def ShutdownInstance(self, instance, dry_run=False, no_remember=False):
901 95ab4de9 David Knowles
    """Shuts down an instance.
902 95ab4de9 David Knowles

903 95ab4de9 David Knowles
    @type instance: str
904 95ab4de9 David Knowles
    @param instance: the instance to shut down
905 95ab4de9 David Knowles
    @type dry_run: bool
906 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
907 2ba39b8f Iustin Pop
    @type no_remember: bool
908 2ba39b8f Iustin Pop
    @param no_remember: if true, will not record the state change
909 d914c76f Simeon Miteff
    @rtype: string
910 d914c76f Simeon Miteff
    @return: job id
911 95ab4de9 David Knowles

912 95ab4de9 David Knowles
    """
913 95ab4de9 David Knowles
    query = []
914 95ab4de9 David Knowles
    if dry_run:
915 95ab4de9 David Knowles
      query.append(("dry-run", 1))
916 2ba39b8f Iustin Pop
    if no_remember:
917 2ba39b8f Iustin Pop
      query.append(("no-remember", 1))
918 95ab4de9 David Knowles
919 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
920 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/shutdown" %
921 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
922 95ab4de9 David Knowles
923 2ba39b8f Iustin Pop
  def StartupInstance(self, instance, dry_run=False, no_remember=False):
924 95ab4de9 David Knowles
    """Starts up an instance.
925 95ab4de9 David Knowles

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

935 95ab4de9 David Knowles
    """
936 95ab4de9 David Knowles
    query = []
937 95ab4de9 David Knowles
    if dry_run:
938 95ab4de9 David Knowles
      query.append(("dry-run", 1))
939 2ba39b8f Iustin Pop
    if no_remember:
940 2ba39b8f Iustin Pop
      query.append(("no-remember", 1))
941 95ab4de9 David Knowles
942 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
943 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/startup" %
944 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
945 95ab4de9 David Knowles
946 c744425f Michael Hanselmann
  def ReinstallInstance(self, instance, os=None, no_startup=False,
947 c744425f Michael Hanselmann
                        osparams=None):
948 95ab4de9 David Knowles
    """Reinstalls an instance.
949 95ab4de9 David Knowles

950 95ab4de9 David Knowles
    @type instance: str
951 fcee9675 David Knowles
    @param instance: The instance to reinstall
952 fcee9675 David Knowles
    @type os: str or None
953 fcee9675 David Knowles
    @param os: The operating system to reinstall. If None, the instance's
954 fcee9675 David Knowles
        current operating system will be installed again
955 95ab4de9 David Knowles
    @type no_startup: bool
956 fcee9675 David Knowles
    @param no_startup: Whether to start the instance automatically
957 d914c76f Simeon Miteff
    @rtype: string
958 d914c76f Simeon Miteff
    @return: job id
959 95ab4de9 David Knowles

960 95ab4de9 David Knowles
    """
961 c744425f Michael Hanselmann
    if _INST_REINSTALL_REQV1 in self.GetFeatures():
962 c744425f Michael Hanselmann
      body = {
963 c744425f Michael Hanselmann
        "start": not no_startup,
964 c744425f Michael Hanselmann
        }
965 c744425f Michael Hanselmann
      if os is not None:
966 c744425f Michael Hanselmann
        body["os"] = os
967 c744425f Michael Hanselmann
      if osparams is not None:
968 c744425f Michael Hanselmann
        body["osparams"] = osparams
969 c744425f Michael Hanselmann
      return self._SendRequest(HTTP_POST,
970 c744425f Michael Hanselmann
                               ("/%s/instances/%s/reinstall" %
971 c744425f Michael Hanselmann
                                (GANETI_RAPI_VERSION, instance)), None, body)
972 c744425f Michael Hanselmann
973 c744425f Michael Hanselmann
    # Use old request format
974 c744425f Michael Hanselmann
    if osparams:
975 c744425f Michael Hanselmann
      raise GanetiApiError("Server does not support specifying OS parameters"
976 c744425f Michael Hanselmann
                           " for instance reinstallation")
977 c744425f Michael Hanselmann
978 fcee9675 David Knowles
    query = []
979 fcee9675 David Knowles
    if os:
980 fcee9675 David Knowles
      query.append(("os", os))
981 95ab4de9 David Knowles
    if no_startup:
982 95ab4de9 David Knowles
      query.append(("nostartup", 1))
983 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_POST,
984 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/reinstall" %
985 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
986 95ab4de9 David Knowles
987 bfc2002f Michael Hanselmann
  def ReplaceInstanceDisks(self, instance, disks=None, mode=REPLACE_DISK_AUTO,
988 539d65ba Michael Hanselmann
                           remote_node=None, iallocator=None):
989 95ab4de9 David Knowles
    """Replaces disks on an instance.
990 95ab4de9 David Knowles

991 95ab4de9 David Knowles
    @type instance: str
992 95ab4de9 David Knowles
    @param instance: instance whose disks to replace
993 bfc2002f Michael Hanselmann
    @type disks: list of ints
994 bfc2002f Michael Hanselmann
    @param disks: Indexes of disks to replace
995 95ab4de9 David Knowles
    @type mode: str
996 cfc03c54 Michael Hanselmann
    @param mode: replacement mode to use (defaults to replace_auto)
997 95ab4de9 David Knowles
    @type remote_node: str or None
998 95ab4de9 David Knowles
    @param remote_node: new secondary node to use (for use with
999 cfc03c54 Michael Hanselmann
        replace_new_secondary mode)
1000 95ab4de9 David Knowles
    @type iallocator: str or None
1001 95ab4de9 David Knowles
    @param iallocator: instance allocator plugin to use (for use with
1002 cfc03c54 Michael Hanselmann
                       replace_auto mode)
1003 95ab4de9 David Knowles

1004 98805538 Michael Hanselmann
    @rtype: string
1005 95ab4de9 David Knowles
    @return: job id
1006 95ab4de9 David Knowles

1007 95ab4de9 David Knowles
    """
1008 cfc03c54 Michael Hanselmann
    query = [
1009 cfc03c54 Michael Hanselmann
      ("mode", mode),
1010 cfc03c54 Michael Hanselmann
      ]
1011 95ab4de9 David Knowles
1012 539d65ba Michael Hanselmann
    # TODO: Convert to body parameters
1013 539d65ba Michael Hanselmann
1014 539d65ba Michael Hanselmann
    if disks is not None:
1015 bfc2002f Michael Hanselmann
      query.append(("disks", ",".join(str(idx) for idx in disks)))
1016 bfc2002f Michael Hanselmann
1017 539d65ba Michael Hanselmann
    if remote_node is not None:
1018 95ab4de9 David Knowles
      query.append(("remote_node", remote_node))
1019 95ab4de9 David Knowles
1020 539d65ba Michael Hanselmann
    if iallocator is not None:
1021 bfc2002f Michael Hanselmann
      query.append(("iallocator", iallocator))
1022 bfc2002f Michael Hanselmann
1023 95ab4de9 David Knowles
    return self._SendRequest(HTTP_POST,
1024 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/replace-disks" %
1025 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
1026 95ab4de9 David Knowles
1027 ebeb600f Michael Hanselmann
  def PrepareExport(self, instance, mode):
1028 ebeb600f Michael Hanselmann
    """Prepares an instance for an export.
1029 ebeb600f Michael Hanselmann

1030 ebeb600f Michael Hanselmann
    @type instance: string
1031 ebeb600f Michael Hanselmann
    @param instance: Instance name
1032 ebeb600f Michael Hanselmann
    @type mode: string
1033 ebeb600f Michael Hanselmann
    @param mode: Export mode
1034 ebeb600f Michael Hanselmann
    @rtype: string
1035 ebeb600f Michael Hanselmann
    @return: Job ID
1036 ebeb600f Michael Hanselmann

1037 ebeb600f Michael Hanselmann
    """
1038 ebeb600f Michael Hanselmann
    query = [("mode", mode)]
1039 ebeb600f Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1040 ebeb600f Michael Hanselmann
                             ("/%s/instances/%s/prepare-export" %
1041 ebeb600f Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
1042 ebeb600f Michael Hanselmann
1043 ebeb600f Michael Hanselmann
  def ExportInstance(self, instance, mode, destination, shutdown=None,
1044 ebeb600f Michael Hanselmann
                     remove_instance=None,
1045 ebeb600f Michael Hanselmann
                     x509_key_name=None, destination_x509_ca=None):
1046 ebeb600f Michael Hanselmann
    """Exports an instance.
1047 ebeb600f Michael Hanselmann

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

1055 ebeb600f Michael Hanselmann
    """
1056 ebeb600f Michael Hanselmann
    body = {
1057 ebeb600f Michael Hanselmann
      "destination": destination,
1058 ebeb600f Michael Hanselmann
      "mode": mode,
1059 ebeb600f Michael Hanselmann
      }
1060 ebeb600f Michael Hanselmann
1061 ebeb600f Michael Hanselmann
    if shutdown is not None:
1062 ebeb600f Michael Hanselmann
      body["shutdown"] = shutdown
1063 ebeb600f Michael Hanselmann
1064 ebeb600f Michael Hanselmann
    if remove_instance is not None:
1065 ebeb600f Michael Hanselmann
      body["remove_instance"] = remove_instance
1066 ebeb600f Michael Hanselmann
1067 ebeb600f Michael Hanselmann
    if x509_key_name is not None:
1068 ebeb600f Michael Hanselmann
      body["x509_key_name"] = x509_key_name
1069 ebeb600f Michael Hanselmann
1070 ebeb600f Michael Hanselmann
    if destination_x509_ca is not None:
1071 ebeb600f Michael Hanselmann
      body["destination_x509_ca"] = destination_x509_ca
1072 ebeb600f Michael Hanselmann
1073 ebeb600f Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1074 ebeb600f Michael Hanselmann
                             ("/%s/instances/%s/export" %
1075 ebeb600f Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, body)
1076 ebeb600f Michael Hanselmann
1077 e0ac6ce6 Michael Hanselmann
  def MigrateInstance(self, instance, mode=None, cleanup=None):
1078 c63eb9c0 Michael Hanselmann
    """Migrates an instance.
1079 e0ac6ce6 Michael Hanselmann

1080 e0ac6ce6 Michael Hanselmann
    @type instance: string
1081 e0ac6ce6 Michael Hanselmann
    @param instance: Instance name
1082 e0ac6ce6 Michael Hanselmann
    @type mode: string
1083 e0ac6ce6 Michael Hanselmann
    @param mode: Migration mode
1084 e0ac6ce6 Michael Hanselmann
    @type cleanup: bool
1085 e0ac6ce6 Michael Hanselmann
    @param cleanup: Whether to clean up a previously failed migration
1086 d914c76f Simeon Miteff
    @rtype: string
1087 d914c76f Simeon Miteff
    @return: job id
1088 e0ac6ce6 Michael Hanselmann

1089 e0ac6ce6 Michael Hanselmann
    """
1090 e0ac6ce6 Michael Hanselmann
    body = {}
1091 e0ac6ce6 Michael Hanselmann
1092 e0ac6ce6 Michael Hanselmann
    if mode is not None:
1093 e0ac6ce6 Michael Hanselmann
      body["mode"] = mode
1094 e0ac6ce6 Michael Hanselmann
1095 e0ac6ce6 Michael Hanselmann
    if cleanup is not None:
1096 e0ac6ce6 Michael Hanselmann
      body["cleanup"] = cleanup
1097 e0ac6ce6 Michael Hanselmann
1098 e0ac6ce6 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1099 e0ac6ce6 Michael Hanselmann
                             ("/%s/instances/%s/migrate" %
1100 e0ac6ce6 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, body)
1101 e0ac6ce6 Michael Hanselmann
1102 c0a146a1 Michael Hanselmann
  def FailoverInstance(self, instance, iallocator=None,
1103 c0a146a1 Michael Hanselmann
                       ignore_consistency=None, target_node=None):
1104 c0a146a1 Michael Hanselmann
    """Does a failover of an instance.
1105 c0a146a1 Michael Hanselmann

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1303 95ab4de9 David Knowles
    @type node: str
1304 95ab4de9 David Knowles
    @param node: node whose info to return
1305 95ab4de9 David Knowles

1306 95ab4de9 David Knowles
    @rtype: dict
1307 95ab4de9 David Knowles
    @return: info about the node
1308 95ab4de9 David Knowles

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

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

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

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

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

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

1404 98805538 Michael Hanselmann
    @rtype: string
1405 95ab4de9 David Knowles
    @return: job id
1406 95ab4de9 David Knowles

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

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

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

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

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

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

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

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

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

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

1506 370f2042 Guido Trotter
    @type node: string
1507 370f2042 Guido Trotter
    @param node: Node name
1508 94497dd1 Michael Hanselmann
    @rtype: string
1509 94497dd1 Michael Hanselmann
    @return: job id
1510 94497dd1 Michael Hanselmann

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

1519 95ab4de9 David Knowles
    @type node: str
1520 95ab4de9 David Knowles
    @param node: the node whose storage units to return
1521 95ab4de9 David Knowles
    @type storage_type: str
1522 95ab4de9 David Knowles
    @param storage_type: storage type whose units to return
1523 95ab4de9 David Knowles
    @type output_fields: str
1524 95ab4de9 David Knowles
    @param output_fields: storage type fields to return
1525 95ab4de9 David Knowles

1526 98805538 Michael Hanselmann
    @rtype: string
1527 95ab4de9 David Knowles
    @return: job id where results can be retrieved
1528 95ab4de9 David Knowles

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

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

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

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

1571 95ab4de9 David Knowles
    @type node: str
1572 95ab4de9 David Knowles
    @param node: node whose storage units to repair
1573 95ab4de9 David Knowles
    @type storage_type: str
1574 95ab4de9 David Knowles
    @param storage_type: storage type to repair
1575 95ab4de9 David Knowles
    @type name: str
1576 95ab4de9 David Knowles
    @param name: name of the storage unit to repair
1577 95ab4de9 David Knowles

1578 98805538 Michael Hanselmann
    @rtype: string
1579 95ab4de9 David Knowles
    @return: job id
1580 95ab4de9 David Knowles

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

1594 95ab4de9 David Knowles
    @type node: str
1595 95ab4de9 David Knowles
    @param node: node whose tags to return
1596 95ab4de9 David Knowles

1597 95ab4de9 David Knowles
    @rtype: list of str
1598 95ab4de9 David Knowles
    @return: tags for the node
1599 95ab4de9 David Knowles

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

1608 95ab4de9 David Knowles
    @type node: str
1609 95ab4de9 David Knowles
    @param node: node to add tags to
1610 95ab4de9 David Knowles
    @type tags: list of str
1611 95ab4de9 David Knowles
    @param tags: tags to add to the node
1612 95ab4de9 David Knowles
    @type dry_run: bool
1613 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1614 95ab4de9 David Knowles

1615 98805538 Michael Hanselmann
    @rtype: string
1616 95ab4de9 David Knowles
    @return: job id
1617 95ab4de9 David Knowles

1618 95ab4de9 David Knowles
    """
1619 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
1620 95ab4de9 David Knowles
    if dry_run:
1621 95ab4de9 David Knowles
      query.append(("dry-run", 1))
1622 95ab4de9 David Knowles
1623 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1624 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/tags" %
1625 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, tags)
1626 95ab4de9 David Knowles
1627 95ab4de9 David Knowles
  def DeleteNodeTags(self, node, tags, dry_run=False):
1628 95ab4de9 David Knowles
    """Delete tags from a node.
1629 95ab4de9 David Knowles

1630 95ab4de9 David Knowles
    @type node: str
1631 95ab4de9 David Knowles
    @param node: node to remove tags from
1632 95ab4de9 David Knowles
    @type tags: list of str
1633 95ab4de9 David Knowles
    @param tags: tags to remove from the node
1634 95ab4de9 David Knowles
    @type dry_run: bool
1635 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1636 95ab4de9 David Knowles

1637 98805538 Michael Hanselmann
    @rtype: string
1638 95ab4de9 David Knowles
    @return: job id
1639 95ab4de9 David Knowles

1640 95ab4de9 David Knowles
    """
1641 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
1642 95ab4de9 David Knowles
    if dry_run:
1643 95ab4de9 David Knowles
      query.append(("dry-run", 1))
1644 95ab4de9 David Knowles
1645 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE,
1646 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/tags" %
1647 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1648 a268af8d Adeodato Simo
1649 a268af8d Adeodato Simo
  def GetGroups(self, bulk=False):
1650 a268af8d Adeodato Simo
    """Gets all node groups in the cluster.
1651 a268af8d Adeodato Simo

1652 a268af8d Adeodato Simo
    @type bulk: bool
1653 a268af8d Adeodato Simo
    @param bulk: whether to return all information about the groups
1654 a268af8d Adeodato Simo

1655 a268af8d Adeodato Simo
    @rtype: list of dict or str
1656 a268af8d Adeodato Simo
    @return: if bulk is true, a list of dictionaries with info about all node
1657 a268af8d Adeodato Simo
        groups in the cluster, else a list of names of those node groups
1658 a268af8d Adeodato Simo

1659 a268af8d Adeodato Simo
    """
1660 a268af8d Adeodato Simo
    query = []
1661 a268af8d Adeodato Simo
    if bulk:
1662 a268af8d Adeodato Simo
      query.append(("bulk", 1))
1663 a268af8d Adeodato Simo
1664 a268af8d Adeodato Simo
    groups = self._SendRequest(HTTP_GET, "/%s/groups" % GANETI_RAPI_VERSION,
1665 a268af8d Adeodato Simo
                               query, None)
1666 a268af8d Adeodato Simo
    if bulk:
1667 a268af8d Adeodato Simo
      return groups
1668 a268af8d Adeodato Simo
    else:
1669 a268af8d Adeodato Simo
      return [g["name"] for g in groups]
1670 a268af8d Adeodato Simo
1671 a268af8d Adeodato Simo
  def GetGroup(self, group):
1672 a268af8d Adeodato Simo
    """Gets information about a node group.
1673 a268af8d Adeodato Simo

1674 a268af8d Adeodato Simo
    @type group: str
1675 a268af8d Adeodato Simo
    @param group: name of the node group whose info to return
1676 a268af8d Adeodato Simo

1677 a268af8d Adeodato Simo
    @rtype: dict
1678 a268af8d Adeodato Simo
    @return: info about the node group
1679 a268af8d Adeodato Simo

1680 a268af8d Adeodato Simo
    """
1681 a268af8d Adeodato Simo
    return self._SendRequest(HTTP_GET,
1682 a268af8d Adeodato Simo
                             "/%s/groups/%s" % (GANETI_RAPI_VERSION, group),
1683 a268af8d Adeodato Simo
                             None, None)
1684 a268af8d Adeodato Simo
1685 90e99856 Adeodato Simo
  def CreateGroup(self, name, alloc_policy=None, dry_run=False):
1686 a268af8d Adeodato Simo
    """Creates a new node group.
1687 a268af8d Adeodato Simo

1688 a268af8d Adeodato Simo
    @type name: str
1689 a268af8d Adeodato Simo
    @param name: the name of node group to create
1690 90e99856 Adeodato Simo
    @type alloc_policy: str
1691 90e99856 Adeodato Simo
    @param alloc_policy: the desired allocation policy for the group, if any
1692 a268af8d Adeodato Simo
    @type dry_run: bool
1693 a268af8d Adeodato Simo
    @param dry_run: whether to peform a dry run
1694 a268af8d Adeodato Simo

1695 98805538 Michael Hanselmann
    @rtype: string
1696 a268af8d Adeodato Simo
    @return: job id
1697 a268af8d Adeodato Simo

1698 a268af8d Adeodato Simo
    """
1699 a268af8d Adeodato Simo
    query = []
1700 a268af8d Adeodato Simo
    if dry_run:
1701 a268af8d Adeodato Simo
      query.append(("dry-run", 1))
1702 a268af8d Adeodato Simo
1703 a268af8d Adeodato Simo
    body = {
1704 a268af8d Adeodato Simo
      "name": name,
1705 90e99856 Adeodato Simo
      "alloc_policy": alloc_policy
1706 a268af8d Adeodato Simo
      }
1707 a268af8d Adeodato Simo
1708 a268af8d Adeodato Simo
    return self._SendRequest(HTTP_POST, "/%s/groups" % GANETI_RAPI_VERSION,
1709 a268af8d Adeodato Simo
                             query, body)
1710 a268af8d Adeodato Simo
1711 f18fab7d Adeodato Simo
  def ModifyGroup(self, group, **kwargs):
1712 f18fab7d Adeodato Simo
    """Modifies a node group.
1713 f18fab7d Adeodato Simo

1714 f18fab7d Adeodato Simo
    More details for parameters can be found in the RAPI documentation.
1715 f18fab7d Adeodato Simo

1716 f18fab7d Adeodato Simo
    @type group: string
1717 f18fab7d Adeodato Simo
    @param group: Node group name
1718 98805538 Michael Hanselmann
    @rtype: string
1719 f18fab7d Adeodato Simo
    @return: job id
1720 f18fab7d Adeodato Simo

1721 f18fab7d Adeodato Simo
    """
1722 f18fab7d Adeodato Simo
    return self._SendRequest(HTTP_PUT,
1723 f18fab7d Adeodato Simo
                             ("/%s/groups/%s/modify" %
1724 f18fab7d Adeodato Simo
                              (GANETI_RAPI_VERSION, group)), None, kwargs)
1725 f18fab7d Adeodato Simo
1726 a268af8d Adeodato Simo
  def DeleteGroup(self, group, dry_run=False):
1727 a268af8d Adeodato Simo
    """Deletes a node group.
1728 a268af8d Adeodato Simo

1729 a268af8d Adeodato Simo
    @type group: str
1730 a268af8d Adeodato Simo
    @param group: the node group to delete
1731 a268af8d Adeodato Simo
    @type dry_run: bool
1732 a268af8d Adeodato Simo
    @param dry_run: whether to peform a dry run
1733 a268af8d Adeodato Simo

1734 98805538 Michael Hanselmann
    @rtype: string
1735 a268af8d Adeodato Simo
    @return: job id
1736 a268af8d Adeodato Simo

1737 a268af8d Adeodato Simo
    """
1738 a268af8d Adeodato Simo
    query = []
1739 a268af8d Adeodato Simo
    if dry_run:
1740 a268af8d Adeodato Simo
      query.append(("dry-run", 1))
1741 a268af8d Adeodato Simo
1742 a268af8d Adeodato Simo
    return self._SendRequest(HTTP_DELETE,
1743 a268af8d Adeodato Simo
                             ("/%s/groups/%s" %
1744 a268af8d Adeodato Simo
                              (GANETI_RAPI_VERSION, group)), query, None)
1745 a268af8d Adeodato Simo
1746 a268af8d Adeodato Simo
  def RenameGroup(self, group, new_name):
1747 a268af8d Adeodato Simo
    """Changes the name of a node group.
1748 a268af8d Adeodato Simo

1749 a268af8d Adeodato Simo
    @type group: string
1750 a268af8d Adeodato Simo
    @param group: Node group name
1751 a268af8d Adeodato Simo
    @type new_name: string
1752 a268af8d Adeodato Simo
    @param new_name: New node group name
1753 a268af8d Adeodato Simo

1754 98805538 Michael Hanselmann
    @rtype: string
1755 a268af8d Adeodato Simo
    @return: job id
1756 a268af8d Adeodato Simo

1757 a268af8d Adeodato Simo
    """
1758 a268af8d Adeodato Simo
    body = {
1759 a268af8d Adeodato Simo
      "new_name": new_name,
1760 a268af8d Adeodato Simo
      }
1761 a268af8d Adeodato Simo
1762 a268af8d Adeodato Simo
    return self._SendRequest(HTTP_PUT,
1763 a268af8d Adeodato Simo
                             ("/%s/groups/%s/rename" %
1764 a268af8d Adeodato Simo
                              (GANETI_RAPI_VERSION, group)), None, body)
1765 4245446f Adeodato Simo
1766 4245446f Adeodato Simo
  def AssignGroupNodes(self, group, nodes, force=False, dry_run=False):
1767 4245446f Adeodato Simo
    """Assigns nodes to a group.
1768 4245446f Adeodato Simo

1769 4245446f Adeodato Simo
    @type group: string
1770 4245446f Adeodato Simo
    @param group: Node gropu name
1771 4245446f Adeodato Simo
    @type nodes: list of strings
1772 4245446f Adeodato Simo
    @param nodes: List of nodes to assign to the group
1773 4245446f Adeodato Simo

1774 98805538 Michael Hanselmann
    @rtype: string
1775 4245446f Adeodato Simo
    @return: job id
1776 4245446f Adeodato Simo

1777 4245446f Adeodato Simo
    """
1778 4245446f Adeodato Simo
    query = []
1779 4245446f Adeodato Simo
1780 4245446f Adeodato Simo
    if force:
1781 4245446f Adeodato Simo
      query.append(("force", 1))
1782 4245446f Adeodato Simo
1783 4245446f Adeodato Simo
    if dry_run:
1784 4245446f Adeodato Simo
      query.append(("dry-run", 1))
1785 4245446f Adeodato Simo
1786 4245446f Adeodato Simo
    body = {
1787 4245446f Adeodato Simo
      "nodes": nodes,
1788 4245446f Adeodato Simo
      }
1789 4245446f Adeodato Simo
1790 4245446f Adeodato Simo
    return self._SendRequest(HTTP_PUT,
1791 4245446f Adeodato Simo
                             ("/%s/groups/%s/assign-nodes" %
1792 4245446f Adeodato Simo
                             (GANETI_RAPI_VERSION, group)), query, body)
1793 208a6cff Michael Hanselmann
1794 414ebaf1 Michael Hanselmann
  def GetGroupTags(self, group):
1795 414ebaf1 Michael Hanselmann
    """Gets tags for a node group.
1796 414ebaf1 Michael Hanselmann

1797 414ebaf1 Michael Hanselmann
    @type group: string
1798 414ebaf1 Michael Hanselmann
    @param group: Node group whose tags to return
1799 414ebaf1 Michael Hanselmann

1800 414ebaf1 Michael Hanselmann
    @rtype: list of strings
1801 414ebaf1 Michael Hanselmann
    @return: tags for the group
1802 414ebaf1 Michael Hanselmann

1803 414ebaf1 Michael Hanselmann
    """
1804 414ebaf1 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1805 414ebaf1 Michael Hanselmann
                             ("/%s/groups/%s/tags" %
1806 414ebaf1 Michael Hanselmann
                              (GANETI_RAPI_VERSION, group)), None, None)
1807 414ebaf1 Michael Hanselmann
1808 414ebaf1 Michael Hanselmann
  def AddGroupTags(self, group, tags, dry_run=False):
1809 414ebaf1 Michael Hanselmann
    """Adds tags to a node group.
1810 414ebaf1 Michael Hanselmann

1811 414ebaf1 Michael Hanselmann
    @type group: str
1812 414ebaf1 Michael Hanselmann
    @param group: group to add tags to
1813 414ebaf1 Michael Hanselmann
    @type tags: list of string
1814 414ebaf1 Michael Hanselmann
    @param tags: tags to add to the group
1815 414ebaf1 Michael Hanselmann
    @type dry_run: bool
1816 414ebaf1 Michael Hanselmann
    @param dry_run: whether to perform a dry run
1817 414ebaf1 Michael Hanselmann

1818 414ebaf1 Michael Hanselmann
    @rtype: string
1819 414ebaf1 Michael Hanselmann
    @return: job id
1820 414ebaf1 Michael Hanselmann

1821 414ebaf1 Michael Hanselmann
    """
1822 414ebaf1 Michael Hanselmann
    query = [("tag", t) for t in tags]
1823 414ebaf1 Michael Hanselmann
    if dry_run:
1824 414ebaf1 Michael Hanselmann
      query.append(("dry-run", 1))
1825 414ebaf1 Michael Hanselmann
1826 414ebaf1 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1827 414ebaf1 Michael Hanselmann
                             ("/%s/groups/%s/tags" %
1828 414ebaf1 Michael Hanselmann
                              (GANETI_RAPI_VERSION, group)), query, None)
1829 414ebaf1 Michael Hanselmann
1830 414ebaf1 Michael Hanselmann
  def DeleteGroupTags(self, group, tags, dry_run=False):
1831 414ebaf1 Michael Hanselmann
    """Deletes tags from a node group.
1832 414ebaf1 Michael Hanselmann

1833 414ebaf1 Michael Hanselmann
    @type group: str
1834 414ebaf1 Michael Hanselmann
    @param group: group to delete tags from
1835 414ebaf1 Michael Hanselmann
    @type tags: list of string
1836 414ebaf1 Michael Hanselmann
    @param tags: tags to delete
1837 414ebaf1 Michael Hanselmann
    @type dry_run: bool
1838 414ebaf1 Michael Hanselmann
    @param dry_run: whether to perform a dry run
1839 414ebaf1 Michael Hanselmann
    @rtype: string
1840 414ebaf1 Michael Hanselmann
    @return: job id
1841 414ebaf1 Michael Hanselmann

1842 414ebaf1 Michael Hanselmann
    """
1843 414ebaf1 Michael Hanselmann
    query = [("tag", t) for t in tags]
1844 414ebaf1 Michael Hanselmann
    if dry_run:
1845 414ebaf1 Michael Hanselmann
      query.append(("dry-run", 1))
1846 414ebaf1 Michael Hanselmann
1847 414ebaf1 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE,
1848 414ebaf1 Michael Hanselmann
                             ("/%s/groups/%s/tags" %
1849 414ebaf1 Michael Hanselmann
                              (GANETI_RAPI_VERSION, group)), query, None)
1850 414ebaf1 Michael Hanselmann
1851 2e5c33db Iustin Pop
  def Query(self, what, fields, qfilter=None):
1852 208a6cff Michael Hanselmann
    """Retrieves information about resources.
1853 208a6cff Michael Hanselmann

1854 208a6cff Michael Hanselmann
    @type what: string
1855 208a6cff Michael Hanselmann
    @param what: Resource name, one of L{constants.QR_VIA_RAPI}
1856 208a6cff Michael Hanselmann
    @type fields: list of string
1857 208a6cff Michael Hanselmann
    @param fields: Requested fields
1858 2e5c33db Iustin Pop
    @type qfilter: None or list
1859 2e5c33db Iustin Pop
    @param qfilter: Query filter
1860 208a6cff Michael Hanselmann

1861 208a6cff Michael Hanselmann
    @rtype: string
1862 208a6cff Michael Hanselmann
    @return: job id
1863 208a6cff Michael Hanselmann

1864 208a6cff Michael Hanselmann
    """
1865 208a6cff Michael Hanselmann
    body = {
1866 208a6cff Michael Hanselmann
      "fields": fields,
1867 208a6cff Michael Hanselmann
      }
1868 208a6cff Michael Hanselmann
1869 2e5c33db Iustin Pop
    if qfilter is not None:
1870 2e5c33db Iustin Pop
      body["qfilter"] = qfilter
1871 2e5c33db Iustin Pop
      # TODO: remove this after 2.7
1872 2e5c33db Iustin Pop
      body["filter"] = qfilter
1873 208a6cff Michael Hanselmann
1874 208a6cff Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1875 208a6cff Michael Hanselmann
                             ("/%s/query/%s" %
1876 208a6cff Michael Hanselmann
                              (GANETI_RAPI_VERSION, what)), None, body)
1877 208a6cff Michael Hanselmann
1878 208a6cff Michael Hanselmann
  def QueryFields(self, what, fields=None):
1879 208a6cff Michael Hanselmann
    """Retrieves available fields for a resource.
1880 208a6cff Michael Hanselmann

1881 208a6cff Michael Hanselmann
    @type what: string
1882 208a6cff Michael Hanselmann
    @param what: Resource name, one of L{constants.QR_VIA_RAPI}
1883 208a6cff Michael Hanselmann
    @type fields: list of string
1884 208a6cff Michael Hanselmann
    @param fields: Requested fields
1885 208a6cff Michael Hanselmann

1886 208a6cff Michael Hanselmann
    @rtype: string
1887 208a6cff Michael Hanselmann
    @return: job id
1888 208a6cff Michael Hanselmann

1889 208a6cff Michael Hanselmann
    """
1890 208a6cff Michael Hanselmann
    query = []
1891 208a6cff Michael Hanselmann
1892 208a6cff Michael Hanselmann
    if fields is not None:
1893 208a6cff Michael Hanselmann
      query.append(("fields", ",".join(fields)))
1894 208a6cff Michael Hanselmann
1895 208a6cff Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1896 208a6cff Michael Hanselmann
                             ("/%s/query/%s/fields" %
1897 208a6cff Michael Hanselmann
                              (GANETI_RAPI_VERSION, what)), query, None)