Statistics
| Branch: | Tag: | Revision:

root / lib / rapi / client.py @ 1f334d96

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

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

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

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

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

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

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

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

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

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

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

261 95ab4de9 David Knowles
    """
262 9279e986 Michael Hanselmann
    self._host = host
263 95ab4de9 David Knowles
    self._port = port
264 9279e986 Michael Hanselmann
    self._logger = logger
265 95ab4de9 David Knowles
266 9279e986 Michael Hanselmann
    self._base_url = "https://%s:%s" % (host, port)
267 f2f88abf David Knowles
268 2a7c3583 Michael Hanselmann
    # Create pycURL object if not supplied
269 2a7c3583 Michael Hanselmann
    if not curl:
270 2a7c3583 Michael Hanselmann
      curl = pycurl.Curl()
271 2a7c3583 Michael Hanselmann
272 2a7c3583 Michael Hanselmann
    # Default cURL settings
273 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.VERBOSE, False)
274 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.FOLLOWLOCATION, False)
275 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.MAXREDIRS, 5)
276 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.NOSIGNAL, True)
277 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.USERAGENT, self.USER_AGENT)
278 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.SSL_VERIFYHOST, 0)
279 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.SSL_VERIFYPEER, False)
280 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.HTTPHEADER, [
281 2a7c3583 Michael Hanselmann
      "Accept: %s" % HTTP_APP_JSON,
282 2a7c3583 Michael Hanselmann
      "Content-type: %s" % HTTP_APP_JSON,
283 2a7c3583 Michael Hanselmann
      ])
284 2a7c3583 Michael Hanselmann
285 2a7c3583 Michael Hanselmann
    # Setup authentication
286 9279e986 Michael Hanselmann
    if username is not None:
287 2a7c3583 Michael Hanselmann
      if password is None:
288 2a7c3583 Michael Hanselmann
        raise Error("Password not specified")
289 2a7c3583 Michael Hanselmann
      curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_BASIC)
290 2a7c3583 Michael Hanselmann
      curl.setopt(pycurl.USERPWD, str("%s:%s" % (username, password)))
291 9279e986 Michael Hanselmann
    elif password:
292 9279e986 Michael Hanselmann
      raise Error("Specified password without username")
293 9279e986 Michael Hanselmann
294 2a7c3583 Michael Hanselmann
    # Call external configuration function
295 2a7c3583 Michael Hanselmann
    if curl_config_fn:
296 2a7c3583 Michael Hanselmann
      curl_config_fn(curl, logger)
297 f2f88abf David Knowles
298 2a7c3583 Michael Hanselmann
    self._curl = curl
299 95ab4de9 David Knowles
300 10f5ab6c Michael Hanselmann
  @staticmethod
301 10f5ab6c Michael Hanselmann
  def _EncodeQuery(query):
302 10f5ab6c Michael Hanselmann
    """Encode query values for RAPI URL.
303 10f5ab6c Michael Hanselmann

304 10f5ab6c Michael Hanselmann
    @type query: list of two-tuples
305 10f5ab6c Michael Hanselmann
    @param query: Query arguments
306 10f5ab6c Michael Hanselmann
    @rtype: list
307 10f5ab6c Michael Hanselmann
    @return: Query list with encoded values
308 10f5ab6c Michael Hanselmann

309 10f5ab6c Michael Hanselmann
    """
310 10f5ab6c Michael Hanselmann
    result = []
311 10f5ab6c Michael Hanselmann
312 10f5ab6c Michael Hanselmann
    for name, value in query:
313 10f5ab6c Michael Hanselmann
      if value is None:
314 10f5ab6c Michael Hanselmann
        result.append((name, ""))
315 10f5ab6c Michael Hanselmann
316 10f5ab6c Michael Hanselmann
      elif isinstance(value, bool):
317 10f5ab6c Michael Hanselmann
        # Boolean values must be encoded as 0 or 1
318 10f5ab6c Michael Hanselmann
        result.append((name, int(value)))
319 10f5ab6c Michael Hanselmann
320 10f5ab6c Michael Hanselmann
      elif isinstance(value, (list, tuple, dict)):
321 10f5ab6c Michael Hanselmann
        raise ValueError("Invalid query data type %r" % type(value).__name__)
322 10f5ab6c Michael Hanselmann
323 10f5ab6c Michael Hanselmann
      else:
324 10f5ab6c Michael Hanselmann
        result.append((name, value))
325 10f5ab6c Michael Hanselmann
326 10f5ab6c Michael Hanselmann
    return result
327 10f5ab6c Michael Hanselmann
328 768747ed Michael Hanselmann
  def _SendRequest(self, method, path, query, content):
329 95ab4de9 David Knowles
    """Sends an HTTP request.
330 95ab4de9 David Knowles

331 95ab4de9 David Knowles
    This constructs a full URL, encodes and decodes HTTP bodies, and
332 95ab4de9 David Knowles
    handles invalid responses in a pythonic way.
333 95ab4de9 David Knowles

334 768747ed Michael Hanselmann
    @type method: string
335 95ab4de9 David Knowles
    @param method: HTTP method to use
336 768747ed Michael Hanselmann
    @type path: string
337 95ab4de9 David Knowles
    @param path: HTTP URL path
338 95ab4de9 David Knowles
    @type query: list of two-tuples
339 95ab4de9 David Knowles
    @param query: query arguments to pass to urllib.urlencode
340 95ab4de9 David Knowles
    @type content: str or None
341 95ab4de9 David Knowles
    @param content: HTTP body content
342 95ab4de9 David Knowles

343 95ab4de9 David Knowles
    @rtype: str
344 95ab4de9 David Knowles
    @return: JSON-Decoded response
345 95ab4de9 David Knowles

346 f2f88abf David Knowles
    @raises CertificateError: If an invalid SSL certificate is found
347 95ab4de9 David Knowles
    @raises GanetiApiError: If an invalid response is returned
348 95ab4de9 David Knowles

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

419 95ab4de9 David Knowles
    @rtype: int
420 f2f88abf David Knowles
    @return: Ganeti Remote API version
421 95ab4de9 David Knowles

422 95ab4de9 David Knowles
    """
423 768747ed Michael Hanselmann
    return self._SendRequest(HTTP_GET, "/version", None, None)
424 95ab4de9 David Knowles
425 7eac4a4d Michael Hanselmann
  def GetFeatures(self):
426 7eac4a4d Michael Hanselmann
    """Gets the list of optional features supported by RAPI server.
427 7eac4a4d Michael Hanselmann

428 7eac4a4d Michael Hanselmann
    @rtype: list
429 7eac4a4d Michael Hanselmann
    @return: List of optional features
430 7eac4a4d Michael Hanselmann

431 7eac4a4d Michael Hanselmann
    """
432 7eac4a4d Michael Hanselmann
    try:
433 7eac4a4d Michael Hanselmann
      return self._SendRequest(HTTP_GET, "/%s/features" % GANETI_RAPI_VERSION,
434 7eac4a4d Michael Hanselmann
                               None, None)
435 7eac4a4d Michael Hanselmann
    except GanetiApiError, err:
436 7eac4a4d Michael Hanselmann
      # Older RAPI servers don't support this resource
437 7eac4a4d Michael Hanselmann
      if err.code == HTTP_NOT_FOUND:
438 7eac4a4d Michael Hanselmann
        return []
439 7eac4a4d Michael Hanselmann
440 7eac4a4d Michael Hanselmann
      raise
441 7eac4a4d Michael Hanselmann
442 95ab4de9 David Knowles
  def GetOperatingSystems(self):
443 95ab4de9 David Knowles
    """Gets the Operating Systems running in the Ganeti cluster.
444 95ab4de9 David Knowles

445 95ab4de9 David Knowles
    @rtype: list of str
446 95ab4de9 David Knowles
    @return: operating systems
447 95ab4de9 David Knowles

448 95ab4de9 David Knowles
    """
449 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET, "/%s/os" % GANETI_RAPI_VERSION,
450 a198b2d9 Michael Hanselmann
                             None, None)
451 95ab4de9 David Knowles
452 95ab4de9 David Knowles
  def GetInfo(self):
453 95ab4de9 David Knowles
    """Gets info about the cluster.
454 95ab4de9 David Knowles

455 95ab4de9 David Knowles
    @rtype: dict
456 95ab4de9 David Knowles
    @return: information about the cluster
457 95ab4de9 David Knowles

458 95ab4de9 David Knowles
    """
459 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET, "/%s/info" % GANETI_RAPI_VERSION,
460 a198b2d9 Michael Hanselmann
                             None, None)
461 95ab4de9 David Knowles
462 95ab4de9 David Knowles
  def GetClusterTags(self):
463 95ab4de9 David Knowles
    """Gets the cluster tags.
464 95ab4de9 David Knowles

465 95ab4de9 David Knowles
    @rtype: list of str
466 95ab4de9 David Knowles
    @return: cluster tags
467 95ab4de9 David Knowles

468 95ab4de9 David Knowles
    """
469 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET, "/%s/tags" % GANETI_RAPI_VERSION,
470 a198b2d9 Michael Hanselmann
                             None, None)
471 95ab4de9 David Knowles
472 95ab4de9 David Knowles
  def AddClusterTags(self, tags, dry_run=False):
473 95ab4de9 David Knowles
    """Adds tags to the cluster.
474 95ab4de9 David Knowles

475 95ab4de9 David Knowles
    @type tags: list of str
476 95ab4de9 David Knowles
    @param tags: tags to add to the cluster
477 95ab4de9 David Knowles
    @type dry_run: bool
478 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
479 95ab4de9 David Knowles

480 95ab4de9 David Knowles
    @rtype: int
481 95ab4de9 David Knowles
    @return: job id
482 95ab4de9 David Knowles

483 95ab4de9 David Knowles
    """
484 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
485 95ab4de9 David Knowles
    if dry_run:
486 95ab4de9 David Knowles
      query.append(("dry-run", 1))
487 95ab4de9 David Knowles
488 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT, "/%s/tags" % GANETI_RAPI_VERSION,
489 a198b2d9 Michael Hanselmann
                             query, None)
490 95ab4de9 David Knowles
491 95ab4de9 David Knowles
  def DeleteClusterTags(self, tags, dry_run=False):
492 95ab4de9 David Knowles
    """Deletes tags from the cluster.
493 95ab4de9 David Knowles

494 95ab4de9 David Knowles
    @type tags: list of str
495 95ab4de9 David Knowles
    @param tags: tags to delete
496 95ab4de9 David Knowles
    @type dry_run: bool
497 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
498 95ab4de9 David Knowles

499 95ab4de9 David Knowles
    """
500 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
501 95ab4de9 David Knowles
    if dry_run:
502 95ab4de9 David Knowles
      query.append(("dry-run", 1))
503 95ab4de9 David Knowles
504 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE, "/%s/tags" % GANETI_RAPI_VERSION,
505 a198b2d9 Michael Hanselmann
                             query, None)
506 95ab4de9 David Knowles
507 95ab4de9 David Knowles
  def GetInstances(self, bulk=False):
508 95ab4de9 David Knowles
    """Gets information about instances on the cluster.
509 95ab4de9 David Knowles

510 95ab4de9 David Knowles
    @type bulk: bool
511 95ab4de9 David Knowles
    @param bulk: whether to return all information about all instances
512 95ab4de9 David Knowles

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

516 95ab4de9 David Knowles
    """
517 95ab4de9 David Knowles
    query = []
518 95ab4de9 David Knowles
    if bulk:
519 95ab4de9 David Knowles
      query.append(("bulk", 1))
520 95ab4de9 David Knowles
521 a198b2d9 Michael Hanselmann
    instances = self._SendRequest(HTTP_GET,
522 a198b2d9 Michael Hanselmann
                                  "/%s/instances" % GANETI_RAPI_VERSION,
523 a198b2d9 Michael Hanselmann
                                  query, None)
524 95ab4de9 David Knowles
    if bulk:
525 95ab4de9 David Knowles
      return instances
526 95ab4de9 David Knowles
    else:
527 95ab4de9 David Knowles
      return [i["id"] for i in instances]
528 95ab4de9 David Knowles
529 591e5103 Michael Hanselmann
  def GetInstance(self, instance):
530 95ab4de9 David Knowles
    """Gets information about an instance.
531 95ab4de9 David Knowles

532 95ab4de9 David Knowles
    @type instance: str
533 95ab4de9 David Knowles
    @param instance: instance whose info to return
534 95ab4de9 David Knowles

535 95ab4de9 David Knowles
    @rtype: dict
536 95ab4de9 David Knowles
    @return: info about the instance
537 95ab4de9 David Knowles

538 95ab4de9 David Knowles
    """
539 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
540 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s" %
541 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, None)
542 95ab4de9 David Knowles
543 591e5103 Michael Hanselmann
  def GetInstanceInfo(self, instance, static=None):
544 591e5103 Michael Hanselmann
    """Gets information about an instance.
545 591e5103 Michael Hanselmann

546 591e5103 Michael Hanselmann
    @type instance: string
547 591e5103 Michael Hanselmann
    @param instance: Instance name
548 591e5103 Michael Hanselmann
    @rtype: string
549 591e5103 Michael Hanselmann
    @return: Job ID
550 591e5103 Michael Hanselmann

551 591e5103 Michael Hanselmann
    """
552 591e5103 Michael Hanselmann
    if static is not None:
553 591e5103 Michael Hanselmann
      query = [("static", static)]
554 591e5103 Michael Hanselmann
    else:
555 591e5103 Michael Hanselmann
      query = None
556 591e5103 Michael Hanselmann
557 591e5103 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
558 591e5103 Michael Hanselmann
                             ("/%s/instances/%s/info" %
559 591e5103 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
560 591e5103 Michael Hanselmann
561 8a47b447 Michael Hanselmann
  def CreateInstance(self, mode, name, disk_template, disks, nics,
562 8a47b447 Michael Hanselmann
                     **kwargs):
563 95ab4de9 David Knowles
    """Creates a new instance.
564 95ab4de9 David Knowles

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

567 8a47b447 Michael Hanselmann
    @type mode: string
568 8a47b447 Michael Hanselmann
    @param mode: Instance creation mode
569 8a47b447 Michael Hanselmann
    @type name: string
570 8a47b447 Michael Hanselmann
    @param name: Hostname of the instance to create
571 8a47b447 Michael Hanselmann
    @type disk_template: string
572 8a47b447 Michael Hanselmann
    @param disk_template: Disk template for instance (e.g. plain, diskless,
573 8a47b447 Michael Hanselmann
                          file, or drbd)
574 8a47b447 Michael Hanselmann
    @type disks: list of dicts
575 8a47b447 Michael Hanselmann
    @param disks: List of disk definitions
576 8a47b447 Michael Hanselmann
    @type nics: list of dicts
577 8a47b447 Michael Hanselmann
    @param nics: List of NIC definitions
578 95ab4de9 David Knowles
    @type dry_run: bool
579 8a47b447 Michael Hanselmann
    @keyword dry_run: whether to perform a dry run
580 95ab4de9 David Knowles

581 95ab4de9 David Knowles
    @rtype: int
582 95ab4de9 David Knowles
    @return: job id
583 95ab4de9 David Knowles

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

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

695 cab667cc David Knowles
    @rtype: int
696 cab667cc David Knowles
    @return: job id
697 cab667cc David Knowles

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

710 95ab4de9 David Knowles
    @type instance: str
711 95ab4de9 David Knowles
    @param instance: instance whose tags to return
712 95ab4de9 David Knowles

713 95ab4de9 David Knowles
    @rtype: list of str
714 95ab4de9 David Knowles
    @return: tags for the instance
715 95ab4de9 David Knowles

716 95ab4de9 David Knowles
    """
717 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
718 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/tags" %
719 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, None)
720 95ab4de9 David Knowles
721 95ab4de9 David Knowles
  def AddInstanceTags(self, instance, tags, dry_run=False):
722 95ab4de9 David Knowles
    """Adds tags to an instance.
723 95ab4de9 David Knowles

724 95ab4de9 David Knowles
    @type instance: str
725 95ab4de9 David Knowles
    @param instance: instance to add tags to
726 95ab4de9 David Knowles
    @type tags: list of str
727 95ab4de9 David Knowles
    @param tags: tags to add to the instance
728 95ab4de9 David Knowles
    @type dry_run: bool
729 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
730 95ab4de9 David Knowles

731 95ab4de9 David Knowles
    @rtype: int
732 95ab4de9 David Knowles
    @return: job id
733 95ab4de9 David Knowles

734 95ab4de9 David Knowles
    """
735 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
736 95ab4de9 David Knowles
    if dry_run:
737 95ab4de9 David Knowles
      query.append(("dry-run", 1))
738 95ab4de9 David Knowles
739 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
740 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/tags" %
741 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
742 95ab4de9 David Knowles
743 95ab4de9 David Knowles
  def DeleteInstanceTags(self, instance, tags, dry_run=False):
744 95ab4de9 David Knowles
    """Deletes tags from an instance.
745 95ab4de9 David Knowles

746 95ab4de9 David Knowles
    @type instance: str
747 95ab4de9 David Knowles
    @param instance: instance to delete tags from
748 95ab4de9 David Knowles
    @type tags: list of str
749 95ab4de9 David Knowles
    @param tags: tags to delete
750 95ab4de9 David Knowles
    @type dry_run: bool
751 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
752 95ab4de9 David Knowles

753 95ab4de9 David Knowles
    """
754 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
755 95ab4de9 David Knowles
    if dry_run:
756 95ab4de9 David Knowles
      query.append(("dry-run", 1))
757 95ab4de9 David Knowles
758 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE,
759 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/tags" %
760 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
761 95ab4de9 David Knowles
762 95ab4de9 David Knowles
  def RebootInstance(self, instance, reboot_type=None, ignore_secondaries=None,
763 95ab4de9 David Knowles
                     dry_run=False):
764 95ab4de9 David Knowles
    """Reboots an instance.
765 95ab4de9 David Knowles

766 95ab4de9 David Knowles
    @type instance: str
767 95ab4de9 David Knowles
    @param instance: instance to rebot
768 95ab4de9 David Knowles
    @type reboot_type: str
769 95ab4de9 David Knowles
    @param reboot_type: one of: hard, soft, full
770 95ab4de9 David Knowles
    @type ignore_secondaries: bool
771 95ab4de9 David Knowles
    @param ignore_secondaries: if True, ignores errors for the secondary node
772 95ab4de9 David Knowles
        while re-assembling disks (in hard-reboot mode only)
773 95ab4de9 David Knowles
    @type dry_run: bool
774 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
775 95ab4de9 David Knowles

776 95ab4de9 David Knowles
    """
777 95ab4de9 David Knowles
    query = []
778 95ab4de9 David Knowles
    if reboot_type:
779 95ab4de9 David Knowles
      query.append(("type", reboot_type))
780 95ab4de9 David Knowles
    if ignore_secondaries is not None:
781 95ab4de9 David Knowles
      query.append(("ignore_secondaries", ignore_secondaries))
782 95ab4de9 David Knowles
    if dry_run:
783 95ab4de9 David Knowles
      query.append(("dry-run", 1))
784 95ab4de9 David Knowles
785 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_POST,
786 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/reboot" %
787 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
788 95ab4de9 David Knowles
789 95ab4de9 David Knowles
  def ShutdownInstance(self, instance, dry_run=False):
790 95ab4de9 David Knowles
    """Shuts down an instance.
791 95ab4de9 David Knowles

792 95ab4de9 David Knowles
    @type instance: str
793 95ab4de9 David Knowles
    @param instance: the instance to shut down
794 95ab4de9 David Knowles
    @type dry_run: bool
795 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
796 95ab4de9 David Knowles

797 95ab4de9 David Knowles
    """
798 95ab4de9 David Knowles
    query = []
799 95ab4de9 David Knowles
    if dry_run:
800 95ab4de9 David Knowles
      query.append(("dry-run", 1))
801 95ab4de9 David Knowles
802 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
803 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/shutdown" %
804 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
805 95ab4de9 David Knowles
806 95ab4de9 David Knowles
  def StartupInstance(self, instance, dry_run=False):
807 95ab4de9 David Knowles
    """Starts up an instance.
808 95ab4de9 David Knowles

809 95ab4de9 David Knowles
    @type instance: str
810 95ab4de9 David Knowles
    @param instance: the instance to start up
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 dry_run:
817 95ab4de9 David Knowles
      query.append(("dry-run", 1))
818 95ab4de9 David Knowles
819 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
820 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/startup" %
821 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
822 95ab4de9 David Knowles
823 95ab4de9 David Knowles
  def ReinstallInstance(self, instance, os, no_startup=False):
824 95ab4de9 David Knowles
    """Reinstalls an instance.
825 95ab4de9 David Knowles

826 95ab4de9 David Knowles
    @type instance: str
827 95ab4de9 David Knowles
    @param instance: the instance to reinstall
828 95ab4de9 David Knowles
    @type os: str
829 95ab4de9 David Knowles
    @param os: the os to reinstall
830 95ab4de9 David Knowles
    @type no_startup: bool
831 95ab4de9 David Knowles
    @param no_startup: whether to start the instance automatically
832 95ab4de9 David Knowles

833 95ab4de9 David Knowles
    """
834 95ab4de9 David Knowles
    query = [("os", os)]
835 95ab4de9 David Knowles
    if no_startup:
836 95ab4de9 David Knowles
      query.append(("nostartup", 1))
837 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_POST,
838 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/reinstall" %
839 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
840 95ab4de9 David Knowles
841 bfc2002f Michael Hanselmann
  def ReplaceInstanceDisks(self, instance, disks=None, mode=REPLACE_DISK_AUTO,
842 cfc03c54 Michael Hanselmann
                           remote_node=None, iallocator=None, dry_run=False):
843 95ab4de9 David Knowles
    """Replaces disks on an instance.
844 95ab4de9 David Knowles

845 95ab4de9 David Knowles
    @type instance: str
846 95ab4de9 David Knowles
    @param instance: instance whose disks to replace
847 bfc2002f Michael Hanselmann
    @type disks: list of ints
848 bfc2002f Michael Hanselmann
    @param disks: Indexes of disks to replace
849 95ab4de9 David Knowles
    @type mode: str
850 cfc03c54 Michael Hanselmann
    @param mode: replacement mode to use (defaults to replace_auto)
851 95ab4de9 David Knowles
    @type remote_node: str or None
852 95ab4de9 David Knowles
    @param remote_node: new secondary node to use (for use with
853 cfc03c54 Michael Hanselmann
        replace_new_secondary mode)
854 95ab4de9 David Knowles
    @type iallocator: str or None
855 95ab4de9 David Knowles
    @param iallocator: instance allocator plugin to use (for use with
856 cfc03c54 Michael Hanselmann
                       replace_auto mode)
857 95ab4de9 David Knowles
    @type dry_run: bool
858 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
859 95ab4de9 David Knowles

860 95ab4de9 David Knowles
    @rtype: int
861 95ab4de9 David Knowles
    @return: job id
862 95ab4de9 David Knowles

863 95ab4de9 David Knowles
    """
864 cfc03c54 Michael Hanselmann
    query = [
865 cfc03c54 Michael Hanselmann
      ("mode", mode),
866 cfc03c54 Michael Hanselmann
      ]
867 95ab4de9 David Knowles
868 bfc2002f Michael Hanselmann
    if disks:
869 bfc2002f Michael Hanselmann
      query.append(("disks", ",".join(str(idx) for idx in disks)))
870 bfc2002f Michael Hanselmann
871 bfc2002f Michael Hanselmann
    if remote_node:
872 95ab4de9 David Knowles
      query.append(("remote_node", remote_node))
873 95ab4de9 David Knowles
874 bfc2002f Michael Hanselmann
    if iallocator:
875 bfc2002f Michael Hanselmann
      query.append(("iallocator", iallocator))
876 bfc2002f Michael Hanselmann
877 95ab4de9 David Knowles
    if dry_run:
878 95ab4de9 David Knowles
      query.append(("dry-run", 1))
879 95ab4de9 David Knowles
880 95ab4de9 David Knowles
    return self._SendRequest(HTTP_POST,
881 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/replace-disks" %
882 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
883 95ab4de9 David Knowles
884 ebeb600f Michael Hanselmann
  def PrepareExport(self, instance, mode):
885 ebeb600f Michael Hanselmann
    """Prepares an instance for an export.
886 ebeb600f Michael Hanselmann

887 ebeb600f Michael Hanselmann
    @type instance: string
888 ebeb600f Michael Hanselmann
    @param instance: Instance name
889 ebeb600f Michael Hanselmann
    @type mode: string
890 ebeb600f Michael Hanselmann
    @param mode: Export mode
891 ebeb600f Michael Hanselmann
    @rtype: string
892 ebeb600f Michael Hanselmann
    @return: Job ID
893 ebeb600f Michael Hanselmann

894 ebeb600f Michael Hanselmann
    """
895 ebeb600f Michael Hanselmann
    query = [("mode", mode)]
896 ebeb600f Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
897 ebeb600f Michael Hanselmann
                             ("/%s/instances/%s/prepare-export" %
898 ebeb600f Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
899 ebeb600f Michael Hanselmann
900 ebeb600f Michael Hanselmann
  def ExportInstance(self, instance, mode, destination, shutdown=None,
901 ebeb600f Michael Hanselmann
                     remove_instance=None,
902 ebeb600f Michael Hanselmann
                     x509_key_name=None, destination_x509_ca=None):
903 ebeb600f Michael Hanselmann
    """Exports an instance.
904 ebeb600f Michael Hanselmann

905 ebeb600f Michael Hanselmann
    @type instance: string
906 ebeb600f Michael Hanselmann
    @param instance: Instance name
907 ebeb600f Michael Hanselmann
    @type mode: string
908 ebeb600f Michael Hanselmann
    @param mode: Export mode
909 ebeb600f Michael Hanselmann
    @rtype: string
910 ebeb600f Michael Hanselmann
    @return: Job ID
911 ebeb600f Michael Hanselmann

912 ebeb600f Michael Hanselmann
    """
913 ebeb600f Michael Hanselmann
    body = {
914 ebeb600f Michael Hanselmann
      "destination": destination,
915 ebeb600f Michael Hanselmann
      "mode": mode,
916 ebeb600f Michael Hanselmann
      }
917 ebeb600f Michael Hanselmann
918 ebeb600f Michael Hanselmann
    if shutdown is not None:
919 ebeb600f Michael Hanselmann
      body["shutdown"] = shutdown
920 ebeb600f Michael Hanselmann
921 ebeb600f Michael Hanselmann
    if remove_instance is not None:
922 ebeb600f Michael Hanselmann
      body["remove_instance"] = remove_instance
923 ebeb600f Michael Hanselmann
924 ebeb600f Michael Hanselmann
    if x509_key_name is not None:
925 ebeb600f Michael Hanselmann
      body["x509_key_name"] = x509_key_name
926 ebeb600f Michael Hanselmann
927 ebeb600f Michael Hanselmann
    if destination_x509_ca is not None:
928 ebeb600f Michael Hanselmann
      body["destination_x509_ca"] = destination_x509_ca
929 ebeb600f Michael Hanselmann
930 ebeb600f Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
931 ebeb600f Michael Hanselmann
                             ("/%s/instances/%s/export" %
932 ebeb600f Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, body)
933 ebeb600f Michael Hanselmann
934 95ab4de9 David Knowles
  def GetJobs(self):
935 95ab4de9 David Knowles
    """Gets all jobs for the cluster.
936 95ab4de9 David Knowles

937 95ab4de9 David Knowles
    @rtype: list of int
938 95ab4de9 David Knowles
    @return: job ids for the cluster
939 95ab4de9 David Knowles

940 95ab4de9 David Knowles
    """
941 768747ed Michael Hanselmann
    return [int(j["id"])
942 a198b2d9 Michael Hanselmann
            for j in self._SendRequest(HTTP_GET,
943 a198b2d9 Michael Hanselmann
                                       "/%s/jobs" % GANETI_RAPI_VERSION,
944 a198b2d9 Michael Hanselmann
                                       None, None)]
945 95ab4de9 David Knowles
946 95ab4de9 David Knowles
  def GetJobStatus(self, job_id):
947 95ab4de9 David Knowles
    """Gets the status of a job.
948 95ab4de9 David Knowles

949 95ab4de9 David Knowles
    @type job_id: int
950 95ab4de9 David Knowles
    @param job_id: job id whose status to query
951 95ab4de9 David Knowles

952 95ab4de9 David Knowles
    @rtype: dict
953 95ab4de9 David Knowles
    @return: job status
954 95ab4de9 David Knowles

955 95ab4de9 David Knowles
    """
956 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
957 a198b2d9 Michael Hanselmann
                             "/%s/jobs/%s" % (GANETI_RAPI_VERSION, job_id),
958 a198b2d9 Michael Hanselmann
                             None, None)
959 95ab4de9 David Knowles
960 d9b67f70 Michael Hanselmann
  def WaitForJobChange(self, job_id, fields, prev_job_info, prev_log_serial):
961 d9b67f70 Michael Hanselmann
    """Waits for job changes.
962 d9b67f70 Michael Hanselmann

963 d9b67f70 Michael Hanselmann
    @type job_id: int
964 d9b67f70 Michael Hanselmann
    @param job_id: Job ID for which to wait
965 d9b67f70 Michael Hanselmann

966 d9b67f70 Michael Hanselmann
    """
967 d9b67f70 Michael Hanselmann
    body = {
968 d9b67f70 Michael Hanselmann
      "fields": fields,
969 d9b67f70 Michael Hanselmann
      "previous_job_info": prev_job_info,
970 d9b67f70 Michael Hanselmann
      "previous_log_serial": prev_log_serial,
971 d9b67f70 Michael Hanselmann
      }
972 d9b67f70 Michael Hanselmann
973 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
974 a198b2d9 Michael Hanselmann
                             "/%s/jobs/%s/wait" % (GANETI_RAPI_VERSION, job_id),
975 a198b2d9 Michael Hanselmann
                             None, body)
976 d9b67f70 Michael Hanselmann
977 cf9ada49 Michael Hanselmann
  def CancelJob(self, job_id, dry_run=False):
978 cf9ada49 Michael Hanselmann
    """Cancels a job.
979 95ab4de9 David Knowles

980 95ab4de9 David Knowles
    @type job_id: int
981 95ab4de9 David Knowles
    @param job_id: id of the job to delete
982 95ab4de9 David Knowles
    @type dry_run: bool
983 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
984 95ab4de9 David Knowles

985 95ab4de9 David Knowles
    """
986 95ab4de9 David Knowles
    query = []
987 95ab4de9 David Knowles
    if dry_run:
988 95ab4de9 David Knowles
      query.append(("dry-run", 1))
989 95ab4de9 David Knowles
990 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE,
991 a198b2d9 Michael Hanselmann
                             "/%s/jobs/%s" % (GANETI_RAPI_VERSION, job_id),
992 a198b2d9 Michael Hanselmann
                             query, None)
993 95ab4de9 David Knowles
994 95ab4de9 David Knowles
  def GetNodes(self, bulk=False):
995 95ab4de9 David Knowles
    """Gets all nodes in the cluster.
996 95ab4de9 David Knowles

997 95ab4de9 David Knowles
    @type bulk: bool
998 95ab4de9 David Knowles
    @param bulk: whether to return all information about all instances
999 95ab4de9 David Knowles

1000 95ab4de9 David Knowles
    @rtype: list of dict or str
1001 95ab4de9 David Knowles
    @return: if bulk is true, info about nodes in the cluster,
1002 95ab4de9 David Knowles
        else list of nodes in the cluster
1003 95ab4de9 David Knowles

1004 95ab4de9 David Knowles
    """
1005 95ab4de9 David Knowles
    query = []
1006 95ab4de9 David Knowles
    if bulk:
1007 95ab4de9 David Knowles
      query.append(("bulk", 1))
1008 95ab4de9 David Knowles
1009 a198b2d9 Michael Hanselmann
    nodes = self._SendRequest(HTTP_GET, "/%s/nodes" % GANETI_RAPI_VERSION,
1010 a198b2d9 Michael Hanselmann
                              query, None)
1011 95ab4de9 David Knowles
    if bulk:
1012 95ab4de9 David Knowles
      return nodes
1013 95ab4de9 David Knowles
    else:
1014 95ab4de9 David Knowles
      return [n["id"] for n in nodes]
1015 95ab4de9 David Knowles
1016 591e5103 Michael Hanselmann
  def GetNode(self, node):
1017 95ab4de9 David Knowles
    """Gets information about a node.
1018 95ab4de9 David Knowles

1019 95ab4de9 David Knowles
    @type node: str
1020 95ab4de9 David Knowles
    @param node: node whose info to return
1021 95ab4de9 David Knowles

1022 95ab4de9 David Knowles
    @rtype: dict
1023 95ab4de9 David Knowles
    @return: info about the node
1024 95ab4de9 David Knowles

1025 95ab4de9 David Knowles
    """
1026 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1027 a198b2d9 Michael Hanselmann
                             "/%s/nodes/%s" % (GANETI_RAPI_VERSION, node),
1028 a198b2d9 Michael Hanselmann
                             None, None)
1029 95ab4de9 David Knowles
1030 95ab4de9 David Knowles
  def EvacuateNode(self, node, iallocator=None, remote_node=None,
1031 941b9309 Iustin Pop
                   dry_run=False, early_release=False):
1032 95ab4de9 David Knowles
    """Evacuates instances from a Ganeti node.
1033 95ab4de9 David Knowles

1034 95ab4de9 David Knowles
    @type node: str
1035 95ab4de9 David Knowles
    @param node: node to evacuate
1036 95ab4de9 David Knowles
    @type iallocator: str or None
1037 95ab4de9 David Knowles
    @param iallocator: instance allocator to use
1038 95ab4de9 David Knowles
    @type remote_node: str
1039 95ab4de9 David Knowles
    @param remote_node: node to evaucate to
1040 95ab4de9 David Knowles
    @type dry_run: bool
1041 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1042 941b9309 Iustin Pop
    @type early_release: bool
1043 941b9309 Iustin Pop
    @param early_release: whether to enable parallelization
1044 95ab4de9 David Knowles

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

1050 941b9309 Iustin Pop
    @raises GanetiApiError: if an iallocator and remote_node are both
1051 941b9309 Iustin Pop
        specified
1052 95ab4de9 David Knowles

1053 95ab4de9 David Knowles
    """
1054 95ab4de9 David Knowles
    if iallocator and remote_node:
1055 cfc03c54 Michael Hanselmann
      raise GanetiApiError("Only one of iallocator or remote_node can be used")
1056 95ab4de9 David Knowles
1057 cfc03c54 Michael Hanselmann
    query = []
1058 95ab4de9 David Knowles
    if iallocator:
1059 95ab4de9 David Knowles
      query.append(("iallocator", iallocator))
1060 95ab4de9 David Knowles
    if remote_node:
1061 95ab4de9 David Knowles
      query.append(("remote_node", remote_node))
1062 95ab4de9 David Knowles
    if dry_run:
1063 95ab4de9 David Knowles
      query.append(("dry-run", 1))
1064 941b9309 Iustin Pop
    if early_release:
1065 941b9309 Iustin Pop
      query.append(("early_release", 1))
1066 95ab4de9 David Knowles
1067 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_POST,
1068 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/evacuate" %
1069 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1070 95ab4de9 David Knowles
1071 1f334d96 Iustin Pop
  def MigrateNode(self, node, mode=None, dry_run=False):
1072 95ab4de9 David Knowles
    """Migrates all primary instances from a node.
1073 95ab4de9 David Knowles

1074 95ab4de9 David Knowles
    @type node: str
1075 95ab4de9 David Knowles
    @param node: node to migrate
1076 1f334d96 Iustin Pop
    @type mode: string
1077 1f334d96 Iustin Pop
    @param mode: if passed, it will overwrite the live migration type,
1078 1f334d96 Iustin Pop
        otherwise the hypervisor default will be used
1079 95ab4de9 David Knowles
    @type dry_run: bool
1080 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1081 95ab4de9 David Knowles

1082 95ab4de9 David Knowles
    @rtype: int
1083 95ab4de9 David Knowles
    @return: job id
1084 95ab4de9 David Knowles

1085 95ab4de9 David Knowles
    """
1086 95ab4de9 David Knowles
    query = []
1087 1f334d96 Iustin Pop
    if mode is not None:
1088 1f334d96 Iustin Pop
      query.append(("mode", mode))
1089 95ab4de9 David Knowles
    if dry_run:
1090 95ab4de9 David Knowles
      query.append(("dry-run", 1))
1091 95ab4de9 David Knowles
1092 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_POST,
1093 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/migrate" %
1094 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1095 95ab4de9 David Knowles
1096 95ab4de9 David Knowles
  def GetNodeRole(self, node):
1097 95ab4de9 David Knowles
    """Gets the current role for a node.
1098 95ab4de9 David Knowles

1099 95ab4de9 David Knowles
    @type node: str
1100 95ab4de9 David Knowles
    @param node: node whose role to return
1101 95ab4de9 David Knowles

1102 95ab4de9 David Knowles
    @rtype: str
1103 95ab4de9 David Knowles
    @return: the current role for a node
1104 95ab4de9 David Knowles

1105 95ab4de9 David Knowles
    """
1106 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1107 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/role" %
1108 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), None, None)
1109 95ab4de9 David Knowles
1110 95ab4de9 David Knowles
  def SetNodeRole(self, node, role, force=False):
1111 95ab4de9 David Knowles
    """Sets the role for a node.
1112 95ab4de9 David Knowles

1113 95ab4de9 David Knowles
    @type node: str
1114 95ab4de9 David Knowles
    @param node: the node whose role to set
1115 95ab4de9 David Knowles
    @type role: str
1116 95ab4de9 David Knowles
    @param role: the role to set for the node
1117 95ab4de9 David Knowles
    @type force: bool
1118 95ab4de9 David Knowles
    @param force: whether to force the role change
1119 95ab4de9 David Knowles

1120 95ab4de9 David Knowles
    @rtype: int
1121 95ab4de9 David Knowles
    @return: job id
1122 95ab4de9 David Knowles

1123 95ab4de9 David Knowles
    """
1124 1068639f Michael Hanselmann
    query = [
1125 1068639f Michael Hanselmann
      ("force", force),
1126 1068639f Michael Hanselmann
      ]
1127 cfc03c54 Michael Hanselmann
1128 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1129 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/role" %
1130 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, role)
1131 95ab4de9 David Knowles
1132 95ab4de9 David Knowles
  def GetNodeStorageUnits(self, node, storage_type, output_fields):
1133 95ab4de9 David Knowles
    """Gets the storage units for a node.
1134 95ab4de9 David Knowles

1135 95ab4de9 David Knowles
    @type node: str
1136 95ab4de9 David Knowles
    @param node: the node whose storage units to return
1137 95ab4de9 David Knowles
    @type storage_type: str
1138 95ab4de9 David Knowles
    @param storage_type: storage type whose units to return
1139 95ab4de9 David Knowles
    @type output_fields: str
1140 95ab4de9 David Knowles
    @param output_fields: storage type fields to return
1141 95ab4de9 David Knowles

1142 95ab4de9 David Knowles
    @rtype: int
1143 95ab4de9 David Knowles
    @return: job id where results can be retrieved
1144 95ab4de9 David Knowles

1145 95ab4de9 David Knowles
    """
1146 cfc03c54 Michael Hanselmann
    query = [
1147 cfc03c54 Michael Hanselmann
      ("storage_type", storage_type),
1148 cfc03c54 Michael Hanselmann
      ("output_fields", output_fields),
1149 cfc03c54 Michael Hanselmann
      ]
1150 95ab4de9 David Knowles
1151 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1152 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/storage" %
1153 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1154 95ab4de9 David Knowles
1155 fde28316 Michael Hanselmann
  def ModifyNodeStorageUnits(self, node, storage_type, name, allocatable=None):
1156 95ab4de9 David Knowles
    """Modifies parameters of storage units on the node.
1157 95ab4de9 David Knowles

1158 95ab4de9 David Knowles
    @type node: str
1159 95ab4de9 David Knowles
    @param node: node whose storage units to modify
1160 95ab4de9 David Knowles
    @type storage_type: str
1161 95ab4de9 David Knowles
    @param storage_type: storage type whose units to modify
1162 95ab4de9 David Knowles
    @type name: str
1163 95ab4de9 David Knowles
    @param name: name of the storage unit
1164 fde28316 Michael Hanselmann
    @type allocatable: bool or None
1165 fde28316 Michael Hanselmann
    @param allocatable: Whether to set the "allocatable" flag on the storage
1166 fde28316 Michael Hanselmann
                        unit (None=no modification, True=set, False=unset)
1167 95ab4de9 David Knowles

1168 95ab4de9 David Knowles
    @rtype: int
1169 95ab4de9 David Knowles
    @return: job id
1170 95ab4de9 David Knowles

1171 95ab4de9 David Knowles
    """
1172 95ab4de9 David Knowles
    query = [
1173 cfc03c54 Michael Hanselmann
      ("storage_type", storage_type),
1174 cfc03c54 Michael Hanselmann
      ("name", name),
1175 cfc03c54 Michael Hanselmann
      ]
1176 cfc03c54 Michael Hanselmann
1177 fde28316 Michael Hanselmann
    if allocatable is not None:
1178 fde28316 Michael Hanselmann
      query.append(("allocatable", allocatable))
1179 fde28316 Michael Hanselmann
1180 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1181 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/storage/modify" %
1182 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1183 95ab4de9 David Knowles
1184 95ab4de9 David Knowles
  def RepairNodeStorageUnits(self, node, storage_type, name):
1185 95ab4de9 David Knowles
    """Repairs a storage unit on the node.
1186 95ab4de9 David Knowles

1187 95ab4de9 David Knowles
    @type node: str
1188 95ab4de9 David Knowles
    @param node: node whose storage units to repair
1189 95ab4de9 David Knowles
    @type storage_type: str
1190 95ab4de9 David Knowles
    @param storage_type: storage type to repair
1191 95ab4de9 David Knowles
    @type name: str
1192 95ab4de9 David Knowles
    @param name: name of the storage unit to repair
1193 95ab4de9 David Knowles

1194 95ab4de9 David Knowles
    @rtype: int
1195 95ab4de9 David Knowles
    @return: job id
1196 95ab4de9 David Knowles

1197 95ab4de9 David Knowles
    """
1198 cfc03c54 Michael Hanselmann
    query = [
1199 cfc03c54 Michael Hanselmann
      ("storage_type", storage_type),
1200 cfc03c54 Michael Hanselmann
      ("name", name),
1201 cfc03c54 Michael Hanselmann
      ]
1202 95ab4de9 David Knowles
1203 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1204 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/storage/repair" %
1205 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1206 95ab4de9 David Knowles
1207 95ab4de9 David Knowles
  def GetNodeTags(self, node):
1208 95ab4de9 David Knowles
    """Gets the tags for a node.
1209 95ab4de9 David Knowles

1210 95ab4de9 David Knowles
    @type node: str
1211 95ab4de9 David Knowles
    @param node: node whose tags to return
1212 95ab4de9 David Knowles

1213 95ab4de9 David Knowles
    @rtype: list of str
1214 95ab4de9 David Knowles
    @return: tags for the node
1215 95ab4de9 David Knowles

1216 95ab4de9 David Knowles
    """
1217 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1218 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/tags" %
1219 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), None, None)
1220 95ab4de9 David Knowles
1221 95ab4de9 David Knowles
  def AddNodeTags(self, node, tags, dry_run=False):
1222 95ab4de9 David Knowles
    """Adds tags to a node.
1223 95ab4de9 David Knowles

1224 95ab4de9 David Knowles
    @type node: str
1225 95ab4de9 David Knowles
    @param node: node to add tags to
1226 95ab4de9 David Knowles
    @type tags: list of str
1227 95ab4de9 David Knowles
    @param tags: tags to add to the node
1228 95ab4de9 David Knowles
    @type dry_run: bool
1229 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1230 95ab4de9 David Knowles

1231 95ab4de9 David Knowles
    @rtype: int
1232 95ab4de9 David Knowles
    @return: job id
1233 95ab4de9 David Knowles

1234 95ab4de9 David Knowles
    """
1235 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
1236 95ab4de9 David Knowles
    if dry_run:
1237 95ab4de9 David Knowles
      query.append(("dry-run", 1))
1238 95ab4de9 David Knowles
1239 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1240 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/tags" %
1241 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, tags)
1242 95ab4de9 David Knowles
1243 95ab4de9 David Knowles
  def DeleteNodeTags(self, node, tags, dry_run=False):
1244 95ab4de9 David Knowles
    """Delete tags from a node.
1245 95ab4de9 David Knowles

1246 95ab4de9 David Knowles
    @type node: str
1247 95ab4de9 David Knowles
    @param node: node to remove tags from
1248 95ab4de9 David Knowles
    @type tags: list of str
1249 95ab4de9 David Knowles
    @param tags: tags to remove from the node
1250 95ab4de9 David Knowles
    @type dry_run: bool
1251 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1252 95ab4de9 David Knowles

1253 95ab4de9 David Knowles
    @rtype: int
1254 95ab4de9 David Knowles
    @return: job id
1255 95ab4de9 David Knowles

1256 95ab4de9 David Knowles
    """
1257 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
1258 95ab4de9 David Knowles
    if dry_run:
1259 95ab4de9 David Knowles
      query.append(("dry-run", 1))
1260 95ab4de9 David Knowles
1261 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE,
1262 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/tags" %
1263 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)