Statistics
| Branch: | Tag: | Revision:

root / lib / rapi / client.py @ 8062638d

History | View | Annotate | Download (39.5 kB)

1 95ab4de9 David Knowles
#
2 95ab4de9 David Knowles
#
3 95ab4de9 David Knowles
4 95ab4de9 David Knowles
# Copyright (C) 2010 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 2a7c3583 Michael Hanselmann
43 2a7c3583 Michael Hanselmann
try:
44 2a7c3583 Michael Hanselmann
  from cStringIO import StringIO
45 2a7c3583 Michael Hanselmann
except ImportError:
46 2a7c3583 Michael Hanselmann
  from StringIO import StringIO
47 95ab4de9 David Knowles
48 95ab4de9 David Knowles
49 9279e986 Michael Hanselmann
GANETI_RAPI_PORT = 5080
50 a198b2d9 Michael Hanselmann
GANETI_RAPI_VERSION = 2
51 9279e986 Michael Hanselmann
52 95ab4de9 David Knowles
HTTP_DELETE = "DELETE"
53 95ab4de9 David Knowles
HTTP_GET = "GET"
54 95ab4de9 David Knowles
HTTP_PUT = "PUT"
55 95ab4de9 David Knowles
HTTP_POST = "POST"
56 9279e986 Michael Hanselmann
HTTP_OK = 200
57 7eac4a4d Michael Hanselmann
HTTP_NOT_FOUND = 404
58 9279e986 Michael Hanselmann
HTTP_APP_JSON = "application/json"
59 9279e986 Michael Hanselmann
60 95ab4de9 David Knowles
REPLACE_DISK_PRI = "replace_on_primary"
61 95ab4de9 David Knowles
REPLACE_DISK_SECONDARY = "replace_on_secondary"
62 95ab4de9 David Knowles
REPLACE_DISK_CHG = "replace_new_secondary"
63 95ab4de9 David Knowles
REPLACE_DISK_AUTO = "replace_auto"
64 1068639f Michael Hanselmann
65 1068639f Michael Hanselmann
NODE_ROLE_DRAINED = "drained"
66 1068639f Michael Hanselmann
NODE_ROLE_MASTER_CANDIATE = "master-candidate"
67 1068639f Michael Hanselmann
NODE_ROLE_MASTER = "master"
68 1068639f Michael Hanselmann
NODE_ROLE_OFFLINE = "offline"
69 1068639f Michael Hanselmann
NODE_ROLE_REGULAR = "regular"
70 95ab4de9 David Knowles
71 8a47b447 Michael Hanselmann
# Internal constants
72 8a47b447 Michael Hanselmann
_REQ_DATA_VERSION_FIELD = "__version__"
73 8a47b447 Michael Hanselmann
_INST_CREATE_REQV1 = "instance-create-reqv1"
74 48436b97 Michael Hanselmann
_INST_NIC_PARAMS = frozenset(["mac", "ip", "mode", "link", "bridge"])
75 48436b97 Michael Hanselmann
_INST_CREATE_V0_DISK_PARAMS = frozenset(["size"])
76 48436b97 Michael Hanselmann
_INST_CREATE_V0_PARAMS = frozenset([
77 48436b97 Michael Hanselmann
  "os", "pnode", "snode", "iallocator", "start", "ip_check", "name_check",
78 48436b97 Michael Hanselmann
  "hypervisor", "file_storage_dir", "file_driver", "dry_run",
79 48436b97 Michael Hanselmann
  ])
80 48436b97 Michael Hanselmann
_INST_CREATE_V0_DPARAMS = frozenset(["beparams", "hvparams"])
81 8a47b447 Michael Hanselmann
82 2a7c3583 Michael Hanselmann
# Older pycURL versions don't have all error constants
83 2a7c3583 Michael Hanselmann
try:
84 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT = pycurl.E_SSL_CACERT
85 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT_BADFILE = pycurl.E_SSL_CACERT_BADFILE
86 2a7c3583 Michael Hanselmann
except AttributeError:
87 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT = 60
88 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT_BADFILE = 77
89 2a7c3583 Michael Hanselmann
90 2a7c3583 Michael Hanselmann
_CURL_SSL_CERT_ERRORS = frozenset([
91 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT,
92 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT_BADFILE,
93 2a7c3583 Michael Hanselmann
  ])
94 2a7c3583 Michael Hanselmann
95 95ab4de9 David Knowles
96 95ab4de9 David Knowles
class Error(Exception):
97 95ab4de9 David Knowles
  """Base error class for this module.
98 95ab4de9 David Knowles

99 95ab4de9 David Knowles
  """
100 95ab4de9 David Knowles
  pass
101 95ab4de9 David Knowles
102 95ab4de9 David Knowles
103 95ab4de9 David Knowles
class CertificateError(Error):
104 95ab4de9 David Knowles
  """Raised when a problem is found with the SSL certificate.
105 95ab4de9 David Knowles

106 95ab4de9 David Knowles
  """
107 95ab4de9 David Knowles
  pass
108 95ab4de9 David Knowles
109 95ab4de9 David Knowles
110 95ab4de9 David Knowles
class GanetiApiError(Error):
111 95ab4de9 David Knowles
  """Generic error raised from Ganeti API.
112 95ab4de9 David Knowles

113 95ab4de9 David Knowles
  """
114 8a019a03 Michael Hanselmann
  def __init__(self, msg, code=None):
115 8a019a03 Michael Hanselmann
    Error.__init__(self, msg)
116 8a019a03 Michael Hanselmann
    self.code = code
117 95ab4de9 David Knowles
118 95ab4de9 David Knowles
119 2a7c3583 Michael Hanselmann
def UsesRapiClient(fn):
120 2a7c3583 Michael Hanselmann
  """Decorator for code using RAPI client to initialize pycURL.
121 9279e986 Michael Hanselmann

122 9279e986 Michael Hanselmann
  """
123 2a7c3583 Michael Hanselmann
  def wrapper(*args, **kwargs):
124 2a7c3583 Michael Hanselmann
    # curl_global_init(3) and curl_global_cleanup(3) must be called with only
125 2a7c3583 Michael Hanselmann
    # one thread running. This check is just a safety measure -- it doesn't
126 2a7c3583 Michael Hanselmann
    # cover all cases.
127 2a7c3583 Michael Hanselmann
    assert threading.activeCount() == 1, \
128 2a7c3583 Michael Hanselmann
           "Found active threads when initializing pycURL"
129 2a7c3583 Michael Hanselmann
130 2a7c3583 Michael Hanselmann
    pycurl.global_init(pycurl.GLOBAL_ALL)
131 2a7c3583 Michael Hanselmann
    try:
132 2a7c3583 Michael Hanselmann
      return fn(*args, **kwargs)
133 2a7c3583 Michael Hanselmann
    finally:
134 2a7c3583 Michael Hanselmann
      pycurl.global_cleanup()
135 2a7c3583 Michael Hanselmann
136 2a7c3583 Michael Hanselmann
  return wrapper
137 2a7c3583 Michael Hanselmann
138 2a7c3583 Michael Hanselmann
139 2a7c3583 Michael Hanselmann
def GenericCurlConfig(verbose=False, use_signal=False,
140 2a7c3583 Michael Hanselmann
                      use_curl_cabundle=False, cafile=None, capath=None,
141 2a7c3583 Michael Hanselmann
                      proxy=None, verify_hostname=False,
142 2a7c3583 Michael Hanselmann
                      connect_timeout=None, timeout=None,
143 2a7c3583 Michael Hanselmann
                      _pycurl_version_fn=pycurl.version_info):
144 2a7c3583 Michael Hanselmann
  """Curl configuration function generator.
145 2a7c3583 Michael Hanselmann

146 2a7c3583 Michael Hanselmann
  @type verbose: bool
147 2a7c3583 Michael Hanselmann
  @param verbose: Whether to set cURL to verbose mode
148 2a7c3583 Michael Hanselmann
  @type use_signal: bool
149 2a7c3583 Michael Hanselmann
  @param use_signal: Whether to allow cURL to use signals
150 2a7c3583 Michael Hanselmann
  @type use_curl_cabundle: bool
151 2a7c3583 Michael Hanselmann
  @param use_curl_cabundle: Whether to use cURL's default CA bundle
152 2a7c3583 Michael Hanselmann
  @type cafile: string
153 2a7c3583 Michael Hanselmann
  @param cafile: In which file we can find the certificates
154 2a7c3583 Michael Hanselmann
  @type capath: string
155 2a7c3583 Michael Hanselmann
  @param capath: In which directory we can find the certificates
156 2a7c3583 Michael Hanselmann
  @type proxy: string
157 2a7c3583 Michael Hanselmann
  @param proxy: Proxy to use, None for default behaviour and empty string for
158 2a7c3583 Michael Hanselmann
                disabling proxies (see curl_easy_setopt(3))
159 2a7c3583 Michael Hanselmann
  @type verify_hostname: bool
160 2a7c3583 Michael Hanselmann
  @param verify_hostname: Whether to verify the remote peer certificate's
161 2a7c3583 Michael Hanselmann
                          commonName
162 2a7c3583 Michael Hanselmann
  @type connect_timeout: number
163 2a7c3583 Michael Hanselmann
  @param connect_timeout: Timeout for establishing connection in seconds
164 2a7c3583 Michael Hanselmann
  @type timeout: number
165 2a7c3583 Michael Hanselmann
  @param timeout: Timeout for complete transfer in seconds (see
166 2a7c3583 Michael Hanselmann
                  curl_easy_setopt(3)).
167 9279e986 Michael Hanselmann

168 9279e986 Michael Hanselmann
  """
169 2a7c3583 Michael Hanselmann
  if use_curl_cabundle and (cafile or capath):
170 2a7c3583 Michael Hanselmann
    raise Error("Can not use default CA bundle when CA file or path is set")
171 9279e986 Michael Hanselmann
172 2a7c3583 Michael Hanselmann
  def _ConfigCurl(curl, logger):
173 2a7c3583 Michael Hanselmann
    """Configures a cURL object
174 9279e986 Michael Hanselmann

175 2a7c3583 Michael Hanselmann
    @type curl: pycurl.Curl
176 2a7c3583 Michael Hanselmann
    @param curl: cURL object
177 9279e986 Michael Hanselmann

178 9279e986 Michael Hanselmann
    """
179 2a7c3583 Michael Hanselmann
    logger.debug("Using cURL version %s", pycurl.version)
180 2a7c3583 Michael Hanselmann
181 2a7c3583 Michael Hanselmann
    # pycurl.version_info returns a tuple with information about the used
182 2a7c3583 Michael Hanselmann
    # version of libcurl. Item 5 is the SSL library linked to it.
183 2a7c3583 Michael Hanselmann
    # e.g.: (3, '7.18.0', 463360, 'x86_64-pc-linux-gnu', 1581, 'GnuTLS/2.0.4',
184 2a7c3583 Michael Hanselmann
    # 0, '1.2.3.3', ...)
185 2a7c3583 Michael Hanselmann
    sslver = _pycurl_version_fn()[5]
186 2a7c3583 Michael Hanselmann
    if not sslver:
187 2a7c3583 Michael Hanselmann
      raise Error("No SSL support in cURL")
188 2a7c3583 Michael Hanselmann
189 2a7c3583 Michael Hanselmann
    lcsslver = sslver.lower()
190 2a7c3583 Michael Hanselmann
    if lcsslver.startswith("openssl/"):
191 2a7c3583 Michael Hanselmann
      pass
192 2a7c3583 Michael Hanselmann
    elif lcsslver.startswith("gnutls/"):
193 2a7c3583 Michael Hanselmann
      if capath:
194 2a7c3583 Michael Hanselmann
        raise Error("cURL linked against GnuTLS has no support for a"
195 2a7c3583 Michael Hanselmann
                    " CA path (%s)" % (pycurl.version, ))
196 9279e986 Michael Hanselmann
    else:
197 2a7c3583 Michael Hanselmann
      raise NotImplementedError("cURL uses unsupported SSL version '%s'" %
198 2a7c3583 Michael Hanselmann
                                sslver)
199 2a7c3583 Michael Hanselmann
200 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.VERBOSE, verbose)
201 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.NOSIGNAL, not use_signal)
202 2a7c3583 Michael Hanselmann
203 2a7c3583 Michael Hanselmann
    # Whether to verify remote peer's CN
204 2a7c3583 Michael Hanselmann
    if verify_hostname:
205 2a7c3583 Michael Hanselmann
      # curl_easy_setopt(3): "When CURLOPT_SSL_VERIFYHOST is 2, that
206 2a7c3583 Michael Hanselmann
      # certificate must indicate that the server is the server to which you
207 2a7c3583 Michael Hanselmann
      # meant to connect, or the connection fails. [...] When the value is 1,
208 2a7c3583 Michael Hanselmann
      # the certificate must contain a Common Name field, but it doesn't matter
209 2a7c3583 Michael Hanselmann
      # what name it says. [...]"
210 2a7c3583 Michael Hanselmann
      curl.setopt(pycurl.SSL_VERIFYHOST, 2)
211 beba56ae Michael Hanselmann
    else:
212 2a7c3583 Michael Hanselmann
      curl.setopt(pycurl.SSL_VERIFYHOST, 0)
213 2a7c3583 Michael Hanselmann
214 2a7c3583 Michael Hanselmann
    if cafile or capath or use_curl_cabundle:
215 2a7c3583 Michael Hanselmann
      # Require certificates to be checked
216 2a7c3583 Michael Hanselmann
      curl.setopt(pycurl.SSL_VERIFYPEER, True)
217 2a7c3583 Michael Hanselmann
      if cafile:
218 2a7c3583 Michael Hanselmann
        curl.setopt(pycurl.CAINFO, str(cafile))
219 2a7c3583 Michael Hanselmann
      if capath:
220 2a7c3583 Michael Hanselmann
        curl.setopt(pycurl.CAPATH, str(capath))
221 2a7c3583 Michael Hanselmann
      # Not changing anything for using default CA bundle
222 2a7c3583 Michael Hanselmann
    else:
223 2a7c3583 Michael Hanselmann
      # Disable SSL certificate verification
224 2a7c3583 Michael Hanselmann
      curl.setopt(pycurl.SSL_VERIFYPEER, False)
225 9279e986 Michael Hanselmann
226 2a7c3583 Michael Hanselmann
    if proxy is not None:
227 2a7c3583 Michael Hanselmann
      curl.setopt(pycurl.PROXY, str(proxy))
228 9279e986 Michael Hanselmann
229 2a7c3583 Michael Hanselmann
    # Timeouts
230 2a7c3583 Michael Hanselmann
    if connect_timeout is not None:
231 2a7c3583 Michael Hanselmann
      curl.setopt(pycurl.CONNECTTIMEOUT, connect_timeout)
232 2a7c3583 Michael Hanselmann
    if timeout is not None:
233 2a7c3583 Michael Hanselmann
      curl.setopt(pycurl.TIMEOUT, timeout)
234 9279e986 Michael Hanselmann
235 2a7c3583 Michael Hanselmann
  return _ConfigCurl
236 9279e986 Michael Hanselmann
237 9279e986 Michael Hanselmann
238 95ab4de9 David Knowles
class GanetiRapiClient(object):
239 95ab4de9 David Knowles
  """Ganeti RAPI client.
240 95ab4de9 David Knowles

241 95ab4de9 David Knowles
  """
242 95ab4de9 David Knowles
  USER_AGENT = "Ganeti RAPI Client"
243 d3844674 Michael Hanselmann
  _json_encoder = simplejson.JSONEncoder(sort_keys=True)
244 95ab4de9 David Knowles
245 9279e986 Michael Hanselmann
  def __init__(self, host, port=GANETI_RAPI_PORT,
246 2a7c3583 Michael Hanselmann
               username=None, password=None, logger=logging,
247 a5eba783 Michael Hanselmann
               curl_config_fn=None, curl_factory=None):
248 2a7c3583 Michael Hanselmann
    """Initializes this class.
249 95ab4de9 David Knowles

250 9279e986 Michael Hanselmann
    @type host: string
251 9279e986 Michael Hanselmann
    @param host: the ganeti cluster master to interact with
252 95ab4de9 David Knowles
    @type port: int
253 9279e986 Michael Hanselmann
    @param port: the port on which the RAPI is running (default is 5080)
254 9279e986 Michael Hanselmann
    @type username: string
255 95ab4de9 David Knowles
    @param username: the username to connect with
256 9279e986 Michael Hanselmann
    @type password: string
257 95ab4de9 David Knowles
    @param password: the password to connect with
258 2a7c3583 Michael Hanselmann
    @type curl_config_fn: callable
259 2a7c3583 Michael Hanselmann
    @param curl_config_fn: Function to configure C{pycurl.Curl} object
260 9279e986 Michael Hanselmann
    @param logger: Logging object
261 95ab4de9 David Knowles

262 95ab4de9 David Knowles
    """
263 a5eba783 Michael Hanselmann
    self._username = username
264 a5eba783 Michael Hanselmann
    self._password = password
265 9279e986 Michael Hanselmann
    self._logger = logger
266 a5eba783 Michael Hanselmann
    self._curl_config_fn = curl_config_fn
267 a5eba783 Michael Hanselmann
    self._curl_factory = curl_factory
268 95ab4de9 David Knowles
269 1a8337f2 Manuel Franceschini
    try:
270 1a8337f2 Manuel Franceschini
      socket.inet_pton(socket.AF_INET6, host)
271 1a8337f2 Manuel Franceschini
      address = "[%s]:%s" % (host, port)
272 1a8337f2 Manuel Franceschini
    except socket.error:
273 1a8337f2 Manuel Franceschini
      address = "%s:%s" % (host, port)
274 1a8337f2 Manuel Franceschini
275 1a8337f2 Manuel Franceschini
    self._base_url = "https://%s" % address
276 f2f88abf David Knowles
277 a5eba783 Michael Hanselmann
    if username is not None:
278 a5eba783 Michael Hanselmann
      if password is None:
279 a5eba783 Michael Hanselmann
        raise Error("Password not specified")
280 a5eba783 Michael Hanselmann
    elif password:
281 a5eba783 Michael Hanselmann
      raise Error("Specified password without username")
282 a5eba783 Michael Hanselmann
283 a5eba783 Michael Hanselmann
  def _CreateCurl(self):
284 a5eba783 Michael Hanselmann
    """Creates a cURL object.
285 a5eba783 Michael Hanselmann

286 a5eba783 Michael Hanselmann
    """
287 a5eba783 Michael Hanselmann
    # Create pycURL object if no factory is provided
288 a5eba783 Michael Hanselmann
    if self._curl_factory:
289 a5eba783 Michael Hanselmann
      curl = self._curl_factory()
290 a5eba783 Michael Hanselmann
    else:
291 2a7c3583 Michael Hanselmann
      curl = pycurl.Curl()
292 2a7c3583 Michael Hanselmann
293 2a7c3583 Michael Hanselmann
    # Default cURL settings
294 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.VERBOSE, False)
295 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.FOLLOWLOCATION, False)
296 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.MAXREDIRS, 5)
297 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.NOSIGNAL, True)
298 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.USERAGENT, self.USER_AGENT)
299 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.SSL_VERIFYHOST, 0)
300 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.SSL_VERIFYPEER, False)
301 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.HTTPHEADER, [
302 2a7c3583 Michael Hanselmann
      "Accept: %s" % HTTP_APP_JSON,
303 2a7c3583 Michael Hanselmann
      "Content-type: %s" % HTTP_APP_JSON,
304 2a7c3583 Michael Hanselmann
      ])
305 2a7c3583 Michael Hanselmann
306 a5eba783 Michael Hanselmann
    assert ((self._username is None and self._password is None) ^
307 a5eba783 Michael Hanselmann
            (self._username is not None and self._password is not None))
308 a5eba783 Michael Hanselmann
309 a5eba783 Michael Hanselmann
    if self._username:
310 a5eba783 Michael Hanselmann
      # Setup authentication
311 2a7c3583 Michael Hanselmann
      curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_BASIC)
312 a5eba783 Michael Hanselmann
      curl.setopt(pycurl.USERPWD,
313 a5eba783 Michael Hanselmann
                  str("%s:%s" % (self._username, self._password)))
314 9279e986 Michael Hanselmann
315 2a7c3583 Michael Hanselmann
    # Call external configuration function
316 a5eba783 Michael Hanselmann
    if self._curl_config_fn:
317 a5eba783 Michael Hanselmann
      self._curl_config_fn(curl, self._logger)
318 f2f88abf David Knowles
319 a5eba783 Michael Hanselmann
    return curl
320 95ab4de9 David Knowles
321 10f5ab6c Michael Hanselmann
  @staticmethod
322 10f5ab6c Michael Hanselmann
  def _EncodeQuery(query):
323 10f5ab6c Michael Hanselmann
    """Encode query values for RAPI URL.
324 10f5ab6c Michael Hanselmann

325 10f5ab6c Michael Hanselmann
    @type query: list of two-tuples
326 10f5ab6c Michael Hanselmann
    @param query: Query arguments
327 10f5ab6c Michael Hanselmann
    @rtype: list
328 10f5ab6c Michael Hanselmann
    @return: Query list with encoded values
329 10f5ab6c Michael Hanselmann

330 10f5ab6c Michael Hanselmann
    """
331 10f5ab6c Michael Hanselmann
    result = []
332 10f5ab6c Michael Hanselmann
333 10f5ab6c Michael Hanselmann
    for name, value in query:
334 10f5ab6c Michael Hanselmann
      if value is None:
335 10f5ab6c Michael Hanselmann
        result.append((name, ""))
336 10f5ab6c Michael Hanselmann
337 10f5ab6c Michael Hanselmann
      elif isinstance(value, bool):
338 10f5ab6c Michael Hanselmann
        # Boolean values must be encoded as 0 or 1
339 10f5ab6c Michael Hanselmann
        result.append((name, int(value)))
340 10f5ab6c Michael Hanselmann
341 10f5ab6c Michael Hanselmann
      elif isinstance(value, (list, tuple, dict)):
342 10f5ab6c Michael Hanselmann
        raise ValueError("Invalid query data type %r" % type(value).__name__)
343 10f5ab6c Michael Hanselmann
344 10f5ab6c Michael Hanselmann
      else:
345 10f5ab6c Michael Hanselmann
        result.append((name, value))
346 10f5ab6c Michael Hanselmann
347 10f5ab6c Michael Hanselmann
    return result
348 10f5ab6c Michael Hanselmann
349 768747ed Michael Hanselmann
  def _SendRequest(self, method, path, query, content):
350 95ab4de9 David Knowles
    """Sends an HTTP request.
351 95ab4de9 David Knowles

352 95ab4de9 David Knowles
    This constructs a full URL, encodes and decodes HTTP bodies, and
353 95ab4de9 David Knowles
    handles invalid responses in a pythonic way.
354 95ab4de9 David Knowles

355 768747ed Michael Hanselmann
    @type method: string
356 95ab4de9 David Knowles
    @param method: HTTP method to use
357 768747ed Michael Hanselmann
    @type path: string
358 95ab4de9 David Knowles
    @param path: HTTP URL path
359 95ab4de9 David Knowles
    @type query: list of two-tuples
360 95ab4de9 David Knowles
    @param query: query arguments to pass to urllib.urlencode
361 95ab4de9 David Knowles
    @type content: str or None
362 95ab4de9 David Knowles
    @param content: HTTP body content
363 95ab4de9 David Knowles

364 95ab4de9 David Knowles
    @rtype: str
365 95ab4de9 David Knowles
    @return: JSON-Decoded response
366 95ab4de9 David Knowles

367 f2f88abf David Knowles
    @raises CertificateError: If an invalid SSL certificate is found
368 95ab4de9 David Knowles
    @raises GanetiApiError: If an invalid response is returned
369 95ab4de9 David Knowles

370 95ab4de9 David Knowles
    """
371 ccd6b542 Michael Hanselmann
    assert path.startswith("/")
372 ccd6b542 Michael Hanselmann
373 a5eba783 Michael Hanselmann
    curl = self._CreateCurl()
374 2a7c3583 Michael Hanselmann
375 8306e0e4 Michael Hanselmann
    if content is not None:
376 d3844674 Michael Hanselmann
      encoded_content = self._json_encoder.encode(content)
377 d3844674 Michael Hanselmann
    else:
378 2a7c3583 Michael Hanselmann
      encoded_content = ""
379 95ab4de9 David Knowles
380 ccd6b542 Michael Hanselmann
    # Build URL
381 f961e2ba Michael Hanselmann
    urlparts = [self._base_url, path]
382 ccd6b542 Michael Hanselmann
    if query:
383 f961e2ba Michael Hanselmann
      urlparts.append("?")
384 f961e2ba Michael Hanselmann
      urlparts.append(urllib.urlencode(self._EncodeQuery(query)))
385 9279e986 Michael Hanselmann
386 f961e2ba Michael Hanselmann
    url = "".join(urlparts)
387 f961e2ba Michael Hanselmann
388 a5eba783 Michael Hanselmann
    self._logger.debug("Sending request %s %s (content=%r)",
389 a5eba783 Michael Hanselmann
                       method, url, encoded_content)
390 2a7c3583 Michael Hanselmann
391 2a7c3583 Michael Hanselmann
    # Buffer for response
392 2a7c3583 Michael Hanselmann
    encoded_resp_body = StringIO()
393 f961e2ba Michael Hanselmann
394 2a7c3583 Michael Hanselmann
    # Configure cURL
395 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.CUSTOMREQUEST, str(method))
396 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.URL, str(url))
397 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.POSTFIELDS, str(encoded_content))
398 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.WRITEFUNCTION, encoded_resp_body.write)
399 9279e986 Michael Hanselmann
400 f2f88abf David Knowles
    try:
401 2a7c3583 Michael Hanselmann
      # Send request and wait for response
402 2a7c3583 Michael Hanselmann
      try:
403 2a7c3583 Michael Hanselmann
        curl.perform()
404 2a7c3583 Michael Hanselmann
      except pycurl.error, err:
405 2a7c3583 Michael Hanselmann
        if err.args[0] in _CURL_SSL_CERT_ERRORS:
406 2a7c3583 Michael Hanselmann
          raise CertificateError("SSL certificate error %s" % err)
407 2a7c3583 Michael Hanselmann
408 2a7c3583 Michael Hanselmann
        raise GanetiApiError(str(err))
409 2a7c3583 Michael Hanselmann
    finally:
410 2a7c3583 Michael Hanselmann
      # Reset settings to not keep references to large objects in memory
411 2a7c3583 Michael Hanselmann
      # between requests
412 2a7c3583 Michael Hanselmann
      curl.setopt(pycurl.POSTFIELDS, "")
413 2a7c3583 Michael Hanselmann
      curl.setopt(pycurl.WRITEFUNCTION, lambda _: None)
414 2a7c3583 Michael Hanselmann
415 2a7c3583 Michael Hanselmann
    # Get HTTP response code
416 2a7c3583 Michael Hanselmann
    http_code = curl.getinfo(pycurl.RESPONSE_CODE)
417 2a7c3583 Michael Hanselmann
418 2a7c3583 Michael Hanselmann
    # Was anything written to the response buffer?
419 2a7c3583 Michael Hanselmann
    if encoded_resp_body.tell():
420 2a7c3583 Michael Hanselmann
      response_content = simplejson.loads(encoded_resp_body.getvalue())
421 d3844674 Michael Hanselmann
    else:
422 d3844674 Michael Hanselmann
      response_content = None
423 95ab4de9 David Knowles
424 2a7c3583 Michael Hanselmann
    if http_code != HTTP_OK:
425 d3844674 Michael Hanselmann
      if isinstance(response_content, dict):
426 95ab4de9 David Knowles
        msg = ("%s %s: %s" %
427 d3844674 Michael Hanselmann
               (response_content["code"],
428 d3844674 Michael Hanselmann
                response_content["message"],
429 d3844674 Michael Hanselmann
                response_content["explain"]))
430 95ab4de9 David Knowles
      else:
431 d3844674 Michael Hanselmann
        msg = str(response_content)
432 d3844674 Michael Hanselmann
433 2a7c3583 Michael Hanselmann
      raise GanetiApiError(msg, code=http_code)
434 95ab4de9 David Knowles
435 d3844674 Michael Hanselmann
    return response_content
436 95ab4de9 David Knowles
437 95ab4de9 David Knowles
  def GetVersion(self):
438 cab667cc David Knowles
    """Gets the Remote API version running on the cluster.
439 95ab4de9 David Knowles

440 95ab4de9 David Knowles
    @rtype: int
441 f2f88abf David Knowles
    @return: Ganeti Remote API version
442 95ab4de9 David Knowles

443 95ab4de9 David Knowles
    """
444 768747ed Michael Hanselmann
    return self._SendRequest(HTTP_GET, "/version", None, None)
445 95ab4de9 David Knowles
446 7eac4a4d Michael Hanselmann
  def GetFeatures(self):
447 7eac4a4d Michael Hanselmann
    """Gets the list of optional features supported by RAPI server.
448 7eac4a4d Michael Hanselmann

449 7eac4a4d Michael Hanselmann
    @rtype: list
450 7eac4a4d Michael Hanselmann
    @return: List of optional features
451 7eac4a4d Michael Hanselmann

452 7eac4a4d Michael Hanselmann
    """
453 7eac4a4d Michael Hanselmann
    try:
454 7eac4a4d Michael Hanselmann
      return self._SendRequest(HTTP_GET, "/%s/features" % GANETI_RAPI_VERSION,
455 7eac4a4d Michael Hanselmann
                               None, None)
456 7eac4a4d Michael Hanselmann
    except GanetiApiError, err:
457 7eac4a4d Michael Hanselmann
      # Older RAPI servers don't support this resource
458 7eac4a4d Michael Hanselmann
      if err.code == HTTP_NOT_FOUND:
459 7eac4a4d Michael Hanselmann
        return []
460 7eac4a4d Michael Hanselmann
461 7eac4a4d Michael Hanselmann
      raise
462 7eac4a4d Michael Hanselmann
463 95ab4de9 David Knowles
  def GetOperatingSystems(self):
464 95ab4de9 David Knowles
    """Gets the Operating Systems running in the Ganeti cluster.
465 95ab4de9 David Knowles

466 95ab4de9 David Knowles
    @rtype: list of str
467 95ab4de9 David Knowles
    @return: operating systems
468 95ab4de9 David Knowles

469 95ab4de9 David Knowles
    """
470 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET, "/%s/os" % GANETI_RAPI_VERSION,
471 a198b2d9 Michael Hanselmann
                             None, None)
472 95ab4de9 David Knowles
473 95ab4de9 David Knowles
  def GetInfo(self):
474 95ab4de9 David Knowles
    """Gets info about the cluster.
475 95ab4de9 David Knowles

476 95ab4de9 David Knowles
    @rtype: dict
477 95ab4de9 David Knowles
    @return: information about the cluster
478 95ab4de9 David Knowles

479 95ab4de9 David Knowles
    """
480 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET, "/%s/info" % GANETI_RAPI_VERSION,
481 a198b2d9 Michael Hanselmann
                             None, None)
482 95ab4de9 David Knowles
483 95ab4de9 David Knowles
  def GetClusterTags(self):
484 95ab4de9 David Knowles
    """Gets the cluster tags.
485 95ab4de9 David Knowles

486 95ab4de9 David Knowles
    @rtype: list of str
487 95ab4de9 David Knowles
    @return: cluster tags
488 95ab4de9 David Knowles

489 95ab4de9 David Knowles
    """
490 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET, "/%s/tags" % GANETI_RAPI_VERSION,
491 a198b2d9 Michael Hanselmann
                             None, None)
492 95ab4de9 David Knowles
493 95ab4de9 David Knowles
  def AddClusterTags(self, tags, dry_run=False):
494 95ab4de9 David Knowles
    """Adds tags to the cluster.
495 95ab4de9 David Knowles

496 95ab4de9 David Knowles
    @type tags: list of str
497 95ab4de9 David Knowles
    @param tags: tags to add to the cluster
498 95ab4de9 David Knowles
    @type dry_run: bool
499 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
500 95ab4de9 David Knowles

501 95ab4de9 David Knowles
    @rtype: int
502 95ab4de9 David Knowles
    @return: job id
503 95ab4de9 David Knowles

504 95ab4de9 David Knowles
    """
505 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
506 95ab4de9 David Knowles
    if dry_run:
507 95ab4de9 David Knowles
      query.append(("dry-run", 1))
508 95ab4de9 David Knowles
509 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT, "/%s/tags" % GANETI_RAPI_VERSION,
510 a198b2d9 Michael Hanselmann
                             query, None)
511 95ab4de9 David Knowles
512 95ab4de9 David Knowles
  def DeleteClusterTags(self, tags, dry_run=False):
513 95ab4de9 David Knowles
    """Deletes tags from the cluster.
514 95ab4de9 David Knowles

515 95ab4de9 David Knowles
    @type tags: list of str
516 95ab4de9 David Knowles
    @param tags: tags to delete
517 95ab4de9 David Knowles
    @type dry_run: bool
518 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
519 95ab4de9 David Knowles

520 95ab4de9 David Knowles
    """
521 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
522 95ab4de9 David Knowles
    if dry_run:
523 95ab4de9 David Knowles
      query.append(("dry-run", 1))
524 95ab4de9 David Knowles
525 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE, "/%s/tags" % GANETI_RAPI_VERSION,
526 a198b2d9 Michael Hanselmann
                             query, None)
527 95ab4de9 David Knowles
528 95ab4de9 David Knowles
  def GetInstances(self, bulk=False):
529 95ab4de9 David Knowles
    """Gets information about instances on the cluster.
530 95ab4de9 David Knowles

531 95ab4de9 David Knowles
    @type bulk: bool
532 95ab4de9 David Knowles
    @param bulk: whether to return all information about all instances
533 95ab4de9 David Knowles

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

537 95ab4de9 David Knowles
    """
538 95ab4de9 David Knowles
    query = []
539 95ab4de9 David Knowles
    if bulk:
540 95ab4de9 David Knowles
      query.append(("bulk", 1))
541 95ab4de9 David Knowles
542 a198b2d9 Michael Hanselmann
    instances = self._SendRequest(HTTP_GET,
543 a198b2d9 Michael Hanselmann
                                  "/%s/instances" % GANETI_RAPI_VERSION,
544 a198b2d9 Michael Hanselmann
                                  query, None)
545 95ab4de9 David Knowles
    if bulk:
546 95ab4de9 David Knowles
      return instances
547 95ab4de9 David Knowles
    else:
548 95ab4de9 David Knowles
      return [i["id"] for i in instances]
549 95ab4de9 David Knowles
550 591e5103 Michael Hanselmann
  def GetInstance(self, instance):
551 95ab4de9 David Knowles
    """Gets information about an instance.
552 95ab4de9 David Knowles

553 95ab4de9 David Knowles
    @type instance: str
554 95ab4de9 David Knowles
    @param instance: instance whose info to return
555 95ab4de9 David Knowles

556 95ab4de9 David Knowles
    @rtype: dict
557 95ab4de9 David Knowles
    @return: info about the instance
558 95ab4de9 David Knowles

559 95ab4de9 David Knowles
    """
560 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
561 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s" %
562 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, None)
563 95ab4de9 David Knowles
564 591e5103 Michael Hanselmann
  def GetInstanceInfo(self, instance, static=None):
565 591e5103 Michael Hanselmann
    """Gets information about an instance.
566 591e5103 Michael Hanselmann

567 591e5103 Michael Hanselmann
    @type instance: string
568 591e5103 Michael Hanselmann
    @param instance: Instance name
569 591e5103 Michael Hanselmann
    @rtype: string
570 591e5103 Michael Hanselmann
    @return: Job ID
571 591e5103 Michael Hanselmann

572 591e5103 Michael Hanselmann
    """
573 591e5103 Michael Hanselmann
    if static is not None:
574 591e5103 Michael Hanselmann
      query = [("static", static)]
575 591e5103 Michael Hanselmann
    else:
576 591e5103 Michael Hanselmann
      query = None
577 591e5103 Michael Hanselmann
578 591e5103 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
579 591e5103 Michael Hanselmann
                             ("/%s/instances/%s/info" %
580 591e5103 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
581 591e5103 Michael Hanselmann
582 8a47b447 Michael Hanselmann
  def CreateInstance(self, mode, name, disk_template, disks, nics,
583 8a47b447 Michael Hanselmann
                     **kwargs):
584 95ab4de9 David Knowles
    """Creates a new instance.
585 95ab4de9 David Knowles

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

588 8a47b447 Michael Hanselmann
    @type mode: string
589 8a47b447 Michael Hanselmann
    @param mode: Instance creation mode
590 8a47b447 Michael Hanselmann
    @type name: string
591 8a47b447 Michael Hanselmann
    @param name: Hostname of the instance to create
592 8a47b447 Michael Hanselmann
    @type disk_template: string
593 8a47b447 Michael Hanselmann
    @param disk_template: Disk template for instance (e.g. plain, diskless,
594 8a47b447 Michael Hanselmann
                          file, or drbd)
595 8a47b447 Michael Hanselmann
    @type disks: list of dicts
596 8a47b447 Michael Hanselmann
    @param disks: List of disk definitions
597 8a47b447 Michael Hanselmann
    @type nics: list of dicts
598 8a47b447 Michael Hanselmann
    @param nics: List of NIC definitions
599 95ab4de9 David Knowles
    @type dry_run: bool
600 8a47b447 Michael Hanselmann
    @keyword dry_run: whether to perform a dry run
601 95ab4de9 David Knowles

602 95ab4de9 David Knowles
    @rtype: int
603 95ab4de9 David Knowles
    @return: job id
604 95ab4de9 David Knowles

605 95ab4de9 David Knowles
    """
606 95ab4de9 David Knowles
    query = []
607 8a47b447 Michael Hanselmann
608 8a47b447 Michael Hanselmann
    if kwargs.get("dry_run"):
609 95ab4de9 David Knowles
      query.append(("dry-run", 1))
610 95ab4de9 David Knowles
611 8a47b447 Michael Hanselmann
    if _INST_CREATE_REQV1 in self.GetFeatures():
612 8a47b447 Michael Hanselmann
      # All required fields for request data version 1
613 8a47b447 Michael Hanselmann
      body = {
614 8a47b447 Michael Hanselmann
        _REQ_DATA_VERSION_FIELD: 1,
615 8a47b447 Michael Hanselmann
        "mode": mode,
616 8a47b447 Michael Hanselmann
        "name": name,
617 8a47b447 Michael Hanselmann
        "disk_template": disk_template,
618 8a47b447 Michael Hanselmann
        "disks": disks,
619 8a47b447 Michael Hanselmann
        "nics": nics,
620 8a47b447 Michael Hanselmann
        }
621 8a47b447 Michael Hanselmann
622 8a47b447 Michael Hanselmann
      conflicts = set(kwargs.iterkeys()) & set(body.iterkeys())
623 8a47b447 Michael Hanselmann
      if conflicts:
624 8a47b447 Michael Hanselmann
        raise GanetiApiError("Required fields can not be specified as"
625 8a47b447 Michael Hanselmann
                             " keywords: %s" % ", ".join(conflicts))
626 8a47b447 Michael Hanselmann
627 8a47b447 Michael Hanselmann
      body.update((key, value) for key, value in kwargs.iteritems()
628 8a47b447 Michael Hanselmann
                  if key != "dry_run")
629 8a47b447 Michael Hanselmann
    else:
630 48436b97 Michael Hanselmann
      # Old request format (version 0)
631 48436b97 Michael Hanselmann
632 48436b97 Michael Hanselmann
      # The following code must make sure that an exception is raised when an
633 48436b97 Michael Hanselmann
      # unsupported setting is requested by the caller. Otherwise this can lead
634 48436b97 Michael Hanselmann
      # to bugs difficult to find. The interface of this function must stay
635 8a47b447 Michael Hanselmann
      # exactly the same for version 0 and 1 (e.g. they aren't allowed to
636 8a47b447 Michael Hanselmann
      # require different data types).
637 48436b97 Michael Hanselmann
638 48436b97 Michael Hanselmann
      # Validate disks
639 48436b97 Michael Hanselmann
      for idx, disk in enumerate(disks):
640 48436b97 Michael Hanselmann
        unsupported = set(disk.keys()) - _INST_CREATE_V0_DISK_PARAMS
641 48436b97 Michael Hanselmann
        if unsupported:
642 48436b97 Michael Hanselmann
          raise GanetiApiError("Server supports request version 0 only, but"
643 48436b97 Michael Hanselmann
                               " disk %s specifies the unsupported parameters"
644 48436b97 Michael Hanselmann
                               " %s, allowed are %s" %
645 48436b97 Michael Hanselmann
                               (idx, unsupported,
646 48436b97 Michael Hanselmann
                                list(_INST_CREATE_V0_DISK_PARAMS)))
647 48436b97 Michael Hanselmann
648 48436b97 Michael Hanselmann
      assert (len(_INST_CREATE_V0_DISK_PARAMS) == 1 and
649 48436b97 Michael Hanselmann
              "size" in _INST_CREATE_V0_DISK_PARAMS)
650 48436b97 Michael Hanselmann
      disk_sizes = [disk["size"] for disk in disks]
651 48436b97 Michael Hanselmann
652 48436b97 Michael Hanselmann
      # Validate NICs
653 48436b97 Michael Hanselmann
      if not nics:
654 48436b97 Michael Hanselmann
        raise GanetiApiError("Server supports request version 0 only, but"
655 48436b97 Michael Hanselmann
                             " no NIC specified")
656 48436b97 Michael Hanselmann
      elif len(nics) > 1:
657 48436b97 Michael Hanselmann
        raise GanetiApiError("Server supports request version 0 only, but"
658 48436b97 Michael Hanselmann
                             " more than one NIC specified")
659 48436b97 Michael Hanselmann
660 48436b97 Michael Hanselmann
      assert len(nics) == 1
661 48436b97 Michael Hanselmann
662 48436b97 Michael Hanselmann
      unsupported = set(nics[0].keys()) - _INST_NIC_PARAMS
663 48436b97 Michael Hanselmann
      if unsupported:
664 48436b97 Michael Hanselmann
        raise GanetiApiError("Server supports request version 0 only, but"
665 48436b97 Michael Hanselmann
                             " NIC 0 specifies the unsupported parameters %s,"
666 48436b97 Michael Hanselmann
                             " allowed are %s" %
667 48436b97 Michael Hanselmann
                             (unsupported, list(_INST_NIC_PARAMS)))
668 48436b97 Michael Hanselmann
669 48436b97 Michael Hanselmann
      # Validate other parameters
670 48436b97 Michael Hanselmann
      unsupported = (set(kwargs.keys()) - _INST_CREATE_V0_PARAMS -
671 48436b97 Michael Hanselmann
                     _INST_CREATE_V0_DPARAMS)
672 48436b97 Michael Hanselmann
      if unsupported:
673 48436b97 Michael Hanselmann
        allowed = _INST_CREATE_V0_PARAMS.union(_INST_CREATE_V0_DPARAMS)
674 48436b97 Michael Hanselmann
        raise GanetiApiError("Server supports request version 0 only, but"
675 48436b97 Michael Hanselmann
                             " the following unsupported parameters are"
676 48436b97 Michael Hanselmann
                             " specified: %s, allowed are %s" %
677 48436b97 Michael Hanselmann
                             (unsupported, list(allowed)))
678 48436b97 Michael Hanselmann
679 48436b97 Michael Hanselmann
      # All required fields for request data version 0
680 48436b97 Michael Hanselmann
      body = {
681 48436b97 Michael Hanselmann
        _REQ_DATA_VERSION_FIELD: 0,
682 48436b97 Michael Hanselmann
        "name": name,
683 48436b97 Michael Hanselmann
        "disk_template": disk_template,
684 48436b97 Michael Hanselmann
        "disks": disk_sizes,
685 48436b97 Michael Hanselmann
        }
686 48436b97 Michael Hanselmann
687 48436b97 Michael Hanselmann
      # NIC fields
688 48436b97 Michael Hanselmann
      assert len(nics) == 1
689 48436b97 Michael Hanselmann
      assert not (set(body.keys()) & set(nics[0].keys()))
690 48436b97 Michael Hanselmann
      body.update(nics[0])
691 48436b97 Michael Hanselmann
692 48436b97 Michael Hanselmann
      # Copy supported fields
693 48436b97 Michael Hanselmann
      assert not (set(body.keys()) & set(kwargs.keys()))
694 48436b97 Michael Hanselmann
      body.update(dict((key, value) for key, value in kwargs.items()
695 48436b97 Michael Hanselmann
                       if key in _INST_CREATE_V0_PARAMS))
696 48436b97 Michael Hanselmann
697 48436b97 Michael Hanselmann
      # Merge dictionaries
698 48436b97 Michael Hanselmann
      for i in (value for key, value in kwargs.items()
699 48436b97 Michael Hanselmann
                if key in _INST_CREATE_V0_DPARAMS):
700 48436b97 Michael Hanselmann
        assert not (set(body.keys()) & set(i.keys()))
701 48436b97 Michael Hanselmann
        body.update(i)
702 48436b97 Michael Hanselmann
703 48436b97 Michael Hanselmann
      assert not (set(kwargs.keys()) -
704 48436b97 Michael Hanselmann
                  (_INST_CREATE_V0_PARAMS | _INST_CREATE_V0_DPARAMS))
705 48436b97 Michael Hanselmann
      assert not (set(body.keys()) & _INST_CREATE_V0_DPARAMS)
706 8a47b447 Michael Hanselmann
707 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_POST, "/%s/instances" % GANETI_RAPI_VERSION,
708 8a47b447 Michael Hanselmann
                             query, body)
709 95ab4de9 David Knowles
710 95ab4de9 David Knowles
  def DeleteInstance(self, instance, dry_run=False):
711 95ab4de9 David Knowles
    """Deletes an instance.
712 95ab4de9 David Knowles

713 95ab4de9 David Knowles
    @type instance: str
714 95ab4de9 David Knowles
    @param instance: the instance to delete
715 95ab4de9 David Knowles

716 cab667cc David Knowles
    @rtype: int
717 cab667cc David Knowles
    @return: job id
718 cab667cc David Knowles

719 95ab4de9 David Knowles
    """
720 95ab4de9 David Knowles
    query = []
721 95ab4de9 David Knowles
    if dry_run:
722 95ab4de9 David Knowles
      query.append(("dry-run", 1))
723 95ab4de9 David Knowles
724 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE,
725 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s" %
726 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
727 95ab4de9 David Knowles
728 3b7158ef Michael Hanselmann
  def ModifyInstance(self, instance, **kwargs):
729 3b7158ef Michael Hanselmann
    """Modifies an instance.
730 3b7158ef Michael Hanselmann

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

733 3b7158ef Michael Hanselmann
    @type instance: string
734 3b7158ef Michael Hanselmann
    @param instance: Instance name
735 3b7158ef Michael Hanselmann
    @rtype: int
736 3b7158ef Michael Hanselmann
    @return: job id
737 3b7158ef Michael Hanselmann

738 3b7158ef Michael Hanselmann
    """
739 3b7158ef Michael Hanselmann
    body = kwargs
740 3b7158ef Michael Hanselmann
741 3b7158ef Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
742 3b7158ef Michael Hanselmann
                             ("/%s/instances/%s/modify" %
743 3b7158ef Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, body)
744 3b7158ef Michael Hanselmann
745 95ab4de9 David Knowles
  def GetInstanceTags(self, instance):
746 95ab4de9 David Knowles
    """Gets tags for an instance.
747 95ab4de9 David Knowles

748 95ab4de9 David Knowles
    @type instance: str
749 95ab4de9 David Knowles
    @param instance: instance whose tags to return
750 95ab4de9 David Knowles

751 95ab4de9 David Knowles
    @rtype: list of str
752 95ab4de9 David Knowles
    @return: tags for the instance
753 95ab4de9 David Knowles

754 95ab4de9 David Knowles
    """
755 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
756 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/tags" %
757 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, None)
758 95ab4de9 David Knowles
759 95ab4de9 David Knowles
  def AddInstanceTags(self, instance, tags, dry_run=False):
760 95ab4de9 David Knowles
    """Adds tags to an instance.
761 95ab4de9 David Knowles

762 95ab4de9 David Knowles
    @type instance: str
763 95ab4de9 David Knowles
    @param instance: instance to add tags to
764 95ab4de9 David Knowles
    @type tags: list of str
765 95ab4de9 David Knowles
    @param tags: tags to add to the instance
766 95ab4de9 David Knowles
    @type dry_run: bool
767 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
768 95ab4de9 David Knowles

769 95ab4de9 David Knowles
    @rtype: int
770 95ab4de9 David Knowles
    @return: job id
771 95ab4de9 David Knowles

772 95ab4de9 David Knowles
    """
773 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
774 95ab4de9 David Knowles
    if dry_run:
775 95ab4de9 David Knowles
      query.append(("dry-run", 1))
776 95ab4de9 David Knowles
777 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
778 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/tags" %
779 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
780 95ab4de9 David Knowles
781 95ab4de9 David Knowles
  def DeleteInstanceTags(self, instance, tags, dry_run=False):
782 95ab4de9 David Knowles
    """Deletes tags from an instance.
783 95ab4de9 David Knowles

784 95ab4de9 David Knowles
    @type instance: str
785 95ab4de9 David Knowles
    @param instance: instance to delete tags from
786 95ab4de9 David Knowles
    @type tags: list of str
787 95ab4de9 David Knowles
    @param tags: tags to delete
788 95ab4de9 David Knowles
    @type dry_run: bool
789 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
790 95ab4de9 David Knowles

791 95ab4de9 David Knowles
    """
792 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
793 95ab4de9 David Knowles
    if dry_run:
794 95ab4de9 David Knowles
      query.append(("dry-run", 1))
795 95ab4de9 David Knowles
796 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE,
797 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/tags" %
798 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
799 95ab4de9 David Knowles
800 95ab4de9 David Knowles
  def RebootInstance(self, instance, reboot_type=None, ignore_secondaries=None,
801 95ab4de9 David Knowles
                     dry_run=False):
802 95ab4de9 David Knowles
    """Reboots an instance.
803 95ab4de9 David Knowles

804 95ab4de9 David Knowles
    @type instance: str
805 95ab4de9 David Knowles
    @param instance: instance to rebot
806 95ab4de9 David Knowles
    @type reboot_type: str
807 95ab4de9 David Knowles
    @param reboot_type: one of: hard, soft, full
808 95ab4de9 David Knowles
    @type ignore_secondaries: bool
809 95ab4de9 David Knowles
    @param ignore_secondaries: if True, ignores errors for the secondary node
810 95ab4de9 David Knowles
        while re-assembling disks (in hard-reboot mode only)
811 95ab4de9 David Knowles
    @type dry_run: bool
812 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
813 95ab4de9 David Knowles

814 95ab4de9 David Knowles
    """
815 95ab4de9 David Knowles
    query = []
816 95ab4de9 David Knowles
    if reboot_type:
817 95ab4de9 David Knowles
      query.append(("type", reboot_type))
818 95ab4de9 David Knowles
    if ignore_secondaries is not None:
819 95ab4de9 David Knowles
      query.append(("ignore_secondaries", ignore_secondaries))
820 95ab4de9 David Knowles
    if dry_run:
821 95ab4de9 David Knowles
      query.append(("dry-run", 1))
822 95ab4de9 David Knowles
823 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_POST,
824 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/reboot" %
825 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
826 95ab4de9 David Knowles
827 95ab4de9 David Knowles
  def ShutdownInstance(self, instance, dry_run=False):
828 95ab4de9 David Knowles
    """Shuts down an instance.
829 95ab4de9 David Knowles

830 95ab4de9 David Knowles
    @type instance: str
831 95ab4de9 David Knowles
    @param instance: the instance to shut down
832 95ab4de9 David Knowles
    @type dry_run: bool
833 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
834 95ab4de9 David Knowles

835 95ab4de9 David Knowles
    """
836 95ab4de9 David Knowles
    query = []
837 95ab4de9 David Knowles
    if dry_run:
838 95ab4de9 David Knowles
      query.append(("dry-run", 1))
839 95ab4de9 David Knowles
840 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
841 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/shutdown" %
842 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
843 95ab4de9 David Knowles
844 95ab4de9 David Knowles
  def StartupInstance(self, instance, dry_run=False):
845 95ab4de9 David Knowles
    """Starts up an instance.
846 95ab4de9 David Knowles

847 95ab4de9 David Knowles
    @type instance: str
848 95ab4de9 David Knowles
    @param instance: the instance to start up
849 95ab4de9 David Knowles
    @type dry_run: bool
850 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
851 95ab4de9 David Knowles

852 95ab4de9 David Knowles
    """
853 95ab4de9 David Knowles
    query = []
854 95ab4de9 David Knowles
    if dry_run:
855 95ab4de9 David Knowles
      query.append(("dry-run", 1))
856 95ab4de9 David Knowles
857 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
858 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/startup" %
859 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
860 95ab4de9 David Knowles
861 fcee9675 David Knowles
  def ReinstallInstance(self, instance, os=None, no_startup=False):
862 95ab4de9 David Knowles
    """Reinstalls an instance.
863 95ab4de9 David Knowles

864 95ab4de9 David Knowles
    @type instance: str
865 fcee9675 David Knowles
    @param instance: The instance to reinstall
866 fcee9675 David Knowles
    @type os: str or None
867 fcee9675 David Knowles
    @param os: The operating system to reinstall. If None, the instance's
868 fcee9675 David Knowles
        current operating system will be installed again
869 95ab4de9 David Knowles
    @type no_startup: bool
870 fcee9675 David Knowles
    @param no_startup: Whether to start the instance automatically
871 95ab4de9 David Knowles

872 95ab4de9 David Knowles
    """
873 fcee9675 David Knowles
    query = []
874 fcee9675 David Knowles
    if os:
875 fcee9675 David Knowles
      query.append(("os", os))
876 95ab4de9 David Knowles
    if no_startup:
877 95ab4de9 David Knowles
      query.append(("nostartup", 1))
878 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_POST,
879 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/reinstall" %
880 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
881 95ab4de9 David Knowles
882 bfc2002f Michael Hanselmann
  def ReplaceInstanceDisks(self, instance, disks=None, mode=REPLACE_DISK_AUTO,
883 cfc03c54 Michael Hanselmann
                           remote_node=None, iallocator=None, dry_run=False):
884 95ab4de9 David Knowles
    """Replaces disks on an instance.
885 95ab4de9 David Knowles

886 95ab4de9 David Knowles
    @type instance: str
887 95ab4de9 David Knowles
    @param instance: instance whose disks to replace
888 bfc2002f Michael Hanselmann
    @type disks: list of ints
889 bfc2002f Michael Hanselmann
    @param disks: Indexes of disks to replace
890 95ab4de9 David Knowles
    @type mode: str
891 cfc03c54 Michael Hanselmann
    @param mode: replacement mode to use (defaults to replace_auto)
892 95ab4de9 David Knowles
    @type remote_node: str or None
893 95ab4de9 David Knowles
    @param remote_node: new secondary node to use (for use with
894 cfc03c54 Michael Hanselmann
        replace_new_secondary mode)
895 95ab4de9 David Knowles
    @type iallocator: str or None
896 95ab4de9 David Knowles
    @param iallocator: instance allocator plugin to use (for use with
897 cfc03c54 Michael Hanselmann
                       replace_auto mode)
898 95ab4de9 David Knowles
    @type dry_run: bool
899 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
900 95ab4de9 David Knowles

901 95ab4de9 David Knowles
    @rtype: int
902 95ab4de9 David Knowles
    @return: job id
903 95ab4de9 David Knowles

904 95ab4de9 David Knowles
    """
905 cfc03c54 Michael Hanselmann
    query = [
906 cfc03c54 Michael Hanselmann
      ("mode", mode),
907 cfc03c54 Michael Hanselmann
      ]
908 95ab4de9 David Knowles
909 bfc2002f Michael Hanselmann
    if disks:
910 bfc2002f Michael Hanselmann
      query.append(("disks", ",".join(str(idx) for idx in disks)))
911 bfc2002f Michael Hanselmann
912 bfc2002f Michael Hanselmann
    if remote_node:
913 95ab4de9 David Knowles
      query.append(("remote_node", remote_node))
914 95ab4de9 David Knowles
915 bfc2002f Michael Hanselmann
    if iallocator:
916 bfc2002f Michael Hanselmann
      query.append(("iallocator", iallocator))
917 bfc2002f Michael Hanselmann
918 95ab4de9 David Knowles
    if dry_run:
919 95ab4de9 David Knowles
      query.append(("dry-run", 1))
920 95ab4de9 David Knowles
921 95ab4de9 David Knowles
    return self._SendRequest(HTTP_POST,
922 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/replace-disks" %
923 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
924 95ab4de9 David Knowles
925 ebeb600f Michael Hanselmann
  def PrepareExport(self, instance, mode):
926 ebeb600f Michael Hanselmann
    """Prepares an instance for an export.
927 ebeb600f Michael Hanselmann

928 ebeb600f Michael Hanselmann
    @type instance: string
929 ebeb600f Michael Hanselmann
    @param instance: Instance name
930 ebeb600f Michael Hanselmann
    @type mode: string
931 ebeb600f Michael Hanselmann
    @param mode: Export mode
932 ebeb600f Michael Hanselmann
    @rtype: string
933 ebeb600f Michael Hanselmann
    @return: Job ID
934 ebeb600f Michael Hanselmann

935 ebeb600f Michael Hanselmann
    """
936 ebeb600f Michael Hanselmann
    query = [("mode", mode)]
937 ebeb600f Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
938 ebeb600f Michael Hanselmann
                             ("/%s/instances/%s/prepare-export" %
939 ebeb600f Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
940 ebeb600f Michael Hanselmann
941 ebeb600f Michael Hanselmann
  def ExportInstance(self, instance, mode, destination, shutdown=None,
942 ebeb600f Michael Hanselmann
                     remove_instance=None,
943 ebeb600f Michael Hanselmann
                     x509_key_name=None, destination_x509_ca=None):
944 ebeb600f Michael Hanselmann
    """Exports an instance.
945 ebeb600f Michael Hanselmann

946 ebeb600f Michael Hanselmann
    @type instance: string
947 ebeb600f Michael Hanselmann
    @param instance: Instance name
948 ebeb600f Michael Hanselmann
    @type mode: string
949 ebeb600f Michael Hanselmann
    @param mode: Export mode
950 ebeb600f Michael Hanselmann
    @rtype: string
951 ebeb600f Michael Hanselmann
    @return: Job ID
952 ebeb600f Michael Hanselmann

953 ebeb600f Michael Hanselmann
    """
954 ebeb600f Michael Hanselmann
    body = {
955 ebeb600f Michael Hanselmann
      "destination": destination,
956 ebeb600f Michael Hanselmann
      "mode": mode,
957 ebeb600f Michael Hanselmann
      }
958 ebeb600f Michael Hanselmann
959 ebeb600f Michael Hanselmann
    if shutdown is not None:
960 ebeb600f Michael Hanselmann
      body["shutdown"] = shutdown
961 ebeb600f Michael Hanselmann
962 ebeb600f Michael Hanselmann
    if remove_instance is not None:
963 ebeb600f Michael Hanselmann
      body["remove_instance"] = remove_instance
964 ebeb600f Michael Hanselmann
965 ebeb600f Michael Hanselmann
    if x509_key_name is not None:
966 ebeb600f Michael Hanselmann
      body["x509_key_name"] = x509_key_name
967 ebeb600f Michael Hanselmann
968 ebeb600f Michael Hanselmann
    if destination_x509_ca is not None:
969 ebeb600f Michael Hanselmann
      body["destination_x509_ca"] = destination_x509_ca
970 ebeb600f Michael Hanselmann
971 ebeb600f Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
972 ebeb600f Michael Hanselmann
                             ("/%s/instances/%s/export" %
973 ebeb600f Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, body)
974 ebeb600f Michael Hanselmann
975 e0ac6ce6 Michael Hanselmann
  def MigrateInstance(self, instance, mode=None, cleanup=None):
976 c63eb9c0 Michael Hanselmann
    """Migrates an instance.
977 e0ac6ce6 Michael Hanselmann

978 e0ac6ce6 Michael Hanselmann
    @type instance: string
979 e0ac6ce6 Michael Hanselmann
    @param instance: Instance name
980 e0ac6ce6 Michael Hanselmann
    @type mode: string
981 e0ac6ce6 Michael Hanselmann
    @param mode: Migration mode
982 e0ac6ce6 Michael Hanselmann
    @type cleanup: bool
983 e0ac6ce6 Michael Hanselmann
    @param cleanup: Whether to clean up a previously failed migration
984 e0ac6ce6 Michael Hanselmann

985 e0ac6ce6 Michael Hanselmann
    """
986 e0ac6ce6 Michael Hanselmann
    body = {}
987 e0ac6ce6 Michael Hanselmann
988 e0ac6ce6 Michael Hanselmann
    if mode is not None:
989 e0ac6ce6 Michael Hanselmann
      body["mode"] = mode
990 e0ac6ce6 Michael Hanselmann
991 e0ac6ce6 Michael Hanselmann
    if cleanup is not None:
992 e0ac6ce6 Michael Hanselmann
      body["cleanup"] = cleanup
993 e0ac6ce6 Michael Hanselmann
994 e0ac6ce6 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
995 e0ac6ce6 Michael Hanselmann
                             ("/%s/instances/%s/migrate" %
996 e0ac6ce6 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, body)
997 e0ac6ce6 Michael Hanselmann
998 d654aae1 Michael Hanselmann
  def RenameInstance(self, instance, new_name, ip_check=None, name_check=None):
999 d654aae1 Michael Hanselmann
    """Changes the name of an instance.
1000 d654aae1 Michael Hanselmann

1001 d654aae1 Michael Hanselmann
    @type instance: string
1002 d654aae1 Michael Hanselmann
    @param instance: Instance name
1003 d654aae1 Michael Hanselmann
    @type new_name: string
1004 d654aae1 Michael Hanselmann
    @param new_name: New instance name
1005 d654aae1 Michael Hanselmann
    @type ip_check: bool
1006 d654aae1 Michael Hanselmann
    @param ip_check: Whether to ensure instance's IP address is inactive
1007 d654aae1 Michael Hanselmann
    @type name_check: bool
1008 d654aae1 Michael Hanselmann
    @param name_check: Whether to ensure instance's name is resolvable
1009 d654aae1 Michael Hanselmann

1010 d654aae1 Michael Hanselmann
    """
1011 d654aae1 Michael Hanselmann
    body = {
1012 d654aae1 Michael Hanselmann
      "new_name": new_name,
1013 d654aae1 Michael Hanselmann
      }
1014 d654aae1 Michael Hanselmann
1015 d654aae1 Michael Hanselmann
    if ip_check is not None:
1016 d654aae1 Michael Hanselmann
      body["ip_check"] = ip_check
1017 d654aae1 Michael Hanselmann
1018 d654aae1 Michael Hanselmann
    if name_check is not None:
1019 d654aae1 Michael Hanselmann
      body["name_check"] = name_check
1020 d654aae1 Michael Hanselmann
1021 d654aae1 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1022 d654aae1 Michael Hanselmann
                             ("/%s/instances/%s/rename" %
1023 d654aae1 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, body)
1024 d654aae1 Michael Hanselmann
1025 95ab4de9 David Knowles
  def GetJobs(self):
1026 95ab4de9 David Knowles
    """Gets all jobs for the cluster.
1027 95ab4de9 David Knowles

1028 95ab4de9 David Knowles
    @rtype: list of int
1029 95ab4de9 David Knowles
    @return: job ids for the cluster
1030 95ab4de9 David Knowles

1031 95ab4de9 David Knowles
    """
1032 768747ed Michael Hanselmann
    return [int(j["id"])
1033 a198b2d9 Michael Hanselmann
            for j in self._SendRequest(HTTP_GET,
1034 a198b2d9 Michael Hanselmann
                                       "/%s/jobs" % GANETI_RAPI_VERSION,
1035 a198b2d9 Michael Hanselmann
                                       None, None)]
1036 95ab4de9 David Knowles
1037 95ab4de9 David Knowles
  def GetJobStatus(self, job_id):
1038 95ab4de9 David Knowles
    """Gets the status of a job.
1039 95ab4de9 David Knowles

1040 95ab4de9 David Knowles
    @type job_id: int
1041 95ab4de9 David Knowles
    @param job_id: job id whose status to query
1042 95ab4de9 David Knowles

1043 95ab4de9 David Knowles
    @rtype: dict
1044 95ab4de9 David Knowles
    @return: job status
1045 95ab4de9 David Knowles

1046 95ab4de9 David Knowles
    """
1047 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1048 a198b2d9 Michael Hanselmann
                             "/%s/jobs/%s" % (GANETI_RAPI_VERSION, job_id),
1049 a198b2d9 Michael Hanselmann
                             None, None)
1050 95ab4de9 David Knowles
1051 d9b67f70 Michael Hanselmann
  def WaitForJobChange(self, job_id, fields, prev_job_info, prev_log_serial):
1052 d9b67f70 Michael Hanselmann
    """Waits for job changes.
1053 d9b67f70 Michael Hanselmann

1054 d9b67f70 Michael Hanselmann
    @type job_id: int
1055 d9b67f70 Michael Hanselmann
    @param job_id: Job ID for which to wait
1056 d9b67f70 Michael Hanselmann

1057 d9b67f70 Michael Hanselmann
    """
1058 d9b67f70 Michael Hanselmann
    body = {
1059 d9b67f70 Michael Hanselmann
      "fields": fields,
1060 d9b67f70 Michael Hanselmann
      "previous_job_info": prev_job_info,
1061 d9b67f70 Michael Hanselmann
      "previous_log_serial": prev_log_serial,
1062 d9b67f70 Michael Hanselmann
      }
1063 d9b67f70 Michael Hanselmann
1064 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1065 a198b2d9 Michael Hanselmann
                             "/%s/jobs/%s/wait" % (GANETI_RAPI_VERSION, job_id),
1066 a198b2d9 Michael Hanselmann
                             None, body)
1067 d9b67f70 Michael Hanselmann
1068 cf9ada49 Michael Hanselmann
  def CancelJob(self, job_id, dry_run=False):
1069 cf9ada49 Michael Hanselmann
    """Cancels a job.
1070 95ab4de9 David Knowles

1071 95ab4de9 David Knowles
    @type job_id: int
1072 95ab4de9 David Knowles
    @param job_id: id of the job to delete
1073 95ab4de9 David Knowles
    @type dry_run: bool
1074 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1075 95ab4de9 David Knowles

1076 95ab4de9 David Knowles
    """
1077 95ab4de9 David Knowles
    query = []
1078 95ab4de9 David Knowles
    if dry_run:
1079 95ab4de9 David Knowles
      query.append(("dry-run", 1))
1080 95ab4de9 David Knowles
1081 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE,
1082 a198b2d9 Michael Hanselmann
                             "/%s/jobs/%s" % (GANETI_RAPI_VERSION, job_id),
1083 a198b2d9 Michael Hanselmann
                             query, None)
1084 95ab4de9 David Knowles
1085 95ab4de9 David Knowles
  def GetNodes(self, bulk=False):
1086 95ab4de9 David Knowles
    """Gets all nodes in the cluster.
1087 95ab4de9 David Knowles

1088 95ab4de9 David Knowles
    @type bulk: bool
1089 95ab4de9 David Knowles
    @param bulk: whether to return all information about all instances
1090 95ab4de9 David Knowles

1091 95ab4de9 David Knowles
    @rtype: list of dict or str
1092 95ab4de9 David Knowles
    @return: if bulk is true, info about nodes in the cluster,
1093 95ab4de9 David Knowles
        else list of nodes in the cluster
1094 95ab4de9 David Knowles

1095 95ab4de9 David Knowles
    """
1096 95ab4de9 David Knowles
    query = []
1097 95ab4de9 David Knowles
    if bulk:
1098 95ab4de9 David Knowles
      query.append(("bulk", 1))
1099 95ab4de9 David Knowles
1100 a198b2d9 Michael Hanselmann
    nodes = self._SendRequest(HTTP_GET, "/%s/nodes" % GANETI_RAPI_VERSION,
1101 a198b2d9 Michael Hanselmann
                              query, None)
1102 95ab4de9 David Knowles
    if bulk:
1103 95ab4de9 David Knowles
      return nodes
1104 95ab4de9 David Knowles
    else:
1105 95ab4de9 David Knowles
      return [n["id"] for n in nodes]
1106 95ab4de9 David Knowles
1107 591e5103 Michael Hanselmann
  def GetNode(self, node):
1108 95ab4de9 David Knowles
    """Gets information about a node.
1109 95ab4de9 David Knowles

1110 95ab4de9 David Knowles
    @type node: str
1111 95ab4de9 David Knowles
    @param node: node whose info to return
1112 95ab4de9 David Knowles

1113 95ab4de9 David Knowles
    @rtype: dict
1114 95ab4de9 David Knowles
    @return: info about the node
1115 95ab4de9 David Knowles

1116 95ab4de9 David Knowles
    """
1117 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1118 a198b2d9 Michael Hanselmann
                             "/%s/nodes/%s" % (GANETI_RAPI_VERSION, node),
1119 a198b2d9 Michael Hanselmann
                             None, None)
1120 95ab4de9 David Knowles
1121 95ab4de9 David Knowles
  def EvacuateNode(self, node, iallocator=None, remote_node=None,
1122 941b9309 Iustin Pop
                   dry_run=False, early_release=False):
1123 95ab4de9 David Knowles
    """Evacuates instances from a Ganeti node.
1124 95ab4de9 David Knowles

1125 95ab4de9 David Knowles
    @type node: str
1126 95ab4de9 David Knowles
    @param node: node to evacuate
1127 95ab4de9 David Knowles
    @type iallocator: str or None
1128 95ab4de9 David Knowles
    @param iallocator: instance allocator to use
1129 95ab4de9 David Knowles
    @type remote_node: str
1130 95ab4de9 David Knowles
    @param remote_node: node to evaucate to
1131 95ab4de9 David Knowles
    @type dry_run: bool
1132 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1133 941b9309 Iustin Pop
    @type early_release: bool
1134 941b9309 Iustin Pop
    @param early_release: whether to enable parallelization
1135 95ab4de9 David Knowles

1136 941b9309 Iustin Pop
    @rtype: list
1137 941b9309 Iustin Pop
    @return: list of (job ID, instance name, new secondary node); if
1138 941b9309 Iustin Pop
        dry_run was specified, then the actual move jobs were not
1139 941b9309 Iustin Pop
        submitted and the job IDs will be C{None}
1140 95ab4de9 David Knowles

1141 941b9309 Iustin Pop
    @raises GanetiApiError: if an iallocator and remote_node are both
1142 941b9309 Iustin Pop
        specified
1143 95ab4de9 David Knowles

1144 95ab4de9 David Knowles
    """
1145 95ab4de9 David Knowles
    if iallocator and remote_node:
1146 cfc03c54 Michael Hanselmann
      raise GanetiApiError("Only one of iallocator or remote_node can be used")
1147 95ab4de9 David Knowles
1148 cfc03c54 Michael Hanselmann
    query = []
1149 95ab4de9 David Knowles
    if iallocator:
1150 95ab4de9 David Knowles
      query.append(("iallocator", iallocator))
1151 95ab4de9 David Knowles
    if remote_node:
1152 95ab4de9 David Knowles
      query.append(("remote_node", remote_node))
1153 95ab4de9 David Knowles
    if dry_run:
1154 95ab4de9 David Knowles
      query.append(("dry-run", 1))
1155 941b9309 Iustin Pop
    if early_release:
1156 941b9309 Iustin Pop
      query.append(("early_release", 1))
1157 95ab4de9 David Knowles
1158 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_POST,
1159 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/evacuate" %
1160 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1161 95ab4de9 David Knowles
1162 1f334d96 Iustin Pop
  def MigrateNode(self, node, mode=None, dry_run=False):
1163 95ab4de9 David Knowles
    """Migrates all primary instances from a node.
1164 95ab4de9 David Knowles

1165 95ab4de9 David Knowles
    @type node: str
1166 95ab4de9 David Knowles
    @param node: node to migrate
1167 1f334d96 Iustin Pop
    @type mode: string
1168 1f334d96 Iustin Pop
    @param mode: if passed, it will overwrite the live migration type,
1169 1f334d96 Iustin Pop
        otherwise the hypervisor default will be used
1170 95ab4de9 David Knowles
    @type dry_run: bool
1171 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1172 95ab4de9 David Knowles

1173 95ab4de9 David Knowles
    @rtype: int
1174 95ab4de9 David Knowles
    @return: job id
1175 95ab4de9 David Knowles

1176 95ab4de9 David Knowles
    """
1177 95ab4de9 David Knowles
    query = []
1178 1f334d96 Iustin Pop
    if mode is not None:
1179 1f334d96 Iustin Pop
      query.append(("mode", mode))
1180 95ab4de9 David Knowles
    if dry_run:
1181 95ab4de9 David Knowles
      query.append(("dry-run", 1))
1182 95ab4de9 David Knowles
1183 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_POST,
1184 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/migrate" %
1185 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1186 95ab4de9 David Knowles
1187 95ab4de9 David Knowles
  def GetNodeRole(self, node):
1188 95ab4de9 David Knowles
    """Gets the current role for a node.
1189 95ab4de9 David Knowles

1190 95ab4de9 David Knowles
    @type node: str
1191 95ab4de9 David Knowles
    @param node: node whose role to return
1192 95ab4de9 David Knowles

1193 95ab4de9 David Knowles
    @rtype: str
1194 95ab4de9 David Knowles
    @return: the current role for a node
1195 95ab4de9 David Knowles

1196 95ab4de9 David Knowles
    """
1197 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1198 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/role" %
1199 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), None, None)
1200 95ab4de9 David Knowles
1201 95ab4de9 David Knowles
  def SetNodeRole(self, node, role, force=False):
1202 95ab4de9 David Knowles
    """Sets the role for a node.
1203 95ab4de9 David Knowles

1204 95ab4de9 David Knowles
    @type node: str
1205 95ab4de9 David Knowles
    @param node: the node whose role to set
1206 95ab4de9 David Knowles
    @type role: str
1207 95ab4de9 David Knowles
    @param role: the role to set for the node
1208 95ab4de9 David Knowles
    @type force: bool
1209 95ab4de9 David Knowles
    @param force: whether to force the role change
1210 95ab4de9 David Knowles

1211 95ab4de9 David Knowles
    @rtype: int
1212 95ab4de9 David Knowles
    @return: job id
1213 95ab4de9 David Knowles

1214 95ab4de9 David Knowles
    """
1215 1068639f Michael Hanselmann
    query = [
1216 1068639f Michael Hanselmann
      ("force", force),
1217 1068639f Michael Hanselmann
      ]
1218 cfc03c54 Michael Hanselmann
1219 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1220 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/role" %
1221 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, role)
1222 95ab4de9 David Knowles
1223 95ab4de9 David Knowles
  def GetNodeStorageUnits(self, node, storage_type, output_fields):
1224 95ab4de9 David Knowles
    """Gets the storage units for a node.
1225 95ab4de9 David Knowles

1226 95ab4de9 David Knowles
    @type node: str
1227 95ab4de9 David Knowles
    @param node: the node whose storage units to return
1228 95ab4de9 David Knowles
    @type storage_type: str
1229 95ab4de9 David Knowles
    @param storage_type: storage type whose units to return
1230 95ab4de9 David Knowles
    @type output_fields: str
1231 95ab4de9 David Knowles
    @param output_fields: storage type fields to return
1232 95ab4de9 David Knowles

1233 95ab4de9 David Knowles
    @rtype: int
1234 95ab4de9 David Knowles
    @return: job id where results can be retrieved
1235 95ab4de9 David Knowles

1236 95ab4de9 David Knowles
    """
1237 cfc03c54 Michael Hanselmann
    query = [
1238 cfc03c54 Michael Hanselmann
      ("storage_type", storage_type),
1239 cfc03c54 Michael Hanselmann
      ("output_fields", output_fields),
1240 cfc03c54 Michael Hanselmann
      ]
1241 95ab4de9 David Knowles
1242 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1243 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/storage" %
1244 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1245 95ab4de9 David Knowles
1246 fde28316 Michael Hanselmann
  def ModifyNodeStorageUnits(self, node, storage_type, name, allocatable=None):
1247 95ab4de9 David Knowles
    """Modifies parameters of storage units on the node.
1248 95ab4de9 David Knowles

1249 95ab4de9 David Knowles
    @type node: str
1250 95ab4de9 David Knowles
    @param node: node whose storage units to modify
1251 95ab4de9 David Knowles
    @type storage_type: str
1252 95ab4de9 David Knowles
    @param storage_type: storage type whose units to modify
1253 95ab4de9 David Knowles
    @type name: str
1254 95ab4de9 David Knowles
    @param name: name of the storage unit
1255 fde28316 Michael Hanselmann
    @type allocatable: bool or None
1256 fde28316 Michael Hanselmann
    @param allocatable: Whether to set the "allocatable" flag on the storage
1257 fde28316 Michael Hanselmann
                        unit (None=no modification, True=set, False=unset)
1258 95ab4de9 David Knowles

1259 95ab4de9 David Knowles
    @rtype: int
1260 95ab4de9 David Knowles
    @return: job id
1261 95ab4de9 David Knowles

1262 95ab4de9 David Knowles
    """
1263 95ab4de9 David Knowles
    query = [
1264 cfc03c54 Michael Hanselmann
      ("storage_type", storage_type),
1265 cfc03c54 Michael Hanselmann
      ("name", name),
1266 cfc03c54 Michael Hanselmann
      ]
1267 cfc03c54 Michael Hanselmann
1268 fde28316 Michael Hanselmann
    if allocatable is not None:
1269 fde28316 Michael Hanselmann
      query.append(("allocatable", allocatable))
1270 fde28316 Michael Hanselmann
1271 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1272 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/storage/modify" %
1273 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1274 95ab4de9 David Knowles
1275 95ab4de9 David Knowles
  def RepairNodeStorageUnits(self, node, storage_type, name):
1276 95ab4de9 David Knowles
    """Repairs a storage unit on the node.
1277 95ab4de9 David Knowles

1278 95ab4de9 David Knowles
    @type node: str
1279 95ab4de9 David Knowles
    @param node: node whose storage units to repair
1280 95ab4de9 David Knowles
    @type storage_type: str
1281 95ab4de9 David Knowles
    @param storage_type: storage type to repair
1282 95ab4de9 David Knowles
    @type name: str
1283 95ab4de9 David Knowles
    @param name: name of the storage unit to repair
1284 95ab4de9 David Knowles

1285 95ab4de9 David Knowles
    @rtype: int
1286 95ab4de9 David Knowles
    @return: job id
1287 95ab4de9 David Knowles

1288 95ab4de9 David Knowles
    """
1289 cfc03c54 Michael Hanselmann
    query = [
1290 cfc03c54 Michael Hanselmann
      ("storage_type", storage_type),
1291 cfc03c54 Michael Hanselmann
      ("name", name),
1292 cfc03c54 Michael Hanselmann
      ]
1293 95ab4de9 David Knowles
1294 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1295 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/storage/repair" %
1296 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1297 95ab4de9 David Knowles
1298 95ab4de9 David Knowles
  def GetNodeTags(self, node):
1299 95ab4de9 David Knowles
    """Gets the tags for a node.
1300 95ab4de9 David Knowles

1301 95ab4de9 David Knowles
    @type node: str
1302 95ab4de9 David Knowles
    @param node: node whose tags to return
1303 95ab4de9 David Knowles

1304 95ab4de9 David Knowles
    @rtype: list of str
1305 95ab4de9 David Knowles
    @return: tags for the node
1306 95ab4de9 David Knowles

1307 95ab4de9 David Knowles
    """
1308 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1309 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/tags" %
1310 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), None, None)
1311 95ab4de9 David Knowles
1312 95ab4de9 David Knowles
  def AddNodeTags(self, node, tags, dry_run=False):
1313 95ab4de9 David Knowles
    """Adds tags to a node.
1314 95ab4de9 David Knowles

1315 95ab4de9 David Knowles
    @type node: str
1316 95ab4de9 David Knowles
    @param node: node to add tags to
1317 95ab4de9 David Knowles
    @type tags: list of str
1318 95ab4de9 David Knowles
    @param tags: tags to add to the node
1319 95ab4de9 David Knowles
    @type dry_run: bool
1320 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1321 95ab4de9 David Knowles

1322 95ab4de9 David Knowles
    @rtype: int
1323 95ab4de9 David Knowles
    @return: job id
1324 95ab4de9 David Knowles

1325 95ab4de9 David Knowles
    """
1326 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
1327 95ab4de9 David Knowles
    if dry_run:
1328 95ab4de9 David Knowles
      query.append(("dry-run", 1))
1329 95ab4de9 David Knowles
1330 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1331 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/tags" %
1332 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, tags)
1333 95ab4de9 David Knowles
1334 95ab4de9 David Knowles
  def DeleteNodeTags(self, node, tags, dry_run=False):
1335 95ab4de9 David Knowles
    """Delete tags from a node.
1336 95ab4de9 David Knowles

1337 95ab4de9 David Knowles
    @type node: str
1338 95ab4de9 David Knowles
    @param node: node to remove tags from
1339 95ab4de9 David Knowles
    @type tags: list of str
1340 95ab4de9 David Knowles
    @param tags: tags to remove from the node
1341 95ab4de9 David Knowles
    @type dry_run: bool
1342 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1343 95ab4de9 David Knowles

1344 95ab4de9 David Knowles
    @rtype: int
1345 95ab4de9 David Knowles
    @return: job id
1346 95ab4de9 David Knowles

1347 95ab4de9 David Knowles
    """
1348 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
1349 95ab4de9 David Knowles
    if dry_run:
1350 95ab4de9 David Knowles
      query.append(("dry-run", 1))
1351 95ab4de9 David Knowles
1352 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE,
1353 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/tags" %
1354 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)