Statistics
| Branch: | Tag: | Revision:

root / lib / rapi / client.py @ 2ba39b8f

History | View | Annotate | Download (46.8 kB)

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

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

31 2a7c3583 Michael Hanselmann
"""
32 95ab4de9 David Knowles
33 5ef5cfea Michael Hanselmann
# No Ganeti-specific modules should be imported. The RAPI client is supposed to
34 5ef5cfea Michael Hanselmann
# be standalone.
35 5ef5cfea Michael Hanselmann
36 9279e986 Michael Hanselmann
import logging
37 95ab4de9 David Knowles
import simplejson
38 1a8337f2 Manuel Franceschini
import socket
39 95ab4de9 David Knowles
import urllib
40 2a7c3583 Michael Hanselmann
import threading
41 2a7c3583 Michael Hanselmann
import pycurl
42 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 c744425f Michael Hanselmann
_INST_REINSTALL_REQV1 = "instance-reinstall-reqv1"
75 48436b97 Michael Hanselmann
_INST_NIC_PARAMS = frozenset(["mac", "ip", "mode", "link", "bridge"])
76 48436b97 Michael Hanselmann
_INST_CREATE_V0_DISK_PARAMS = frozenset(["size"])
77 48436b97 Michael Hanselmann
_INST_CREATE_V0_PARAMS = frozenset([
78 48436b97 Michael Hanselmann
  "os", "pnode", "snode", "iallocator", "start", "ip_check", "name_check",
79 48436b97 Michael Hanselmann
  "hypervisor", "file_storage_dir", "file_driver", "dry_run",
80 48436b97 Michael Hanselmann
  ])
81 48436b97 Michael Hanselmann
_INST_CREATE_V0_DPARAMS = frozenset(["beparams", "hvparams"])
82 8a47b447 Michael Hanselmann
83 2a7c3583 Michael Hanselmann
# Older pycURL versions don't have all error constants
84 2a7c3583 Michael Hanselmann
try:
85 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT = pycurl.E_SSL_CACERT
86 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT_BADFILE = pycurl.E_SSL_CACERT_BADFILE
87 2a7c3583 Michael Hanselmann
except AttributeError:
88 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT = 60
89 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT_BADFILE = 77
90 2a7c3583 Michael Hanselmann
91 2a7c3583 Michael Hanselmann
_CURL_SSL_CERT_ERRORS = frozenset([
92 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT,
93 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT_BADFILE,
94 2a7c3583 Michael Hanselmann
  ])
95 2a7c3583 Michael Hanselmann
96 95ab4de9 David Knowles
97 95ab4de9 David Knowles
class Error(Exception):
98 95ab4de9 David Knowles
  """Base error class for this module.
99 95ab4de9 David Knowles

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

480 95ab4de9 David Knowles
    """
481 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET, "/%s/info" % GANETI_RAPI_VERSION,
482 a198b2d9 Michael Hanselmann
                             None, None)
483 95ab4de9 David Knowles
484 54d4c13b Michael Hanselmann
  def RedistributeConfig(self):
485 54d4c13b Michael Hanselmann
    """Tells the cluster to redistribute its configuration files.
486 54d4c13b Michael Hanselmann

487 54d4c13b Michael Hanselmann
    @return: job id
488 54d4c13b Michael Hanselmann

489 54d4c13b Michael Hanselmann
    """
490 54d4c13b Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
491 54d4c13b Michael Hanselmann
                             "/%s/redistribute-config" % GANETI_RAPI_VERSION,
492 54d4c13b Michael Hanselmann
                             None, None)
493 54d4c13b Michael Hanselmann
494 62e999a5 Michael Hanselmann
  def ModifyCluster(self, **kwargs):
495 62e999a5 Michael Hanselmann
    """Modifies cluster parameters.
496 62e999a5 Michael Hanselmann

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

499 62e999a5 Michael Hanselmann
    @rtype: int
500 62e999a5 Michael Hanselmann
    @return: job id
501 62e999a5 Michael Hanselmann

502 62e999a5 Michael Hanselmann
    """
503 62e999a5 Michael Hanselmann
    body = kwargs
504 62e999a5 Michael Hanselmann
505 62e999a5 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
506 62e999a5 Michael Hanselmann
                             "/%s/modify" % GANETI_RAPI_VERSION, None, body)
507 62e999a5 Michael Hanselmann
508 95ab4de9 David Knowles
  def GetClusterTags(self):
509 95ab4de9 David Knowles
    """Gets the cluster tags.
510 95ab4de9 David Knowles

511 95ab4de9 David Knowles
    @rtype: list of str
512 95ab4de9 David Knowles
    @return: cluster tags
513 95ab4de9 David Knowles

514 95ab4de9 David Knowles
    """
515 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET, "/%s/tags" % GANETI_RAPI_VERSION,
516 a198b2d9 Michael Hanselmann
                             None, None)
517 95ab4de9 David Knowles
518 95ab4de9 David Knowles
  def AddClusterTags(self, tags, dry_run=False):
519 95ab4de9 David Knowles
    """Adds tags to the cluster.
520 95ab4de9 David Knowles

521 95ab4de9 David Knowles
    @type tags: list of str
522 95ab4de9 David Knowles
    @param tags: tags to add to the cluster
523 95ab4de9 David Knowles
    @type dry_run: bool
524 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
525 95ab4de9 David Knowles

526 95ab4de9 David Knowles
    @rtype: int
527 95ab4de9 David Knowles
    @return: job id
528 95ab4de9 David Knowles

529 95ab4de9 David Knowles
    """
530 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
531 95ab4de9 David Knowles
    if dry_run:
532 95ab4de9 David Knowles
      query.append(("dry-run", 1))
533 95ab4de9 David Knowles
534 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT, "/%s/tags" % GANETI_RAPI_VERSION,
535 a198b2d9 Michael Hanselmann
                             query, None)
536 95ab4de9 David Knowles
537 95ab4de9 David Knowles
  def DeleteClusterTags(self, tags, dry_run=False):
538 95ab4de9 David Knowles
    """Deletes tags from the cluster.
539 95ab4de9 David Knowles

540 95ab4de9 David Knowles
    @type tags: list of str
541 95ab4de9 David Knowles
    @param tags: tags to delete
542 95ab4de9 David Knowles
    @type dry_run: bool
543 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
544 95ab4de9 David Knowles

545 95ab4de9 David Knowles
    """
546 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
547 95ab4de9 David Knowles
    if dry_run:
548 95ab4de9 David Knowles
      query.append(("dry-run", 1))
549 95ab4de9 David Knowles
550 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE, "/%s/tags" % GANETI_RAPI_VERSION,
551 a198b2d9 Michael Hanselmann
                             query, None)
552 95ab4de9 David Knowles
553 95ab4de9 David Knowles
  def GetInstances(self, bulk=False):
554 95ab4de9 David Knowles
    """Gets information about instances on the cluster.
555 95ab4de9 David Knowles

556 95ab4de9 David Knowles
    @type bulk: bool
557 95ab4de9 David Knowles
    @param bulk: whether to return all information about all instances
558 95ab4de9 David Knowles

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

562 95ab4de9 David Knowles
    """
563 95ab4de9 David Knowles
    query = []
564 95ab4de9 David Knowles
    if bulk:
565 95ab4de9 David Knowles
      query.append(("bulk", 1))
566 95ab4de9 David Knowles
567 a198b2d9 Michael Hanselmann
    instances = self._SendRequest(HTTP_GET,
568 a198b2d9 Michael Hanselmann
                                  "/%s/instances" % GANETI_RAPI_VERSION,
569 a198b2d9 Michael Hanselmann
                                  query, None)
570 95ab4de9 David Knowles
    if bulk:
571 95ab4de9 David Knowles
      return instances
572 95ab4de9 David Knowles
    else:
573 95ab4de9 David Knowles
      return [i["id"] for i in instances]
574 95ab4de9 David Knowles
575 591e5103 Michael Hanselmann
  def GetInstance(self, instance):
576 95ab4de9 David Knowles
    """Gets information about an instance.
577 95ab4de9 David Knowles

578 95ab4de9 David Knowles
    @type instance: str
579 95ab4de9 David Knowles
    @param instance: instance whose info to return
580 95ab4de9 David Knowles

581 95ab4de9 David Knowles
    @rtype: dict
582 95ab4de9 David Knowles
    @return: info about the instance
583 95ab4de9 David Knowles

584 95ab4de9 David Knowles
    """
585 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
586 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s" %
587 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, None)
588 95ab4de9 David Knowles
589 591e5103 Michael Hanselmann
  def GetInstanceInfo(self, instance, static=None):
590 591e5103 Michael Hanselmann
    """Gets information about an instance.
591 591e5103 Michael Hanselmann

592 591e5103 Michael Hanselmann
    @type instance: string
593 591e5103 Michael Hanselmann
    @param instance: Instance name
594 591e5103 Michael Hanselmann
    @rtype: string
595 591e5103 Michael Hanselmann
    @return: Job ID
596 591e5103 Michael Hanselmann

597 591e5103 Michael Hanselmann
    """
598 591e5103 Michael Hanselmann
    if static is not None:
599 591e5103 Michael Hanselmann
      query = [("static", static)]
600 591e5103 Michael Hanselmann
    else:
601 591e5103 Michael Hanselmann
      query = None
602 591e5103 Michael Hanselmann
603 591e5103 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
604 591e5103 Michael Hanselmann
                             ("/%s/instances/%s/info" %
605 591e5103 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
606 591e5103 Michael Hanselmann
607 8a47b447 Michael Hanselmann
  def CreateInstance(self, mode, name, disk_template, disks, nics,
608 8a47b447 Michael Hanselmann
                     **kwargs):
609 95ab4de9 David Knowles
    """Creates a new instance.
610 95ab4de9 David Knowles

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

613 8a47b447 Michael Hanselmann
    @type mode: string
614 8a47b447 Michael Hanselmann
    @param mode: Instance creation mode
615 8a47b447 Michael Hanselmann
    @type name: string
616 8a47b447 Michael Hanselmann
    @param name: Hostname of the instance to create
617 8a47b447 Michael Hanselmann
    @type disk_template: string
618 8a47b447 Michael Hanselmann
    @param disk_template: Disk template for instance (e.g. plain, diskless,
619 8a47b447 Michael Hanselmann
                          file, or drbd)
620 8a47b447 Michael Hanselmann
    @type disks: list of dicts
621 8a47b447 Michael Hanselmann
    @param disks: List of disk definitions
622 8a47b447 Michael Hanselmann
    @type nics: list of dicts
623 8a47b447 Michael Hanselmann
    @param nics: List of NIC definitions
624 95ab4de9 David Knowles
    @type dry_run: bool
625 8a47b447 Michael Hanselmann
    @keyword dry_run: whether to perform a dry run
626 95ab4de9 David Knowles

627 95ab4de9 David Knowles
    @rtype: int
628 95ab4de9 David Knowles
    @return: job id
629 95ab4de9 David Knowles

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

738 95ab4de9 David Knowles
    @type instance: str
739 95ab4de9 David Knowles
    @param instance: the instance to delete
740 95ab4de9 David Knowles

741 cab667cc David Knowles
    @rtype: int
742 cab667cc David Knowles
    @return: job id
743 cab667cc David Knowles

744 95ab4de9 David Knowles
    """
745 95ab4de9 David Knowles
    query = []
746 95ab4de9 David Knowles
    if dry_run:
747 95ab4de9 David Knowles
      query.append(("dry-run", 1))
748 95ab4de9 David Knowles
749 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE,
750 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s" %
751 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
752 95ab4de9 David Knowles
753 3b7158ef Michael Hanselmann
  def ModifyInstance(self, instance, **kwargs):
754 3b7158ef Michael Hanselmann
    """Modifies an instance.
755 3b7158ef Michael Hanselmann

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

758 3b7158ef Michael Hanselmann
    @type instance: string
759 3b7158ef Michael Hanselmann
    @param instance: Instance name
760 3b7158ef Michael Hanselmann
    @rtype: int
761 3b7158ef Michael Hanselmann
    @return: job id
762 3b7158ef Michael Hanselmann

763 3b7158ef Michael Hanselmann
    """
764 3b7158ef Michael Hanselmann
    body = kwargs
765 3b7158ef Michael Hanselmann
766 3b7158ef Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
767 3b7158ef Michael Hanselmann
                             ("/%s/instances/%s/modify" %
768 3b7158ef Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, body)
769 3b7158ef Michael Hanselmann
770 b680c8be Michael Hanselmann
  def ActivateInstanceDisks(self, instance, ignore_size=None):
771 b680c8be Michael Hanselmann
    """Activates an instance's disks.
772 b680c8be Michael Hanselmann

773 b680c8be Michael Hanselmann
    @type instance: string
774 b680c8be Michael Hanselmann
    @param instance: Instance name
775 b680c8be Michael Hanselmann
    @type ignore_size: bool
776 b680c8be Michael Hanselmann
    @param ignore_size: Whether to ignore recorded size
777 b680c8be Michael Hanselmann
    @return: job id
778 b680c8be Michael Hanselmann

779 b680c8be Michael Hanselmann
    """
780 b680c8be Michael Hanselmann
    query = []
781 b680c8be Michael Hanselmann
    if ignore_size:
782 b680c8be Michael Hanselmann
      query.append(("ignore_size", 1))
783 b680c8be Michael Hanselmann
784 b680c8be Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
785 b680c8be Michael Hanselmann
                             ("/%s/instances/%s/activate-disks" %
786 b680c8be Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
787 b680c8be Michael Hanselmann
788 b680c8be Michael Hanselmann
  def DeactivateInstanceDisks(self, instance):
789 b680c8be Michael Hanselmann
    """Deactivates an instance's disks.
790 b680c8be Michael Hanselmann

791 b680c8be Michael Hanselmann
    @type instance: string
792 b680c8be Michael Hanselmann
    @param instance: Instance name
793 b680c8be Michael Hanselmann
    @return: job id
794 b680c8be Michael Hanselmann

795 b680c8be Michael Hanselmann
    """
796 b680c8be Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
797 b680c8be Michael Hanselmann
                             ("/%s/instances/%s/deactivate-disks" %
798 b680c8be Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, None)
799 b680c8be Michael Hanselmann
800 e23881ed Michael Hanselmann
  def GrowInstanceDisk(self, instance, disk, amount, wait_for_sync=None):
801 e23881ed Michael Hanselmann
    """Grows a disk of an instance.
802 e23881ed Michael Hanselmann

803 e23881ed Michael Hanselmann
    More details for parameters can be found in the RAPI documentation.
804 e23881ed Michael Hanselmann

805 e23881ed Michael Hanselmann
    @type instance: string
806 e23881ed Michael Hanselmann
    @param instance: Instance name
807 e23881ed Michael Hanselmann
    @type disk: integer
808 e23881ed Michael Hanselmann
    @param disk: Disk index
809 e23881ed Michael Hanselmann
    @type amount: integer
810 e23881ed Michael Hanselmann
    @param amount: Grow disk by this amount (MiB)
811 e23881ed Michael Hanselmann
    @type wait_for_sync: bool
812 e23881ed Michael Hanselmann
    @param wait_for_sync: Wait for disk to synchronize
813 e23881ed Michael Hanselmann
    @rtype: int
814 e23881ed Michael Hanselmann
    @return: job id
815 e23881ed Michael Hanselmann

816 e23881ed Michael Hanselmann
    """
817 e23881ed Michael Hanselmann
    body = {
818 e23881ed Michael Hanselmann
      "amount": amount,
819 e23881ed Michael Hanselmann
      }
820 e23881ed Michael Hanselmann
821 e23881ed Michael Hanselmann
    if wait_for_sync is not None:
822 e23881ed Michael Hanselmann
      body["wait_for_sync"] = wait_for_sync
823 e23881ed Michael Hanselmann
824 e23881ed Michael Hanselmann
    return self._SendRequest(HTTP_POST,
825 e23881ed Michael Hanselmann
                             ("/%s/instances/%s/disk/%s/grow" %
826 e23881ed Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance, disk)),
827 e23881ed Michael Hanselmann
                             None, body)
828 e23881ed Michael Hanselmann
829 95ab4de9 David Knowles
  def GetInstanceTags(self, instance):
830 95ab4de9 David Knowles
    """Gets tags for an instance.
831 95ab4de9 David Knowles

832 95ab4de9 David Knowles
    @type instance: str
833 95ab4de9 David Knowles
    @param instance: instance whose tags to return
834 95ab4de9 David Knowles

835 95ab4de9 David Knowles
    @rtype: list of str
836 95ab4de9 David Knowles
    @return: tags for the instance
837 95ab4de9 David Knowles

838 95ab4de9 David Knowles
    """
839 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
840 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/tags" %
841 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, None)
842 95ab4de9 David Knowles
843 95ab4de9 David Knowles
  def AddInstanceTags(self, instance, tags, dry_run=False):
844 95ab4de9 David Knowles
    """Adds tags to an instance.
845 95ab4de9 David Knowles

846 95ab4de9 David Knowles
    @type instance: str
847 95ab4de9 David Knowles
    @param instance: instance to add tags to
848 95ab4de9 David Knowles
    @type tags: list of str
849 95ab4de9 David Knowles
    @param tags: tags to add to the instance
850 95ab4de9 David Knowles
    @type dry_run: bool
851 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
852 95ab4de9 David Knowles

853 95ab4de9 David Knowles
    @rtype: int
854 95ab4de9 David Knowles
    @return: job id
855 95ab4de9 David Knowles

856 95ab4de9 David Knowles
    """
857 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
858 95ab4de9 David Knowles
    if dry_run:
859 95ab4de9 David Knowles
      query.append(("dry-run", 1))
860 95ab4de9 David Knowles
861 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
862 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/tags" %
863 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
864 95ab4de9 David Knowles
865 95ab4de9 David Knowles
  def DeleteInstanceTags(self, instance, tags, dry_run=False):
866 95ab4de9 David Knowles
    """Deletes tags from an instance.
867 95ab4de9 David Knowles

868 95ab4de9 David Knowles
    @type instance: str
869 95ab4de9 David Knowles
    @param instance: instance to delete tags from
870 95ab4de9 David Knowles
    @type tags: list of str
871 95ab4de9 David Knowles
    @param tags: tags to delete
872 95ab4de9 David Knowles
    @type dry_run: bool
873 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
874 95ab4de9 David Knowles

875 95ab4de9 David Knowles
    """
876 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
877 95ab4de9 David Knowles
    if dry_run:
878 95ab4de9 David Knowles
      query.append(("dry-run", 1))
879 95ab4de9 David Knowles
880 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE,
881 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/tags" %
882 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
883 95ab4de9 David Knowles
884 95ab4de9 David Knowles
  def RebootInstance(self, instance, reboot_type=None, ignore_secondaries=None,
885 95ab4de9 David Knowles
                     dry_run=False):
886 95ab4de9 David Knowles
    """Reboots an instance.
887 95ab4de9 David Knowles

888 95ab4de9 David Knowles
    @type instance: str
889 95ab4de9 David Knowles
    @param instance: instance to rebot
890 95ab4de9 David Knowles
    @type reboot_type: str
891 95ab4de9 David Knowles
    @param reboot_type: one of: hard, soft, full
892 95ab4de9 David Knowles
    @type ignore_secondaries: bool
893 95ab4de9 David Knowles
    @param ignore_secondaries: if True, ignores errors for the secondary node
894 95ab4de9 David Knowles
        while re-assembling disks (in hard-reboot mode only)
895 95ab4de9 David Knowles
    @type dry_run: bool
896 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
897 95ab4de9 David Knowles

898 95ab4de9 David Knowles
    """
899 95ab4de9 David Knowles
    query = []
900 95ab4de9 David Knowles
    if reboot_type:
901 95ab4de9 David Knowles
      query.append(("type", reboot_type))
902 95ab4de9 David Knowles
    if ignore_secondaries is not None:
903 95ab4de9 David Knowles
      query.append(("ignore_secondaries", ignore_secondaries))
904 95ab4de9 David Knowles
    if dry_run:
905 95ab4de9 David Knowles
      query.append(("dry-run", 1))
906 95ab4de9 David Knowles
907 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_POST,
908 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/reboot" %
909 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
910 95ab4de9 David Knowles
911 2ba39b8f Iustin Pop
  def ShutdownInstance(self, instance, dry_run=False, no_remember=False):
912 95ab4de9 David Knowles
    """Shuts down an instance.
913 95ab4de9 David Knowles

914 95ab4de9 David Knowles
    @type instance: str
915 95ab4de9 David Knowles
    @param instance: the instance to shut down
916 95ab4de9 David Knowles
    @type dry_run: bool
917 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
918 2ba39b8f Iustin Pop
    @type no_remember: bool
919 2ba39b8f Iustin Pop
    @param no_remember: if true, will not record the state change
920 95ab4de9 David Knowles

921 95ab4de9 David Knowles
    """
922 95ab4de9 David Knowles
    query = []
923 95ab4de9 David Knowles
    if dry_run:
924 95ab4de9 David Knowles
      query.append(("dry-run", 1))
925 2ba39b8f Iustin Pop
    if no_remember:
926 2ba39b8f Iustin Pop
      query.append(("no-remember", 1))
927 95ab4de9 David Knowles
928 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
929 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/shutdown" %
930 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
931 95ab4de9 David Knowles
932 2ba39b8f Iustin Pop
  def StartupInstance(self, instance, dry_run=False, no_remember=False):
933 95ab4de9 David Knowles
    """Starts up an instance.
934 95ab4de9 David Knowles

935 95ab4de9 David Knowles
    @type instance: str
936 95ab4de9 David Knowles
    @param instance: the instance to start up
937 95ab4de9 David Knowles
    @type dry_run: bool
938 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
939 2ba39b8f Iustin Pop
    @type no_remember: bool
940 2ba39b8f Iustin Pop
    @param no_remember: if true, will not record the state change
941 95ab4de9 David Knowles

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

957 95ab4de9 David Knowles
    @type instance: str
958 fcee9675 David Knowles
    @param instance: The instance to reinstall
959 fcee9675 David Knowles
    @type os: str or None
960 fcee9675 David Knowles
    @param os: The operating system to reinstall. If None, the instance's
961 fcee9675 David Knowles
        current operating system will be installed again
962 95ab4de9 David Knowles
    @type no_startup: bool
963 fcee9675 David Knowles
    @param no_startup: Whether to start the instance automatically
964 95ab4de9 David Knowles

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

996 95ab4de9 David Knowles
    @type instance: str
997 95ab4de9 David Knowles
    @param instance: instance whose disks to replace
998 bfc2002f Michael Hanselmann
    @type disks: list of ints
999 bfc2002f Michael Hanselmann
    @param disks: Indexes of disks to replace
1000 95ab4de9 David Knowles
    @type mode: str
1001 cfc03c54 Michael Hanselmann
    @param mode: replacement mode to use (defaults to replace_auto)
1002 95ab4de9 David Knowles
    @type remote_node: str or None
1003 95ab4de9 David Knowles
    @param remote_node: new secondary node to use (for use with
1004 cfc03c54 Michael Hanselmann
        replace_new_secondary mode)
1005 95ab4de9 David Knowles
    @type iallocator: str or None
1006 95ab4de9 David Knowles
    @param iallocator: instance allocator plugin to use (for use with
1007 cfc03c54 Michael Hanselmann
                       replace_auto mode)
1008 95ab4de9 David Knowles
    @type dry_run: bool
1009 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1010 95ab4de9 David Knowles

1011 95ab4de9 David Knowles
    @rtype: int
1012 95ab4de9 David Knowles
    @return: job id
1013 95ab4de9 David Knowles

1014 95ab4de9 David Knowles
    """
1015 cfc03c54 Michael Hanselmann
    query = [
1016 cfc03c54 Michael Hanselmann
      ("mode", mode),
1017 cfc03c54 Michael Hanselmann
      ]
1018 95ab4de9 David Knowles
1019 bfc2002f Michael Hanselmann
    if disks:
1020 bfc2002f Michael Hanselmann
      query.append(("disks", ",".join(str(idx) for idx in disks)))
1021 bfc2002f Michael Hanselmann
1022 bfc2002f Michael Hanselmann
    if remote_node:
1023 95ab4de9 David Knowles
      query.append(("remote_node", remote_node))
1024 95ab4de9 David Knowles
1025 bfc2002f Michael Hanselmann
    if iallocator:
1026 bfc2002f Michael Hanselmann
      query.append(("iallocator", iallocator))
1027 bfc2002f Michael Hanselmann
1028 95ab4de9 David Knowles
    if dry_run:
1029 95ab4de9 David Knowles
      query.append(("dry-run", 1))
1030 95ab4de9 David Knowles
1031 95ab4de9 David Knowles
    return self._SendRequest(HTTP_POST,
1032 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/replace-disks" %
1033 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
1034 95ab4de9 David Knowles
1035 ebeb600f Michael Hanselmann
  def PrepareExport(self, instance, mode):
1036 ebeb600f Michael Hanselmann
    """Prepares an instance for an export.
1037 ebeb600f Michael Hanselmann

1038 ebeb600f Michael Hanselmann
    @type instance: string
1039 ebeb600f Michael Hanselmann
    @param instance: Instance name
1040 ebeb600f Michael Hanselmann
    @type mode: string
1041 ebeb600f Michael Hanselmann
    @param mode: Export mode
1042 ebeb600f Michael Hanselmann
    @rtype: string
1043 ebeb600f Michael Hanselmann
    @return: Job ID
1044 ebeb600f Michael Hanselmann

1045 ebeb600f Michael Hanselmann
    """
1046 ebeb600f Michael Hanselmann
    query = [("mode", mode)]
1047 ebeb600f Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1048 ebeb600f Michael Hanselmann
                             ("/%s/instances/%s/prepare-export" %
1049 ebeb600f Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
1050 ebeb600f Michael Hanselmann
1051 ebeb600f Michael Hanselmann
  def ExportInstance(self, instance, mode, destination, shutdown=None,
1052 ebeb600f Michael Hanselmann
                     remove_instance=None,
1053 ebeb600f Michael Hanselmann
                     x509_key_name=None, destination_x509_ca=None):
1054 ebeb600f Michael Hanselmann
    """Exports an instance.
1055 ebeb600f Michael Hanselmann

1056 ebeb600f Michael Hanselmann
    @type instance: string
1057 ebeb600f Michael Hanselmann
    @param instance: Instance name
1058 ebeb600f Michael Hanselmann
    @type mode: string
1059 ebeb600f Michael Hanselmann
    @param mode: Export mode
1060 ebeb600f Michael Hanselmann
    @rtype: string
1061 ebeb600f Michael Hanselmann
    @return: Job ID
1062 ebeb600f Michael Hanselmann

1063 ebeb600f Michael Hanselmann
    """
1064 ebeb600f Michael Hanselmann
    body = {
1065 ebeb600f Michael Hanselmann
      "destination": destination,
1066 ebeb600f Michael Hanselmann
      "mode": mode,
1067 ebeb600f Michael Hanselmann
      }
1068 ebeb600f Michael Hanselmann
1069 ebeb600f Michael Hanselmann
    if shutdown is not None:
1070 ebeb600f Michael Hanselmann
      body["shutdown"] = shutdown
1071 ebeb600f Michael Hanselmann
1072 ebeb600f Michael Hanselmann
    if remove_instance is not None:
1073 ebeb600f Michael Hanselmann
      body["remove_instance"] = remove_instance
1074 ebeb600f Michael Hanselmann
1075 ebeb600f Michael Hanselmann
    if x509_key_name is not None:
1076 ebeb600f Michael Hanselmann
      body["x509_key_name"] = x509_key_name
1077 ebeb600f Michael Hanselmann
1078 ebeb600f Michael Hanselmann
    if destination_x509_ca is not None:
1079 ebeb600f Michael Hanselmann
      body["destination_x509_ca"] = destination_x509_ca
1080 ebeb600f Michael Hanselmann
1081 ebeb600f Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1082 ebeb600f Michael Hanselmann
                             ("/%s/instances/%s/export" %
1083 ebeb600f Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, body)
1084 ebeb600f Michael Hanselmann
1085 e0ac6ce6 Michael Hanselmann
  def MigrateInstance(self, instance, mode=None, cleanup=None):
1086 c63eb9c0 Michael Hanselmann
    """Migrates an instance.
1087 e0ac6ce6 Michael Hanselmann

1088 e0ac6ce6 Michael Hanselmann
    @type instance: string
1089 e0ac6ce6 Michael Hanselmann
    @param instance: Instance name
1090 e0ac6ce6 Michael Hanselmann
    @type mode: string
1091 e0ac6ce6 Michael Hanselmann
    @param mode: Migration mode
1092 e0ac6ce6 Michael Hanselmann
    @type cleanup: bool
1093 e0ac6ce6 Michael Hanselmann
    @param cleanup: Whether to clean up a previously failed migration
1094 e0ac6ce6 Michael Hanselmann

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

1111 d654aae1 Michael Hanselmann
    @type instance: string
1112 d654aae1 Michael Hanselmann
    @param instance: Instance name
1113 d654aae1 Michael Hanselmann
    @type new_name: string
1114 d654aae1 Michael Hanselmann
    @param new_name: New instance name
1115 d654aae1 Michael Hanselmann
    @type ip_check: bool
1116 d654aae1 Michael Hanselmann
    @param ip_check: Whether to ensure instance's IP address is inactive
1117 d654aae1 Michael Hanselmann
    @type name_check: bool
1118 d654aae1 Michael Hanselmann
    @param name_check: Whether to ensure instance's name is resolvable
1119 d654aae1 Michael Hanselmann

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

1138 b82d4c5e Michael Hanselmann
    @type instance: string
1139 b82d4c5e Michael Hanselmann
    @param instance: Instance name
1140 b82d4c5e Michael Hanselmann

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

1149 95ab4de9 David Knowles
    @rtype: list of int
1150 95ab4de9 David Knowles
    @return: job ids for the cluster
1151 95ab4de9 David Knowles

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

1161 95ab4de9 David Knowles
    @type job_id: int
1162 95ab4de9 David Knowles
    @param job_id: job id whose status to query
1163 95ab4de9 David Knowles

1164 95ab4de9 David Knowles
    @rtype: dict
1165 95ab4de9 David Knowles
    @return: job status
1166 95ab4de9 David Knowles

1167 95ab4de9 David Knowles
    """
1168 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1169 a198b2d9 Michael Hanselmann
                             "/%s/jobs/%s" % (GANETI_RAPI_VERSION, job_id),
1170 a198b2d9 Michael Hanselmann
                             None, None)
1171 95ab4de9 David Knowles
1172 d9b67f70 Michael Hanselmann
  def WaitForJobChange(self, job_id, fields, prev_job_info, prev_log_serial):
1173 d9b67f70 Michael Hanselmann
    """Waits for job changes.
1174 d9b67f70 Michael Hanselmann

1175 d9b67f70 Michael Hanselmann
    @type job_id: int
1176 d9b67f70 Michael Hanselmann
    @param job_id: Job ID for which to wait
1177 d9b67f70 Michael Hanselmann

1178 d9b67f70 Michael Hanselmann
    """
1179 d9b67f70 Michael Hanselmann
    body = {
1180 d9b67f70 Michael Hanselmann
      "fields": fields,
1181 d9b67f70 Michael Hanselmann
      "previous_job_info": prev_job_info,
1182 d9b67f70 Michael Hanselmann
      "previous_log_serial": prev_log_serial,
1183 d9b67f70 Michael Hanselmann
      }
1184 d9b67f70 Michael Hanselmann
1185 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1186 a198b2d9 Michael Hanselmann
                             "/%s/jobs/%s/wait" % (GANETI_RAPI_VERSION, job_id),
1187 a198b2d9 Michael Hanselmann
                             None, body)
1188 d9b67f70 Michael Hanselmann
1189 cf9ada49 Michael Hanselmann
  def CancelJob(self, job_id, dry_run=False):
1190 cf9ada49 Michael Hanselmann
    """Cancels a job.
1191 95ab4de9 David Knowles

1192 95ab4de9 David Knowles
    @type job_id: int
1193 95ab4de9 David Knowles
    @param job_id: id of the job to delete
1194 95ab4de9 David Knowles
    @type dry_run: bool
1195 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1196 95ab4de9 David Knowles

1197 95ab4de9 David Knowles
    """
1198 95ab4de9 David Knowles
    query = []
1199 95ab4de9 David Knowles
    if dry_run:
1200 95ab4de9 David Knowles
      query.append(("dry-run", 1))
1201 95ab4de9 David Knowles
1202 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE,
1203 a198b2d9 Michael Hanselmann
                             "/%s/jobs/%s" % (GANETI_RAPI_VERSION, job_id),
1204 a198b2d9 Michael Hanselmann
                             query, None)
1205 95ab4de9 David Knowles
1206 95ab4de9 David Knowles
  def GetNodes(self, bulk=False):
1207 95ab4de9 David Knowles
    """Gets all nodes in the cluster.
1208 95ab4de9 David Knowles

1209 95ab4de9 David Knowles
    @type bulk: bool
1210 95ab4de9 David Knowles
    @param bulk: whether to return all information about all instances
1211 95ab4de9 David Knowles

1212 95ab4de9 David Knowles
    @rtype: list of dict or str
1213 95ab4de9 David Knowles
    @return: if bulk is true, info about nodes in the cluster,
1214 95ab4de9 David Knowles
        else list of nodes in the cluster
1215 95ab4de9 David Knowles

1216 95ab4de9 David Knowles
    """
1217 95ab4de9 David Knowles
    query = []
1218 95ab4de9 David Knowles
    if bulk:
1219 95ab4de9 David Knowles
      query.append(("bulk", 1))
1220 95ab4de9 David Knowles
1221 a198b2d9 Michael Hanselmann
    nodes = self._SendRequest(HTTP_GET, "/%s/nodes" % GANETI_RAPI_VERSION,
1222 a198b2d9 Michael Hanselmann
                              query, None)
1223 95ab4de9 David Knowles
    if bulk:
1224 95ab4de9 David Knowles
      return nodes
1225 95ab4de9 David Knowles
    else:
1226 95ab4de9 David Knowles
      return [n["id"] for n in nodes]
1227 95ab4de9 David Knowles
1228 591e5103 Michael Hanselmann
  def GetNode(self, node):
1229 95ab4de9 David Knowles
    """Gets information about a node.
1230 95ab4de9 David Knowles

1231 95ab4de9 David Knowles
    @type node: str
1232 95ab4de9 David Knowles
    @param node: node whose info to return
1233 95ab4de9 David Knowles

1234 95ab4de9 David Knowles
    @rtype: dict
1235 95ab4de9 David Knowles
    @return: info about the node
1236 95ab4de9 David Knowles

1237 95ab4de9 David Knowles
    """
1238 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1239 a198b2d9 Michael Hanselmann
                             "/%s/nodes/%s" % (GANETI_RAPI_VERSION, node),
1240 a198b2d9 Michael Hanselmann
                             None, None)
1241 95ab4de9 David Knowles
1242 95ab4de9 David Knowles
  def EvacuateNode(self, node, iallocator=None, remote_node=None,
1243 941b9309 Iustin Pop
                   dry_run=False, early_release=False):
1244 95ab4de9 David Knowles
    """Evacuates instances from a Ganeti node.
1245 95ab4de9 David Knowles

1246 95ab4de9 David Knowles
    @type node: str
1247 95ab4de9 David Knowles
    @param node: node to evacuate
1248 95ab4de9 David Knowles
    @type iallocator: str or None
1249 95ab4de9 David Knowles
    @param iallocator: instance allocator to use
1250 95ab4de9 David Knowles
    @type remote_node: str
1251 95ab4de9 David Knowles
    @param remote_node: node to evaucate to
1252 95ab4de9 David Knowles
    @type dry_run: bool
1253 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1254 941b9309 Iustin Pop
    @type early_release: bool
1255 941b9309 Iustin Pop
    @param early_release: whether to enable parallelization
1256 95ab4de9 David Knowles

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

1262 941b9309 Iustin Pop
    @raises GanetiApiError: if an iallocator and remote_node are both
1263 941b9309 Iustin Pop
        specified
1264 95ab4de9 David Knowles

1265 95ab4de9 David Knowles
    """
1266 95ab4de9 David Knowles
    if iallocator and remote_node:
1267 cfc03c54 Michael Hanselmann
      raise GanetiApiError("Only one of iallocator or remote_node can be used")
1268 95ab4de9 David Knowles
1269 cfc03c54 Michael Hanselmann
    query = []
1270 95ab4de9 David Knowles
    if iallocator:
1271 95ab4de9 David Knowles
      query.append(("iallocator", iallocator))
1272 95ab4de9 David Knowles
    if remote_node:
1273 95ab4de9 David Knowles
      query.append(("remote_node", remote_node))
1274 95ab4de9 David Knowles
    if dry_run:
1275 95ab4de9 David Knowles
      query.append(("dry-run", 1))
1276 941b9309 Iustin Pop
    if early_release:
1277 941b9309 Iustin Pop
      query.append(("early_release", 1))
1278 95ab4de9 David Knowles
1279 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_POST,
1280 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/evacuate" %
1281 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1282 95ab4de9 David Knowles
1283 1f334d96 Iustin Pop
  def MigrateNode(self, node, mode=None, dry_run=False):
1284 95ab4de9 David Knowles
    """Migrates all primary instances from a node.
1285 95ab4de9 David Knowles

1286 95ab4de9 David Knowles
    @type node: str
1287 95ab4de9 David Knowles
    @param node: node to migrate
1288 1f334d96 Iustin Pop
    @type mode: string
1289 1f334d96 Iustin Pop
    @param mode: if passed, it will overwrite the live migration type,
1290 1f334d96 Iustin Pop
        otherwise the hypervisor default will be used
1291 95ab4de9 David Knowles
    @type dry_run: bool
1292 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1293 95ab4de9 David Knowles

1294 95ab4de9 David Knowles
    @rtype: int
1295 95ab4de9 David Knowles
    @return: job id
1296 95ab4de9 David Knowles

1297 95ab4de9 David Knowles
    """
1298 95ab4de9 David Knowles
    query = []
1299 1f334d96 Iustin Pop
    if mode is not None:
1300 1f334d96 Iustin Pop
      query.append(("mode", mode))
1301 95ab4de9 David Knowles
    if dry_run:
1302 95ab4de9 David Knowles
      query.append(("dry-run", 1))
1303 95ab4de9 David Knowles
1304 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_POST,
1305 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/migrate" %
1306 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1307 95ab4de9 David Knowles
1308 95ab4de9 David Knowles
  def GetNodeRole(self, node):
1309 95ab4de9 David Knowles
    """Gets the current role for a node.
1310 95ab4de9 David Knowles

1311 95ab4de9 David Knowles
    @type node: str
1312 95ab4de9 David Knowles
    @param node: node whose role to return
1313 95ab4de9 David Knowles

1314 95ab4de9 David Knowles
    @rtype: str
1315 95ab4de9 David Knowles
    @return: the current role for a node
1316 95ab4de9 David Knowles

1317 95ab4de9 David Knowles
    """
1318 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1319 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/role" %
1320 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), None, None)
1321 95ab4de9 David Knowles
1322 95ab4de9 David Knowles
  def SetNodeRole(self, node, role, force=False):
1323 95ab4de9 David Knowles
    """Sets the role for a node.
1324 95ab4de9 David Knowles

1325 95ab4de9 David Knowles
    @type node: str
1326 95ab4de9 David Knowles
    @param node: the node whose role to set
1327 95ab4de9 David Knowles
    @type role: str
1328 95ab4de9 David Knowles
    @param role: the role to set for the node
1329 95ab4de9 David Knowles
    @type force: bool
1330 95ab4de9 David Knowles
    @param force: whether to force the role change
1331 95ab4de9 David Knowles

1332 95ab4de9 David Knowles
    @rtype: int
1333 95ab4de9 David Knowles
    @return: job id
1334 95ab4de9 David Knowles

1335 95ab4de9 David Knowles
    """
1336 1068639f Michael Hanselmann
    query = [
1337 1068639f Michael Hanselmann
      ("force", force),
1338 1068639f Michael Hanselmann
      ]
1339 cfc03c54 Michael Hanselmann
1340 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1341 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/role" %
1342 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, role)
1343 95ab4de9 David Knowles
1344 95ab4de9 David Knowles
  def GetNodeStorageUnits(self, node, storage_type, output_fields):
1345 95ab4de9 David Knowles
    """Gets the storage units for a node.
1346 95ab4de9 David Knowles

1347 95ab4de9 David Knowles
    @type node: str
1348 95ab4de9 David Knowles
    @param node: the node whose storage units to return
1349 95ab4de9 David Knowles
    @type storage_type: str
1350 95ab4de9 David Knowles
    @param storage_type: storage type whose units to return
1351 95ab4de9 David Knowles
    @type output_fields: str
1352 95ab4de9 David Knowles
    @param output_fields: storage type fields to return
1353 95ab4de9 David Knowles

1354 95ab4de9 David Knowles
    @rtype: int
1355 95ab4de9 David Knowles
    @return: job id where results can be retrieved
1356 95ab4de9 David Knowles

1357 95ab4de9 David Knowles
    """
1358 cfc03c54 Michael Hanselmann
    query = [
1359 cfc03c54 Michael Hanselmann
      ("storage_type", storage_type),
1360 cfc03c54 Michael Hanselmann
      ("output_fields", output_fields),
1361 cfc03c54 Michael Hanselmann
      ]
1362 95ab4de9 David Knowles
1363 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1364 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/storage" %
1365 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1366 95ab4de9 David Knowles
1367 fde28316 Michael Hanselmann
  def ModifyNodeStorageUnits(self, node, storage_type, name, allocatable=None):
1368 95ab4de9 David Knowles
    """Modifies parameters of storage units on the node.
1369 95ab4de9 David Knowles

1370 95ab4de9 David Knowles
    @type node: str
1371 95ab4de9 David Knowles
    @param node: node whose storage units to modify
1372 95ab4de9 David Knowles
    @type storage_type: str
1373 95ab4de9 David Knowles
    @param storage_type: storage type whose units to modify
1374 95ab4de9 David Knowles
    @type name: str
1375 95ab4de9 David Knowles
    @param name: name of the storage unit
1376 fde28316 Michael Hanselmann
    @type allocatable: bool or None
1377 fde28316 Michael Hanselmann
    @param allocatable: Whether to set the "allocatable" flag on the storage
1378 fde28316 Michael Hanselmann
                        unit (None=no modification, True=set, False=unset)
1379 95ab4de9 David Knowles

1380 95ab4de9 David Knowles
    @rtype: int
1381 95ab4de9 David Knowles
    @return: job id
1382 95ab4de9 David Knowles

1383 95ab4de9 David Knowles
    """
1384 95ab4de9 David Knowles
    query = [
1385 cfc03c54 Michael Hanselmann
      ("storage_type", storage_type),
1386 cfc03c54 Michael Hanselmann
      ("name", name),
1387 cfc03c54 Michael Hanselmann
      ]
1388 cfc03c54 Michael Hanselmann
1389 fde28316 Michael Hanselmann
    if allocatable is not None:
1390 fde28316 Michael Hanselmann
      query.append(("allocatable", allocatable))
1391 fde28316 Michael Hanselmann
1392 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1393 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/storage/modify" %
1394 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1395 95ab4de9 David Knowles
1396 95ab4de9 David Knowles
  def RepairNodeStorageUnits(self, node, storage_type, name):
1397 95ab4de9 David Knowles
    """Repairs a storage unit on the node.
1398 95ab4de9 David Knowles

1399 95ab4de9 David Knowles
    @type node: str
1400 95ab4de9 David Knowles
    @param node: node whose storage units to repair
1401 95ab4de9 David Knowles
    @type storage_type: str
1402 95ab4de9 David Knowles
    @param storage_type: storage type to repair
1403 95ab4de9 David Knowles
    @type name: str
1404 95ab4de9 David Knowles
    @param name: name of the storage unit to repair
1405 95ab4de9 David Knowles

1406 95ab4de9 David Knowles
    @rtype: int
1407 95ab4de9 David Knowles
    @return: job id
1408 95ab4de9 David Knowles

1409 95ab4de9 David Knowles
    """
1410 cfc03c54 Michael Hanselmann
    query = [
1411 cfc03c54 Michael Hanselmann
      ("storage_type", storage_type),
1412 cfc03c54 Michael Hanselmann
      ("name", name),
1413 cfc03c54 Michael Hanselmann
      ]
1414 95ab4de9 David Knowles
1415 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1416 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/storage/repair" %
1417 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1418 95ab4de9 David Knowles
1419 95ab4de9 David Knowles
  def GetNodeTags(self, node):
1420 95ab4de9 David Knowles
    """Gets the tags for a node.
1421 95ab4de9 David Knowles

1422 95ab4de9 David Knowles
    @type node: str
1423 95ab4de9 David Knowles
    @param node: node whose tags to return
1424 95ab4de9 David Knowles

1425 95ab4de9 David Knowles
    @rtype: list of str
1426 95ab4de9 David Knowles
    @return: tags for the node
1427 95ab4de9 David Knowles

1428 95ab4de9 David Knowles
    """
1429 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1430 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/tags" %
1431 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), None, None)
1432 95ab4de9 David Knowles
1433 95ab4de9 David Knowles
  def AddNodeTags(self, node, tags, dry_run=False):
1434 95ab4de9 David Knowles
    """Adds tags to a node.
1435 95ab4de9 David Knowles

1436 95ab4de9 David Knowles
    @type node: str
1437 95ab4de9 David Knowles
    @param node: node to add tags to
1438 95ab4de9 David Knowles
    @type tags: list of str
1439 95ab4de9 David Knowles
    @param tags: tags to add to the node
1440 95ab4de9 David Knowles
    @type dry_run: bool
1441 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1442 95ab4de9 David Knowles

1443 95ab4de9 David Knowles
    @rtype: int
1444 95ab4de9 David Knowles
    @return: job id
1445 95ab4de9 David Knowles

1446 95ab4de9 David Knowles
    """
1447 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
1448 95ab4de9 David Knowles
    if dry_run:
1449 95ab4de9 David Knowles
      query.append(("dry-run", 1))
1450 95ab4de9 David Knowles
1451 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1452 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/tags" %
1453 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, tags)
1454 95ab4de9 David Knowles
1455 95ab4de9 David Knowles
  def DeleteNodeTags(self, node, tags, dry_run=False):
1456 95ab4de9 David Knowles
    """Delete tags from a node.
1457 95ab4de9 David Knowles

1458 95ab4de9 David Knowles
    @type node: str
1459 95ab4de9 David Knowles
    @param node: node to remove tags from
1460 95ab4de9 David Knowles
    @type tags: list of str
1461 95ab4de9 David Knowles
    @param tags: tags to remove from the node
1462 95ab4de9 David Knowles
    @type dry_run: bool
1463 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1464 95ab4de9 David Knowles

1465 95ab4de9 David Knowles
    @rtype: int
1466 95ab4de9 David Knowles
    @return: job id
1467 95ab4de9 David Knowles

1468 95ab4de9 David Knowles
    """
1469 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
1470 95ab4de9 David Knowles
    if dry_run:
1471 95ab4de9 David Knowles
      query.append(("dry-run", 1))
1472 95ab4de9 David Knowles
1473 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE,
1474 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/tags" %
1475 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1476 a268af8d Adeodato Simo
1477 a268af8d Adeodato Simo
  def GetGroups(self, bulk=False):
1478 a268af8d Adeodato Simo
    """Gets all node groups in the cluster.
1479 a268af8d Adeodato Simo

1480 a268af8d Adeodato Simo
    @type bulk: bool
1481 a268af8d Adeodato Simo
    @param bulk: whether to return all information about the groups
1482 a268af8d Adeodato Simo

1483 a268af8d Adeodato Simo
    @rtype: list of dict or str
1484 a268af8d Adeodato Simo
    @return: if bulk is true, a list of dictionaries with info about all node
1485 a268af8d Adeodato Simo
        groups in the cluster, else a list of names of those node groups
1486 a268af8d Adeodato Simo

1487 a268af8d Adeodato Simo
    """
1488 a268af8d Adeodato Simo
    query = []
1489 a268af8d Adeodato Simo
    if bulk:
1490 a268af8d Adeodato Simo
      query.append(("bulk", 1))
1491 a268af8d Adeodato Simo
1492 a268af8d Adeodato Simo
    groups = self._SendRequest(HTTP_GET, "/%s/groups" % GANETI_RAPI_VERSION,
1493 a268af8d Adeodato Simo
                               query, None)
1494 a268af8d Adeodato Simo
    if bulk:
1495 a268af8d Adeodato Simo
      return groups
1496 a268af8d Adeodato Simo
    else:
1497 a268af8d Adeodato Simo
      return [g["name"] for g in groups]
1498 a268af8d Adeodato Simo
1499 a268af8d Adeodato Simo
  def GetGroup(self, group):
1500 a268af8d Adeodato Simo
    """Gets information about a node group.
1501 a268af8d Adeodato Simo

1502 a268af8d Adeodato Simo
    @type group: str
1503 a268af8d Adeodato Simo
    @param group: name of the node group whose info to return
1504 a268af8d Adeodato Simo

1505 a268af8d Adeodato Simo
    @rtype: dict
1506 a268af8d Adeodato Simo
    @return: info about the node group
1507 a268af8d Adeodato Simo

1508 a268af8d Adeodato Simo
    """
1509 a268af8d Adeodato Simo
    return self._SendRequest(HTTP_GET,
1510 a268af8d Adeodato Simo
                             "/%s/groups/%s" % (GANETI_RAPI_VERSION, group),
1511 a268af8d Adeodato Simo
                             None, None)
1512 a268af8d Adeodato Simo
1513 90e99856 Adeodato Simo
  def CreateGroup(self, name, alloc_policy=None, dry_run=False):
1514 a268af8d Adeodato Simo
    """Creates a new node group.
1515 a268af8d Adeodato Simo

1516 a268af8d Adeodato Simo
    @type name: str
1517 a268af8d Adeodato Simo
    @param name: the name of node group to create
1518 90e99856 Adeodato Simo
    @type alloc_policy: str
1519 90e99856 Adeodato Simo
    @param alloc_policy: the desired allocation policy for the group, if any
1520 a268af8d Adeodato Simo
    @type dry_run: bool
1521 a268af8d Adeodato Simo
    @param dry_run: whether to peform a dry run
1522 a268af8d Adeodato Simo

1523 a268af8d Adeodato Simo
    @rtype: int
1524 a268af8d Adeodato Simo
    @return: job id
1525 a268af8d Adeodato Simo

1526 a268af8d Adeodato Simo
    """
1527 a268af8d Adeodato Simo
    query = []
1528 a268af8d Adeodato Simo
    if dry_run:
1529 a268af8d Adeodato Simo
      query.append(("dry-run", 1))
1530 a268af8d Adeodato Simo
1531 a268af8d Adeodato Simo
    body = {
1532 a268af8d Adeodato Simo
      "name": name,
1533 90e99856 Adeodato Simo
      "alloc_policy": alloc_policy
1534 a268af8d Adeodato Simo
      }
1535 a268af8d Adeodato Simo
1536 a268af8d Adeodato Simo
    return self._SendRequest(HTTP_POST, "/%s/groups" % GANETI_RAPI_VERSION,
1537 a268af8d Adeodato Simo
                             query, body)
1538 a268af8d Adeodato Simo
1539 f18fab7d Adeodato Simo
  def ModifyGroup(self, group, **kwargs):
1540 f18fab7d Adeodato Simo
    """Modifies a node group.
1541 f18fab7d Adeodato Simo

1542 f18fab7d Adeodato Simo
    More details for parameters can be found in the RAPI documentation.
1543 f18fab7d Adeodato Simo

1544 f18fab7d Adeodato Simo
    @type group: string
1545 f18fab7d Adeodato Simo
    @param group: Node group name
1546 f18fab7d Adeodato Simo
    @rtype: int
1547 f18fab7d Adeodato Simo
    @return: job id
1548 f18fab7d Adeodato Simo

1549 f18fab7d Adeodato Simo
    """
1550 f18fab7d Adeodato Simo
    return self._SendRequest(HTTP_PUT,
1551 f18fab7d Adeodato Simo
                             ("/%s/groups/%s/modify" %
1552 f18fab7d Adeodato Simo
                              (GANETI_RAPI_VERSION, group)), None, kwargs)
1553 f18fab7d Adeodato Simo
1554 a268af8d Adeodato Simo
  def DeleteGroup(self, group, dry_run=False):
1555 a268af8d Adeodato Simo
    """Deletes a node group.
1556 a268af8d Adeodato Simo

1557 a268af8d Adeodato Simo
    @type group: str
1558 a268af8d Adeodato Simo
    @param group: the node group to delete
1559 a268af8d Adeodato Simo
    @type dry_run: bool
1560 a268af8d Adeodato Simo
    @param dry_run: whether to peform a dry run
1561 a268af8d Adeodato Simo

1562 a268af8d Adeodato Simo
    @rtype: int
1563 a268af8d Adeodato Simo
    @return: job id
1564 a268af8d Adeodato Simo

1565 a268af8d Adeodato Simo
    """
1566 a268af8d Adeodato Simo
    query = []
1567 a268af8d Adeodato Simo
    if dry_run:
1568 a268af8d Adeodato Simo
      query.append(("dry-run", 1))
1569 a268af8d Adeodato Simo
1570 a268af8d Adeodato Simo
    return self._SendRequest(HTTP_DELETE,
1571 a268af8d Adeodato Simo
                             ("/%s/groups/%s" %
1572 a268af8d Adeodato Simo
                              (GANETI_RAPI_VERSION, group)), query, None)
1573 a268af8d Adeodato Simo
1574 a268af8d Adeodato Simo
  def RenameGroup(self, group, new_name):
1575 a268af8d Adeodato Simo
    """Changes the name of a node group.
1576 a268af8d Adeodato Simo

1577 a268af8d Adeodato Simo
    @type group: string
1578 a268af8d Adeodato Simo
    @param group: Node group name
1579 a268af8d Adeodato Simo
    @type new_name: string
1580 a268af8d Adeodato Simo
    @param new_name: New node group name
1581 a268af8d Adeodato Simo

1582 a268af8d Adeodato Simo
    @rtype: int
1583 a268af8d Adeodato Simo
    @return: job id
1584 a268af8d Adeodato Simo

1585 a268af8d Adeodato Simo
    """
1586 a268af8d Adeodato Simo
    body = {
1587 a268af8d Adeodato Simo
      "new_name": new_name,
1588 a268af8d Adeodato Simo
      }
1589 a268af8d Adeodato Simo
1590 a268af8d Adeodato Simo
    return self._SendRequest(HTTP_PUT,
1591 a268af8d Adeodato Simo
                             ("/%s/groups/%s/rename" %
1592 a268af8d Adeodato Simo
                              (GANETI_RAPI_VERSION, group)), None, body)
1593 4245446f Adeodato Simo
1594 4245446f Adeodato Simo
1595 4245446f Adeodato Simo
  def AssignGroupNodes(self, group, nodes, force=False, dry_run=False):
1596 4245446f Adeodato Simo
    """Assigns nodes to a group.
1597 4245446f Adeodato Simo

1598 4245446f Adeodato Simo
    @type group: string
1599 4245446f Adeodato Simo
    @param group: Node gropu name
1600 4245446f Adeodato Simo
    @type nodes: list of strings
1601 4245446f Adeodato Simo
    @param nodes: List of nodes to assign to the group
1602 4245446f Adeodato Simo

1603 4245446f Adeodato Simo
    @rtype: int
1604 4245446f Adeodato Simo
    @return: job id
1605 4245446f Adeodato Simo

1606 4245446f Adeodato Simo
    """
1607 4245446f Adeodato Simo
    query = []
1608 4245446f Adeodato Simo
1609 4245446f Adeodato Simo
    if force:
1610 4245446f Adeodato Simo
      query.append(("force", 1))
1611 4245446f Adeodato Simo
1612 4245446f Adeodato Simo
    if dry_run:
1613 4245446f Adeodato Simo
      query.append(("dry-run", 1))
1614 4245446f Adeodato Simo
1615 4245446f Adeodato Simo
    body = {
1616 4245446f Adeodato Simo
      "nodes": nodes,
1617 4245446f Adeodato Simo
      }
1618 4245446f Adeodato Simo
1619 4245446f Adeodato Simo
    return self._SendRequest(HTTP_PUT,
1620 4245446f Adeodato Simo
                             ("/%s/groups/%s/assign-nodes" %
1621 4245446f Adeodato Simo
                             (GANETI_RAPI_VERSION, group)), query, body)