Statistics
| Branch: | Tag: | Revision:

root / lib / rapi / client.py @ 4ff922a2

History | View | Annotate | Download (44.9 kB)

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

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

31 2a7c3583 Michael Hanselmann
"""
32 95ab4de9 David Knowles
33 5ef5cfea Michael Hanselmann
# No Ganeti-specific modules should be imported. The RAPI client is supposed to
34 5ef5cfea Michael Hanselmann
# be standalone.
35 5ef5cfea Michael Hanselmann
36 9279e986 Michael Hanselmann
import logging
37 95ab4de9 David Knowles
import simplejson
38 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 95ab4de9 David Knowles
class GanetiRapiClient(object):
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 62e999a5 Michael Hanselmann
  def ModifyCluster(self, **kwargs):
485 62e999a5 Michael Hanselmann
    """Modifies cluster parameters.
486 62e999a5 Michael Hanselmann

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

489 62e999a5 Michael Hanselmann
    @rtype: int
490 62e999a5 Michael Hanselmann
    @return: job id
491 62e999a5 Michael Hanselmann

492 62e999a5 Michael Hanselmann
    """
493 62e999a5 Michael Hanselmann
    body = kwargs
494 62e999a5 Michael Hanselmann
495 62e999a5 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
496 62e999a5 Michael Hanselmann
                             "/%s/modify" % GANETI_RAPI_VERSION, None, body)
497 62e999a5 Michael Hanselmann
498 95ab4de9 David Knowles
  def GetClusterTags(self):
499 95ab4de9 David Knowles
    """Gets the cluster tags.
500 95ab4de9 David Knowles

501 95ab4de9 David Knowles
    @rtype: list of str
502 95ab4de9 David Knowles
    @return: cluster tags
503 95ab4de9 David Knowles

504 95ab4de9 David Knowles
    """
505 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET, "/%s/tags" % GANETI_RAPI_VERSION,
506 a198b2d9 Michael Hanselmann
                             None, None)
507 95ab4de9 David Knowles
508 95ab4de9 David Knowles
  def AddClusterTags(self, tags, dry_run=False):
509 95ab4de9 David Knowles
    """Adds tags to the cluster.
510 95ab4de9 David Knowles

511 95ab4de9 David Knowles
    @type tags: list of str
512 95ab4de9 David Knowles
    @param tags: tags to add to the cluster
513 95ab4de9 David Knowles
    @type dry_run: bool
514 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
515 95ab4de9 David Knowles

516 95ab4de9 David Knowles
    @rtype: int
517 95ab4de9 David Knowles
    @return: job id
518 95ab4de9 David Knowles

519 95ab4de9 David Knowles
    """
520 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
521 95ab4de9 David Knowles
    if dry_run:
522 95ab4de9 David Knowles
      query.append(("dry-run", 1))
523 95ab4de9 David Knowles
524 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT, "/%s/tags" % GANETI_RAPI_VERSION,
525 a198b2d9 Michael Hanselmann
                             query, None)
526 95ab4de9 David Knowles
527 95ab4de9 David Knowles
  def DeleteClusterTags(self, tags, dry_run=False):
528 95ab4de9 David Knowles
    """Deletes tags from the cluster.
529 95ab4de9 David Knowles

530 95ab4de9 David Knowles
    @type tags: list of str
531 95ab4de9 David Knowles
    @param tags: tags to delete
532 95ab4de9 David Knowles
    @type dry_run: bool
533 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
534 95ab4de9 David Knowles

535 95ab4de9 David Knowles
    """
536 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
537 95ab4de9 David Knowles
    if dry_run:
538 95ab4de9 David Knowles
      query.append(("dry-run", 1))
539 95ab4de9 David Knowles
540 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE, "/%s/tags" % GANETI_RAPI_VERSION,
541 a198b2d9 Michael Hanselmann
                             query, None)
542 95ab4de9 David Knowles
543 95ab4de9 David Knowles
  def GetInstances(self, bulk=False):
544 95ab4de9 David Knowles
    """Gets information about instances on the cluster.
545 95ab4de9 David Knowles

546 95ab4de9 David Knowles
    @type bulk: bool
547 95ab4de9 David Knowles
    @param bulk: whether to return all information about all instances
548 95ab4de9 David Knowles

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

552 95ab4de9 David Knowles
    """
553 95ab4de9 David Knowles
    query = []
554 95ab4de9 David Knowles
    if bulk:
555 95ab4de9 David Knowles
      query.append(("bulk", 1))
556 95ab4de9 David Knowles
557 a198b2d9 Michael Hanselmann
    instances = self._SendRequest(HTTP_GET,
558 a198b2d9 Michael Hanselmann
                                  "/%s/instances" % GANETI_RAPI_VERSION,
559 a198b2d9 Michael Hanselmann
                                  query, None)
560 95ab4de9 David Knowles
    if bulk:
561 95ab4de9 David Knowles
      return instances
562 95ab4de9 David Knowles
    else:
563 95ab4de9 David Knowles
      return [i["id"] for i in instances]
564 95ab4de9 David Knowles
565 591e5103 Michael Hanselmann
  def GetInstance(self, instance):
566 95ab4de9 David Knowles
    """Gets information about an instance.
567 95ab4de9 David Knowles

568 95ab4de9 David Knowles
    @type instance: str
569 95ab4de9 David Knowles
    @param instance: instance whose info to return
570 95ab4de9 David Knowles

571 95ab4de9 David Knowles
    @rtype: dict
572 95ab4de9 David Knowles
    @return: info about the instance
573 95ab4de9 David Knowles

574 95ab4de9 David Knowles
    """
575 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
576 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s" %
577 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, None)
578 95ab4de9 David Knowles
579 591e5103 Michael Hanselmann
  def GetInstanceInfo(self, instance, static=None):
580 591e5103 Michael Hanselmann
    """Gets information about an instance.
581 591e5103 Michael Hanselmann

582 591e5103 Michael Hanselmann
    @type instance: string
583 591e5103 Michael Hanselmann
    @param instance: Instance name
584 591e5103 Michael Hanselmann
    @rtype: string
585 591e5103 Michael Hanselmann
    @return: Job ID
586 591e5103 Michael Hanselmann

587 591e5103 Michael Hanselmann
    """
588 591e5103 Michael Hanselmann
    if static is not None:
589 591e5103 Michael Hanselmann
      query = [("static", static)]
590 591e5103 Michael Hanselmann
    else:
591 591e5103 Michael Hanselmann
      query = None
592 591e5103 Michael Hanselmann
593 591e5103 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
594 591e5103 Michael Hanselmann
                             ("/%s/instances/%s/info" %
595 591e5103 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
596 591e5103 Michael Hanselmann
597 8a47b447 Michael Hanselmann
  def CreateInstance(self, mode, name, disk_template, disks, nics,
598 8a47b447 Michael Hanselmann
                     **kwargs):
599 95ab4de9 David Knowles
    """Creates a new instance.
600 95ab4de9 David Knowles

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

603 8a47b447 Michael Hanselmann
    @type mode: string
604 8a47b447 Michael Hanselmann
    @param mode: Instance creation mode
605 8a47b447 Michael Hanselmann
    @type name: string
606 8a47b447 Michael Hanselmann
    @param name: Hostname of the instance to create
607 8a47b447 Michael Hanselmann
    @type disk_template: string
608 8a47b447 Michael Hanselmann
    @param disk_template: Disk template for instance (e.g. plain, diskless,
609 8a47b447 Michael Hanselmann
                          file, or drbd)
610 8a47b447 Michael Hanselmann
    @type disks: list of dicts
611 8a47b447 Michael Hanselmann
    @param disks: List of disk definitions
612 8a47b447 Michael Hanselmann
    @type nics: list of dicts
613 8a47b447 Michael Hanselmann
    @param nics: List of NIC definitions
614 95ab4de9 David Knowles
    @type dry_run: bool
615 8a47b447 Michael Hanselmann
    @keyword dry_run: whether to perform a dry run
616 95ab4de9 David Knowles

617 95ab4de9 David Knowles
    @rtype: int
618 95ab4de9 David Knowles
    @return: job id
619 95ab4de9 David Knowles

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

728 95ab4de9 David Knowles
    @type instance: str
729 95ab4de9 David Knowles
    @param instance: the instance to delete
730 95ab4de9 David Knowles

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

734 95ab4de9 David Knowles
    """
735 95ab4de9 David Knowles
    query = []
736 95ab4de9 David Knowles
    if dry_run:
737 95ab4de9 David Knowles
      query.append(("dry-run", 1))
738 95ab4de9 David Knowles
739 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE,
740 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s" %
741 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
742 95ab4de9 David Knowles
743 3b7158ef Michael Hanselmann
  def ModifyInstance(self, instance, **kwargs):
744 3b7158ef Michael Hanselmann
    """Modifies an instance.
745 3b7158ef Michael Hanselmann

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

748 3b7158ef Michael Hanselmann
    @type instance: string
749 3b7158ef Michael Hanselmann
    @param instance: Instance name
750 3b7158ef Michael Hanselmann
    @rtype: int
751 3b7158ef Michael Hanselmann
    @return: job id
752 3b7158ef Michael Hanselmann

753 3b7158ef Michael Hanselmann
    """
754 3b7158ef Michael Hanselmann
    body = kwargs
755 3b7158ef Michael Hanselmann
756 3b7158ef Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
757 3b7158ef Michael Hanselmann
                             ("/%s/instances/%s/modify" %
758 3b7158ef Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, body)
759 3b7158ef Michael Hanselmann
760 e23881ed Michael Hanselmann
  def GrowInstanceDisk(self, instance, disk, amount, wait_for_sync=None):
761 e23881ed Michael Hanselmann
    """Grows a disk of an instance.
762 e23881ed Michael Hanselmann

763 e23881ed Michael Hanselmann
    More details for parameters can be found in the RAPI documentation.
764 e23881ed Michael Hanselmann

765 e23881ed Michael Hanselmann
    @type instance: string
766 e23881ed Michael Hanselmann
    @param instance: Instance name
767 e23881ed Michael Hanselmann
    @type disk: integer
768 e23881ed Michael Hanselmann
    @param disk: Disk index
769 e23881ed Michael Hanselmann
    @type amount: integer
770 e23881ed Michael Hanselmann
    @param amount: Grow disk by this amount (MiB)
771 e23881ed Michael Hanselmann
    @type wait_for_sync: bool
772 e23881ed Michael Hanselmann
    @param wait_for_sync: Wait for disk to synchronize
773 e23881ed Michael Hanselmann
    @rtype: int
774 e23881ed Michael Hanselmann
    @return: job id
775 e23881ed Michael Hanselmann

776 e23881ed Michael Hanselmann
    """
777 e23881ed Michael Hanselmann
    body = {
778 e23881ed Michael Hanselmann
      "amount": amount,
779 e23881ed Michael Hanselmann
      }
780 e23881ed Michael Hanselmann
781 e23881ed Michael Hanselmann
    if wait_for_sync is not None:
782 e23881ed Michael Hanselmann
      body["wait_for_sync"] = wait_for_sync
783 e23881ed Michael Hanselmann
784 e23881ed Michael Hanselmann
    return self._SendRequest(HTTP_POST,
785 e23881ed Michael Hanselmann
                             ("/%s/instances/%s/disk/%s/grow" %
786 e23881ed Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance, disk)),
787 e23881ed Michael Hanselmann
                             None, body)
788 e23881ed Michael Hanselmann
789 95ab4de9 David Knowles
  def GetInstanceTags(self, instance):
790 95ab4de9 David Knowles
    """Gets tags for an instance.
791 95ab4de9 David Knowles

792 95ab4de9 David Knowles
    @type instance: str
793 95ab4de9 David Knowles
    @param instance: instance whose tags to return
794 95ab4de9 David Knowles

795 95ab4de9 David Knowles
    @rtype: list of str
796 95ab4de9 David Knowles
    @return: tags for the instance
797 95ab4de9 David Knowles

798 95ab4de9 David Knowles
    """
799 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
800 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/tags" %
801 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, None)
802 95ab4de9 David Knowles
803 95ab4de9 David Knowles
  def AddInstanceTags(self, instance, tags, dry_run=False):
804 95ab4de9 David Knowles
    """Adds tags to an instance.
805 95ab4de9 David Knowles

806 95ab4de9 David Knowles
    @type instance: str
807 95ab4de9 David Knowles
    @param instance: instance to add tags to
808 95ab4de9 David Knowles
    @type tags: list of str
809 95ab4de9 David Knowles
    @param tags: tags to add to the instance
810 95ab4de9 David Knowles
    @type dry_run: bool
811 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
812 95ab4de9 David Knowles

813 95ab4de9 David Knowles
    @rtype: int
814 95ab4de9 David Knowles
    @return: job id
815 95ab4de9 David Knowles

816 95ab4de9 David Knowles
    """
817 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
818 95ab4de9 David Knowles
    if dry_run:
819 95ab4de9 David Knowles
      query.append(("dry-run", 1))
820 95ab4de9 David Knowles
821 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
822 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/tags" %
823 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
824 95ab4de9 David Knowles
825 95ab4de9 David Knowles
  def DeleteInstanceTags(self, instance, tags, dry_run=False):
826 95ab4de9 David Knowles
    """Deletes tags from an instance.
827 95ab4de9 David Knowles

828 95ab4de9 David Knowles
    @type instance: str
829 95ab4de9 David Knowles
    @param instance: instance to delete tags from
830 95ab4de9 David Knowles
    @type tags: list of str
831 95ab4de9 David Knowles
    @param tags: tags to delete
832 95ab4de9 David Knowles
    @type dry_run: bool
833 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
834 95ab4de9 David Knowles

835 95ab4de9 David Knowles
    """
836 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
837 95ab4de9 David Knowles
    if dry_run:
838 95ab4de9 David Knowles
      query.append(("dry-run", 1))
839 95ab4de9 David Knowles
840 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE,
841 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/tags" %
842 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
843 95ab4de9 David Knowles
844 95ab4de9 David Knowles
  def RebootInstance(self, instance, reboot_type=None, ignore_secondaries=None,
845 95ab4de9 David Knowles
                     dry_run=False):
846 95ab4de9 David Knowles
    """Reboots an instance.
847 95ab4de9 David Knowles

848 95ab4de9 David Knowles
    @type instance: str
849 95ab4de9 David Knowles
    @param instance: instance to rebot
850 95ab4de9 David Knowles
    @type reboot_type: str
851 95ab4de9 David Knowles
    @param reboot_type: one of: hard, soft, full
852 95ab4de9 David Knowles
    @type ignore_secondaries: bool
853 95ab4de9 David Knowles
    @param ignore_secondaries: if True, ignores errors for the secondary node
854 95ab4de9 David Knowles
        while re-assembling disks (in hard-reboot mode only)
855 95ab4de9 David Knowles
    @type dry_run: bool
856 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
857 95ab4de9 David Knowles

858 95ab4de9 David Knowles
    """
859 95ab4de9 David Knowles
    query = []
860 95ab4de9 David Knowles
    if reboot_type:
861 95ab4de9 David Knowles
      query.append(("type", reboot_type))
862 95ab4de9 David Knowles
    if ignore_secondaries is not None:
863 95ab4de9 David Knowles
      query.append(("ignore_secondaries", ignore_secondaries))
864 95ab4de9 David Knowles
    if dry_run:
865 95ab4de9 David Knowles
      query.append(("dry-run", 1))
866 95ab4de9 David Knowles
867 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_POST,
868 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/reboot" %
869 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
870 95ab4de9 David Knowles
871 95ab4de9 David Knowles
  def ShutdownInstance(self, instance, dry_run=False):
872 95ab4de9 David Knowles
    """Shuts down an instance.
873 95ab4de9 David Knowles

874 95ab4de9 David Knowles
    @type instance: str
875 95ab4de9 David Knowles
    @param instance: the instance to shut down
876 95ab4de9 David Knowles
    @type dry_run: bool
877 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
878 95ab4de9 David Knowles

879 95ab4de9 David Knowles
    """
880 95ab4de9 David Knowles
    query = []
881 95ab4de9 David Knowles
    if dry_run:
882 95ab4de9 David Knowles
      query.append(("dry-run", 1))
883 95ab4de9 David Knowles
884 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
885 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/shutdown" %
886 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
887 95ab4de9 David Knowles
888 95ab4de9 David Knowles
  def StartupInstance(self, instance, dry_run=False):
889 95ab4de9 David Knowles
    """Starts up an instance.
890 95ab4de9 David Knowles

891 95ab4de9 David Knowles
    @type instance: str
892 95ab4de9 David Knowles
    @param instance: the instance to start up
893 95ab4de9 David Knowles
    @type dry_run: bool
894 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
895 95ab4de9 David Knowles

896 95ab4de9 David Knowles
    """
897 95ab4de9 David Knowles
    query = []
898 95ab4de9 David Knowles
    if dry_run:
899 95ab4de9 David Knowles
      query.append(("dry-run", 1))
900 95ab4de9 David Knowles
901 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
902 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/startup" %
903 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
904 95ab4de9 David Knowles
905 c744425f Michael Hanselmann
  def ReinstallInstance(self, instance, os=None, no_startup=False,
906 c744425f Michael Hanselmann
                        osparams=None):
907 95ab4de9 David Knowles
    """Reinstalls an instance.
908 95ab4de9 David Knowles

909 95ab4de9 David Knowles
    @type instance: str
910 fcee9675 David Knowles
    @param instance: The instance to reinstall
911 fcee9675 David Knowles
    @type os: str or None
912 fcee9675 David Knowles
    @param os: The operating system to reinstall. If None, the instance's
913 fcee9675 David Knowles
        current operating system will be installed again
914 95ab4de9 David Knowles
    @type no_startup: bool
915 fcee9675 David Knowles
    @param no_startup: Whether to start the instance automatically
916 95ab4de9 David Knowles

917 95ab4de9 David Knowles
    """
918 c744425f Michael Hanselmann
    if _INST_REINSTALL_REQV1 in self.GetFeatures():
919 c744425f Michael Hanselmann
      body = {
920 c744425f Michael Hanselmann
        "start": not no_startup,
921 c744425f Michael Hanselmann
        }
922 c744425f Michael Hanselmann
      if os is not None:
923 c744425f Michael Hanselmann
        body["os"] = os
924 c744425f Michael Hanselmann
      if osparams is not None:
925 c744425f Michael Hanselmann
        body["osparams"] = osparams
926 c744425f Michael Hanselmann
      return self._SendRequest(HTTP_POST,
927 c744425f Michael Hanselmann
                               ("/%s/instances/%s/reinstall" %
928 c744425f Michael Hanselmann
                                (GANETI_RAPI_VERSION, instance)), None, body)
929 c744425f Michael Hanselmann
930 c744425f Michael Hanselmann
    # Use old request format
931 c744425f Michael Hanselmann
    if osparams:
932 c744425f Michael Hanselmann
      raise GanetiApiError("Server does not support specifying OS parameters"
933 c744425f Michael Hanselmann
                           " for instance reinstallation")
934 c744425f Michael Hanselmann
935 fcee9675 David Knowles
    query = []
936 fcee9675 David Knowles
    if os:
937 fcee9675 David Knowles
      query.append(("os", os))
938 95ab4de9 David Knowles
    if no_startup:
939 95ab4de9 David Knowles
      query.append(("nostartup", 1))
940 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_POST,
941 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/reinstall" %
942 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
943 95ab4de9 David Knowles
944 bfc2002f Michael Hanselmann
  def ReplaceInstanceDisks(self, instance, disks=None, mode=REPLACE_DISK_AUTO,
945 cfc03c54 Michael Hanselmann
                           remote_node=None, iallocator=None, dry_run=False):
946 95ab4de9 David Knowles
    """Replaces disks on an instance.
947 95ab4de9 David Knowles

948 95ab4de9 David Knowles
    @type instance: str
949 95ab4de9 David Knowles
    @param instance: instance whose disks to replace
950 bfc2002f Michael Hanselmann
    @type disks: list of ints
951 bfc2002f Michael Hanselmann
    @param disks: Indexes of disks to replace
952 95ab4de9 David Knowles
    @type mode: str
953 cfc03c54 Michael Hanselmann
    @param mode: replacement mode to use (defaults to replace_auto)
954 95ab4de9 David Knowles
    @type remote_node: str or None
955 95ab4de9 David Knowles
    @param remote_node: new secondary node to use (for use with
956 cfc03c54 Michael Hanselmann
        replace_new_secondary mode)
957 95ab4de9 David Knowles
    @type iallocator: str or None
958 95ab4de9 David Knowles
    @param iallocator: instance allocator plugin to use (for use with
959 cfc03c54 Michael Hanselmann
                       replace_auto mode)
960 95ab4de9 David Knowles
    @type dry_run: bool
961 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
962 95ab4de9 David Knowles

963 95ab4de9 David Knowles
    @rtype: int
964 95ab4de9 David Knowles
    @return: job id
965 95ab4de9 David Knowles

966 95ab4de9 David Knowles
    """
967 cfc03c54 Michael Hanselmann
    query = [
968 cfc03c54 Michael Hanselmann
      ("mode", mode),
969 cfc03c54 Michael Hanselmann
      ]
970 95ab4de9 David Knowles
971 bfc2002f Michael Hanselmann
    if disks:
972 bfc2002f Michael Hanselmann
      query.append(("disks", ",".join(str(idx) for idx in disks)))
973 bfc2002f Michael Hanselmann
974 bfc2002f Michael Hanselmann
    if remote_node:
975 95ab4de9 David Knowles
      query.append(("remote_node", remote_node))
976 95ab4de9 David Knowles
977 bfc2002f Michael Hanselmann
    if iallocator:
978 bfc2002f Michael Hanselmann
      query.append(("iallocator", iallocator))
979 bfc2002f Michael Hanselmann
980 95ab4de9 David Knowles
    if dry_run:
981 95ab4de9 David Knowles
      query.append(("dry-run", 1))
982 95ab4de9 David Knowles
983 95ab4de9 David Knowles
    return self._SendRequest(HTTP_POST,
984 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/replace-disks" %
985 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
986 95ab4de9 David Knowles
987 ebeb600f Michael Hanselmann
  def PrepareExport(self, instance, mode):
988 ebeb600f Michael Hanselmann
    """Prepares an instance for an export.
989 ebeb600f Michael Hanselmann

990 ebeb600f Michael Hanselmann
    @type instance: string
991 ebeb600f Michael Hanselmann
    @param instance: Instance name
992 ebeb600f Michael Hanselmann
    @type mode: string
993 ebeb600f Michael Hanselmann
    @param mode: Export mode
994 ebeb600f Michael Hanselmann
    @rtype: string
995 ebeb600f Michael Hanselmann
    @return: Job ID
996 ebeb600f Michael Hanselmann

997 ebeb600f Michael Hanselmann
    """
998 ebeb600f Michael Hanselmann
    query = [("mode", mode)]
999 ebeb600f Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1000 ebeb600f Michael Hanselmann
                             ("/%s/instances/%s/prepare-export" %
1001 ebeb600f Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
1002 ebeb600f Michael Hanselmann
1003 ebeb600f Michael Hanselmann
  def ExportInstance(self, instance, mode, destination, shutdown=None,
1004 ebeb600f Michael Hanselmann
                     remove_instance=None,
1005 ebeb600f Michael Hanselmann
                     x509_key_name=None, destination_x509_ca=None):
1006 ebeb600f Michael Hanselmann
    """Exports an instance.
1007 ebeb600f Michael Hanselmann

1008 ebeb600f Michael Hanselmann
    @type instance: string
1009 ebeb600f Michael Hanselmann
    @param instance: Instance name
1010 ebeb600f Michael Hanselmann
    @type mode: string
1011 ebeb600f Michael Hanselmann
    @param mode: Export mode
1012 ebeb600f Michael Hanselmann
    @rtype: string
1013 ebeb600f Michael Hanselmann
    @return: Job ID
1014 ebeb600f Michael Hanselmann

1015 ebeb600f Michael Hanselmann
    """
1016 ebeb600f Michael Hanselmann
    body = {
1017 ebeb600f Michael Hanselmann
      "destination": destination,
1018 ebeb600f Michael Hanselmann
      "mode": mode,
1019 ebeb600f Michael Hanselmann
      }
1020 ebeb600f Michael Hanselmann
1021 ebeb600f Michael Hanselmann
    if shutdown is not None:
1022 ebeb600f Michael Hanselmann
      body["shutdown"] = shutdown
1023 ebeb600f Michael Hanselmann
1024 ebeb600f Michael Hanselmann
    if remove_instance is not None:
1025 ebeb600f Michael Hanselmann
      body["remove_instance"] = remove_instance
1026 ebeb600f Michael Hanselmann
1027 ebeb600f Michael Hanselmann
    if x509_key_name is not None:
1028 ebeb600f Michael Hanselmann
      body["x509_key_name"] = x509_key_name
1029 ebeb600f Michael Hanselmann
1030 ebeb600f Michael Hanselmann
    if destination_x509_ca is not None:
1031 ebeb600f Michael Hanselmann
      body["destination_x509_ca"] = destination_x509_ca
1032 ebeb600f Michael Hanselmann
1033 ebeb600f Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1034 ebeb600f Michael Hanselmann
                             ("/%s/instances/%s/export" %
1035 ebeb600f Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, body)
1036 ebeb600f Michael Hanselmann
1037 e0ac6ce6 Michael Hanselmann
  def MigrateInstance(self, instance, mode=None, cleanup=None):
1038 c63eb9c0 Michael Hanselmann
    """Migrates an instance.
1039 e0ac6ce6 Michael Hanselmann

1040 e0ac6ce6 Michael Hanselmann
    @type instance: string
1041 e0ac6ce6 Michael Hanselmann
    @param instance: Instance name
1042 e0ac6ce6 Michael Hanselmann
    @type mode: string
1043 e0ac6ce6 Michael Hanselmann
    @param mode: Migration mode
1044 e0ac6ce6 Michael Hanselmann
    @type cleanup: bool
1045 e0ac6ce6 Michael Hanselmann
    @param cleanup: Whether to clean up a previously failed migration
1046 e0ac6ce6 Michael Hanselmann

1047 e0ac6ce6 Michael Hanselmann
    """
1048 e0ac6ce6 Michael Hanselmann
    body = {}
1049 e0ac6ce6 Michael Hanselmann
1050 e0ac6ce6 Michael Hanselmann
    if mode is not None:
1051 e0ac6ce6 Michael Hanselmann
      body["mode"] = mode
1052 e0ac6ce6 Michael Hanselmann
1053 e0ac6ce6 Michael Hanselmann
    if cleanup is not None:
1054 e0ac6ce6 Michael Hanselmann
      body["cleanup"] = cleanup
1055 e0ac6ce6 Michael Hanselmann
1056 e0ac6ce6 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1057 e0ac6ce6 Michael Hanselmann
                             ("/%s/instances/%s/migrate" %
1058 e0ac6ce6 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, body)
1059 e0ac6ce6 Michael Hanselmann
1060 d654aae1 Michael Hanselmann
  def RenameInstance(self, instance, new_name, ip_check=None, name_check=None):
1061 d654aae1 Michael Hanselmann
    """Changes the name of an instance.
1062 d654aae1 Michael Hanselmann

1063 d654aae1 Michael Hanselmann
    @type instance: string
1064 d654aae1 Michael Hanselmann
    @param instance: Instance name
1065 d654aae1 Michael Hanselmann
    @type new_name: string
1066 d654aae1 Michael Hanselmann
    @param new_name: New instance name
1067 d654aae1 Michael Hanselmann
    @type ip_check: bool
1068 d654aae1 Michael Hanselmann
    @param ip_check: Whether to ensure instance's IP address is inactive
1069 d654aae1 Michael Hanselmann
    @type name_check: bool
1070 d654aae1 Michael Hanselmann
    @param name_check: Whether to ensure instance's name is resolvable
1071 d654aae1 Michael Hanselmann

1072 d654aae1 Michael Hanselmann
    """
1073 d654aae1 Michael Hanselmann
    body = {
1074 d654aae1 Michael Hanselmann
      "new_name": new_name,
1075 d654aae1 Michael Hanselmann
      }
1076 d654aae1 Michael Hanselmann
1077 d654aae1 Michael Hanselmann
    if ip_check is not None:
1078 d654aae1 Michael Hanselmann
      body["ip_check"] = ip_check
1079 d654aae1 Michael Hanselmann
1080 d654aae1 Michael Hanselmann
    if name_check is not None:
1081 d654aae1 Michael Hanselmann
      body["name_check"] = name_check
1082 d654aae1 Michael Hanselmann
1083 d654aae1 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1084 d654aae1 Michael Hanselmann
                             ("/%s/instances/%s/rename" %
1085 d654aae1 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, body)
1086 d654aae1 Michael Hanselmann
1087 95ab4de9 David Knowles
  def GetJobs(self):
1088 95ab4de9 David Knowles
    """Gets all jobs for the cluster.
1089 95ab4de9 David Knowles

1090 95ab4de9 David Knowles
    @rtype: list of int
1091 95ab4de9 David Knowles
    @return: job ids for the cluster
1092 95ab4de9 David Knowles

1093 95ab4de9 David Knowles
    """
1094 768747ed Michael Hanselmann
    return [int(j["id"])
1095 a198b2d9 Michael Hanselmann
            for j in self._SendRequest(HTTP_GET,
1096 a198b2d9 Michael Hanselmann
                                       "/%s/jobs" % GANETI_RAPI_VERSION,
1097 a198b2d9 Michael Hanselmann
                                       None, None)]
1098 95ab4de9 David Knowles
1099 95ab4de9 David Knowles
  def GetJobStatus(self, job_id):
1100 95ab4de9 David Knowles
    """Gets the status of a job.
1101 95ab4de9 David Knowles

1102 95ab4de9 David Knowles
    @type job_id: int
1103 95ab4de9 David Knowles
    @param job_id: job id whose status to query
1104 95ab4de9 David Knowles

1105 95ab4de9 David Knowles
    @rtype: dict
1106 95ab4de9 David Knowles
    @return: job status
1107 95ab4de9 David Knowles

1108 95ab4de9 David Knowles
    """
1109 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1110 a198b2d9 Michael Hanselmann
                             "/%s/jobs/%s" % (GANETI_RAPI_VERSION, job_id),
1111 a198b2d9 Michael Hanselmann
                             None, None)
1112 95ab4de9 David Knowles
1113 d9b67f70 Michael Hanselmann
  def WaitForJobChange(self, job_id, fields, prev_job_info, prev_log_serial):
1114 d9b67f70 Michael Hanselmann
    """Waits for job changes.
1115 d9b67f70 Michael Hanselmann

1116 d9b67f70 Michael Hanselmann
    @type job_id: int
1117 d9b67f70 Michael Hanselmann
    @param job_id: Job ID for which to wait
1118 d9b67f70 Michael Hanselmann

1119 d9b67f70 Michael Hanselmann
    """
1120 d9b67f70 Michael Hanselmann
    body = {
1121 d9b67f70 Michael Hanselmann
      "fields": fields,
1122 d9b67f70 Michael Hanselmann
      "previous_job_info": prev_job_info,
1123 d9b67f70 Michael Hanselmann
      "previous_log_serial": prev_log_serial,
1124 d9b67f70 Michael Hanselmann
      }
1125 d9b67f70 Michael Hanselmann
1126 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1127 a198b2d9 Michael Hanselmann
                             "/%s/jobs/%s/wait" % (GANETI_RAPI_VERSION, job_id),
1128 a198b2d9 Michael Hanselmann
                             None, body)
1129 d9b67f70 Michael Hanselmann
1130 cf9ada49 Michael Hanselmann
  def CancelJob(self, job_id, dry_run=False):
1131 cf9ada49 Michael Hanselmann
    """Cancels a job.
1132 95ab4de9 David Knowles

1133 95ab4de9 David Knowles
    @type job_id: int
1134 95ab4de9 David Knowles
    @param job_id: id of the job to delete
1135 95ab4de9 David Knowles
    @type dry_run: bool
1136 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1137 95ab4de9 David Knowles

1138 95ab4de9 David Knowles
    """
1139 95ab4de9 David Knowles
    query = []
1140 95ab4de9 David Knowles
    if dry_run:
1141 95ab4de9 David Knowles
      query.append(("dry-run", 1))
1142 95ab4de9 David Knowles
1143 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE,
1144 a198b2d9 Michael Hanselmann
                             "/%s/jobs/%s" % (GANETI_RAPI_VERSION, job_id),
1145 a198b2d9 Michael Hanselmann
                             query, None)
1146 95ab4de9 David Knowles
1147 95ab4de9 David Knowles
  def GetNodes(self, bulk=False):
1148 95ab4de9 David Knowles
    """Gets all nodes in the cluster.
1149 95ab4de9 David Knowles

1150 95ab4de9 David Knowles
    @type bulk: bool
1151 95ab4de9 David Knowles
    @param bulk: whether to return all information about all instances
1152 95ab4de9 David Knowles

1153 95ab4de9 David Knowles
    @rtype: list of dict or str
1154 95ab4de9 David Knowles
    @return: if bulk is true, info about nodes in the cluster,
1155 95ab4de9 David Knowles
        else list of nodes in the cluster
1156 95ab4de9 David Knowles

1157 95ab4de9 David Knowles
    """
1158 95ab4de9 David Knowles
    query = []
1159 95ab4de9 David Knowles
    if bulk:
1160 95ab4de9 David Knowles
      query.append(("bulk", 1))
1161 95ab4de9 David Knowles
1162 a198b2d9 Michael Hanselmann
    nodes = self._SendRequest(HTTP_GET, "/%s/nodes" % GANETI_RAPI_VERSION,
1163 a198b2d9 Michael Hanselmann
                              query, None)
1164 95ab4de9 David Knowles
    if bulk:
1165 95ab4de9 David Knowles
      return nodes
1166 95ab4de9 David Knowles
    else:
1167 95ab4de9 David Knowles
      return [n["id"] for n in nodes]
1168 95ab4de9 David Knowles
1169 591e5103 Michael Hanselmann
  def GetNode(self, node):
1170 95ab4de9 David Knowles
    """Gets information about a node.
1171 95ab4de9 David Knowles

1172 95ab4de9 David Knowles
    @type node: str
1173 95ab4de9 David Knowles
    @param node: node whose info to return
1174 95ab4de9 David Knowles

1175 95ab4de9 David Knowles
    @rtype: dict
1176 95ab4de9 David Knowles
    @return: info about the node
1177 95ab4de9 David Knowles

1178 95ab4de9 David Knowles
    """
1179 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1180 a198b2d9 Michael Hanselmann
                             "/%s/nodes/%s" % (GANETI_RAPI_VERSION, node),
1181 a198b2d9 Michael Hanselmann
                             None, None)
1182 95ab4de9 David Knowles
1183 95ab4de9 David Knowles
  def EvacuateNode(self, node, iallocator=None, remote_node=None,
1184 941b9309 Iustin Pop
                   dry_run=False, early_release=False):
1185 95ab4de9 David Knowles
    """Evacuates instances from a Ganeti node.
1186 95ab4de9 David Knowles

1187 95ab4de9 David Knowles
    @type node: str
1188 95ab4de9 David Knowles
    @param node: node to evacuate
1189 95ab4de9 David Knowles
    @type iallocator: str or None
1190 95ab4de9 David Knowles
    @param iallocator: instance allocator to use
1191 95ab4de9 David Knowles
    @type remote_node: str
1192 95ab4de9 David Knowles
    @param remote_node: node to evaucate to
1193 95ab4de9 David Knowles
    @type dry_run: bool
1194 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1195 941b9309 Iustin Pop
    @type early_release: bool
1196 941b9309 Iustin Pop
    @param early_release: whether to enable parallelization
1197 95ab4de9 David Knowles

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

1203 941b9309 Iustin Pop
    @raises GanetiApiError: if an iallocator and remote_node are both
1204 941b9309 Iustin Pop
        specified
1205 95ab4de9 David Knowles

1206 95ab4de9 David Knowles
    """
1207 95ab4de9 David Knowles
    if iallocator and remote_node:
1208 cfc03c54 Michael Hanselmann
      raise GanetiApiError("Only one of iallocator or remote_node can be used")
1209 95ab4de9 David Knowles
1210 cfc03c54 Michael Hanselmann
    query = []
1211 95ab4de9 David Knowles
    if iallocator:
1212 95ab4de9 David Knowles
      query.append(("iallocator", iallocator))
1213 95ab4de9 David Knowles
    if remote_node:
1214 95ab4de9 David Knowles
      query.append(("remote_node", remote_node))
1215 95ab4de9 David Knowles
    if dry_run:
1216 95ab4de9 David Knowles
      query.append(("dry-run", 1))
1217 941b9309 Iustin Pop
    if early_release:
1218 941b9309 Iustin Pop
      query.append(("early_release", 1))
1219 95ab4de9 David Knowles
1220 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_POST,
1221 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/evacuate" %
1222 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1223 95ab4de9 David Knowles
1224 1f334d96 Iustin Pop
  def MigrateNode(self, node, mode=None, dry_run=False):
1225 95ab4de9 David Knowles
    """Migrates all primary instances from a node.
1226 95ab4de9 David Knowles

1227 95ab4de9 David Knowles
    @type node: str
1228 95ab4de9 David Knowles
    @param node: node to migrate
1229 1f334d96 Iustin Pop
    @type mode: string
1230 1f334d96 Iustin Pop
    @param mode: if passed, it will overwrite the live migration type,
1231 1f334d96 Iustin Pop
        otherwise the hypervisor default will be used
1232 95ab4de9 David Knowles
    @type dry_run: bool
1233 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1234 95ab4de9 David Knowles

1235 95ab4de9 David Knowles
    @rtype: int
1236 95ab4de9 David Knowles
    @return: job id
1237 95ab4de9 David Knowles

1238 95ab4de9 David Knowles
    """
1239 95ab4de9 David Knowles
    query = []
1240 1f334d96 Iustin Pop
    if mode is not None:
1241 1f334d96 Iustin Pop
      query.append(("mode", mode))
1242 95ab4de9 David Knowles
    if dry_run:
1243 95ab4de9 David Knowles
      query.append(("dry-run", 1))
1244 95ab4de9 David Knowles
1245 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_POST,
1246 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/migrate" %
1247 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1248 95ab4de9 David Knowles
1249 95ab4de9 David Knowles
  def GetNodeRole(self, node):
1250 95ab4de9 David Knowles
    """Gets the current role for a node.
1251 95ab4de9 David Knowles

1252 95ab4de9 David Knowles
    @type node: str
1253 95ab4de9 David Knowles
    @param node: node whose role to return
1254 95ab4de9 David Knowles

1255 95ab4de9 David Knowles
    @rtype: str
1256 95ab4de9 David Knowles
    @return: the current role for a node
1257 95ab4de9 David Knowles

1258 95ab4de9 David Knowles
    """
1259 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1260 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/role" %
1261 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), None, None)
1262 95ab4de9 David Knowles
1263 95ab4de9 David Knowles
  def SetNodeRole(self, node, role, force=False):
1264 95ab4de9 David Knowles
    """Sets the role for a node.
1265 95ab4de9 David Knowles

1266 95ab4de9 David Knowles
    @type node: str
1267 95ab4de9 David Knowles
    @param node: the node whose role to set
1268 95ab4de9 David Knowles
    @type role: str
1269 95ab4de9 David Knowles
    @param role: the role to set for the node
1270 95ab4de9 David Knowles
    @type force: bool
1271 95ab4de9 David Knowles
    @param force: whether to force the role change
1272 95ab4de9 David Knowles

1273 95ab4de9 David Knowles
    @rtype: int
1274 95ab4de9 David Knowles
    @return: job id
1275 95ab4de9 David Knowles

1276 95ab4de9 David Knowles
    """
1277 1068639f Michael Hanselmann
    query = [
1278 1068639f Michael Hanselmann
      ("force", force),
1279 1068639f Michael Hanselmann
      ]
1280 cfc03c54 Michael Hanselmann
1281 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1282 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/role" %
1283 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, role)
1284 95ab4de9 David Knowles
1285 95ab4de9 David Knowles
  def GetNodeStorageUnits(self, node, storage_type, output_fields):
1286 95ab4de9 David Knowles
    """Gets the storage units for a node.
1287 95ab4de9 David Knowles

1288 95ab4de9 David Knowles
    @type node: str
1289 95ab4de9 David Knowles
    @param node: the node whose storage units to return
1290 95ab4de9 David Knowles
    @type storage_type: str
1291 95ab4de9 David Knowles
    @param storage_type: storage type whose units to return
1292 95ab4de9 David Knowles
    @type output_fields: str
1293 95ab4de9 David Knowles
    @param output_fields: storage type fields to return
1294 95ab4de9 David Knowles

1295 95ab4de9 David Knowles
    @rtype: int
1296 95ab4de9 David Knowles
    @return: job id where results can be retrieved
1297 95ab4de9 David Knowles

1298 95ab4de9 David Knowles
    """
1299 cfc03c54 Michael Hanselmann
    query = [
1300 cfc03c54 Michael Hanselmann
      ("storage_type", storage_type),
1301 cfc03c54 Michael Hanselmann
      ("output_fields", output_fields),
1302 cfc03c54 Michael Hanselmann
      ]
1303 95ab4de9 David Knowles
1304 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1305 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/storage" %
1306 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1307 95ab4de9 David Knowles
1308 fde28316 Michael Hanselmann
  def ModifyNodeStorageUnits(self, node, storage_type, name, allocatable=None):
1309 95ab4de9 David Knowles
    """Modifies parameters of storage units on the node.
1310 95ab4de9 David Knowles

1311 95ab4de9 David Knowles
    @type node: str
1312 95ab4de9 David Knowles
    @param node: node whose storage units to modify
1313 95ab4de9 David Knowles
    @type storage_type: str
1314 95ab4de9 David Knowles
    @param storage_type: storage type whose units to modify
1315 95ab4de9 David Knowles
    @type name: str
1316 95ab4de9 David Knowles
    @param name: name of the storage unit
1317 fde28316 Michael Hanselmann
    @type allocatable: bool or None
1318 fde28316 Michael Hanselmann
    @param allocatable: Whether to set the "allocatable" flag on the storage
1319 fde28316 Michael Hanselmann
                        unit (None=no modification, True=set, False=unset)
1320 95ab4de9 David Knowles

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

1324 95ab4de9 David Knowles
    """
1325 95ab4de9 David Knowles
    query = [
1326 cfc03c54 Michael Hanselmann
      ("storage_type", storage_type),
1327 cfc03c54 Michael Hanselmann
      ("name", name),
1328 cfc03c54 Michael Hanselmann
      ]
1329 cfc03c54 Michael Hanselmann
1330 fde28316 Michael Hanselmann
    if allocatable is not None:
1331 fde28316 Michael Hanselmann
      query.append(("allocatable", allocatable))
1332 fde28316 Michael Hanselmann
1333 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1334 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/storage/modify" %
1335 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1336 95ab4de9 David Knowles
1337 95ab4de9 David Knowles
  def RepairNodeStorageUnits(self, node, storage_type, name):
1338 95ab4de9 David Knowles
    """Repairs a storage unit on the node.
1339 95ab4de9 David Knowles

1340 95ab4de9 David Knowles
    @type node: str
1341 95ab4de9 David Knowles
    @param node: node whose storage units to repair
1342 95ab4de9 David Knowles
    @type storage_type: str
1343 95ab4de9 David Knowles
    @param storage_type: storage type to repair
1344 95ab4de9 David Knowles
    @type name: str
1345 95ab4de9 David Knowles
    @param name: name of the storage unit to repair
1346 95ab4de9 David Knowles

1347 95ab4de9 David Knowles
    @rtype: int
1348 95ab4de9 David Knowles
    @return: job id
1349 95ab4de9 David Knowles

1350 95ab4de9 David Knowles
    """
1351 cfc03c54 Michael Hanselmann
    query = [
1352 cfc03c54 Michael Hanselmann
      ("storage_type", storage_type),
1353 cfc03c54 Michael Hanselmann
      ("name", name),
1354 cfc03c54 Michael Hanselmann
      ]
1355 95ab4de9 David Knowles
1356 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1357 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/storage/repair" %
1358 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1359 95ab4de9 David Knowles
1360 95ab4de9 David Knowles
  def GetNodeTags(self, node):
1361 95ab4de9 David Knowles
    """Gets the tags for a node.
1362 95ab4de9 David Knowles

1363 95ab4de9 David Knowles
    @type node: str
1364 95ab4de9 David Knowles
    @param node: node whose tags to return
1365 95ab4de9 David Knowles

1366 95ab4de9 David Knowles
    @rtype: list of str
1367 95ab4de9 David Knowles
    @return: tags for the node
1368 95ab4de9 David Knowles

1369 95ab4de9 David Knowles
    """
1370 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1371 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/tags" %
1372 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), None, None)
1373 95ab4de9 David Knowles
1374 95ab4de9 David Knowles
  def AddNodeTags(self, node, tags, dry_run=False):
1375 95ab4de9 David Knowles
    """Adds tags to a node.
1376 95ab4de9 David Knowles

1377 95ab4de9 David Knowles
    @type node: str
1378 95ab4de9 David Knowles
    @param node: node to add tags to
1379 95ab4de9 David Knowles
    @type tags: list of str
1380 95ab4de9 David Knowles
    @param tags: tags to add to the node
1381 95ab4de9 David Knowles
    @type dry_run: bool
1382 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1383 95ab4de9 David Knowles

1384 95ab4de9 David Knowles
    @rtype: int
1385 95ab4de9 David Knowles
    @return: job id
1386 95ab4de9 David Knowles

1387 95ab4de9 David Knowles
    """
1388 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
1389 95ab4de9 David Knowles
    if dry_run:
1390 95ab4de9 David Knowles
      query.append(("dry-run", 1))
1391 95ab4de9 David Knowles
1392 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1393 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/tags" %
1394 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, tags)
1395 95ab4de9 David Knowles
1396 95ab4de9 David Knowles
  def DeleteNodeTags(self, node, tags, dry_run=False):
1397 95ab4de9 David Knowles
    """Delete tags from a node.
1398 95ab4de9 David Knowles

1399 95ab4de9 David Knowles
    @type node: str
1400 95ab4de9 David Knowles
    @param node: node to remove tags from
1401 95ab4de9 David Knowles
    @type tags: list of str
1402 95ab4de9 David Knowles
    @param tags: tags to remove from the node
1403 95ab4de9 David Knowles
    @type dry_run: bool
1404 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
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 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
1411 95ab4de9 David Knowles
    if dry_run:
1412 95ab4de9 David Knowles
      query.append(("dry-run", 1))
1413 95ab4de9 David Knowles
1414 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE,
1415 a198b2d9 Michael Hanselmann
                             ("/%s/nodes/%s/tags" %
1416 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, node)), query, None)
1417 a268af8d Adeodato Simo
1418 a268af8d Adeodato Simo
  def GetGroups(self, bulk=False):
1419 a268af8d Adeodato Simo
    """Gets all node groups in the cluster.
1420 a268af8d Adeodato Simo

1421 a268af8d Adeodato Simo
    @type bulk: bool
1422 a268af8d Adeodato Simo
    @param bulk: whether to return all information about the groups
1423 a268af8d Adeodato Simo

1424 a268af8d Adeodato Simo
    @rtype: list of dict or str
1425 a268af8d Adeodato Simo
    @return: if bulk is true, a list of dictionaries with info about all node
1426 a268af8d Adeodato Simo
        groups in the cluster, else a list of names of those node groups
1427 a268af8d Adeodato Simo

1428 a268af8d Adeodato Simo
    """
1429 a268af8d Adeodato Simo
    query = []
1430 a268af8d Adeodato Simo
    if bulk:
1431 a268af8d Adeodato Simo
      query.append(("bulk", 1))
1432 a268af8d Adeodato Simo
1433 a268af8d Adeodato Simo
    groups = self._SendRequest(HTTP_GET, "/%s/groups" % GANETI_RAPI_VERSION,
1434 a268af8d Adeodato Simo
                               query, None)
1435 a268af8d Adeodato Simo
    if bulk:
1436 a268af8d Adeodato Simo
      return groups
1437 a268af8d Adeodato Simo
    else:
1438 a268af8d Adeodato Simo
      return [g["name"] for g in groups]
1439 a268af8d Adeodato Simo
1440 a268af8d Adeodato Simo
  def GetGroup(self, group):
1441 a268af8d Adeodato Simo
    """Gets information about a node group.
1442 a268af8d Adeodato Simo

1443 a268af8d Adeodato Simo
    @type group: str
1444 a268af8d Adeodato Simo
    @param group: name of the node group whose info to return
1445 a268af8d Adeodato Simo

1446 a268af8d Adeodato Simo
    @rtype: dict
1447 a268af8d Adeodato Simo
    @return: info about the node group
1448 a268af8d Adeodato Simo

1449 a268af8d Adeodato Simo
    """
1450 a268af8d Adeodato Simo
    return self._SendRequest(HTTP_GET,
1451 a268af8d Adeodato Simo
                             "/%s/groups/%s" % (GANETI_RAPI_VERSION, group),
1452 a268af8d Adeodato Simo
                             None, None)
1453 a268af8d Adeodato Simo
1454 90e99856 Adeodato Simo
  def CreateGroup(self, name, alloc_policy=None, dry_run=False):
1455 a268af8d Adeodato Simo
    """Creates a new node group.
1456 a268af8d Adeodato Simo

1457 a268af8d Adeodato Simo
    @type name: str
1458 a268af8d Adeodato Simo
    @param name: the name of node group to create
1459 90e99856 Adeodato Simo
    @type alloc_policy: str
1460 90e99856 Adeodato Simo
    @param alloc_policy: the desired allocation policy for the group, if any
1461 a268af8d Adeodato Simo
    @type dry_run: bool
1462 a268af8d Adeodato Simo
    @param dry_run: whether to peform a dry run
1463 a268af8d Adeodato Simo

1464 a268af8d Adeodato Simo
    @rtype: int
1465 a268af8d Adeodato Simo
    @return: job id
1466 a268af8d Adeodato Simo

1467 a268af8d Adeodato Simo
    """
1468 a268af8d Adeodato Simo
    query = []
1469 a268af8d Adeodato Simo
    if dry_run:
1470 a268af8d Adeodato Simo
      query.append(("dry-run", 1))
1471 a268af8d Adeodato Simo
1472 a268af8d Adeodato Simo
    body = {
1473 a268af8d Adeodato Simo
      "name": name,
1474 90e99856 Adeodato Simo
      "alloc_policy": alloc_policy
1475 a268af8d Adeodato Simo
      }
1476 a268af8d Adeodato Simo
1477 a268af8d Adeodato Simo
    return self._SendRequest(HTTP_POST, "/%s/groups" % GANETI_RAPI_VERSION,
1478 a268af8d Adeodato Simo
                             query, body)
1479 a268af8d Adeodato Simo
1480 f18fab7d Adeodato Simo
  def ModifyGroup(self, group, **kwargs):
1481 f18fab7d Adeodato Simo
    """Modifies a node group.
1482 f18fab7d Adeodato Simo

1483 f18fab7d Adeodato Simo
    More details for parameters can be found in the RAPI documentation.
1484 f18fab7d Adeodato Simo

1485 f18fab7d Adeodato Simo
    @type group: string
1486 f18fab7d Adeodato Simo
    @param group: Node group name
1487 f18fab7d Adeodato Simo
    @rtype: int
1488 f18fab7d Adeodato Simo
    @return: job id
1489 f18fab7d Adeodato Simo

1490 f18fab7d Adeodato Simo
    """
1491 f18fab7d Adeodato Simo
    return self._SendRequest(HTTP_PUT,
1492 f18fab7d Adeodato Simo
                             ("/%s/groups/%s/modify" %
1493 f18fab7d Adeodato Simo
                              (GANETI_RAPI_VERSION, group)), None, kwargs)
1494 f18fab7d Adeodato Simo
1495 a268af8d Adeodato Simo
  def DeleteGroup(self, group, dry_run=False):
1496 a268af8d Adeodato Simo
    """Deletes a node group.
1497 a268af8d Adeodato Simo

1498 a268af8d Adeodato Simo
    @type group: str
1499 a268af8d Adeodato Simo
    @param group: the node group to delete
1500 a268af8d Adeodato Simo
    @type dry_run: bool
1501 a268af8d Adeodato Simo
    @param dry_run: whether to peform a dry run
1502 a268af8d Adeodato Simo

1503 a268af8d Adeodato Simo
    @rtype: int
1504 a268af8d Adeodato Simo
    @return: job id
1505 a268af8d Adeodato Simo

1506 a268af8d Adeodato Simo
    """
1507 a268af8d Adeodato Simo
    query = []
1508 a268af8d Adeodato Simo
    if dry_run:
1509 a268af8d Adeodato Simo
      query.append(("dry-run", 1))
1510 a268af8d Adeodato Simo
1511 a268af8d Adeodato Simo
    return self._SendRequest(HTTP_DELETE,
1512 a268af8d Adeodato Simo
                             ("/%s/groups/%s" %
1513 a268af8d Adeodato Simo
                              (GANETI_RAPI_VERSION, group)), query, None)
1514 a268af8d Adeodato Simo
1515 a268af8d Adeodato Simo
  def RenameGroup(self, group, new_name):
1516 a268af8d Adeodato Simo
    """Changes the name of a node group.
1517 a268af8d Adeodato Simo

1518 a268af8d Adeodato Simo
    @type group: string
1519 a268af8d Adeodato Simo
    @param group: Node group name
1520 a268af8d Adeodato Simo
    @type new_name: string
1521 a268af8d Adeodato Simo
    @param new_name: New node group name
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
    body = {
1528 a268af8d Adeodato Simo
      "new_name": new_name,
1529 a268af8d Adeodato Simo
      }
1530 a268af8d Adeodato Simo
1531 a268af8d Adeodato Simo
    return self._SendRequest(HTTP_PUT,
1532 a268af8d Adeodato Simo
                             ("/%s/groups/%s/rename" %
1533 a268af8d Adeodato Simo
                              (GANETI_RAPI_VERSION, group)), None, body)
1534 4245446f Adeodato Simo
1535 4245446f Adeodato Simo
1536 4245446f Adeodato Simo
  def AssignGroupNodes(self, group, nodes, force=False, dry_run=False):
1537 4245446f Adeodato Simo
    """Assigns nodes to a group.
1538 4245446f Adeodato Simo

1539 4245446f Adeodato Simo
    @type group: string
1540 4245446f Adeodato Simo
    @param group: Node gropu name
1541 4245446f Adeodato Simo
    @type nodes: list of strings
1542 4245446f Adeodato Simo
    @param nodes: List of nodes to assign to the group
1543 4245446f Adeodato Simo

1544 4245446f Adeodato Simo
    @rtype: int
1545 4245446f Adeodato Simo
    @return: job id
1546 4245446f Adeodato Simo

1547 4245446f Adeodato Simo
    """
1548 4245446f Adeodato Simo
    query = []
1549 4245446f Adeodato Simo
1550 4245446f Adeodato Simo
    if force:
1551 4245446f Adeodato Simo
      query.append(("force", 1))
1552 4245446f Adeodato Simo
1553 4245446f Adeodato Simo
    if dry_run:
1554 4245446f Adeodato Simo
      query.append(("dry-run", 1))
1555 4245446f Adeodato Simo
1556 4245446f Adeodato Simo
    body = {
1557 4245446f Adeodato Simo
      "nodes": nodes,
1558 4245446f Adeodato Simo
      }
1559 4245446f Adeodato Simo
1560 4245446f Adeodato Simo
    return self._SendRequest(HTTP_PUT,
1561 4245446f Adeodato Simo
                             ("/%s/groups/%s/assign-nodes" %
1562 4245446f Adeodato Simo
                             (GANETI_RAPI_VERSION, group)), query, body)