Statistics
| Branch: | Tag: | Revision:

root / util / rapi.py @ 1f6ba360

History | View | Annotate | Download (46.5 kB)

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

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

31 e5e20779 Faidon Liambotis
"""
32 e5e20779 Faidon Liambotis
33 e5e20779 Faidon Liambotis
# No Ganeti-specific modules should be imported. The RAPI client is supposed to
34 e5e20779 Faidon Liambotis
# be standalone.
35 e5e20779 Faidon Liambotis
36 e5e20779 Faidon Liambotis
import logging
37 e5e20779 Faidon Liambotis
import socket
38 e5e20779 Faidon Liambotis
import urllib
39 e5e20779 Faidon Liambotis
import threading
40 e5e20779 Faidon Liambotis
import pycurl
41 e5e20779 Faidon Liambotis
42 e5e20779 Faidon Liambotis
try:
43 3481786b Faidon Liambotis
  import simplejson as json
44 3481786b Faidon Liambotis
except ImportError:
45 3481786b Faidon Liambotis
  import json
46 3481786b Faidon Liambotis
47 3481786b Faidon Liambotis
try:
48 e5e20779 Faidon Liambotis
  from cStringIO import StringIO
49 e5e20779 Faidon Liambotis
except ImportError:
50 e5e20779 Faidon Liambotis
  from StringIO import StringIO
51 e5e20779 Faidon Liambotis
52 e5e20779 Faidon Liambotis
53 e5e20779 Faidon Liambotis
GANETI_RAPI_PORT = 5080
54 e5e20779 Faidon Liambotis
GANETI_RAPI_VERSION = 2
55 e5e20779 Faidon Liambotis
56 e5e20779 Faidon Liambotis
HTTP_DELETE = "DELETE"
57 e5e20779 Faidon Liambotis
HTTP_GET = "GET"
58 e5e20779 Faidon Liambotis
HTTP_PUT = "PUT"
59 e5e20779 Faidon Liambotis
HTTP_POST = "POST"
60 e5e20779 Faidon Liambotis
HTTP_OK = 200
61 e5e20779 Faidon Liambotis
HTTP_NOT_FOUND = 404
62 e5e20779 Faidon Liambotis
HTTP_APP_JSON = "application/json"
63 e5e20779 Faidon Liambotis
64 e5e20779 Faidon Liambotis
REPLACE_DISK_PRI = "replace_on_primary"
65 e5e20779 Faidon Liambotis
REPLACE_DISK_SECONDARY = "replace_on_secondary"
66 e5e20779 Faidon Liambotis
REPLACE_DISK_CHG = "replace_new_secondary"
67 e5e20779 Faidon Liambotis
REPLACE_DISK_AUTO = "replace_auto"
68 e5e20779 Faidon Liambotis
69 e5e20779 Faidon Liambotis
NODE_ROLE_DRAINED = "drained"
70 e5e20779 Faidon Liambotis
NODE_ROLE_MASTER_CANDIATE = "master-candidate"
71 e5e20779 Faidon Liambotis
NODE_ROLE_MASTER = "master"
72 e5e20779 Faidon Liambotis
NODE_ROLE_OFFLINE = "offline"
73 e5e20779 Faidon Liambotis
NODE_ROLE_REGULAR = "regular"
74 e5e20779 Faidon Liambotis
75 e5e20779 Faidon Liambotis
# Internal constants
76 e5e20779 Faidon Liambotis
_REQ_DATA_VERSION_FIELD = "__version__"
77 e5e20779 Faidon Liambotis
_INST_CREATE_REQV1 = "instance-create-reqv1"
78 067dda99 Vangelis Koukis
_INST_REINSTALL_REQV1 = "instance-reinstall-reqv1"
79 e5e20779 Faidon Liambotis
_INST_NIC_PARAMS = frozenset(["mac", "ip", "mode", "link", "bridge"])
80 e5e20779 Faidon Liambotis
_INST_CREATE_V0_DISK_PARAMS = frozenset(["size"])
81 e5e20779 Faidon Liambotis
_INST_CREATE_V0_PARAMS = frozenset([
82 e5e20779 Faidon Liambotis
  "os", "pnode", "snode", "iallocator", "start", "ip_check", "name_check",
83 e5e20779 Faidon Liambotis
  "hypervisor", "file_storage_dir", "file_driver", "dry_run",
84 e5e20779 Faidon Liambotis
  ])
85 e5e20779 Faidon Liambotis
_INST_CREATE_V0_DPARAMS = frozenset(["beparams", "hvparams"])
86 e5e20779 Faidon Liambotis
87 e5e20779 Faidon Liambotis
# Older pycURL versions don't have all error constants
88 e5e20779 Faidon Liambotis
try:
89 e5e20779 Faidon Liambotis
  _CURLE_SSL_CACERT = pycurl.E_SSL_CACERT
90 e5e20779 Faidon Liambotis
  _CURLE_SSL_CACERT_BADFILE = pycurl.E_SSL_CACERT_BADFILE
91 e5e20779 Faidon Liambotis
except AttributeError:
92 e5e20779 Faidon Liambotis
  _CURLE_SSL_CACERT = 60
93 e5e20779 Faidon Liambotis
  _CURLE_SSL_CACERT_BADFILE = 77
94 e5e20779 Faidon Liambotis
95 e5e20779 Faidon Liambotis
_CURL_SSL_CERT_ERRORS = frozenset([
96 e5e20779 Faidon Liambotis
  _CURLE_SSL_CACERT,
97 e5e20779 Faidon Liambotis
  _CURLE_SSL_CACERT_BADFILE,
98 e5e20779 Faidon Liambotis
  ])
99 e5e20779 Faidon Liambotis
100 e5e20779 Faidon Liambotis
101 e5e20779 Faidon Liambotis
class Error(Exception):
102 e5e20779 Faidon Liambotis
  """Base error class for this module.
103 e5e20779 Faidon Liambotis

104 e5e20779 Faidon Liambotis
  """
105 e5e20779 Faidon Liambotis
  pass
106 e5e20779 Faidon Liambotis
107 e5e20779 Faidon Liambotis
108 e5e20779 Faidon Liambotis
class CertificateError(Error):
109 e5e20779 Faidon Liambotis
  """Raised when a problem is found with the SSL certificate.
110 e5e20779 Faidon Liambotis

111 e5e20779 Faidon Liambotis
  """
112 e5e20779 Faidon Liambotis
  pass
113 e5e20779 Faidon Liambotis
114 e5e20779 Faidon Liambotis
115 e5e20779 Faidon Liambotis
class GanetiApiError(Error):
116 e5e20779 Faidon Liambotis
  """Generic error raised from Ganeti API.
117 e5e20779 Faidon Liambotis

118 e5e20779 Faidon Liambotis
  """
119 e5e20779 Faidon Liambotis
  def __init__(self, msg, code=None):
120 e5e20779 Faidon Liambotis
    Error.__init__(self, msg)
121 e5e20779 Faidon Liambotis
    self.code = code
122 e5e20779 Faidon Liambotis
123 e5e20779 Faidon Liambotis
124 e5e20779 Faidon Liambotis
def UsesRapiClient(fn):
125 e5e20779 Faidon Liambotis
  """Decorator for code using RAPI client to initialize pycURL.
126 e5e20779 Faidon Liambotis

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

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

173 e5e20779 Faidon Liambotis
  """
174 e5e20779 Faidon Liambotis
  if use_curl_cabundle and (cafile or capath):
175 e5e20779 Faidon Liambotis
    raise Error("Can not use default CA bundle when CA file or path is set")
176 e5e20779 Faidon Liambotis
177 e5e20779 Faidon Liambotis
  def _ConfigCurl(curl, logger):
178 e5e20779 Faidon Liambotis
    """Configures a cURL object
179 e5e20779 Faidon Liambotis

180 e5e20779 Faidon Liambotis
    @type curl: pycurl.Curl
181 e5e20779 Faidon Liambotis
    @param curl: cURL object
182 e5e20779 Faidon Liambotis

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

246 e5e20779 Faidon Liambotis
  """
247 e5e20779 Faidon Liambotis
  USER_AGENT = "Ganeti RAPI Client"
248 1f6ba360 Faidon Liambotis
  _json_encoder = json.JSONEncoder(sort_keys=True)
249 e5e20779 Faidon Liambotis
250 e5e20779 Faidon Liambotis
  def __init__(self, host, port=GANETI_RAPI_PORT,
251 e5e20779 Faidon Liambotis
               username=None, password=None, logger=logging,
252 e5e20779 Faidon Liambotis
               curl_config_fn=None, curl_factory=None):
253 e5e20779 Faidon Liambotis
    """Initializes this class.
254 e5e20779 Faidon Liambotis

255 e5e20779 Faidon Liambotis
    @type host: string
256 e5e20779 Faidon Liambotis
    @param host: the ganeti cluster master to interact with
257 e5e20779 Faidon Liambotis
    @type port: int
258 e5e20779 Faidon Liambotis
    @param port: the port on which the RAPI is running (default is 5080)
259 e5e20779 Faidon Liambotis
    @type username: string
260 e5e20779 Faidon Liambotis
    @param username: the username to connect with
261 e5e20779 Faidon Liambotis
    @type password: string
262 e5e20779 Faidon Liambotis
    @param password: the password to connect with
263 e5e20779 Faidon Liambotis
    @type curl_config_fn: callable
264 e5e20779 Faidon Liambotis
    @param curl_config_fn: Function to configure C{pycurl.Curl} object
265 e5e20779 Faidon Liambotis
    @param logger: Logging object
266 e5e20779 Faidon Liambotis

267 e5e20779 Faidon Liambotis
    """
268 e5e20779 Faidon Liambotis
    self._username = username
269 e5e20779 Faidon Liambotis
    self._password = password
270 e5e20779 Faidon Liambotis
    self._logger = logger
271 e5e20779 Faidon Liambotis
    self._curl_config_fn = curl_config_fn
272 e5e20779 Faidon Liambotis
    self._curl_factory = curl_factory
273 e5e20779 Faidon Liambotis
274 e5e20779 Faidon Liambotis
    try:
275 e5e20779 Faidon Liambotis
      socket.inet_pton(socket.AF_INET6, host)
276 e5e20779 Faidon Liambotis
      address = "[%s]:%s" % (host, port)
277 e5e20779 Faidon Liambotis
    except socket.error:
278 e5e20779 Faidon Liambotis
      address = "%s:%s" % (host, port)
279 e5e20779 Faidon Liambotis
280 e5e20779 Faidon Liambotis
    self._base_url = "https://%s" % address
281 e5e20779 Faidon Liambotis
282 e5e20779 Faidon Liambotis
    if username is not None:
283 e5e20779 Faidon Liambotis
      if password is None:
284 e5e20779 Faidon Liambotis
        raise Error("Password not specified")
285 e5e20779 Faidon Liambotis
    elif password:
286 e5e20779 Faidon Liambotis
      raise Error("Specified password without username")
287 e5e20779 Faidon Liambotis
288 e5e20779 Faidon Liambotis
  def _CreateCurl(self):
289 e5e20779 Faidon Liambotis
    """Creates a cURL object.
290 e5e20779 Faidon Liambotis

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

330 e5e20779 Faidon Liambotis
    @type query: list of two-tuples
331 e5e20779 Faidon Liambotis
    @param query: Query arguments
332 e5e20779 Faidon Liambotis
    @rtype: list
333 e5e20779 Faidon Liambotis
    @return: Query list with encoded values
334 e5e20779 Faidon Liambotis

335 e5e20779 Faidon Liambotis
    """
336 e5e20779 Faidon Liambotis
    result = []
337 e5e20779 Faidon Liambotis
338 e5e20779 Faidon Liambotis
    for name, value in query:
339 e5e20779 Faidon Liambotis
      if value is None:
340 e5e20779 Faidon Liambotis
        result.append((name, ""))
341 e5e20779 Faidon Liambotis
342 e5e20779 Faidon Liambotis
      elif isinstance(value, bool):
343 e5e20779 Faidon Liambotis
        # Boolean values must be encoded as 0 or 1
344 e5e20779 Faidon Liambotis
        result.append((name, int(value)))
345 e5e20779 Faidon Liambotis
346 e5e20779 Faidon Liambotis
      elif isinstance(value, (list, tuple, dict)):
347 e5e20779 Faidon Liambotis
        raise ValueError("Invalid query data type %r" % type(value).__name__)
348 e5e20779 Faidon Liambotis
349 e5e20779 Faidon Liambotis
      else:
350 e5e20779 Faidon Liambotis
        result.append((name, value))
351 e5e20779 Faidon Liambotis
352 e5e20779 Faidon Liambotis
    return result
353 e5e20779 Faidon Liambotis
354 e5e20779 Faidon Liambotis
  def _SendRequest(self, method, path, query, content):
355 e5e20779 Faidon Liambotis
    """Sends an HTTP request.
356 e5e20779 Faidon Liambotis

357 e5e20779 Faidon Liambotis
    This constructs a full URL, encodes and decodes HTTP bodies, and
358 e5e20779 Faidon Liambotis
    handles invalid responses in a pythonic way.
359 e5e20779 Faidon Liambotis

360 e5e20779 Faidon Liambotis
    @type method: string
361 e5e20779 Faidon Liambotis
    @param method: HTTP method to use
362 e5e20779 Faidon Liambotis
    @type path: string
363 e5e20779 Faidon Liambotis
    @param path: HTTP URL path
364 e5e20779 Faidon Liambotis
    @type query: list of two-tuples
365 e5e20779 Faidon Liambotis
    @param query: query arguments to pass to urllib.urlencode
366 e5e20779 Faidon Liambotis
    @type content: str or None
367 e5e20779 Faidon Liambotis
    @param content: HTTP body content
368 e5e20779 Faidon Liambotis

369 e5e20779 Faidon Liambotis
    @rtype: str
370 e5e20779 Faidon Liambotis
    @return: JSON-Decoded response
371 e5e20779 Faidon Liambotis

372 e5e20779 Faidon Liambotis
    @raises CertificateError: If an invalid SSL certificate is found
373 e5e20779 Faidon Liambotis
    @raises GanetiApiError: If an invalid response is returned
374 e5e20779 Faidon Liambotis

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

445 e5e20779 Faidon Liambotis
    @rtype: int
446 e5e20779 Faidon Liambotis
    @return: Ganeti Remote API version
447 e5e20779 Faidon Liambotis

448 e5e20779 Faidon Liambotis
    """
449 e5e20779 Faidon Liambotis
    return self._SendRequest(HTTP_GET, "/version", None, None)
450 e5e20779 Faidon Liambotis
451 e5e20779 Faidon Liambotis
  def GetFeatures(self):
452 e5e20779 Faidon Liambotis
    """Gets the list of optional features supported by RAPI server.
453 e5e20779 Faidon Liambotis

454 e5e20779 Faidon Liambotis
    @rtype: list
455 e5e20779 Faidon Liambotis
    @return: List of optional features
456 e5e20779 Faidon Liambotis

457 e5e20779 Faidon Liambotis
    """
458 e5e20779 Faidon Liambotis
    try:
459 e5e20779 Faidon Liambotis
      return self._SendRequest(HTTP_GET, "/%s/features" % GANETI_RAPI_VERSION,
460 e5e20779 Faidon Liambotis
                               None, None)
461 e5e20779 Faidon Liambotis
    except GanetiApiError, err:
462 e5e20779 Faidon Liambotis
      # Older RAPI servers don't support this resource
463 e5e20779 Faidon Liambotis
      if err.code == HTTP_NOT_FOUND:
464 e5e20779 Faidon Liambotis
        return []
465 e5e20779 Faidon Liambotis
466 e5e20779 Faidon Liambotis
      raise
467 e5e20779 Faidon Liambotis
468 e5e20779 Faidon Liambotis
  def GetOperatingSystems(self):
469 e5e20779 Faidon Liambotis
    """Gets the Operating Systems running in the Ganeti cluster.
470 e5e20779 Faidon Liambotis

471 e5e20779 Faidon Liambotis
    @rtype: list of str
472 e5e20779 Faidon Liambotis
    @return: operating systems
473 e5e20779 Faidon Liambotis

474 e5e20779 Faidon Liambotis
    """
475 e5e20779 Faidon Liambotis
    return self._SendRequest(HTTP_GET, "/%s/os" % GANETI_RAPI_VERSION,
476 e5e20779 Faidon Liambotis
                             None, None)
477 e5e20779 Faidon Liambotis
478 e5e20779 Faidon Liambotis
  def GetInfo(self):
479 e5e20779 Faidon Liambotis
    """Gets info about the cluster.
480 e5e20779 Faidon Liambotis

481 e5e20779 Faidon Liambotis
    @rtype: dict
482 e5e20779 Faidon Liambotis
    @return: information about the cluster
483 e5e20779 Faidon Liambotis

484 e5e20779 Faidon Liambotis
    """
485 e5e20779 Faidon Liambotis
    return self._SendRequest(HTTP_GET, "/%s/info" % GANETI_RAPI_VERSION,
486 e5e20779 Faidon Liambotis
                             None, None)
487 e5e20779 Faidon Liambotis
488 067dda99 Vangelis Koukis
  def RedistributeConfig(self):
489 067dda99 Vangelis Koukis
    """Tells the cluster to redistribute its configuration files.
490 067dda99 Vangelis Koukis

491 067dda99 Vangelis Koukis
    @return: job id
492 067dda99 Vangelis Koukis

493 067dda99 Vangelis Koukis
    """
494 067dda99 Vangelis Koukis
    return self._SendRequest(HTTP_PUT,
495 067dda99 Vangelis Koukis
                             "/%s/redistribute-config" % GANETI_RAPI_VERSION,
496 067dda99 Vangelis Koukis
                             None, None)
497 067dda99 Vangelis Koukis
498 067dda99 Vangelis Koukis
  def ModifyCluster(self, **kwargs):
499 067dda99 Vangelis Koukis
    """Modifies cluster parameters.
500 067dda99 Vangelis Koukis

501 067dda99 Vangelis Koukis
    More details for parameters can be found in the RAPI documentation.
502 067dda99 Vangelis Koukis

503 067dda99 Vangelis Koukis
    @rtype: int
504 067dda99 Vangelis Koukis
    @return: job id
505 067dda99 Vangelis Koukis

506 067dda99 Vangelis Koukis
    """
507 067dda99 Vangelis Koukis
    body = kwargs
508 067dda99 Vangelis Koukis
509 067dda99 Vangelis Koukis
    return self._SendRequest(HTTP_PUT,
510 067dda99 Vangelis Koukis
                             "/%s/modify" % GANETI_RAPI_VERSION, None, body)
511 067dda99 Vangelis Koukis
512 e5e20779 Faidon Liambotis
  def GetClusterTags(self):
513 e5e20779 Faidon Liambotis
    """Gets the cluster tags.
514 e5e20779 Faidon Liambotis

515 e5e20779 Faidon Liambotis
    @rtype: list of str
516 e5e20779 Faidon Liambotis
    @return: cluster tags
517 e5e20779 Faidon Liambotis

518 e5e20779 Faidon Liambotis
    """
519 e5e20779 Faidon Liambotis
    return self._SendRequest(HTTP_GET, "/%s/tags" % GANETI_RAPI_VERSION,
520 e5e20779 Faidon Liambotis
                             None, None)
521 e5e20779 Faidon Liambotis
522 e5e20779 Faidon Liambotis
  def AddClusterTags(self, tags, dry_run=False):
523 e5e20779 Faidon Liambotis
    """Adds tags to the cluster.
524 e5e20779 Faidon Liambotis

525 e5e20779 Faidon Liambotis
    @type tags: list of str
526 e5e20779 Faidon Liambotis
    @param tags: tags to add to the cluster
527 e5e20779 Faidon Liambotis
    @type dry_run: bool
528 e5e20779 Faidon Liambotis
    @param dry_run: whether to perform a dry run
529 e5e20779 Faidon Liambotis

530 e5e20779 Faidon Liambotis
    @rtype: int
531 e5e20779 Faidon Liambotis
    @return: job id
532 e5e20779 Faidon Liambotis

533 e5e20779 Faidon Liambotis
    """
534 e5e20779 Faidon Liambotis
    query = [("tag", t) for t in tags]
535 e5e20779 Faidon Liambotis
    if dry_run:
536 e5e20779 Faidon Liambotis
      query.append(("dry-run", 1))
537 e5e20779 Faidon Liambotis
538 e5e20779 Faidon Liambotis
    return self._SendRequest(HTTP_PUT, "/%s/tags" % GANETI_RAPI_VERSION,
539 e5e20779 Faidon Liambotis
                             query, None)
540 e5e20779 Faidon Liambotis
541 e5e20779 Faidon Liambotis
  def DeleteClusterTags(self, tags, dry_run=False):
542 e5e20779 Faidon Liambotis
    """Deletes tags from the cluster.
543 e5e20779 Faidon Liambotis

544 e5e20779 Faidon Liambotis
    @type tags: list of str
545 e5e20779 Faidon Liambotis
    @param tags: tags to delete
546 e5e20779 Faidon Liambotis
    @type dry_run: bool
547 e5e20779 Faidon Liambotis
    @param dry_run: whether to perform a dry run
548 e5e20779 Faidon Liambotis

549 e5e20779 Faidon Liambotis
    """
550 e5e20779 Faidon Liambotis
    query = [("tag", t) for t in tags]
551 e5e20779 Faidon Liambotis
    if dry_run:
552 e5e20779 Faidon Liambotis
      query.append(("dry-run", 1))
553 e5e20779 Faidon Liambotis
554 e5e20779 Faidon Liambotis
    return self._SendRequest(HTTP_DELETE, "/%s/tags" % GANETI_RAPI_VERSION,
555 e5e20779 Faidon Liambotis
                             query, None)
556 e5e20779 Faidon Liambotis
557 e5e20779 Faidon Liambotis
  def GetInstances(self, bulk=False):
558 e5e20779 Faidon Liambotis
    """Gets information about instances on the cluster.
559 e5e20779 Faidon Liambotis

560 e5e20779 Faidon Liambotis
    @type bulk: bool
561 e5e20779 Faidon Liambotis
    @param bulk: whether to return all information about all instances
562 e5e20779 Faidon Liambotis

563 e5e20779 Faidon Liambotis
    @rtype: list of dict or list of str
564 e5e20779 Faidon Liambotis
    @return: if bulk is True, info about the instances, else a list of instances
565 e5e20779 Faidon Liambotis

566 e5e20779 Faidon Liambotis
    """
567 e5e20779 Faidon Liambotis
    query = []
568 e5e20779 Faidon Liambotis
    if bulk:
569 e5e20779 Faidon Liambotis
      query.append(("bulk", 1))
570 e5e20779 Faidon Liambotis
571 e5e20779 Faidon Liambotis
    instances = self._SendRequest(HTTP_GET,
572 e5e20779 Faidon Liambotis
                                  "/%s/instances" % GANETI_RAPI_VERSION,
573 e5e20779 Faidon Liambotis
                                  query, None)
574 e5e20779 Faidon Liambotis
    if bulk:
575 e5e20779 Faidon Liambotis
      return instances
576 e5e20779 Faidon Liambotis
    else:
577 e5e20779 Faidon Liambotis
      return [i["id"] for i in instances]
578 e5e20779 Faidon Liambotis
579 e5e20779 Faidon Liambotis
  def GetInstance(self, instance):
580 e5e20779 Faidon Liambotis
    """Gets information about an instance.
581 e5e20779 Faidon Liambotis

582 e5e20779 Faidon Liambotis
    @type instance: str
583 e5e20779 Faidon Liambotis
    @param instance: instance whose info to return
584 e5e20779 Faidon Liambotis

585 e5e20779 Faidon Liambotis
    @rtype: dict
586 e5e20779 Faidon Liambotis
    @return: info about the instance
587 e5e20779 Faidon Liambotis

588 e5e20779 Faidon Liambotis
    """
589 e5e20779 Faidon Liambotis
    return self._SendRequest(HTTP_GET,
590 e5e20779 Faidon Liambotis
                             ("/%s/instances/%s" %
591 e5e20779 Faidon Liambotis
                              (GANETI_RAPI_VERSION, instance)), None, None)
592 e5e20779 Faidon Liambotis
593 e5e20779 Faidon Liambotis
  def GetInstanceInfo(self, instance, static=None):
594 e5e20779 Faidon Liambotis
    """Gets information about an instance.
595 e5e20779 Faidon Liambotis

596 e5e20779 Faidon Liambotis
    @type instance: string
597 e5e20779 Faidon Liambotis
    @param instance: Instance name
598 e5e20779 Faidon Liambotis
    @rtype: string
599 e5e20779 Faidon Liambotis
    @return: Job ID
600 e5e20779 Faidon Liambotis

601 e5e20779 Faidon Liambotis
    """
602 e5e20779 Faidon Liambotis
    if static is not None:
603 e5e20779 Faidon Liambotis
      query = [("static", static)]
604 e5e20779 Faidon Liambotis
    else:
605 e5e20779 Faidon Liambotis
      query = None
606 e5e20779 Faidon Liambotis
607 e5e20779 Faidon Liambotis
    return self._SendRequest(HTTP_GET,
608 e5e20779 Faidon Liambotis
                             ("/%s/instances/%s/info" %
609 e5e20779 Faidon Liambotis
                              (GANETI_RAPI_VERSION, instance)), query, None)
610 e5e20779 Faidon Liambotis
611 e5e20779 Faidon Liambotis
  def CreateInstance(self, mode, name, disk_template, disks, nics,
612 e5e20779 Faidon Liambotis
                     **kwargs):
613 e5e20779 Faidon Liambotis
    """Creates a new instance.
614 e5e20779 Faidon Liambotis

615 e5e20779 Faidon Liambotis
    More details for parameters can be found in the RAPI documentation.
616 e5e20779 Faidon Liambotis

617 e5e20779 Faidon Liambotis
    @type mode: string
618 e5e20779 Faidon Liambotis
    @param mode: Instance creation mode
619 e5e20779 Faidon Liambotis
    @type name: string
620 e5e20779 Faidon Liambotis
    @param name: Hostname of the instance to create
621 e5e20779 Faidon Liambotis
    @type disk_template: string
622 e5e20779 Faidon Liambotis
    @param disk_template: Disk template for instance (e.g. plain, diskless,
623 e5e20779 Faidon Liambotis
                          file, or drbd)
624 e5e20779 Faidon Liambotis
    @type disks: list of dicts
625 e5e20779 Faidon Liambotis
    @param disks: List of disk definitions
626 e5e20779 Faidon Liambotis
    @type nics: list of dicts
627 e5e20779 Faidon Liambotis
    @param nics: List of NIC definitions
628 e5e20779 Faidon Liambotis
    @type dry_run: bool
629 e5e20779 Faidon Liambotis
    @keyword dry_run: whether to perform a dry run
630 e5e20779 Faidon Liambotis

631 e5e20779 Faidon Liambotis
    @rtype: int
632 e5e20779 Faidon Liambotis
    @return: job id
633 e5e20779 Faidon Liambotis

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

742 e5e20779 Faidon Liambotis
    @type instance: str
743 e5e20779 Faidon Liambotis
    @param instance: the instance to delete
744 e5e20779 Faidon Liambotis

745 e5e20779 Faidon Liambotis
    @rtype: int
746 e5e20779 Faidon Liambotis
    @return: job id
747 e5e20779 Faidon Liambotis

748 e5e20779 Faidon Liambotis
    """
749 e5e20779 Faidon Liambotis
    query = []
750 e5e20779 Faidon Liambotis
    if dry_run:
751 e5e20779 Faidon Liambotis
      query.append(("dry-run", 1))
752 e5e20779 Faidon Liambotis
753 e5e20779 Faidon Liambotis
    return self._SendRequest(HTTP_DELETE,
754 e5e20779 Faidon Liambotis
                             ("/%s/instances/%s" %
755 e5e20779 Faidon Liambotis
                              (GANETI_RAPI_VERSION, instance)), query, None)
756 e5e20779 Faidon Liambotis
757 e5e20779 Faidon Liambotis
  def ModifyInstance(self, instance, **kwargs):
758 e5e20779 Faidon Liambotis
    """Modifies an instance.
759 e5e20779 Faidon Liambotis

760 e5e20779 Faidon Liambotis
    More details for parameters can be found in the RAPI documentation.
761 e5e20779 Faidon Liambotis

762 e5e20779 Faidon Liambotis
    @type instance: string
763 e5e20779 Faidon Liambotis
    @param instance: Instance name
764 e5e20779 Faidon Liambotis
    @rtype: int
765 e5e20779 Faidon Liambotis
    @return: job id
766 e5e20779 Faidon Liambotis

767 e5e20779 Faidon Liambotis
    """
768 e5e20779 Faidon Liambotis
    body = kwargs
769 e5e20779 Faidon Liambotis
770 e5e20779 Faidon Liambotis
    return self._SendRequest(HTTP_PUT,
771 e5e20779 Faidon Liambotis
                             ("/%s/instances/%s/modify" %
772 e5e20779 Faidon Liambotis
                              (GANETI_RAPI_VERSION, instance)), None, body)
773 e5e20779 Faidon Liambotis
774 067dda99 Vangelis Koukis
  def ActivateInstanceDisks(self, instance, ignore_size=None):
775 067dda99 Vangelis Koukis
    """Activates an instance's disks.
776 067dda99 Vangelis Koukis

777 067dda99 Vangelis Koukis
    @type instance: string
778 067dda99 Vangelis Koukis
    @param instance: Instance name
779 067dda99 Vangelis Koukis
    @type ignore_size: bool
780 067dda99 Vangelis Koukis
    @param ignore_size: Whether to ignore recorded size
781 067dda99 Vangelis Koukis
    @return: job id
782 067dda99 Vangelis Koukis

783 067dda99 Vangelis Koukis
    """
784 067dda99 Vangelis Koukis
    query = []
785 067dda99 Vangelis Koukis
    if ignore_size:
786 067dda99 Vangelis Koukis
      query.append(("ignore_size", 1))
787 067dda99 Vangelis Koukis
788 067dda99 Vangelis Koukis
    return self._SendRequest(HTTP_PUT,
789 067dda99 Vangelis Koukis
                             ("/%s/instances/%s/activate-disks" %
790 067dda99 Vangelis Koukis
                              (GANETI_RAPI_VERSION, instance)), query, None)
791 067dda99 Vangelis Koukis
792 067dda99 Vangelis Koukis
  def DeactivateInstanceDisks(self, instance):
793 067dda99 Vangelis Koukis
    """Deactivates an instance's disks.
794 067dda99 Vangelis Koukis

795 067dda99 Vangelis Koukis
    @type instance: string
796 067dda99 Vangelis Koukis
    @param instance: Instance name
797 067dda99 Vangelis Koukis
    @return: job id
798 067dda99 Vangelis Koukis

799 067dda99 Vangelis Koukis
    """
800 067dda99 Vangelis Koukis
    return self._SendRequest(HTTP_PUT,
801 067dda99 Vangelis Koukis
                             ("/%s/instances/%s/deactivate-disks" %
802 067dda99 Vangelis Koukis
                              (GANETI_RAPI_VERSION, instance)), None, None)
803 067dda99 Vangelis Koukis
804 067dda99 Vangelis Koukis
  def GrowInstanceDisk(self, instance, disk, amount, wait_for_sync=None):
805 067dda99 Vangelis Koukis
    """Grows a disk of an instance.
806 067dda99 Vangelis Koukis

807 067dda99 Vangelis Koukis
    More details for parameters can be found in the RAPI documentation.
808 067dda99 Vangelis Koukis

809 067dda99 Vangelis Koukis
    @type instance: string
810 067dda99 Vangelis Koukis
    @param instance: Instance name
811 067dda99 Vangelis Koukis
    @type disk: integer
812 067dda99 Vangelis Koukis
    @param disk: Disk index
813 067dda99 Vangelis Koukis
    @type amount: integer
814 067dda99 Vangelis Koukis
    @param amount: Grow disk by this amount (MiB)
815 067dda99 Vangelis Koukis
    @type wait_for_sync: bool
816 067dda99 Vangelis Koukis
    @param wait_for_sync: Wait for disk to synchronize
817 067dda99 Vangelis Koukis
    @rtype: int
818 067dda99 Vangelis Koukis
    @return: job id
819 067dda99 Vangelis Koukis

820 067dda99 Vangelis Koukis
    """
821 067dda99 Vangelis Koukis
    body = {
822 067dda99 Vangelis Koukis
      "amount": amount,
823 067dda99 Vangelis Koukis
      }
824 067dda99 Vangelis Koukis
825 067dda99 Vangelis Koukis
    if wait_for_sync is not None:
826 067dda99 Vangelis Koukis
      body["wait_for_sync"] = wait_for_sync
827 067dda99 Vangelis Koukis
828 067dda99 Vangelis Koukis
    return self._SendRequest(HTTP_POST,
829 067dda99 Vangelis Koukis
                             ("/%s/instances/%s/disk/%s/grow" %
830 067dda99 Vangelis Koukis
                              (GANETI_RAPI_VERSION, instance, disk)),
831 067dda99 Vangelis Koukis
                             None, body)
832 067dda99 Vangelis Koukis
833 e5e20779 Faidon Liambotis
  def GetInstanceTags(self, instance):
834 e5e20779 Faidon Liambotis
    """Gets tags for an instance.
835 e5e20779 Faidon Liambotis

836 e5e20779 Faidon Liambotis
    @type instance: str
837 e5e20779 Faidon Liambotis
    @param instance: instance whose tags to return
838 e5e20779 Faidon Liambotis

839 e5e20779 Faidon Liambotis
    @rtype: list of str
840 e5e20779 Faidon Liambotis
    @return: tags for the instance
841 e5e20779 Faidon Liambotis

842 e5e20779 Faidon Liambotis
    """
843 e5e20779 Faidon Liambotis
    return self._SendRequest(HTTP_GET,
844 e5e20779 Faidon Liambotis
                             ("/%s/instances/%s/tags" %
845 e5e20779 Faidon Liambotis
                              (GANETI_RAPI_VERSION, instance)), None, None)
846 e5e20779 Faidon Liambotis
847 e5e20779 Faidon Liambotis
  def AddInstanceTags(self, instance, tags, dry_run=False):
848 e5e20779 Faidon Liambotis
    """Adds tags to an instance.
849 e5e20779 Faidon Liambotis

850 e5e20779 Faidon Liambotis
    @type instance: str
851 e5e20779 Faidon Liambotis
    @param instance: instance to add tags to
852 e5e20779 Faidon Liambotis
    @type tags: list of str
853 e5e20779 Faidon Liambotis
    @param tags: tags to add to the instance
854 e5e20779 Faidon Liambotis
    @type dry_run: bool
855 e5e20779 Faidon Liambotis
    @param dry_run: whether to perform a dry run
856 e5e20779 Faidon Liambotis

857 e5e20779 Faidon Liambotis
    @rtype: int
858 e5e20779 Faidon Liambotis
    @return: job id
859 e5e20779 Faidon Liambotis

860 e5e20779 Faidon Liambotis
    """
861 e5e20779 Faidon Liambotis
    query = [("tag", t) for t in tags]
862 e5e20779 Faidon Liambotis
    if dry_run:
863 e5e20779 Faidon Liambotis
      query.append(("dry-run", 1))
864 e5e20779 Faidon Liambotis
865 e5e20779 Faidon Liambotis
    return self._SendRequest(HTTP_PUT,
866 e5e20779 Faidon Liambotis
                             ("/%s/instances/%s/tags" %
867 e5e20779 Faidon Liambotis
                              (GANETI_RAPI_VERSION, instance)), query, None)
868 e5e20779 Faidon Liambotis
869 e5e20779 Faidon Liambotis
  def DeleteInstanceTags(self, instance, tags, dry_run=False):
870 e5e20779 Faidon Liambotis
    """Deletes tags from an instance.
871 e5e20779 Faidon Liambotis

872 e5e20779 Faidon Liambotis
    @type instance: str
873 e5e20779 Faidon Liambotis
    @param instance: instance to delete tags from
874 e5e20779 Faidon Liambotis
    @type tags: list of str
875 e5e20779 Faidon Liambotis
    @param tags: tags to delete
876 e5e20779 Faidon Liambotis
    @type dry_run: bool
877 e5e20779 Faidon Liambotis
    @param dry_run: whether to perform a dry run
878 e5e20779 Faidon Liambotis

879 e5e20779 Faidon Liambotis
    """
880 e5e20779 Faidon Liambotis
    query = [("tag", t) for t in tags]
881 e5e20779 Faidon Liambotis
    if dry_run:
882 e5e20779 Faidon Liambotis
      query.append(("dry-run", 1))
883 e5e20779 Faidon Liambotis
884 e5e20779 Faidon Liambotis
    return self._SendRequest(HTTP_DELETE,
885 e5e20779 Faidon Liambotis
                             ("/%s/instances/%s/tags" %
886 e5e20779 Faidon Liambotis
                              (GANETI_RAPI_VERSION, instance)), query, None)
887 e5e20779 Faidon Liambotis
888 e5e20779 Faidon Liambotis
  def RebootInstance(self, instance, reboot_type=None, ignore_secondaries=None,
889 e5e20779 Faidon Liambotis
                     dry_run=False):
890 e5e20779 Faidon Liambotis
    """Reboots an instance.
891 e5e20779 Faidon Liambotis

892 e5e20779 Faidon Liambotis
    @type instance: str
893 e5e20779 Faidon Liambotis
    @param instance: instance to rebot
894 e5e20779 Faidon Liambotis
    @type reboot_type: str
895 e5e20779 Faidon Liambotis
    @param reboot_type: one of: hard, soft, full
896 e5e20779 Faidon Liambotis
    @type ignore_secondaries: bool
897 e5e20779 Faidon Liambotis
    @param ignore_secondaries: if True, ignores errors for the secondary node
898 e5e20779 Faidon Liambotis
        while re-assembling disks (in hard-reboot mode only)
899 e5e20779 Faidon Liambotis
    @type dry_run: bool
900 e5e20779 Faidon Liambotis
    @param dry_run: whether to perform a dry run
901 e5e20779 Faidon Liambotis

902 e5e20779 Faidon Liambotis
    """
903 e5e20779 Faidon Liambotis
    query = []
904 e5e20779 Faidon Liambotis
    if reboot_type:
905 e5e20779 Faidon Liambotis
      query.append(("type", reboot_type))
906 e5e20779 Faidon Liambotis
    if ignore_secondaries is not None:
907 e5e20779 Faidon Liambotis
      query.append(("ignore_secondaries", ignore_secondaries))
908 e5e20779 Faidon Liambotis
    if dry_run:
909 e5e20779 Faidon Liambotis
      query.append(("dry-run", 1))
910 e5e20779 Faidon Liambotis
911 e5e20779 Faidon Liambotis
    return self._SendRequest(HTTP_POST,
912 e5e20779 Faidon Liambotis
                             ("/%s/instances/%s/reboot" %
913 e5e20779 Faidon Liambotis
                              (GANETI_RAPI_VERSION, instance)), query, None)
914 e5e20779 Faidon Liambotis
915 e5e20779 Faidon Liambotis
  def ShutdownInstance(self, instance, dry_run=False):
916 e5e20779 Faidon Liambotis
    """Shuts down an instance.
917 e5e20779 Faidon Liambotis

918 e5e20779 Faidon Liambotis
    @type instance: str
919 e5e20779 Faidon Liambotis
    @param instance: the instance to shut down
920 e5e20779 Faidon Liambotis
    @type dry_run: bool
921 e5e20779 Faidon Liambotis
    @param dry_run: whether to perform a dry run
922 e5e20779 Faidon Liambotis

923 e5e20779 Faidon Liambotis
    """
924 e5e20779 Faidon Liambotis
    query = []
925 e5e20779 Faidon Liambotis
    if dry_run:
926 e5e20779 Faidon Liambotis
      query.append(("dry-run", 1))
927 e5e20779 Faidon Liambotis
928 e5e20779 Faidon Liambotis
    return self._SendRequest(HTTP_PUT,
929 e5e20779 Faidon Liambotis
                             ("/%s/instances/%s/shutdown" %
930 e5e20779 Faidon Liambotis
                              (GANETI_RAPI_VERSION, instance)), query, None)
931 e5e20779 Faidon Liambotis
932 e5e20779 Faidon Liambotis
  def StartupInstance(self, instance, dry_run=False):
933 e5e20779 Faidon Liambotis
    """Starts up an instance.
934 e5e20779 Faidon Liambotis

935 e5e20779 Faidon Liambotis
    @type instance: str
936 e5e20779 Faidon Liambotis
    @param instance: the instance to start up
937 e5e20779 Faidon Liambotis
    @type dry_run: bool
938 e5e20779 Faidon Liambotis
    @param dry_run: whether to perform a dry run
939 e5e20779 Faidon Liambotis

940 e5e20779 Faidon Liambotis
    """
941 e5e20779 Faidon Liambotis
    query = []
942 e5e20779 Faidon Liambotis
    if dry_run:
943 e5e20779 Faidon Liambotis
      query.append(("dry-run", 1))
944 e5e20779 Faidon Liambotis
945 e5e20779 Faidon Liambotis
    return self._SendRequest(HTTP_PUT,
946 e5e20779 Faidon Liambotis
                             ("/%s/instances/%s/startup" %
947 e5e20779 Faidon Liambotis
                              (GANETI_RAPI_VERSION, instance)), query, None)
948 e5e20779 Faidon Liambotis
949 067dda99 Vangelis Koukis
  def ReinstallInstance(self, instance, os=None, no_startup=False,
950 067dda99 Vangelis Koukis
                        osparams=None):
951 e5e20779 Faidon Liambotis
    """Reinstalls an instance.
952 e5e20779 Faidon Liambotis

953 e5e20779 Faidon Liambotis
    @type instance: str
954 e5e20779 Faidon Liambotis
    @param instance: The instance to reinstall
955 e5e20779 Faidon Liambotis
    @type os: str or None
956 e5e20779 Faidon Liambotis
    @param os: The operating system to reinstall. If None, the instance's
957 e5e20779 Faidon Liambotis
        current operating system will be installed again
958 e5e20779 Faidon Liambotis
    @type no_startup: bool
959 e5e20779 Faidon Liambotis
    @param no_startup: Whether to start the instance automatically
960 e5e20779 Faidon Liambotis

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

992 e5e20779 Faidon Liambotis
    @type instance: str
993 e5e20779 Faidon Liambotis
    @param instance: instance whose disks to replace
994 e5e20779 Faidon Liambotis
    @type disks: list of ints
995 e5e20779 Faidon Liambotis
    @param disks: Indexes of disks to replace
996 e5e20779 Faidon Liambotis
    @type mode: str
997 e5e20779 Faidon Liambotis
    @param mode: replacement mode to use (defaults to replace_auto)
998 e5e20779 Faidon Liambotis
    @type remote_node: str or None
999 e5e20779 Faidon Liambotis
    @param remote_node: new secondary node to use (for use with
1000 e5e20779 Faidon Liambotis
        replace_new_secondary mode)
1001 e5e20779 Faidon Liambotis
    @type iallocator: str or None
1002 e5e20779 Faidon Liambotis
    @param iallocator: instance allocator plugin to use (for use with
1003 e5e20779 Faidon Liambotis
                       replace_auto mode)
1004 e5e20779 Faidon Liambotis
    @type dry_run: bool
1005 e5e20779 Faidon Liambotis
    @param dry_run: whether to perform a dry run
1006 e5e20779 Faidon Liambotis

1007 e5e20779 Faidon Liambotis
    @rtype: int
1008 e5e20779 Faidon Liambotis
    @return: job id
1009 e5e20779 Faidon Liambotis

1010 e5e20779 Faidon Liambotis
    """
1011 e5e20779 Faidon Liambotis
    query = [
1012 e5e20779 Faidon Liambotis
      ("mode", mode),
1013 e5e20779 Faidon Liambotis
      ]
1014 e5e20779 Faidon Liambotis
1015 e5e20779 Faidon Liambotis
    if disks:
1016 e5e20779 Faidon Liambotis
      query.append(("disks", ",".join(str(idx) for idx in disks)))
1017 e5e20779 Faidon Liambotis
1018 e5e20779 Faidon Liambotis
    if remote_node:
1019 e5e20779 Faidon Liambotis
      query.append(("remote_node", remote_node))
1020 e5e20779 Faidon Liambotis
1021 e5e20779 Faidon Liambotis
    if iallocator:
1022 e5e20779 Faidon Liambotis
      query.append(("iallocator", iallocator))
1023 e5e20779 Faidon Liambotis
1024 e5e20779 Faidon Liambotis
    if dry_run:
1025 e5e20779 Faidon Liambotis
      query.append(("dry-run", 1))
1026 e5e20779 Faidon Liambotis
1027 e5e20779 Faidon Liambotis
    return self._SendRequest(HTTP_POST,
1028 e5e20779 Faidon Liambotis
                             ("/%s/instances/%s/replace-disks" %
1029 e5e20779 Faidon Liambotis
                              (GANETI_RAPI_VERSION, instance)), query, None)
1030 e5e20779 Faidon Liambotis
1031 e5e20779 Faidon Liambotis
  def PrepareExport(self, instance, mode):
1032 e5e20779 Faidon Liambotis
    """Prepares an instance for an export.
1033 e5e20779 Faidon Liambotis

1034 e5e20779 Faidon Liambotis
    @type instance: string
1035 e5e20779 Faidon Liambotis
    @param instance: Instance name
1036 e5e20779 Faidon Liambotis
    @type mode: string
1037 e5e20779 Faidon Liambotis
    @param mode: Export mode
1038 e5e20779 Faidon Liambotis
    @rtype: string
1039 e5e20779 Faidon Liambotis
    @return: Job ID
1040 e5e20779 Faidon Liambotis

1041 e5e20779 Faidon Liambotis
    """
1042 e5e20779 Faidon Liambotis
    query = [("mode", mode)]
1043 e5e20779 Faidon Liambotis
    return self._SendRequest(HTTP_PUT,
1044 e5e20779 Faidon Liambotis
                             ("/%s/instances/%s/prepare-export" %
1045 e5e20779 Faidon Liambotis
                              (GANETI_RAPI_VERSION, instance)), query, None)
1046 e5e20779 Faidon Liambotis
1047 e5e20779 Faidon Liambotis
  def ExportInstance(self, instance, mode, destination, shutdown=None,
1048 e5e20779 Faidon Liambotis
                     remove_instance=None,
1049 e5e20779 Faidon Liambotis
                     x509_key_name=None, destination_x509_ca=None):
1050 e5e20779 Faidon Liambotis
    """Exports an instance.
1051 e5e20779 Faidon Liambotis

1052 e5e20779 Faidon Liambotis
    @type instance: string
1053 e5e20779 Faidon Liambotis
    @param instance: Instance name
1054 e5e20779 Faidon Liambotis
    @type mode: string
1055 e5e20779 Faidon Liambotis
    @param mode: Export mode
1056 e5e20779 Faidon Liambotis
    @rtype: string
1057 e5e20779 Faidon Liambotis
    @return: Job ID
1058 e5e20779 Faidon Liambotis

1059 e5e20779 Faidon Liambotis
    """
1060 e5e20779 Faidon Liambotis
    body = {
1061 e5e20779 Faidon Liambotis
      "destination": destination,
1062 e5e20779 Faidon Liambotis
      "mode": mode,
1063 e5e20779 Faidon Liambotis
      }
1064 e5e20779 Faidon Liambotis
1065 e5e20779 Faidon Liambotis
    if shutdown is not None:
1066 e5e20779 Faidon Liambotis
      body["shutdown"] = shutdown
1067 e5e20779 Faidon Liambotis
1068 e5e20779 Faidon Liambotis
    if remove_instance is not None:
1069 e5e20779 Faidon Liambotis
      body["remove_instance"] = remove_instance
1070 e5e20779 Faidon Liambotis
1071 e5e20779 Faidon Liambotis
    if x509_key_name is not None:
1072 e5e20779 Faidon Liambotis
      body["x509_key_name"] = x509_key_name
1073 e5e20779 Faidon Liambotis
1074 e5e20779 Faidon Liambotis
    if destination_x509_ca is not None:
1075 e5e20779 Faidon Liambotis
      body["destination_x509_ca"] = destination_x509_ca
1076 e5e20779 Faidon Liambotis
1077 e5e20779 Faidon Liambotis
    return self._SendRequest(HTTP_PUT,
1078 e5e20779 Faidon Liambotis
                             ("/%s/instances/%s/export" %
1079 e5e20779 Faidon Liambotis
                              (GANETI_RAPI_VERSION, instance)), None, body)
1080 e5e20779 Faidon Liambotis
1081 e5e20779 Faidon Liambotis
  def MigrateInstance(self, instance, mode=None, cleanup=None):
1082 e5e20779 Faidon Liambotis
    """Migrates an instance.
1083 e5e20779 Faidon Liambotis

1084 e5e20779 Faidon Liambotis
    @type instance: string
1085 e5e20779 Faidon Liambotis
    @param instance: Instance name
1086 e5e20779 Faidon Liambotis
    @type mode: string
1087 e5e20779 Faidon Liambotis
    @param mode: Migration mode
1088 e5e20779 Faidon Liambotis
    @type cleanup: bool
1089 e5e20779 Faidon Liambotis
    @param cleanup: Whether to clean up a previously failed migration
1090 e5e20779 Faidon Liambotis

1091 e5e20779 Faidon Liambotis
    """
1092 e5e20779 Faidon Liambotis
    body = {}
1093 e5e20779 Faidon Liambotis
1094 e5e20779 Faidon Liambotis
    if mode is not None:
1095 e5e20779 Faidon Liambotis
      body["mode"] = mode
1096 e5e20779 Faidon Liambotis
1097 e5e20779 Faidon Liambotis
    if cleanup is not None:
1098 e5e20779 Faidon Liambotis
      body["cleanup"] = cleanup
1099 e5e20779 Faidon Liambotis
1100 e5e20779 Faidon Liambotis
    return self._SendRequest(HTTP_PUT,
1101 e5e20779 Faidon Liambotis
                             ("/%s/instances/%s/migrate" %
1102 e5e20779 Faidon Liambotis
                              (GANETI_RAPI_VERSION, instance)), None, body)
1103 e5e20779 Faidon Liambotis
1104 e5e20779 Faidon Liambotis
  def RenameInstance(self, instance, new_name, ip_check=None, name_check=None):
1105 e5e20779 Faidon Liambotis
    """Changes the name of an instance.
1106 e5e20779 Faidon Liambotis

1107 e5e20779 Faidon Liambotis
    @type instance: string
1108 e5e20779 Faidon Liambotis
    @param instance: Instance name
1109 e5e20779 Faidon Liambotis
    @type new_name: string
1110 e5e20779 Faidon Liambotis
    @param new_name: New instance name
1111 e5e20779 Faidon Liambotis
    @type ip_check: bool
1112 e5e20779 Faidon Liambotis
    @param ip_check: Whether to ensure instance's IP address is inactive
1113 e5e20779 Faidon Liambotis
    @type name_check: bool
1114 e5e20779 Faidon Liambotis
    @param name_check: Whether to ensure instance's name is resolvable
1115 e5e20779 Faidon Liambotis

1116 e5e20779 Faidon Liambotis
    """
1117 e5e20779 Faidon Liambotis
    body = {
1118 e5e20779 Faidon Liambotis
      "new_name": new_name,
1119 e5e20779 Faidon Liambotis
      }
1120 e5e20779 Faidon Liambotis
1121 e5e20779 Faidon Liambotis
    if ip_check is not None:
1122 e5e20779 Faidon Liambotis
      body["ip_check"] = ip_check
1123 e5e20779 Faidon Liambotis
1124 e5e20779 Faidon Liambotis
    if name_check is not None:
1125 e5e20779 Faidon Liambotis
      body["name_check"] = name_check
1126 e5e20779 Faidon Liambotis
1127 e5e20779 Faidon Liambotis
    return self._SendRequest(HTTP_PUT,
1128 e5e20779 Faidon Liambotis
                             ("/%s/instances/%s/rename" %
1129 e5e20779 Faidon Liambotis
                              (GANETI_RAPI_VERSION, instance)), None, body)
1130 e5e20779 Faidon Liambotis
1131 067dda99 Vangelis Koukis
  def GetInstanceConsole(self, instance):
1132 067dda99 Vangelis Koukis
    """Request information for connecting to instance's console.
1133 067dda99 Vangelis Koukis

1134 067dda99 Vangelis Koukis
    @type instance: string
1135 067dda99 Vangelis Koukis
    @param instance: Instance name
1136 067dda99 Vangelis Koukis

1137 067dda99 Vangelis Koukis
    """
1138 067dda99 Vangelis Koukis
    return self._SendRequest(HTTP_GET,
1139 067dda99 Vangelis Koukis
                             ("/%s/instances/%s/console" %
1140 067dda99 Vangelis Koukis
                              (GANETI_RAPI_VERSION, instance)), None, None)
1141 067dda99 Vangelis Koukis
1142 e5e20779 Faidon Liambotis
  def GetJobs(self):
1143 e5e20779 Faidon Liambotis
    """Gets all jobs for the cluster.
1144 e5e20779 Faidon Liambotis

1145 e5e20779 Faidon Liambotis
    @rtype: list of int
1146 e5e20779 Faidon Liambotis
    @return: job ids for the cluster
1147 e5e20779 Faidon Liambotis

1148 e5e20779 Faidon Liambotis
    """
1149 e5e20779 Faidon Liambotis
    return [int(j["id"])
1150 e5e20779 Faidon Liambotis
            for j in self._SendRequest(HTTP_GET,
1151 e5e20779 Faidon Liambotis
                                       "/%s/jobs" % GANETI_RAPI_VERSION,
1152 e5e20779 Faidon Liambotis
                                       None, None)]
1153 e5e20779 Faidon Liambotis
1154 e5e20779 Faidon Liambotis
  def GetJobStatus(self, job_id):
1155 e5e20779 Faidon Liambotis
    """Gets the status of a job.
1156 e5e20779 Faidon Liambotis

1157 e5e20779 Faidon Liambotis
    @type job_id: int
1158 e5e20779 Faidon Liambotis
    @param job_id: job id whose status to query
1159 e5e20779 Faidon Liambotis

1160 e5e20779 Faidon Liambotis
    @rtype: dict
1161 e5e20779 Faidon Liambotis
    @return: job status
1162 e5e20779 Faidon Liambotis

1163 e5e20779 Faidon Liambotis
    """
1164 e5e20779 Faidon Liambotis
    return self._SendRequest(HTTP_GET,
1165 e5e20779 Faidon Liambotis
                             "/%s/jobs/%s" % (GANETI_RAPI_VERSION, job_id),
1166 e5e20779 Faidon Liambotis
                             None, None)
1167 e5e20779 Faidon Liambotis
1168 e5e20779 Faidon Liambotis
  def WaitForJobChange(self, job_id, fields, prev_job_info, prev_log_serial):
1169 e5e20779 Faidon Liambotis
    """Waits for job changes.
1170 e5e20779 Faidon Liambotis

1171 e5e20779 Faidon Liambotis
    @type job_id: int
1172 e5e20779 Faidon Liambotis
    @param job_id: Job ID for which to wait
1173 e5e20779 Faidon Liambotis

1174 e5e20779 Faidon Liambotis
    """
1175 e5e20779 Faidon Liambotis
    body = {
1176 e5e20779 Faidon Liambotis
      "fields": fields,
1177 e5e20779 Faidon Liambotis
      "previous_job_info": prev_job_info,
1178 e5e20779 Faidon Liambotis
      "previous_log_serial": prev_log_serial,
1179 e5e20779 Faidon Liambotis
      }
1180 e5e20779 Faidon Liambotis
1181 e5e20779 Faidon Liambotis
    return self._SendRequest(HTTP_GET,
1182 e5e20779 Faidon Liambotis
                             "/%s/jobs/%s/wait" % (GANETI_RAPI_VERSION, job_id),
1183 e5e20779 Faidon Liambotis
                             None, body)
1184 e5e20779 Faidon Liambotis
1185 e5e20779 Faidon Liambotis
  def CancelJob(self, job_id, dry_run=False):
1186 e5e20779 Faidon Liambotis
    """Cancels a job.
1187 e5e20779 Faidon Liambotis

1188 e5e20779 Faidon Liambotis
    @type job_id: int
1189 e5e20779 Faidon Liambotis
    @param job_id: id of the job to delete
1190 e5e20779 Faidon Liambotis
    @type dry_run: bool
1191 e5e20779 Faidon Liambotis
    @param dry_run: whether to perform a dry run
1192 e5e20779 Faidon Liambotis

1193 e5e20779 Faidon Liambotis
    """
1194 e5e20779 Faidon Liambotis
    query = []
1195 e5e20779 Faidon Liambotis
    if dry_run:
1196 e5e20779 Faidon Liambotis
      query.append(("dry-run", 1))
1197 e5e20779 Faidon Liambotis
1198 e5e20779 Faidon Liambotis
    return self._SendRequest(HTTP_DELETE,
1199 e5e20779 Faidon Liambotis
                             "/%s/jobs/%s" % (GANETI_RAPI_VERSION, job_id),
1200 e5e20779 Faidon Liambotis
                             query, None)
1201 e5e20779 Faidon Liambotis
1202 e5e20779 Faidon Liambotis
  def GetNodes(self, bulk=False):
1203 e5e20779 Faidon Liambotis
    """Gets all nodes in the cluster.
1204 e5e20779 Faidon Liambotis

1205 e5e20779 Faidon Liambotis
    @type bulk: bool
1206 e5e20779 Faidon Liambotis
    @param bulk: whether to return all information about all instances
1207 e5e20779 Faidon Liambotis

1208 e5e20779 Faidon Liambotis
    @rtype: list of dict or str
1209 e5e20779 Faidon Liambotis
    @return: if bulk is true, info about nodes in the cluster,
1210 e5e20779 Faidon Liambotis
        else list of nodes in the cluster
1211 e5e20779 Faidon Liambotis

1212 e5e20779 Faidon Liambotis
    """
1213 e5e20779 Faidon Liambotis
    query = []
1214 e5e20779 Faidon Liambotis
    if bulk:
1215 e5e20779 Faidon Liambotis
      query.append(("bulk", 1))
1216 e5e20779 Faidon Liambotis
1217 e5e20779 Faidon Liambotis
    nodes = self._SendRequest(HTTP_GET, "/%s/nodes" % GANETI_RAPI_VERSION,
1218 e5e20779 Faidon Liambotis
                              query, None)
1219 e5e20779 Faidon Liambotis
    if bulk:
1220 e5e20779 Faidon Liambotis
      return nodes
1221 e5e20779 Faidon Liambotis
    else:
1222 e5e20779 Faidon Liambotis
      return [n["id"] for n in nodes]
1223 e5e20779 Faidon Liambotis
1224 e5e20779 Faidon Liambotis
  def GetNode(self, node):
1225 e5e20779 Faidon Liambotis
    """Gets information about a node.
1226 e5e20779 Faidon Liambotis

1227 e5e20779 Faidon Liambotis
    @type node: str
1228 e5e20779 Faidon Liambotis
    @param node: node whose info to return
1229 e5e20779 Faidon Liambotis

1230 e5e20779 Faidon Liambotis
    @rtype: dict
1231 e5e20779 Faidon Liambotis
    @return: info about the node
1232 e5e20779 Faidon Liambotis

1233 e5e20779 Faidon Liambotis
    """
1234 e5e20779 Faidon Liambotis
    return self._SendRequest(HTTP_GET,
1235 e5e20779 Faidon Liambotis
                             "/%s/nodes/%s" % (GANETI_RAPI_VERSION, node),
1236 e5e20779 Faidon Liambotis
                             None, None)
1237 e5e20779 Faidon Liambotis
1238 e5e20779 Faidon Liambotis
  def EvacuateNode(self, node, iallocator=None, remote_node=None,
1239 e5e20779 Faidon Liambotis
                   dry_run=False, early_release=False):
1240 e5e20779 Faidon Liambotis
    """Evacuates instances from a Ganeti node.
1241 e5e20779 Faidon Liambotis

1242 e5e20779 Faidon Liambotis
    @type node: str
1243 e5e20779 Faidon Liambotis
    @param node: node to evacuate
1244 e5e20779 Faidon Liambotis
    @type iallocator: str or None
1245 e5e20779 Faidon Liambotis
    @param iallocator: instance allocator to use
1246 e5e20779 Faidon Liambotis
    @type remote_node: str
1247 e5e20779 Faidon Liambotis
    @param remote_node: node to evaucate to
1248 e5e20779 Faidon Liambotis
    @type dry_run: bool
1249 e5e20779 Faidon Liambotis
    @param dry_run: whether to perform a dry run
1250 e5e20779 Faidon Liambotis
    @type early_release: bool
1251 e5e20779 Faidon Liambotis
    @param early_release: whether to enable parallelization
1252 e5e20779 Faidon Liambotis

1253 e5e20779 Faidon Liambotis
    @rtype: list
1254 e5e20779 Faidon Liambotis
    @return: list of (job ID, instance name, new secondary node); if
1255 e5e20779 Faidon Liambotis
        dry_run was specified, then the actual move jobs were not
1256 e5e20779 Faidon Liambotis
        submitted and the job IDs will be C{None}
1257 e5e20779 Faidon Liambotis

1258 e5e20779 Faidon Liambotis
    @raises GanetiApiError: if an iallocator and remote_node are both
1259 e5e20779 Faidon Liambotis
        specified
1260 e5e20779 Faidon Liambotis

1261 e5e20779 Faidon Liambotis
    """
1262 e5e20779 Faidon Liambotis
    if iallocator and remote_node:
1263 e5e20779 Faidon Liambotis
      raise GanetiApiError("Only one of iallocator or remote_node can be used")
1264 e5e20779 Faidon Liambotis
1265 e5e20779 Faidon Liambotis
    query = []
1266 e5e20779 Faidon Liambotis
    if iallocator:
1267 e5e20779 Faidon Liambotis
      query.append(("iallocator", iallocator))
1268 e5e20779 Faidon Liambotis
    if remote_node:
1269 e5e20779 Faidon Liambotis
      query.append(("remote_node", remote_node))
1270 e5e20779 Faidon Liambotis
    if dry_run:
1271 e5e20779 Faidon Liambotis
      query.append(("dry-run", 1))
1272 e5e20779 Faidon Liambotis
    if early_release:
1273 e5e20779 Faidon Liambotis
      query.append(("early_release", 1))
1274 e5e20779 Faidon Liambotis
1275 e5e20779 Faidon Liambotis
    return self._SendRequest(HTTP_POST,
1276 e5e20779 Faidon Liambotis
                             ("/%s/nodes/%s/evacuate" %
1277 e5e20779 Faidon Liambotis
                              (GANETI_RAPI_VERSION, node)), query, None)
1278 e5e20779 Faidon Liambotis
1279 e5e20779 Faidon Liambotis
  def MigrateNode(self, node, mode=None, dry_run=False):
1280 e5e20779 Faidon Liambotis
    """Migrates all primary instances from a node.
1281 e5e20779 Faidon Liambotis

1282 e5e20779 Faidon Liambotis
    @type node: str
1283 e5e20779 Faidon Liambotis
    @param node: node to migrate
1284 e5e20779 Faidon Liambotis
    @type mode: string
1285 e5e20779 Faidon Liambotis
    @param mode: if passed, it will overwrite the live migration type,
1286 e5e20779 Faidon Liambotis
        otherwise the hypervisor default will be used
1287 e5e20779 Faidon Liambotis
    @type dry_run: bool
1288 e5e20779 Faidon Liambotis
    @param dry_run: whether to perform a dry run
1289 e5e20779 Faidon Liambotis

1290 e5e20779 Faidon Liambotis
    @rtype: int
1291 e5e20779 Faidon Liambotis
    @return: job id
1292 e5e20779 Faidon Liambotis

1293 e5e20779 Faidon Liambotis
    """
1294 e5e20779 Faidon Liambotis
    query = []
1295 e5e20779 Faidon Liambotis
    if mode is not None:
1296 e5e20779 Faidon Liambotis
      query.append(("mode", mode))
1297 e5e20779 Faidon Liambotis
    if dry_run:
1298 e5e20779 Faidon Liambotis
      query.append(("dry-run", 1))
1299 e5e20779 Faidon Liambotis
1300 e5e20779 Faidon Liambotis
    return self._SendRequest(HTTP_POST,
1301 e5e20779 Faidon Liambotis
                             ("/%s/nodes/%s/migrate" %
1302 e5e20779 Faidon Liambotis
                              (GANETI_RAPI_VERSION, node)), query, None)
1303 e5e20779 Faidon Liambotis
1304 e5e20779 Faidon Liambotis
  def GetNodeRole(self, node):
1305 e5e20779 Faidon Liambotis
    """Gets the current role for a node.
1306 e5e20779 Faidon Liambotis

1307 e5e20779 Faidon Liambotis
    @type node: str
1308 e5e20779 Faidon Liambotis
    @param node: node whose role to return
1309 e5e20779 Faidon Liambotis

1310 e5e20779 Faidon Liambotis
    @rtype: str
1311 e5e20779 Faidon Liambotis
    @return: the current role for a node
1312 e5e20779 Faidon Liambotis

1313 e5e20779 Faidon Liambotis
    """
1314 e5e20779 Faidon Liambotis
    return self._SendRequest(HTTP_GET,
1315 e5e20779 Faidon Liambotis
                             ("/%s/nodes/%s/role" %
1316 e5e20779 Faidon Liambotis
                              (GANETI_RAPI_VERSION, node)), None, None)
1317 e5e20779 Faidon Liambotis
1318 e5e20779 Faidon Liambotis
  def SetNodeRole(self, node, role, force=False):
1319 e5e20779 Faidon Liambotis
    """Sets the role for a node.
1320 e5e20779 Faidon Liambotis

1321 e5e20779 Faidon Liambotis
    @type node: str
1322 e5e20779 Faidon Liambotis
    @param node: the node whose role to set
1323 e5e20779 Faidon Liambotis
    @type role: str
1324 e5e20779 Faidon Liambotis
    @param role: the role to set for the node
1325 e5e20779 Faidon Liambotis
    @type force: bool
1326 e5e20779 Faidon Liambotis
    @param force: whether to force the role change
1327 e5e20779 Faidon Liambotis

1328 e5e20779 Faidon Liambotis
    @rtype: int
1329 e5e20779 Faidon Liambotis
    @return: job id
1330 e5e20779 Faidon Liambotis

1331 e5e20779 Faidon Liambotis
    """
1332 e5e20779 Faidon Liambotis
    query = [
1333 e5e20779 Faidon Liambotis
      ("force", force),
1334 e5e20779 Faidon Liambotis
      ]
1335 e5e20779 Faidon Liambotis
1336 e5e20779 Faidon Liambotis
    return self._SendRequest(HTTP_PUT,
1337 e5e20779 Faidon Liambotis
                             ("/%s/nodes/%s/role" %
1338 e5e20779 Faidon Liambotis
                              (GANETI_RAPI_VERSION, node)), query, role)
1339 e5e20779 Faidon Liambotis
1340 e5e20779 Faidon Liambotis
  def GetNodeStorageUnits(self, node, storage_type, output_fields):
1341 e5e20779 Faidon Liambotis
    """Gets the storage units for a node.
1342 e5e20779 Faidon Liambotis

1343 e5e20779 Faidon Liambotis
    @type node: str
1344 e5e20779 Faidon Liambotis
    @param node: the node whose storage units to return
1345 e5e20779 Faidon Liambotis
    @type storage_type: str
1346 e5e20779 Faidon Liambotis
    @param storage_type: storage type whose units to return
1347 e5e20779 Faidon Liambotis
    @type output_fields: str
1348 e5e20779 Faidon Liambotis
    @param output_fields: storage type fields to return
1349 e5e20779 Faidon Liambotis

1350 e5e20779 Faidon Liambotis
    @rtype: int
1351 e5e20779 Faidon Liambotis
    @return: job id where results can be retrieved
1352 e5e20779 Faidon Liambotis

1353 e5e20779 Faidon Liambotis
    """
1354 e5e20779 Faidon Liambotis
    query = [
1355 e5e20779 Faidon Liambotis
      ("storage_type", storage_type),
1356 e5e20779 Faidon Liambotis
      ("output_fields", output_fields),
1357 e5e20779 Faidon Liambotis
      ]
1358 e5e20779 Faidon Liambotis
1359 e5e20779 Faidon Liambotis
    return self._SendRequest(HTTP_GET,
1360 e5e20779 Faidon Liambotis
                             ("/%s/nodes/%s/storage" %
1361 e5e20779 Faidon Liambotis
                              (GANETI_RAPI_VERSION, node)), query, None)
1362 e5e20779 Faidon Liambotis
1363 e5e20779 Faidon Liambotis
  def ModifyNodeStorageUnits(self, node, storage_type, name, allocatable=None):
1364 e5e20779 Faidon Liambotis
    """Modifies parameters of storage units on the node.
1365 e5e20779 Faidon Liambotis

1366 e5e20779 Faidon Liambotis
    @type node: str
1367 e5e20779 Faidon Liambotis
    @param node: node whose storage units to modify
1368 e5e20779 Faidon Liambotis
    @type storage_type: str
1369 e5e20779 Faidon Liambotis
    @param storage_type: storage type whose units to modify
1370 e5e20779 Faidon Liambotis
    @type name: str
1371 e5e20779 Faidon Liambotis
    @param name: name of the storage unit
1372 e5e20779 Faidon Liambotis
    @type allocatable: bool or None
1373 e5e20779 Faidon Liambotis
    @param allocatable: Whether to set the "allocatable" flag on the storage
1374 e5e20779 Faidon Liambotis
                        unit (None=no modification, True=set, False=unset)
1375 e5e20779 Faidon Liambotis

1376 e5e20779 Faidon Liambotis
    @rtype: int
1377 e5e20779 Faidon Liambotis
    @return: job id
1378 e5e20779 Faidon Liambotis

1379 e5e20779 Faidon Liambotis
    """
1380 e5e20779 Faidon Liambotis
    query = [
1381 e5e20779 Faidon Liambotis
      ("storage_type", storage_type),
1382 e5e20779 Faidon Liambotis
      ("name", name),
1383 e5e20779 Faidon Liambotis
      ]
1384 e5e20779 Faidon Liambotis
1385 e5e20779 Faidon Liambotis
    if allocatable is not None:
1386 e5e20779 Faidon Liambotis
      query.append(("allocatable", allocatable))
1387 e5e20779 Faidon Liambotis
1388 e5e20779 Faidon Liambotis
    return self._SendRequest(HTTP_PUT,
1389 e5e20779 Faidon Liambotis
                             ("/%s/nodes/%s/storage/modify" %
1390 e5e20779 Faidon Liambotis
                              (GANETI_RAPI_VERSION, node)), query, None)
1391 e5e20779 Faidon Liambotis
1392 e5e20779 Faidon Liambotis
  def RepairNodeStorageUnits(self, node, storage_type, name):
1393 e5e20779 Faidon Liambotis
    """Repairs a storage unit on the node.
1394 e5e20779 Faidon Liambotis

1395 e5e20779 Faidon Liambotis
    @type node: str
1396 e5e20779 Faidon Liambotis
    @param node: node whose storage units to repair
1397 e5e20779 Faidon Liambotis
    @type storage_type: str
1398 e5e20779 Faidon Liambotis
    @param storage_type: storage type to repair
1399 e5e20779 Faidon Liambotis
    @type name: str
1400 e5e20779 Faidon Liambotis
    @param name: name of the storage unit to repair
1401 e5e20779 Faidon Liambotis

1402 e5e20779 Faidon Liambotis
    @rtype: int
1403 e5e20779 Faidon Liambotis
    @return: job id
1404 e5e20779 Faidon Liambotis

1405 e5e20779 Faidon Liambotis
    """
1406 e5e20779 Faidon Liambotis
    query = [
1407 e5e20779 Faidon Liambotis
      ("storage_type", storage_type),
1408 e5e20779 Faidon Liambotis
      ("name", name),
1409 e5e20779 Faidon Liambotis
      ]
1410 e5e20779 Faidon Liambotis
1411 e5e20779 Faidon Liambotis
    return self._SendRequest(HTTP_PUT,
1412 e5e20779 Faidon Liambotis
                             ("/%s/nodes/%s/storage/repair" %
1413 e5e20779 Faidon Liambotis
                              (GANETI_RAPI_VERSION, node)), query, None)
1414 e5e20779 Faidon Liambotis
1415 e5e20779 Faidon Liambotis
  def GetNodeTags(self, node):
1416 e5e20779 Faidon Liambotis
    """Gets the tags for a node.
1417 e5e20779 Faidon Liambotis

1418 e5e20779 Faidon Liambotis
    @type node: str
1419 e5e20779 Faidon Liambotis
    @param node: node whose tags to return
1420 e5e20779 Faidon Liambotis

1421 e5e20779 Faidon Liambotis
    @rtype: list of str
1422 e5e20779 Faidon Liambotis
    @return: tags for the node
1423 e5e20779 Faidon Liambotis

1424 e5e20779 Faidon Liambotis
    """
1425 e5e20779 Faidon Liambotis
    return self._SendRequest(HTTP_GET,
1426 e5e20779 Faidon Liambotis
                             ("/%s/nodes/%s/tags" %
1427 e5e20779 Faidon Liambotis
                              (GANETI_RAPI_VERSION, node)), None, None)
1428 e5e20779 Faidon Liambotis
1429 e5e20779 Faidon Liambotis
  def AddNodeTags(self, node, tags, dry_run=False):
1430 e5e20779 Faidon Liambotis
    """Adds tags to a node.
1431 e5e20779 Faidon Liambotis

1432 e5e20779 Faidon Liambotis
    @type node: str
1433 e5e20779 Faidon Liambotis
    @param node: node to add tags to
1434 e5e20779 Faidon Liambotis
    @type tags: list of str
1435 e5e20779 Faidon Liambotis
    @param tags: tags to add to the node
1436 e5e20779 Faidon Liambotis
    @type dry_run: bool
1437 e5e20779 Faidon Liambotis
    @param dry_run: whether to perform a dry run
1438 e5e20779 Faidon Liambotis

1439 e5e20779 Faidon Liambotis
    @rtype: int
1440 e5e20779 Faidon Liambotis
    @return: job id
1441 e5e20779 Faidon Liambotis

1442 e5e20779 Faidon Liambotis
    """
1443 e5e20779 Faidon Liambotis
    query = [("tag", t) for t in tags]
1444 e5e20779 Faidon Liambotis
    if dry_run:
1445 e5e20779 Faidon Liambotis
      query.append(("dry-run", 1))
1446 e5e20779 Faidon Liambotis
1447 e5e20779 Faidon Liambotis
    return self._SendRequest(HTTP_PUT,
1448 e5e20779 Faidon Liambotis
                             ("/%s/nodes/%s/tags" %
1449 e5e20779 Faidon Liambotis
                              (GANETI_RAPI_VERSION, node)), query, tags)
1450 e5e20779 Faidon Liambotis
1451 e5e20779 Faidon Liambotis
  def DeleteNodeTags(self, node, tags, dry_run=False):
1452 e5e20779 Faidon Liambotis
    """Delete tags from a node.
1453 e5e20779 Faidon Liambotis

1454 e5e20779 Faidon Liambotis
    @type node: str
1455 e5e20779 Faidon Liambotis
    @param node: node to remove tags from
1456 e5e20779 Faidon Liambotis
    @type tags: list of str
1457 e5e20779 Faidon Liambotis
    @param tags: tags to remove from the node
1458 e5e20779 Faidon Liambotis
    @type dry_run: bool
1459 e5e20779 Faidon Liambotis
    @param dry_run: whether to perform a dry run
1460 e5e20779 Faidon Liambotis

1461 e5e20779 Faidon Liambotis
    @rtype: int
1462 e5e20779 Faidon Liambotis
    @return: job id
1463 e5e20779 Faidon Liambotis

1464 e5e20779 Faidon Liambotis
    """
1465 e5e20779 Faidon Liambotis
    query = [("tag", t) for t in tags]
1466 e5e20779 Faidon Liambotis
    if dry_run:
1467 e5e20779 Faidon Liambotis
      query.append(("dry-run", 1))
1468 e5e20779 Faidon Liambotis
1469 e5e20779 Faidon Liambotis
    return self._SendRequest(HTTP_DELETE,
1470 e5e20779 Faidon Liambotis
                             ("/%s/nodes/%s/tags" %
1471 e5e20779 Faidon Liambotis
                              (GANETI_RAPI_VERSION, node)), query, None)
1472 067dda99 Vangelis Koukis
1473 067dda99 Vangelis Koukis
  def GetGroups(self, bulk=False):
1474 067dda99 Vangelis Koukis
    """Gets all node groups in the cluster.
1475 067dda99 Vangelis Koukis

1476 067dda99 Vangelis Koukis
    @type bulk: bool
1477 067dda99 Vangelis Koukis
    @param bulk: whether to return all information about the groups
1478 067dda99 Vangelis Koukis

1479 067dda99 Vangelis Koukis
    @rtype: list of dict or str
1480 067dda99 Vangelis Koukis
    @return: if bulk is true, a list of dictionaries with info about all node
1481 067dda99 Vangelis Koukis
        groups in the cluster, else a list of names of those node groups
1482 067dda99 Vangelis Koukis

1483 067dda99 Vangelis Koukis
    """
1484 067dda99 Vangelis Koukis
    query = []
1485 067dda99 Vangelis Koukis
    if bulk:
1486 067dda99 Vangelis Koukis
      query.append(("bulk", 1))
1487 067dda99 Vangelis Koukis
1488 067dda99 Vangelis Koukis
    groups = self._SendRequest(HTTP_GET, "/%s/groups" % GANETI_RAPI_VERSION,
1489 067dda99 Vangelis Koukis
                               query, None)
1490 067dda99 Vangelis Koukis
    if bulk:
1491 067dda99 Vangelis Koukis
      return groups
1492 067dda99 Vangelis Koukis
    else:
1493 067dda99 Vangelis Koukis
      return [g["name"] for g in groups]
1494 067dda99 Vangelis Koukis
1495 067dda99 Vangelis Koukis
  def GetGroup(self, group):
1496 067dda99 Vangelis Koukis
    """Gets information about a node group.
1497 067dda99 Vangelis Koukis

1498 067dda99 Vangelis Koukis
    @type group: str
1499 067dda99 Vangelis Koukis
    @param group: name of the node group whose info to return
1500 067dda99 Vangelis Koukis

1501 067dda99 Vangelis Koukis
    @rtype: dict
1502 067dda99 Vangelis Koukis
    @return: info about the node group
1503 067dda99 Vangelis Koukis

1504 067dda99 Vangelis Koukis
    """
1505 067dda99 Vangelis Koukis
    return self._SendRequest(HTTP_GET,
1506 067dda99 Vangelis Koukis
                             "/%s/groups/%s" % (GANETI_RAPI_VERSION, group),
1507 067dda99 Vangelis Koukis
                             None, None)
1508 067dda99 Vangelis Koukis
1509 067dda99 Vangelis Koukis
  def CreateGroup(self, name, alloc_policy=None, dry_run=False):
1510 067dda99 Vangelis Koukis
    """Creates a new node group.
1511 067dda99 Vangelis Koukis

1512 067dda99 Vangelis Koukis
    @type name: str
1513 067dda99 Vangelis Koukis
    @param name: the name of node group to create
1514 067dda99 Vangelis Koukis
    @type alloc_policy: str
1515 067dda99 Vangelis Koukis
    @param alloc_policy: the desired allocation policy for the group, if any
1516 067dda99 Vangelis Koukis
    @type dry_run: bool
1517 067dda99 Vangelis Koukis
    @param dry_run: whether to peform a dry run
1518 067dda99 Vangelis Koukis

1519 067dda99 Vangelis Koukis
    @rtype: int
1520 067dda99 Vangelis Koukis
    @return: job id
1521 067dda99 Vangelis Koukis

1522 067dda99 Vangelis Koukis
    """
1523 067dda99 Vangelis Koukis
    query = []
1524 067dda99 Vangelis Koukis
    if dry_run:
1525 067dda99 Vangelis Koukis
      query.append(("dry-run", 1))
1526 067dda99 Vangelis Koukis
1527 067dda99 Vangelis Koukis
    body = {
1528 067dda99 Vangelis Koukis
      "name": name,
1529 067dda99 Vangelis Koukis
      "alloc_policy": alloc_policy
1530 067dda99 Vangelis Koukis
      }
1531 067dda99 Vangelis Koukis
1532 067dda99 Vangelis Koukis
    return self._SendRequest(HTTP_POST, "/%s/groups" % GANETI_RAPI_VERSION,
1533 067dda99 Vangelis Koukis
                             query, body)
1534 067dda99 Vangelis Koukis
1535 067dda99 Vangelis Koukis
  def ModifyGroup(self, group, **kwargs):
1536 067dda99 Vangelis Koukis
    """Modifies a node group.
1537 067dda99 Vangelis Koukis

1538 067dda99 Vangelis Koukis
    More details for parameters can be found in the RAPI documentation.
1539 067dda99 Vangelis Koukis

1540 067dda99 Vangelis Koukis
    @type group: string
1541 067dda99 Vangelis Koukis
    @param group: Node group name
1542 067dda99 Vangelis Koukis
    @rtype: int
1543 067dda99 Vangelis Koukis
    @return: job id
1544 067dda99 Vangelis Koukis

1545 067dda99 Vangelis Koukis
    """
1546 067dda99 Vangelis Koukis
    return self._SendRequest(HTTP_PUT,
1547 067dda99 Vangelis Koukis
                             ("/%s/groups/%s/modify" %
1548 067dda99 Vangelis Koukis
                              (GANETI_RAPI_VERSION, group)), None, kwargs)
1549 067dda99 Vangelis Koukis
1550 067dda99 Vangelis Koukis
  def DeleteGroup(self, group, dry_run=False):
1551 067dda99 Vangelis Koukis
    """Deletes a node group.
1552 067dda99 Vangelis Koukis

1553 067dda99 Vangelis Koukis
    @type group: str
1554 067dda99 Vangelis Koukis
    @param group: the node group to delete
1555 067dda99 Vangelis Koukis
    @type dry_run: bool
1556 067dda99 Vangelis Koukis
    @param dry_run: whether to peform a dry run
1557 067dda99 Vangelis Koukis

1558 067dda99 Vangelis Koukis
    @rtype: int
1559 067dda99 Vangelis Koukis
    @return: job id
1560 067dda99 Vangelis Koukis

1561 067dda99 Vangelis Koukis
    """
1562 067dda99 Vangelis Koukis
    query = []
1563 067dda99 Vangelis Koukis
    if dry_run:
1564 067dda99 Vangelis Koukis
      query.append(("dry-run", 1))
1565 067dda99 Vangelis Koukis
1566 067dda99 Vangelis Koukis
    return self._SendRequest(HTTP_DELETE,
1567 067dda99 Vangelis Koukis
                             ("/%s/groups/%s" %
1568 067dda99 Vangelis Koukis
                              (GANETI_RAPI_VERSION, group)), query, None)
1569 067dda99 Vangelis Koukis
1570 067dda99 Vangelis Koukis
  def RenameGroup(self, group, new_name):
1571 067dda99 Vangelis Koukis
    """Changes the name of a node group.
1572 067dda99 Vangelis Koukis

1573 067dda99 Vangelis Koukis
    @type group: string
1574 067dda99 Vangelis Koukis
    @param group: Node group name
1575 067dda99 Vangelis Koukis
    @type new_name: string
1576 067dda99 Vangelis Koukis
    @param new_name: New node group name
1577 067dda99 Vangelis Koukis

1578 067dda99 Vangelis Koukis
    @rtype: int
1579 067dda99 Vangelis Koukis
    @return: job id
1580 067dda99 Vangelis Koukis

1581 067dda99 Vangelis Koukis
    """
1582 067dda99 Vangelis Koukis
    body = {
1583 067dda99 Vangelis Koukis
      "new_name": new_name,
1584 067dda99 Vangelis Koukis
      }
1585 067dda99 Vangelis Koukis
1586 067dda99 Vangelis Koukis
    return self._SendRequest(HTTP_PUT,
1587 067dda99 Vangelis Koukis
                             ("/%s/groups/%s/rename" %
1588 067dda99 Vangelis Koukis
                              (GANETI_RAPI_VERSION, group)), None, body)
1589 067dda99 Vangelis Koukis
1590 067dda99 Vangelis Koukis
1591 067dda99 Vangelis Koukis
  def AssignGroupNodes(self, group, nodes, force=False, dry_run=False):
1592 067dda99 Vangelis Koukis
    """Assigns nodes to a group.
1593 067dda99 Vangelis Koukis

1594 067dda99 Vangelis Koukis
    @type group: string
1595 067dda99 Vangelis Koukis
    @param group: Node gropu name
1596 067dda99 Vangelis Koukis
    @type nodes: list of strings
1597 067dda99 Vangelis Koukis
    @param nodes: List of nodes to assign to the group
1598 067dda99 Vangelis Koukis

1599 067dda99 Vangelis Koukis
    @rtype: int
1600 067dda99 Vangelis Koukis
    @return: job id
1601 067dda99 Vangelis Koukis

1602 067dda99 Vangelis Koukis
    """
1603 067dda99 Vangelis Koukis
    query = []
1604 067dda99 Vangelis Koukis
1605 067dda99 Vangelis Koukis
    if force:
1606 067dda99 Vangelis Koukis
      query.append(("force", 1))
1607 067dda99 Vangelis Koukis
1608 067dda99 Vangelis Koukis
    if dry_run:
1609 067dda99 Vangelis Koukis
      query.append(("dry-run", 1))
1610 067dda99 Vangelis Koukis
1611 067dda99 Vangelis Koukis
    body = {
1612 067dda99 Vangelis Koukis
      "nodes": nodes,
1613 067dda99 Vangelis Koukis
      }
1614 067dda99 Vangelis Koukis
1615 067dda99 Vangelis Koukis
    return self._SendRequest(HTTP_PUT,
1616 067dda99 Vangelis Koukis
                             ("/%s/groups/%s/assign-nodes" %
1617 067dda99 Vangelis Koukis
                             (GANETI_RAPI_VERSION, group)), query, body)