Statistics
| Branch: | Tag: | Revision:

root / lib / rapi / client.py @ 9a8ae794

History | View | Annotate | Download (46.9 kB)

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

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

31 2a7c3583 Michael Hanselmann
"""
32 95ab4de9 David Knowles
33 5ef5cfea Michael Hanselmann
# No Ganeti-specific modules should be imported. The RAPI client is supposed to
34 5ef5cfea Michael Hanselmann
# be standalone.
35 5ef5cfea Michael Hanselmann
36 9279e986 Michael Hanselmann
import logging
37 95ab4de9 David Knowles
import simplejson
38 1a8337f2 Manuel Franceschini
import socket
39 95ab4de9 David Knowles
import urllib
40 2a7c3583 Michael Hanselmann
import threading
41 2a7c3583 Michael Hanselmann
import pycurl
42 16c13387 Theo Van Dinter
import time
43 2a7c3583 Michael Hanselmann
44 2a7c3583 Michael Hanselmann
try:
45 2a7c3583 Michael Hanselmann
  from cStringIO import StringIO
46 2a7c3583 Michael Hanselmann
except ImportError:
47 2a7c3583 Michael Hanselmann
  from StringIO import StringIO
48 95ab4de9 David Knowles
49 95ab4de9 David Knowles
50 9279e986 Michael Hanselmann
GANETI_RAPI_PORT = 5080
51 a198b2d9 Michael Hanselmann
GANETI_RAPI_VERSION = 2
52 9279e986 Michael Hanselmann
53 95ab4de9 David Knowles
HTTP_DELETE = "DELETE"
54 95ab4de9 David Knowles
HTTP_GET = "GET"
55 95ab4de9 David Knowles
HTTP_PUT = "PUT"
56 95ab4de9 David Knowles
HTTP_POST = "POST"
57 9279e986 Michael Hanselmann
HTTP_OK = 200
58 7eac4a4d Michael Hanselmann
HTTP_NOT_FOUND = 404
59 9279e986 Michael Hanselmann
HTTP_APP_JSON = "application/json"
60 9279e986 Michael Hanselmann
61 95ab4de9 David Knowles
REPLACE_DISK_PRI = "replace_on_primary"
62 95ab4de9 David Knowles
REPLACE_DISK_SECONDARY = "replace_on_secondary"
63 95ab4de9 David Knowles
REPLACE_DISK_CHG = "replace_new_secondary"
64 95ab4de9 David Knowles
REPLACE_DISK_AUTO = "replace_auto"
65 1068639f Michael Hanselmann
66 1068639f Michael Hanselmann
NODE_ROLE_DRAINED = "drained"
67 1068639f Michael Hanselmann
NODE_ROLE_MASTER_CANDIATE = "master-candidate"
68 1068639f Michael Hanselmann
NODE_ROLE_MASTER = "master"
69 1068639f Michael Hanselmann
NODE_ROLE_OFFLINE = "offline"
70 1068639f Michael Hanselmann
NODE_ROLE_REGULAR = "regular"
71 95ab4de9 David Knowles
72 63d5eb8a Michael Hanselmann
JOB_STATUS_QUEUED = "queued"
73 63d5eb8a Michael Hanselmann
JOB_STATUS_WAITLOCK = "waiting"
74 63d5eb8a Michael Hanselmann
JOB_STATUS_CANCELING = "canceling"
75 63d5eb8a Michael Hanselmann
JOB_STATUS_RUNNING = "running"
76 63d5eb8a Michael Hanselmann
JOB_STATUS_CANCELED = "canceled"
77 63d5eb8a Michael Hanselmann
JOB_STATUS_SUCCESS = "success"
78 63d5eb8a Michael Hanselmann
JOB_STATUS_ERROR = "error"
79 63d5eb8a Michael Hanselmann
JOB_STATUS_FINALIZED = frozenset([
80 63d5eb8a Michael Hanselmann
  JOB_STATUS_CANCELED,
81 63d5eb8a Michael Hanselmann
  JOB_STATUS_SUCCESS,
82 63d5eb8a Michael Hanselmann
  JOB_STATUS_ERROR,
83 63d5eb8a Michael Hanselmann
  ])
84 63d5eb8a Michael Hanselmann
JOB_STATUS_ALL = frozenset([
85 63d5eb8a Michael Hanselmann
  JOB_STATUS_QUEUED,
86 63d5eb8a Michael Hanselmann
  JOB_STATUS_WAITLOCK,
87 63d5eb8a Michael Hanselmann
  JOB_STATUS_CANCELING,
88 63d5eb8a Michael Hanselmann
  JOB_STATUS_RUNNING,
89 63d5eb8a Michael Hanselmann
  ]) | JOB_STATUS_FINALIZED
90 63d5eb8a Michael Hanselmann
91 8a47b447 Michael Hanselmann
# Internal constants
92 8a47b447 Michael Hanselmann
_REQ_DATA_VERSION_FIELD = "__version__"
93 8a47b447 Michael Hanselmann
_INST_CREATE_REQV1 = "instance-create-reqv1"
94 c744425f Michael Hanselmann
_INST_REINSTALL_REQV1 = "instance-reinstall-reqv1"
95 c97fcab8 Guido Trotter
_INST_NIC_PARAMS = frozenset(["mac", "ip", "mode", "link"])
96 48436b97 Michael Hanselmann
_INST_CREATE_V0_DISK_PARAMS = frozenset(["size"])
97 48436b97 Michael Hanselmann
_INST_CREATE_V0_PARAMS = frozenset([
98 48436b97 Michael Hanselmann
  "os", "pnode", "snode", "iallocator", "start", "ip_check", "name_check",
99 48436b97 Michael Hanselmann
  "hypervisor", "file_storage_dir", "file_driver", "dry_run",
100 48436b97 Michael Hanselmann
  ])
101 48436b97 Michael Hanselmann
_INST_CREATE_V0_DPARAMS = frozenset(["beparams", "hvparams"])
102 8a47b447 Michael Hanselmann
103 2a7c3583 Michael Hanselmann
# Older pycURL versions don't have all error constants
104 2a7c3583 Michael Hanselmann
try:
105 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT = pycurl.E_SSL_CACERT
106 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT_BADFILE = pycurl.E_SSL_CACERT_BADFILE
107 2a7c3583 Michael Hanselmann
except AttributeError:
108 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT = 60
109 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT_BADFILE = 77
110 2a7c3583 Michael Hanselmann
111 2a7c3583 Michael Hanselmann
_CURL_SSL_CERT_ERRORS = frozenset([
112 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT,
113 2a7c3583 Michael Hanselmann
  _CURLE_SSL_CACERT_BADFILE,
114 2a7c3583 Michael Hanselmann
  ])
115 2a7c3583 Michael Hanselmann
116 95ab4de9 David Knowles
117 95ab4de9 David Knowles
class Error(Exception):
118 95ab4de9 David Knowles
  """Base error class for this module.
119 95ab4de9 David Knowles

120 95ab4de9 David Knowles
  """
121 95ab4de9 David Knowles
  pass
122 95ab4de9 David Knowles
123 95ab4de9 David Knowles
124 95ab4de9 David Knowles
class CertificateError(Error):
125 95ab4de9 David Knowles
  """Raised when a problem is found with the SSL certificate.
126 95ab4de9 David Knowles

127 95ab4de9 David Knowles
  """
128 95ab4de9 David Knowles
  pass
129 95ab4de9 David Knowles
130 95ab4de9 David Knowles
131 95ab4de9 David Knowles
class GanetiApiError(Error):
132 95ab4de9 David Knowles
  """Generic error raised from Ganeti API.
133 95ab4de9 David Knowles

134 95ab4de9 David Knowles
  """
135 8a019a03 Michael Hanselmann
  def __init__(self, msg, code=None):
136 8a019a03 Michael Hanselmann
    Error.__init__(self, msg)
137 8a019a03 Michael Hanselmann
    self.code = code
138 95ab4de9 David Knowles
139 95ab4de9 David Knowles
140 2a7c3583 Michael Hanselmann
def UsesRapiClient(fn):
141 2a7c3583 Michael Hanselmann
  """Decorator for code using RAPI client to initialize pycURL.
142 9279e986 Michael Hanselmann

143 9279e986 Michael Hanselmann
  """
144 2a7c3583 Michael Hanselmann
  def wrapper(*args, **kwargs):
145 2a7c3583 Michael Hanselmann
    # curl_global_init(3) and curl_global_cleanup(3) must be called with only
146 2a7c3583 Michael Hanselmann
    # one thread running. This check is just a safety measure -- it doesn't
147 2a7c3583 Michael Hanselmann
    # cover all cases.
148 2a7c3583 Michael Hanselmann
    assert threading.activeCount() == 1, \
149 2a7c3583 Michael Hanselmann
           "Found active threads when initializing pycURL"
150 2a7c3583 Michael Hanselmann
151 2a7c3583 Michael Hanselmann
    pycurl.global_init(pycurl.GLOBAL_ALL)
152 2a7c3583 Michael Hanselmann
    try:
153 2a7c3583 Michael Hanselmann
      return fn(*args, **kwargs)
154 2a7c3583 Michael Hanselmann
    finally:
155 2a7c3583 Michael Hanselmann
      pycurl.global_cleanup()
156 2a7c3583 Michael Hanselmann
157 2a7c3583 Michael Hanselmann
  return wrapper
158 2a7c3583 Michael Hanselmann
159 2a7c3583 Michael Hanselmann
160 2a7c3583 Michael Hanselmann
def GenericCurlConfig(verbose=False, use_signal=False,
161 2a7c3583 Michael Hanselmann
                      use_curl_cabundle=False, cafile=None, capath=None,
162 2a7c3583 Michael Hanselmann
                      proxy=None, verify_hostname=False,
163 2a7c3583 Michael Hanselmann
                      connect_timeout=None, timeout=None,
164 2a7c3583 Michael Hanselmann
                      _pycurl_version_fn=pycurl.version_info):
165 2a7c3583 Michael Hanselmann
  """Curl configuration function generator.
166 2a7c3583 Michael Hanselmann

167 2a7c3583 Michael Hanselmann
  @type verbose: bool
168 2a7c3583 Michael Hanselmann
  @param verbose: Whether to set cURL to verbose mode
169 2a7c3583 Michael Hanselmann
  @type use_signal: bool
170 2a7c3583 Michael Hanselmann
  @param use_signal: Whether to allow cURL to use signals
171 2a7c3583 Michael Hanselmann
  @type use_curl_cabundle: bool
172 2a7c3583 Michael Hanselmann
  @param use_curl_cabundle: Whether to use cURL's default CA bundle
173 2a7c3583 Michael Hanselmann
  @type cafile: string
174 2a7c3583 Michael Hanselmann
  @param cafile: In which file we can find the certificates
175 2a7c3583 Michael Hanselmann
  @type capath: string
176 2a7c3583 Michael Hanselmann
  @param capath: In which directory we can find the certificates
177 2a7c3583 Michael Hanselmann
  @type proxy: string
178 2a7c3583 Michael Hanselmann
  @param proxy: Proxy to use, None for default behaviour and empty string for
179 2a7c3583 Michael Hanselmann
                disabling proxies (see curl_easy_setopt(3))
180 2a7c3583 Michael Hanselmann
  @type verify_hostname: bool
181 2a7c3583 Michael Hanselmann
  @param verify_hostname: Whether to verify the remote peer certificate's
182 2a7c3583 Michael Hanselmann
                          commonName
183 2a7c3583 Michael Hanselmann
  @type connect_timeout: number
184 2a7c3583 Michael Hanselmann
  @param connect_timeout: Timeout for establishing connection in seconds
185 2a7c3583 Michael Hanselmann
  @type timeout: number
186 2a7c3583 Michael Hanselmann
  @param timeout: Timeout for complete transfer in seconds (see
187 2a7c3583 Michael Hanselmann
                  curl_easy_setopt(3)).
188 9279e986 Michael Hanselmann

189 9279e986 Michael Hanselmann
  """
190 2a7c3583 Michael Hanselmann
  if use_curl_cabundle and (cafile or capath):
191 2a7c3583 Michael Hanselmann
    raise Error("Can not use default CA bundle when CA file or path is set")
192 9279e986 Michael Hanselmann
193 2a7c3583 Michael Hanselmann
  def _ConfigCurl(curl, logger):
194 2a7c3583 Michael Hanselmann
    """Configures a cURL object
195 9279e986 Michael Hanselmann

196 2a7c3583 Michael Hanselmann
    @type curl: pycurl.Curl
197 2a7c3583 Michael Hanselmann
    @param curl: cURL object
198 9279e986 Michael Hanselmann

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

262 95ab4de9 David Knowles
  """
263 95ab4de9 David Knowles
  USER_AGENT = "Ganeti RAPI Client"
264 d3844674 Michael Hanselmann
  _json_encoder = simplejson.JSONEncoder(sort_keys=True)
265 95ab4de9 David Knowles
266 9279e986 Michael Hanselmann
  def __init__(self, host, port=GANETI_RAPI_PORT,
267 2a7c3583 Michael Hanselmann
               username=None, password=None, logger=logging,
268 a5eba783 Michael Hanselmann
               curl_config_fn=None, curl_factory=None):
269 2a7c3583 Michael Hanselmann
    """Initializes this class.
270 95ab4de9 David Knowles

271 9279e986 Michael Hanselmann
    @type host: string
272 9279e986 Michael Hanselmann
    @param host: the ganeti cluster master to interact with
273 95ab4de9 David Knowles
    @type port: int
274 9279e986 Michael Hanselmann
    @param port: the port on which the RAPI is running (default is 5080)
275 9279e986 Michael Hanselmann
    @type username: string
276 95ab4de9 David Knowles
    @param username: the username to connect with
277 9279e986 Michael Hanselmann
    @type password: string
278 95ab4de9 David Knowles
    @param password: the password to connect with
279 2a7c3583 Michael Hanselmann
    @type curl_config_fn: callable
280 2a7c3583 Michael Hanselmann
    @param curl_config_fn: Function to configure C{pycurl.Curl} object
281 9279e986 Michael Hanselmann
    @param logger: Logging object
282 95ab4de9 David Knowles

283 95ab4de9 David Knowles
    """
284 a5eba783 Michael Hanselmann
    self._username = username
285 a5eba783 Michael Hanselmann
    self._password = password
286 9279e986 Michael Hanselmann
    self._logger = logger
287 a5eba783 Michael Hanselmann
    self._curl_config_fn = curl_config_fn
288 a5eba783 Michael Hanselmann
    self._curl_factory = curl_factory
289 95ab4de9 David Knowles
290 1a8337f2 Manuel Franceschini
    try:
291 1a8337f2 Manuel Franceschini
      socket.inet_pton(socket.AF_INET6, host)
292 1a8337f2 Manuel Franceschini
      address = "[%s]:%s" % (host, port)
293 1a8337f2 Manuel Franceschini
    except socket.error:
294 1a8337f2 Manuel Franceschini
      address = "%s:%s" % (host, port)
295 1a8337f2 Manuel Franceschini
296 1a8337f2 Manuel Franceschini
    self._base_url = "https://%s" % address
297 f2f88abf David Knowles
298 a5eba783 Michael Hanselmann
    if username is not None:
299 a5eba783 Michael Hanselmann
      if password is None:
300 a5eba783 Michael Hanselmann
        raise Error("Password not specified")
301 a5eba783 Michael Hanselmann
    elif password:
302 a5eba783 Michael Hanselmann
      raise Error("Specified password without username")
303 a5eba783 Michael Hanselmann
304 a5eba783 Michael Hanselmann
  def _CreateCurl(self):
305 a5eba783 Michael Hanselmann
    """Creates a cURL object.
306 a5eba783 Michael Hanselmann

307 a5eba783 Michael Hanselmann
    """
308 a5eba783 Michael Hanselmann
    # Create pycURL object if no factory is provided
309 a5eba783 Michael Hanselmann
    if self._curl_factory:
310 a5eba783 Michael Hanselmann
      curl = self._curl_factory()
311 a5eba783 Michael Hanselmann
    else:
312 2a7c3583 Michael Hanselmann
      curl = pycurl.Curl()
313 2a7c3583 Michael Hanselmann
314 2a7c3583 Michael Hanselmann
    # Default cURL settings
315 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.VERBOSE, False)
316 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.FOLLOWLOCATION, False)
317 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.MAXREDIRS, 5)
318 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.NOSIGNAL, True)
319 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.USERAGENT, self.USER_AGENT)
320 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.SSL_VERIFYHOST, 0)
321 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.SSL_VERIFYPEER, False)
322 2a7c3583 Michael Hanselmann
    curl.setopt(pycurl.HTTPHEADER, [
323 2a7c3583 Michael Hanselmann
      "Accept: %s" % HTTP_APP_JSON,
324 2a7c3583 Michael Hanselmann
      "Content-type: %s" % HTTP_APP_JSON,
325 2a7c3583 Michael Hanselmann
      ])
326 2a7c3583 Michael Hanselmann
327 a5eba783 Michael Hanselmann
    assert ((self._username is None and self._password is None) ^
328 a5eba783 Michael Hanselmann
            (self._username is not None and self._password is not None))
329 a5eba783 Michael Hanselmann
330 a5eba783 Michael Hanselmann
    if self._username:
331 a5eba783 Michael Hanselmann
      # Setup authentication
332 2a7c3583 Michael Hanselmann
      curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_BASIC)
333 a5eba783 Michael Hanselmann
      curl.setopt(pycurl.USERPWD,
334 a5eba783 Michael Hanselmann
                  str("%s:%s" % (self._username, self._password)))
335 9279e986 Michael Hanselmann
336 2a7c3583 Michael Hanselmann
    # Call external configuration function
337 a5eba783 Michael Hanselmann
    if self._curl_config_fn:
338 a5eba783 Michael Hanselmann
      self._curl_config_fn(curl, self._logger)
339 f2f88abf David Knowles
340 a5eba783 Michael Hanselmann
    return curl
341 95ab4de9 David Knowles
342 10f5ab6c Michael Hanselmann
  @staticmethod
343 10f5ab6c Michael Hanselmann
  def _EncodeQuery(query):
344 10f5ab6c Michael Hanselmann
    """Encode query values for RAPI URL.
345 10f5ab6c Michael Hanselmann

346 10f5ab6c Michael Hanselmann
    @type query: list of two-tuples
347 10f5ab6c Michael Hanselmann
    @param query: Query arguments
348 10f5ab6c Michael Hanselmann
    @rtype: list
349 10f5ab6c Michael Hanselmann
    @return: Query list with encoded values
350 10f5ab6c Michael Hanselmann

351 10f5ab6c Michael Hanselmann
    """
352 10f5ab6c Michael Hanselmann
    result = []
353 10f5ab6c Michael Hanselmann
354 10f5ab6c Michael Hanselmann
    for name, value in query:
355 10f5ab6c Michael Hanselmann
      if value is None:
356 10f5ab6c Michael Hanselmann
        result.append((name, ""))
357 10f5ab6c Michael Hanselmann
358 10f5ab6c Michael Hanselmann
      elif isinstance(value, bool):
359 10f5ab6c Michael Hanselmann
        # Boolean values must be encoded as 0 or 1
360 10f5ab6c Michael Hanselmann
        result.append((name, int(value)))
361 10f5ab6c Michael Hanselmann
362 10f5ab6c Michael Hanselmann
      elif isinstance(value, (list, tuple, dict)):
363 10f5ab6c Michael Hanselmann
        raise ValueError("Invalid query data type %r" % type(value).__name__)
364 10f5ab6c Michael Hanselmann
365 10f5ab6c Michael Hanselmann
      else:
366 10f5ab6c Michael Hanselmann
        result.append((name, value))
367 10f5ab6c Michael Hanselmann
368 10f5ab6c Michael Hanselmann
    return result
369 10f5ab6c Michael Hanselmann
370 768747ed Michael Hanselmann
  def _SendRequest(self, method, path, query, content):
371 95ab4de9 David Knowles
    """Sends an HTTP request.
372 95ab4de9 David Knowles

373 95ab4de9 David Knowles
    This constructs a full URL, encodes and decodes HTTP bodies, and
374 95ab4de9 David Knowles
    handles invalid responses in a pythonic way.
375 95ab4de9 David Knowles

376 768747ed Michael Hanselmann
    @type method: string
377 95ab4de9 David Knowles
    @param method: HTTP method to use
378 768747ed Michael Hanselmann
    @type path: string
379 95ab4de9 David Knowles
    @param path: HTTP URL path
380 95ab4de9 David Knowles
    @type query: list of two-tuples
381 95ab4de9 David Knowles
    @param query: query arguments to pass to urllib.urlencode
382 95ab4de9 David Knowles
    @type content: str or None
383 95ab4de9 David Knowles
    @param content: HTTP body content
384 95ab4de9 David Knowles

385 95ab4de9 David Knowles
    @rtype: str
386 95ab4de9 David Knowles
    @return: JSON-Decoded response
387 95ab4de9 David Knowles

388 f2f88abf David Knowles
    @raises CertificateError: If an invalid SSL certificate is found
389 95ab4de9 David Knowles
    @raises GanetiApiError: If an invalid response is returned
390 95ab4de9 David Knowles

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

461 95ab4de9 David Knowles
    @rtype: int
462 f2f88abf David Knowles
    @return: Ganeti Remote API version
463 95ab4de9 David Knowles

464 95ab4de9 David Knowles
    """
465 768747ed Michael Hanselmann
    return self._SendRequest(HTTP_GET, "/version", None, None)
466 95ab4de9 David Knowles
467 7eac4a4d Michael Hanselmann
  def GetFeatures(self):
468 7eac4a4d Michael Hanselmann
    """Gets the list of optional features supported by RAPI server.
469 7eac4a4d Michael Hanselmann

470 7eac4a4d Michael Hanselmann
    @rtype: list
471 7eac4a4d Michael Hanselmann
    @return: List of optional features
472 7eac4a4d Michael Hanselmann

473 7eac4a4d Michael Hanselmann
    """
474 7eac4a4d Michael Hanselmann
    try:
475 7eac4a4d Michael Hanselmann
      return self._SendRequest(HTTP_GET, "/%s/features" % GANETI_RAPI_VERSION,
476 7eac4a4d Michael Hanselmann
                               None, None)
477 7eac4a4d Michael Hanselmann
    except GanetiApiError, err:
478 7eac4a4d Michael Hanselmann
      # Older RAPI servers don't support this resource
479 7eac4a4d Michael Hanselmann
      if err.code == HTTP_NOT_FOUND:
480 7eac4a4d Michael Hanselmann
        return []
481 7eac4a4d Michael Hanselmann
482 7eac4a4d Michael Hanselmann
      raise
483 7eac4a4d Michael Hanselmann
484 95ab4de9 David Knowles
  def GetOperatingSystems(self):
485 95ab4de9 David Knowles
    """Gets the Operating Systems running in the Ganeti cluster.
486 95ab4de9 David Knowles

487 95ab4de9 David Knowles
    @rtype: list of str
488 95ab4de9 David Knowles
    @return: operating systems
489 95ab4de9 David Knowles

490 95ab4de9 David Knowles
    """
491 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET, "/%s/os" % GANETI_RAPI_VERSION,
492 a198b2d9 Michael Hanselmann
                             None, None)
493 95ab4de9 David Knowles
494 95ab4de9 David Knowles
  def GetInfo(self):
495 95ab4de9 David Knowles
    """Gets info about the cluster.
496 95ab4de9 David Knowles

497 95ab4de9 David Knowles
    @rtype: dict
498 95ab4de9 David Knowles
    @return: information about the cluster
499 95ab4de9 David Knowles

500 95ab4de9 David Knowles
    """
501 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET, "/%s/info" % GANETI_RAPI_VERSION,
502 a198b2d9 Michael Hanselmann
                             None, None)
503 95ab4de9 David Knowles
504 54d4c13b Michael Hanselmann
  def RedistributeConfig(self):
505 54d4c13b Michael Hanselmann
    """Tells the cluster to redistribute its configuration files.
506 54d4c13b Michael Hanselmann

507 d914c76f Simeon Miteff
    @rtype: string
508 54d4c13b Michael Hanselmann
    @return: job id
509 54d4c13b Michael Hanselmann

510 54d4c13b Michael Hanselmann
    """
511 54d4c13b Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
512 54d4c13b Michael Hanselmann
                             "/%s/redistribute-config" % GANETI_RAPI_VERSION,
513 54d4c13b Michael Hanselmann
                             None, None)
514 54d4c13b Michael Hanselmann
515 62e999a5 Michael Hanselmann
  def ModifyCluster(self, **kwargs):
516 62e999a5 Michael Hanselmann
    """Modifies cluster parameters.
517 62e999a5 Michael Hanselmann

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

520 98805538 Michael Hanselmann
    @rtype: string
521 62e999a5 Michael Hanselmann
    @return: job id
522 62e999a5 Michael Hanselmann

523 62e999a5 Michael Hanselmann
    """
524 62e999a5 Michael Hanselmann
    body = kwargs
525 62e999a5 Michael Hanselmann
526 62e999a5 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
527 62e999a5 Michael Hanselmann
                             "/%s/modify" % GANETI_RAPI_VERSION, None, body)
528 62e999a5 Michael Hanselmann
529 95ab4de9 David Knowles
  def GetClusterTags(self):
530 95ab4de9 David Knowles
    """Gets the cluster tags.
531 95ab4de9 David Knowles

532 95ab4de9 David Knowles
    @rtype: list of str
533 95ab4de9 David Knowles
    @return: cluster tags
534 95ab4de9 David Knowles

535 95ab4de9 David Knowles
    """
536 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET, "/%s/tags" % GANETI_RAPI_VERSION,
537 a198b2d9 Michael Hanselmann
                             None, None)
538 95ab4de9 David Knowles
539 95ab4de9 David Knowles
  def AddClusterTags(self, tags, dry_run=False):
540 95ab4de9 David Knowles
    """Adds tags to the cluster.
541 95ab4de9 David Knowles

542 95ab4de9 David Knowles
    @type tags: list of str
543 95ab4de9 David Knowles
    @param tags: tags to add to the cluster
544 95ab4de9 David Knowles
    @type dry_run: bool
545 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
546 95ab4de9 David Knowles

547 98805538 Michael Hanselmann
    @rtype: string
548 95ab4de9 David Knowles
    @return: job id
549 95ab4de9 David Knowles

550 95ab4de9 David Knowles
    """
551 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
552 95ab4de9 David Knowles
    if dry_run:
553 95ab4de9 David Knowles
      query.append(("dry-run", 1))
554 95ab4de9 David Knowles
555 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT, "/%s/tags" % GANETI_RAPI_VERSION,
556 a198b2d9 Michael Hanselmann
                             query, None)
557 95ab4de9 David Knowles
558 95ab4de9 David Knowles
  def DeleteClusterTags(self, tags, dry_run=False):
559 95ab4de9 David Knowles
    """Deletes tags from the cluster.
560 95ab4de9 David Knowles

561 95ab4de9 David Knowles
    @type tags: list of str
562 95ab4de9 David Knowles
    @param tags: tags to delete
563 95ab4de9 David Knowles
    @type dry_run: bool
564 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
565 d914c76f Simeon Miteff
    @rtype: string
566 d914c76f Simeon Miteff
    @return: job id
567 95ab4de9 David Knowles

568 95ab4de9 David Knowles
    """
569 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
570 95ab4de9 David Knowles
    if dry_run:
571 95ab4de9 David Knowles
      query.append(("dry-run", 1))
572 95ab4de9 David Knowles
573 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE, "/%s/tags" % GANETI_RAPI_VERSION,
574 a198b2d9 Michael Hanselmann
                             query, None)
575 95ab4de9 David Knowles
576 95ab4de9 David Knowles
  def GetInstances(self, bulk=False):
577 95ab4de9 David Knowles
    """Gets information about instances on the cluster.
578 95ab4de9 David Knowles

579 95ab4de9 David Knowles
    @type bulk: bool
580 95ab4de9 David Knowles
    @param bulk: whether to return all information about all instances
581 95ab4de9 David Knowles

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

585 95ab4de9 David Knowles
    """
586 95ab4de9 David Knowles
    query = []
587 95ab4de9 David Knowles
    if bulk:
588 95ab4de9 David Knowles
      query.append(("bulk", 1))
589 95ab4de9 David Knowles
590 a198b2d9 Michael Hanselmann
    instances = self._SendRequest(HTTP_GET,
591 a198b2d9 Michael Hanselmann
                                  "/%s/instances" % GANETI_RAPI_VERSION,
592 a198b2d9 Michael Hanselmann
                                  query, None)
593 95ab4de9 David Knowles
    if bulk:
594 95ab4de9 David Knowles
      return instances
595 95ab4de9 David Knowles
    else:
596 95ab4de9 David Knowles
      return [i["id"] for i in instances]
597 95ab4de9 David Knowles
598 591e5103 Michael Hanselmann
  def GetInstance(self, instance):
599 95ab4de9 David Knowles
    """Gets information about an instance.
600 95ab4de9 David Knowles

601 95ab4de9 David Knowles
    @type instance: str
602 95ab4de9 David Knowles
    @param instance: instance whose info to return
603 95ab4de9 David Knowles

604 95ab4de9 David Knowles
    @rtype: dict
605 95ab4de9 David Knowles
    @return: info about the instance
606 95ab4de9 David Knowles

607 95ab4de9 David Knowles
    """
608 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
609 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s" %
610 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, None)
611 95ab4de9 David Knowles
612 591e5103 Michael Hanselmann
  def GetInstanceInfo(self, instance, static=None):
613 591e5103 Michael Hanselmann
    """Gets information about an instance.
614 591e5103 Michael Hanselmann

615 591e5103 Michael Hanselmann
    @type instance: string
616 591e5103 Michael Hanselmann
    @param instance: Instance name
617 591e5103 Michael Hanselmann
    @rtype: string
618 591e5103 Michael Hanselmann
    @return: Job ID
619 591e5103 Michael Hanselmann

620 591e5103 Michael Hanselmann
    """
621 591e5103 Michael Hanselmann
    if static is not None:
622 591e5103 Michael Hanselmann
      query = [("static", static)]
623 591e5103 Michael Hanselmann
    else:
624 591e5103 Michael Hanselmann
      query = None
625 591e5103 Michael Hanselmann
626 591e5103 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
627 591e5103 Michael Hanselmann
                             ("/%s/instances/%s/info" %
628 591e5103 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
629 591e5103 Michael Hanselmann
630 8a47b447 Michael Hanselmann
  def CreateInstance(self, mode, name, disk_template, disks, nics,
631 8a47b447 Michael Hanselmann
                     **kwargs):
632 95ab4de9 David Knowles
    """Creates a new instance.
633 95ab4de9 David Knowles

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

636 8a47b447 Michael Hanselmann
    @type mode: string
637 8a47b447 Michael Hanselmann
    @param mode: Instance creation mode
638 8a47b447 Michael Hanselmann
    @type name: string
639 8a47b447 Michael Hanselmann
    @param name: Hostname of the instance to create
640 8a47b447 Michael Hanselmann
    @type disk_template: string
641 8a47b447 Michael Hanselmann
    @param disk_template: Disk template for instance (e.g. plain, diskless,
642 8a47b447 Michael Hanselmann
                          file, or drbd)
643 8a47b447 Michael Hanselmann
    @type disks: list of dicts
644 8a47b447 Michael Hanselmann
    @param disks: List of disk definitions
645 8a47b447 Michael Hanselmann
    @type nics: list of dicts
646 8a47b447 Michael Hanselmann
    @param nics: List of NIC definitions
647 95ab4de9 David Knowles
    @type dry_run: bool
648 8a47b447 Michael Hanselmann
    @keyword dry_run: whether to perform a dry run
649 95ab4de9 David Knowles

650 98805538 Michael Hanselmann
    @rtype: string
651 95ab4de9 David Knowles
    @return: job id
652 95ab4de9 David Knowles

653 95ab4de9 David Knowles
    """
654 95ab4de9 David Knowles
    query = []
655 8a47b447 Michael Hanselmann
656 8a47b447 Michael Hanselmann
    if kwargs.get("dry_run"):
657 95ab4de9 David Knowles
      query.append(("dry-run", 1))
658 95ab4de9 David Knowles
659 8a47b447 Michael Hanselmann
    if _INST_CREATE_REQV1 in self.GetFeatures():
660 8a47b447 Michael Hanselmann
      # All required fields for request data version 1
661 8a47b447 Michael Hanselmann
      body = {
662 8a47b447 Michael Hanselmann
        _REQ_DATA_VERSION_FIELD: 1,
663 8a47b447 Michael Hanselmann
        "mode": mode,
664 8a47b447 Michael Hanselmann
        "name": name,
665 8a47b447 Michael Hanselmann
        "disk_template": disk_template,
666 8a47b447 Michael Hanselmann
        "disks": disks,
667 8a47b447 Michael Hanselmann
        "nics": nics,
668 8a47b447 Michael Hanselmann
        }
669 8a47b447 Michael Hanselmann
670 8a47b447 Michael Hanselmann
      conflicts = set(kwargs.iterkeys()) & set(body.iterkeys())
671 8a47b447 Michael Hanselmann
      if conflicts:
672 8a47b447 Michael Hanselmann
        raise GanetiApiError("Required fields can not be specified as"
673 8a47b447 Michael Hanselmann
                             " keywords: %s" % ", ".join(conflicts))
674 8a47b447 Michael Hanselmann
675 8a47b447 Michael Hanselmann
      body.update((key, value) for key, value in kwargs.iteritems()
676 8a47b447 Michael Hanselmann
                  if key != "dry_run")
677 8a47b447 Michael Hanselmann
    else:
678 9a8ae794 Michael Hanselmann
      raise GanetiApiError("Server does not support new-style (version 1)"
679 9a8ae794 Michael Hanselmann
                           " instance creation requests")
680 8a47b447 Michael Hanselmann
681 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_POST, "/%s/instances" % GANETI_RAPI_VERSION,
682 8a47b447 Michael Hanselmann
                             query, body)
683 95ab4de9 David Knowles
684 95ab4de9 David Knowles
  def DeleteInstance(self, instance, dry_run=False):
685 95ab4de9 David Knowles
    """Deletes an instance.
686 95ab4de9 David Knowles

687 95ab4de9 David Knowles
    @type instance: str
688 95ab4de9 David Knowles
    @param instance: the instance to delete
689 95ab4de9 David Knowles

690 98805538 Michael Hanselmann
    @rtype: string
691 cab667cc David Knowles
    @return: job id
692 cab667cc David Knowles

693 95ab4de9 David Knowles
    """
694 95ab4de9 David Knowles
    query = []
695 95ab4de9 David Knowles
    if dry_run:
696 95ab4de9 David Knowles
      query.append(("dry-run", 1))
697 95ab4de9 David Knowles
698 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE,
699 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s" %
700 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
701 95ab4de9 David Knowles
702 3b7158ef Michael Hanselmann
  def ModifyInstance(self, instance, **kwargs):
703 3b7158ef Michael Hanselmann
    """Modifies an instance.
704 3b7158ef Michael Hanselmann

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

707 3b7158ef Michael Hanselmann
    @type instance: string
708 3b7158ef Michael Hanselmann
    @param instance: Instance name
709 98805538 Michael Hanselmann
    @rtype: string
710 3b7158ef Michael Hanselmann
    @return: job id
711 3b7158ef Michael Hanselmann

712 3b7158ef Michael Hanselmann
    """
713 3b7158ef Michael Hanselmann
    body = kwargs
714 3b7158ef Michael Hanselmann
715 3b7158ef Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
716 3b7158ef Michael Hanselmann
                             ("/%s/instances/%s/modify" %
717 3b7158ef Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, body)
718 3b7158ef Michael Hanselmann
719 b680c8be Michael Hanselmann
  def ActivateInstanceDisks(self, instance, ignore_size=None):
720 b680c8be Michael Hanselmann
    """Activates an instance's disks.
721 b680c8be Michael Hanselmann

722 b680c8be Michael Hanselmann
    @type instance: string
723 b680c8be Michael Hanselmann
    @param instance: Instance name
724 b680c8be Michael Hanselmann
    @type ignore_size: bool
725 b680c8be Michael Hanselmann
    @param ignore_size: Whether to ignore recorded size
726 d914c76f Simeon Miteff
    @rtype: string
727 b680c8be Michael Hanselmann
    @return: job id
728 b680c8be Michael Hanselmann

729 b680c8be Michael Hanselmann
    """
730 b680c8be Michael Hanselmann
    query = []
731 b680c8be Michael Hanselmann
    if ignore_size:
732 b680c8be Michael Hanselmann
      query.append(("ignore_size", 1))
733 b680c8be Michael Hanselmann
734 b680c8be Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
735 b680c8be Michael Hanselmann
                             ("/%s/instances/%s/activate-disks" %
736 b680c8be Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
737 b680c8be Michael Hanselmann
738 b680c8be Michael Hanselmann
  def DeactivateInstanceDisks(self, instance):
739 b680c8be Michael Hanselmann
    """Deactivates an instance's disks.
740 b680c8be Michael Hanselmann

741 b680c8be Michael Hanselmann
    @type instance: string
742 b680c8be Michael Hanselmann
    @param instance: Instance name
743 d914c76f Simeon Miteff
    @rtype: string
744 b680c8be Michael Hanselmann
    @return: job id
745 b680c8be Michael Hanselmann

746 b680c8be Michael Hanselmann
    """
747 b680c8be Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
748 b680c8be Michael Hanselmann
                             ("/%s/instances/%s/deactivate-disks" %
749 b680c8be Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, None)
750 b680c8be Michael Hanselmann
751 e23881ed Michael Hanselmann
  def GrowInstanceDisk(self, instance, disk, amount, wait_for_sync=None):
752 e23881ed Michael Hanselmann
    """Grows a disk of an instance.
753 e23881ed Michael Hanselmann

754 e23881ed Michael Hanselmann
    More details for parameters can be found in the RAPI documentation.
755 e23881ed Michael Hanselmann

756 e23881ed Michael Hanselmann
    @type instance: string
757 e23881ed Michael Hanselmann
    @param instance: Instance name
758 e23881ed Michael Hanselmann
    @type disk: integer
759 e23881ed Michael Hanselmann
    @param disk: Disk index
760 e23881ed Michael Hanselmann
    @type amount: integer
761 e23881ed Michael Hanselmann
    @param amount: Grow disk by this amount (MiB)
762 e23881ed Michael Hanselmann
    @type wait_for_sync: bool
763 e23881ed Michael Hanselmann
    @param wait_for_sync: Wait for disk to synchronize
764 98805538 Michael Hanselmann
    @rtype: string
765 e23881ed Michael Hanselmann
    @return: job id
766 e23881ed Michael Hanselmann

767 e23881ed Michael Hanselmann
    """
768 e23881ed Michael Hanselmann
    body = {
769 e23881ed Michael Hanselmann
      "amount": amount,
770 e23881ed Michael Hanselmann
      }
771 e23881ed Michael Hanselmann
772 e23881ed Michael Hanselmann
    if wait_for_sync is not None:
773 e23881ed Michael Hanselmann
      body["wait_for_sync"] = wait_for_sync
774 e23881ed Michael Hanselmann
775 e23881ed Michael Hanselmann
    return self._SendRequest(HTTP_POST,
776 e23881ed Michael Hanselmann
                             ("/%s/instances/%s/disk/%s/grow" %
777 e23881ed Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance, disk)),
778 e23881ed Michael Hanselmann
                             None, body)
779 e23881ed Michael Hanselmann
780 95ab4de9 David Knowles
  def GetInstanceTags(self, instance):
781 95ab4de9 David Knowles
    """Gets tags for an instance.
782 95ab4de9 David Knowles

783 95ab4de9 David Knowles
    @type instance: str
784 95ab4de9 David Knowles
    @param instance: instance whose tags to return
785 95ab4de9 David Knowles

786 95ab4de9 David Knowles
    @rtype: list of str
787 95ab4de9 David Knowles
    @return: tags for the instance
788 95ab4de9 David Knowles

789 95ab4de9 David Knowles
    """
790 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
791 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/tags" %
792 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, None)
793 95ab4de9 David Knowles
794 95ab4de9 David Knowles
  def AddInstanceTags(self, instance, tags, dry_run=False):
795 95ab4de9 David Knowles
    """Adds tags to an instance.
796 95ab4de9 David Knowles

797 95ab4de9 David Knowles
    @type instance: str
798 95ab4de9 David Knowles
    @param instance: instance to add tags to
799 95ab4de9 David Knowles
    @type tags: list of str
800 95ab4de9 David Knowles
    @param tags: tags to add to the instance
801 95ab4de9 David Knowles
    @type dry_run: bool
802 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
803 95ab4de9 David Knowles

804 98805538 Michael Hanselmann
    @rtype: string
805 95ab4de9 David Knowles
    @return: job id
806 95ab4de9 David Knowles

807 95ab4de9 David Knowles
    """
808 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
809 95ab4de9 David Knowles
    if dry_run:
810 95ab4de9 David Knowles
      query.append(("dry-run", 1))
811 95ab4de9 David Knowles
812 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
813 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/tags" %
814 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
815 95ab4de9 David Knowles
816 95ab4de9 David Knowles
  def DeleteInstanceTags(self, instance, tags, dry_run=False):
817 95ab4de9 David Knowles
    """Deletes tags from an instance.
818 95ab4de9 David Knowles

819 95ab4de9 David Knowles
    @type instance: str
820 95ab4de9 David Knowles
    @param instance: instance to delete tags from
821 95ab4de9 David Knowles
    @type tags: list of str
822 95ab4de9 David Knowles
    @param tags: tags to delete
823 95ab4de9 David Knowles
    @type dry_run: bool
824 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
825 d914c76f Simeon Miteff
    @rtype: string
826 d914c76f Simeon Miteff
    @return: job id
827 95ab4de9 David Knowles

828 95ab4de9 David Knowles
    """
829 95ab4de9 David Knowles
    query = [("tag", t) for t in tags]
830 95ab4de9 David Knowles
    if dry_run:
831 95ab4de9 David Knowles
      query.append(("dry-run", 1))
832 95ab4de9 David Knowles
833 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_DELETE,
834 a198b2d9 Michael Hanselmann
                             ("/%s/instances/%s/tags" %
835 a198b2d9 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), query, None)
836 95ab4de9 David Knowles
837 95ab4de9 David Knowles
  def RebootInstance(self, instance, reboot_type=None, ignore_secondaries=None,
838 95ab4de9 David Knowles
                     dry_run=False):
839 95ab4de9 David Knowles
    """Reboots an instance.
840 95ab4de9 David Knowles

841 95ab4de9 David Knowles
    @type instance: str
842 95ab4de9 David Knowles
    @param instance: instance to rebot
843 95ab4de9 David Knowles
    @type reboot_type: str
844 95ab4de9 David Knowles
    @param reboot_type: one of: hard, soft, full
845 95ab4de9 David Knowles
    @type ignore_secondaries: bool
846 95ab4de9 David Knowles
    @param ignore_secondaries: if True, ignores errors for the secondary node
847 95ab4de9 David Knowles
        while re-assembling disks (in hard-reboot mode only)
848 95ab4de9 David Knowles
    @type dry_run: bool
849 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
850 d914c76f Simeon Miteff
    @rtype: string
851 d914c76f Simeon Miteff
    @return: job id
852 95ab4de9 David Knowles

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

869 95ab4de9 David Knowles
    @type instance: str
870 95ab4de9 David Knowles
    @param instance: the instance to shut down
871 95ab4de9 David Knowles
    @type dry_run: bool
872 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
873 d914c76f Simeon Miteff
    @rtype: string
874 d914c76f Simeon Miteff
    @return: job id
875 95ab4de9 David Knowles

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

888 95ab4de9 David Knowles
    @type instance: str
889 95ab4de9 David Knowles
    @param instance: the instance to start up
890 95ab4de9 David Knowles
    @type dry_run: bool
891 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
892 d914c76f Simeon Miteff
    @rtype: string
893 d914c76f Simeon Miteff
    @return: job id
894 95ab4de9 David Knowles

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

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

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

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

964 98805538 Michael Hanselmann
    @rtype: string
965 95ab4de9 David Knowles
    @return: job id
966 95ab4de9 David Knowles

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

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

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

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

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

1041 e0ac6ce6 Michael Hanselmann
    @type instance: string
1042 e0ac6ce6 Michael Hanselmann
    @param instance: Instance name
1043 e0ac6ce6 Michael Hanselmann
    @type mode: string
1044 e0ac6ce6 Michael Hanselmann
    @param mode: Migration mode
1045 e0ac6ce6 Michael Hanselmann
    @type cleanup: bool
1046 e0ac6ce6 Michael Hanselmann
    @param cleanup: Whether to clean up a previously failed migration
1047 d914c76f Simeon Miteff
    @rtype: string
1048 d914c76f Simeon Miteff
    @return: job id
1049 e0ac6ce6 Michael Hanselmann

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

1066 d654aae1 Michael Hanselmann
    @type instance: string
1067 d654aae1 Michael Hanselmann
    @param instance: Instance name
1068 d654aae1 Michael Hanselmann
    @type new_name: string
1069 d654aae1 Michael Hanselmann
    @param new_name: New instance name
1070 d654aae1 Michael Hanselmann
    @type ip_check: bool
1071 d654aae1 Michael Hanselmann
    @param ip_check: Whether to ensure instance's IP address is inactive
1072 d654aae1 Michael Hanselmann
    @type name_check: bool
1073 d654aae1 Michael Hanselmann
    @param name_check: Whether to ensure instance's name is resolvable
1074 d914c76f Simeon Miteff
    @rtype: string
1075 d914c76f Simeon Miteff
    @return: job id
1076 d654aae1 Michael Hanselmann

1077 d654aae1 Michael Hanselmann
    """
1078 d654aae1 Michael Hanselmann
    body = {
1079 d654aae1 Michael Hanselmann
      "new_name": new_name,
1080 d654aae1 Michael Hanselmann
      }
1081 d654aae1 Michael Hanselmann
1082 d654aae1 Michael Hanselmann
    if ip_check is not None:
1083 d654aae1 Michael Hanselmann
      body["ip_check"] = ip_check
1084 d654aae1 Michael Hanselmann
1085 d654aae1 Michael Hanselmann
    if name_check is not None:
1086 d654aae1 Michael Hanselmann
      body["name_check"] = name_check
1087 d654aae1 Michael Hanselmann
1088 d654aae1 Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1089 d654aae1 Michael Hanselmann
                             ("/%s/instances/%s/rename" %
1090 d654aae1 Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, body)
1091 d654aae1 Michael Hanselmann
1092 b82d4c5e Michael Hanselmann
  def GetInstanceConsole(self, instance):
1093 b82d4c5e Michael Hanselmann
    """Request information for connecting to instance's console.
1094 b82d4c5e Michael Hanselmann

1095 b82d4c5e Michael Hanselmann
    @type instance: string
1096 b82d4c5e Michael Hanselmann
    @param instance: Instance name
1097 d914c76f Simeon Miteff
    @rtype: dict
1098 d914c76f Simeon Miteff
    @return: dictionary containing information about instance's console
1099 b82d4c5e Michael Hanselmann

1100 b82d4c5e Michael Hanselmann
    """
1101 b82d4c5e Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1102 b82d4c5e Michael Hanselmann
                             ("/%s/instances/%s/console" %
1103 b82d4c5e Michael Hanselmann
                              (GANETI_RAPI_VERSION, instance)), None, None)
1104 b82d4c5e Michael Hanselmann
1105 95ab4de9 David Knowles
  def GetJobs(self):
1106 95ab4de9 David Knowles
    """Gets all jobs for the cluster.
1107 95ab4de9 David Knowles

1108 95ab4de9 David Knowles
    @rtype: list of int
1109 95ab4de9 David Knowles
    @return: job ids for the cluster
1110 95ab4de9 David Knowles

1111 95ab4de9 David Knowles
    """
1112 768747ed Michael Hanselmann
    return [int(j["id"])
1113 a198b2d9 Michael Hanselmann
            for j in self._SendRequest(HTTP_GET,
1114 a198b2d9 Michael Hanselmann
                                       "/%s/jobs" % GANETI_RAPI_VERSION,
1115 a198b2d9 Michael Hanselmann
                                       None, None)]
1116 95ab4de9 David Knowles
1117 95ab4de9 David Knowles
  def GetJobStatus(self, job_id):
1118 95ab4de9 David Knowles
    """Gets the status of a job.
1119 95ab4de9 David Knowles

1120 98805538 Michael Hanselmann
    @type job_id: string
1121 95ab4de9 David Knowles
    @param job_id: job id whose status to query
1122 95ab4de9 David Knowles

1123 95ab4de9 David Knowles
    @rtype: dict
1124 95ab4de9 David Knowles
    @return: job status
1125 95ab4de9 David Knowles

1126 95ab4de9 David Knowles
    """
1127 a198b2d9 Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1128 a198b2d9 Michael Hanselmann
                             "/%s/jobs/%s" % (GANETI_RAPI_VERSION, job_id),
1129 a198b2d9 Michael Hanselmann
                             None, None)
1130 95ab4de9 David Knowles
1131 16c13387 Theo Van Dinter
  def WaitForJobCompletion(self, job_id, period=5, retries=-1):
1132 16c13387 Theo Van Dinter
    """Polls cluster for job status until completion.
1133 16c13387 Theo Van Dinter

1134 dde0e97c Michael Hanselmann
    Completion is defined as any of the following states listed in
1135 dde0e97c Michael Hanselmann
    L{JOB_STATUS_FINALIZED}.
1136 16c13387 Theo Van Dinter

1137 cfda0e48 Iustin Pop
    @type job_id: string
1138 16c13387 Theo Van Dinter
    @param job_id: job id to watch
1139 16c13387 Theo Van Dinter
    @type period: int
1140 16c13387 Theo Van Dinter
    @param period: how often to poll for status (optional, default 5s)
1141 16c13387 Theo Van Dinter
    @type retries: int
1142 16c13387 Theo Van Dinter
    @param retries: how many time to poll before giving up
1143 16c13387 Theo Van Dinter
                    (optional, default -1 means unlimited)
1144 16c13387 Theo Van Dinter

1145 16c13387 Theo Van Dinter
    @rtype: bool
1146 dde0e97c Michael Hanselmann
    @return: C{True} if job succeeded or C{False} if failed/status timeout
1147 dde0e97c Michael Hanselmann
    @deprecated: It is recommended to use L{WaitForJobChange} wherever
1148 dde0e97c Michael Hanselmann
      possible; L{WaitForJobChange} returns immediately after a job changed and
1149 dde0e97c Michael Hanselmann
      does not use polling
1150 cfda0e48 Iustin Pop

1151 16c13387 Theo Van Dinter
    """
1152 16c13387 Theo Van Dinter
    while retries != 0:
1153 16c13387 Theo Van Dinter
      job_result = self.GetJobStatus(job_id)
1154 dde0e97c Michael Hanselmann
1155 dde0e97c Michael Hanselmann
      if job_result and job_result["status"] == JOB_STATUS_SUCCESS:
1156 16c13387 Theo Van Dinter
        return True
1157 dde0e97c Michael Hanselmann
      elif not job_result or job_result["status"] in JOB_STATUS_FINALIZED:
1158 dde0e97c Michael Hanselmann
        return False
1159 dde0e97c Michael Hanselmann
1160 dde0e97c Michael Hanselmann
      if period:
1161 dde0e97c Michael Hanselmann
        time.sleep(period)
1162 dde0e97c Michael Hanselmann
1163 16c13387 Theo Van Dinter
      if retries > 0:
1164 16c13387 Theo Van Dinter
        retries -= 1
1165 dde0e97c Michael Hanselmann
1166 16c13387 Theo Van Dinter
    return False
1167 16c13387 Theo Van Dinter
1168 d9b67f70 Michael Hanselmann
  def WaitForJobChange(self, job_id, fields, prev_job_info, prev_log_serial):
1169 d9b67f70 Michael Hanselmann
    """Waits for job changes.
1170 d9b67f70 Michael Hanselmann

1171 98805538 Michael Hanselmann
    @type job_id: string
1172 d9b67f70 Michael Hanselmann
    @param job_id: Job ID for which to wait
1173 d914c76f Simeon Miteff
    @return: C{None} if no changes have been detected and a dict with two keys,
1174 d914c76f Simeon Miteff
      C{job_info} and C{log_entries} otherwise.
1175 d914c76f Simeon Miteff
    @rtype: dict
1176 d9b67f70 Michael Hanselmann

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

1191 98805538 Michael Hanselmann
    @type job_id: string
1192 95ab4de9 David Knowles
    @param job_id: id of the job to delete
1193 95ab4de9 David Knowles
    @type dry_run: bool
1194 95ab4de9 David Knowles
    @param dry_run: whether to perform a dry run
1195 d914c76f Simeon Miteff
    @rtype: tuple
1196 d914c76f Simeon Miteff
    @return: tuple containing the result, and a message (bool, string)
1197 95ab4de9 David Knowles

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

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

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

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

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

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

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

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

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

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

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

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

1295 98805538 Michael Hanselmann
    @rtype: string
1296 95ab4de9 David Knowles
    @return: job id
1297 95ab4de9 David Knowles

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

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

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

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

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

1333 98805538 Michael Hanselmann
    @rtype: string
1334 95ab4de9 David Knowles
    @return: job id
1335 95ab4de9 David Knowles

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

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

1355 98805538 Michael Hanselmann
    @rtype: string
1356 95ab4de9 David Knowles
    @return: job id where results can be retrieved
1357 95ab4de9 David Knowles

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

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

1381 98805538 Michael Hanselmann
    @rtype: string
1382 95ab4de9 David Knowles
    @return: job id
1383 95ab4de9 David Knowles

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

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

1407 98805538 Michael Hanselmann
    @rtype: string
1408 95ab4de9 David Knowles
    @return: job id
1409 95ab4de9 David Knowles

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

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

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

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

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

1444 98805538 Michael Hanselmann
    @rtype: string
1445 95ab4de9 David Knowles
    @return: job id
1446 95ab4de9 David Knowles

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

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

1466 98805538 Michael Hanselmann
    @rtype: string
1467 95ab4de9 David Knowles
    @return: job id
1468 95ab4de9 David Knowles

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

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

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

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

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

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

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

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

1524 98805538 Michael Hanselmann
    @rtype: string
1525 a268af8d Adeodato Simo
    @return: job id
1526 a268af8d Adeodato Simo

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

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

1545 f18fab7d Adeodato Simo
    @type group: string
1546 f18fab7d Adeodato Simo
    @param group: Node group name
1547 98805538 Michael Hanselmann
    @rtype: string
1548 f18fab7d Adeodato Simo
    @return: job id
1549 f18fab7d Adeodato Simo

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

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

1563 98805538 Michael Hanselmann
    @rtype: string
1564 a268af8d Adeodato Simo
    @return: job id
1565 a268af8d Adeodato Simo

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

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

1583 98805538 Michael Hanselmann
    @rtype: string
1584 a268af8d Adeodato Simo
    @return: job id
1585 a268af8d Adeodato Simo

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

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

1603 98805538 Michael Hanselmann
    @rtype: string
1604 4245446f Adeodato Simo
    @return: job id
1605 4245446f Adeodato Simo

1606 4245446f Adeodato Simo
    """
1607 4245446f Adeodato Simo
    query = []
1608 4245446f Adeodato Simo
1609 4245446f Adeodato Simo
    if force:
1610 4245446f Adeodato Simo
      query.append(("force", 1))
1611 4245446f Adeodato Simo
1612 4245446f Adeodato Simo
    if dry_run:
1613 4245446f Adeodato Simo
      query.append(("dry-run", 1))
1614 4245446f Adeodato Simo
1615 4245446f Adeodato Simo
    body = {
1616 4245446f Adeodato Simo
      "nodes": nodes,
1617 4245446f Adeodato Simo
      }
1618 4245446f Adeodato Simo
1619 4245446f Adeodato Simo
    return self._SendRequest(HTTP_PUT,
1620 4245446f Adeodato Simo
                             ("/%s/groups/%s/assign-nodes" %
1621 4245446f Adeodato Simo
                             (GANETI_RAPI_VERSION, group)), query, body)
1622 208a6cff Michael Hanselmann
1623 208a6cff Michael Hanselmann
  def Query(self, what, fields, filter_=None):
1624 208a6cff Michael Hanselmann
    """Retrieves information about resources.
1625 208a6cff Michael Hanselmann

1626 208a6cff Michael Hanselmann
    @type what: string
1627 208a6cff Michael Hanselmann
    @param what: Resource name, one of L{constants.QR_VIA_RAPI}
1628 208a6cff Michael Hanselmann
    @type fields: list of string
1629 208a6cff Michael Hanselmann
    @param fields: Requested fields
1630 208a6cff Michael Hanselmann
    @type filter_: None or list
1631 d914c76f Simeon Miteff
    @param filter_: Query filter
1632 208a6cff Michael Hanselmann

1633 208a6cff Michael Hanselmann
    @rtype: string
1634 208a6cff Michael Hanselmann
    @return: job id
1635 208a6cff Michael Hanselmann

1636 208a6cff Michael Hanselmann
    """
1637 208a6cff Michael Hanselmann
    body = {
1638 208a6cff Michael Hanselmann
      "fields": fields,
1639 208a6cff Michael Hanselmann
      }
1640 208a6cff Michael Hanselmann
1641 208a6cff Michael Hanselmann
    if filter_ is not None:
1642 208a6cff Michael Hanselmann
      body["filter"] = filter_
1643 208a6cff Michael Hanselmann
1644 208a6cff Michael Hanselmann
    return self._SendRequest(HTTP_PUT,
1645 208a6cff Michael Hanselmann
                             ("/%s/query/%s" %
1646 208a6cff Michael Hanselmann
                              (GANETI_RAPI_VERSION, what)), None, body)
1647 208a6cff Michael Hanselmann
1648 208a6cff Michael Hanselmann
  def QueryFields(self, what, fields=None):
1649 208a6cff Michael Hanselmann
    """Retrieves available fields for a resource.
1650 208a6cff Michael Hanselmann

1651 208a6cff Michael Hanselmann
    @type what: string
1652 208a6cff Michael Hanselmann
    @param what: Resource name, one of L{constants.QR_VIA_RAPI}
1653 208a6cff Michael Hanselmann
    @type fields: list of string
1654 208a6cff Michael Hanselmann
    @param fields: Requested fields
1655 208a6cff Michael Hanselmann

1656 208a6cff Michael Hanselmann
    @rtype: string
1657 208a6cff Michael Hanselmann
    @return: job id
1658 208a6cff Michael Hanselmann

1659 208a6cff Michael Hanselmann
    """
1660 208a6cff Michael Hanselmann
    query = []
1661 208a6cff Michael Hanselmann
1662 208a6cff Michael Hanselmann
    if fields is not None:
1663 208a6cff Michael Hanselmann
      query.append(("fields", ",".join(fields)))
1664 208a6cff Michael Hanselmann
1665 208a6cff Michael Hanselmann
    return self._SendRequest(HTTP_GET,
1666 208a6cff Michael Hanselmann
                             ("/%s/query/%s/fields" %
1667 208a6cff Michael Hanselmann
                              (GANETI_RAPI_VERSION, what)), query, None)