Package ganeti :: Package rapi :: Module client
[hide private]
[frames] | no frames]

Source Code for Module ganeti.rapi.client

   1  # 
   2  # 
   3   
   4  # Copyright (C) 2010, 2011, 2012 Google Inc. 
   5  # All rights reserved. 
   6  # 
   7  # Redistribution and use in source and binary forms, with or without 
   8  # modification, are permitted provided that the following conditions are 
   9  # met: 
  10  # 
  11  # 1. Redistributions of source code must retain the above copyright notice, 
  12  # this list of conditions and the following disclaimer. 
  13  # 
  14  # 2. Redistributions in binary form must reproduce the above copyright 
  15  # notice, this list of conditions and the following disclaimer in the 
  16  # documentation and/or other materials provided with the distribution. 
  17  # 
  18  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 
  19  # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 
  20  # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
  21  # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 
  22  # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
  23  # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
  24  # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
  25  # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
  26  # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
  27  # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
  28  # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
  29   
  30   
  31  """Ganeti RAPI client. 
  32   
  33  @attention: To use the RAPI client, the application B{must} call 
  34              C{pycurl.global_init} during initialization and 
  35              C{pycurl.global_cleanup} before exiting the process. This is very 
  36              important in multi-threaded programs. See curl_global_init(3) and 
  37              curl_global_cleanup(3) for details. The decorator L{UsesRapiClient} 
  38              can be used. 
  39   
  40  """ 
  41   
  42  # No Ganeti-specific modules should be imported. The RAPI client is supposed to 
  43  # be standalone. 
  44   
  45  import logging 
  46  import simplejson 
  47  import socket 
  48  import urllib 
  49  import threading 
  50  import pycurl 
  51  import time 
  52   
  53  try: 
  54    from cStringIO import StringIO 
  55  except ImportError: 
  56    from StringIO import StringIO 
  57   
  58   
  59  GANETI_RAPI_PORT = 5080 
  60  GANETI_RAPI_VERSION = 2 
  61   
  62  HTTP_DELETE = "DELETE" 
  63  HTTP_GET = "GET" 
  64  HTTP_PUT = "PUT" 
  65  HTTP_POST = "POST" 
  66  HTTP_OK = 200 
  67  HTTP_NOT_FOUND = 404 
  68  HTTP_APP_JSON = "application/json" 
  69   
  70  REPLACE_DISK_PRI = "replace_on_primary" 
  71  REPLACE_DISK_SECONDARY = "replace_on_secondary" 
  72  REPLACE_DISK_CHG = "replace_new_secondary" 
  73  REPLACE_DISK_AUTO = "replace_auto" 
  74   
  75  NODE_EVAC_PRI = "primary-only" 
  76  NODE_EVAC_SEC = "secondary-only" 
  77  NODE_EVAC_ALL = "all" 
  78   
  79  NODE_ROLE_DRAINED = "drained" 
  80  NODE_ROLE_MASTER_CANDIATE = "master-candidate" 
  81  NODE_ROLE_MASTER = "master" 
  82  NODE_ROLE_OFFLINE = "offline" 
  83  NODE_ROLE_REGULAR = "regular" 
  84   
  85  JOB_STATUS_QUEUED = "queued" 
  86  JOB_STATUS_WAITING = "waiting" 
  87  JOB_STATUS_CANCELING = "canceling" 
  88  JOB_STATUS_RUNNING = "running" 
  89  JOB_STATUS_CANCELED = "canceled" 
  90  JOB_STATUS_SUCCESS = "success" 
  91  JOB_STATUS_ERROR = "error" 
  92  JOB_STATUS_PENDING = frozenset([ 
  93    JOB_STATUS_QUEUED, 
  94    JOB_STATUS_WAITING, 
  95    JOB_STATUS_CANCELING, 
  96    ]) 
  97  JOB_STATUS_FINALIZED = frozenset([ 
  98    JOB_STATUS_CANCELED, 
  99    JOB_STATUS_SUCCESS, 
 100    JOB_STATUS_ERROR, 
 101    ]) 
 102  JOB_STATUS_ALL = frozenset([ 
 103    JOB_STATUS_RUNNING, 
 104    ]) | JOB_STATUS_PENDING | JOB_STATUS_FINALIZED 
 105   
 106  # Legacy name 
 107  JOB_STATUS_WAITLOCK = JOB_STATUS_WAITING 
 108   
 109  # Internal constants 
 110  _REQ_DATA_VERSION_FIELD = "__version__" 
 111  _QPARAM_DRY_RUN = "dry-run" 
 112  _QPARAM_FORCE = "force" 
 113   
 114  # Feature strings 
 115  INST_CREATE_REQV1 = "instance-create-reqv1" 
 116  INST_REINSTALL_REQV1 = "instance-reinstall-reqv1" 
 117  NODE_MIGRATE_REQV1 = "node-migrate-reqv1" 
 118  NODE_EVAC_RES1 = "node-evac-res1" 
 119   
 120  # Old feature constant names in case they're references by users of this module 
 121  _INST_CREATE_REQV1 = INST_CREATE_REQV1 
 122  _INST_REINSTALL_REQV1 = INST_REINSTALL_REQV1 
 123  _NODE_MIGRATE_REQV1 = NODE_MIGRATE_REQV1 
 124  _NODE_EVAC_RES1 = NODE_EVAC_RES1 
 125   
 126  #: Resolver errors 
 127  ECODE_RESOLVER = "resolver_error" 
 128   
 129  #: Not enough resources (iallocator failure, disk space, memory, etc.) 
 130  ECODE_NORES = "insufficient_resources" 
 131   
 132  #: Temporarily out of resources; operation can be tried again 
 133  ECODE_TEMP_NORES = "temp_insufficient_resources" 
 134   
 135  #: Wrong arguments (at syntax level) 
 136  ECODE_INVAL = "wrong_input" 
 137   
 138  #: Wrong entity state 
 139  ECODE_STATE = "wrong_state" 
 140   
 141  #: Entity not found 
 142  ECODE_NOENT = "unknown_entity" 
 143   
 144  #: Entity already exists 
 145  ECODE_EXISTS = "already_exists" 
 146   
 147  #: Resource not unique (e.g. MAC or IP duplication) 
 148  ECODE_NOTUNIQUE = "resource_not_unique" 
 149   
 150  #: Internal cluster error 
 151  ECODE_FAULT = "internal_error" 
 152   
 153  #: Environment error (e.g. node disk error) 
 154  ECODE_ENVIRON = "environment_error" 
 155   
 156  #: List of all failure types 
 157  ECODE_ALL = frozenset([ 
 158    ECODE_RESOLVER, 
 159    ECODE_NORES, 
 160    ECODE_TEMP_NORES, 
 161    ECODE_INVAL, 
 162    ECODE_STATE, 
 163    ECODE_NOENT, 
 164    ECODE_EXISTS, 
 165    ECODE_NOTUNIQUE, 
 166    ECODE_FAULT, 
 167    ECODE_ENVIRON, 
 168    ]) 
 169   
 170  # Older pycURL versions don't have all error constants 
 171  try: 
 172    _CURLE_SSL_CACERT = pycurl.E_SSL_CACERT 
 173    _CURLE_SSL_CACERT_BADFILE = pycurl.E_SSL_CACERT_BADFILE 
 174  except AttributeError: 
 175    _CURLE_SSL_CACERT = 60 
 176    _CURLE_SSL_CACERT_BADFILE = 77 
 177   
 178  _CURL_SSL_CERT_ERRORS = frozenset([ 
 179    _CURLE_SSL_CACERT, 
 180    _CURLE_SSL_CACERT_BADFILE, 
 181    ]) 
182 183 184 -class Error(Exception):
185 """Base error class for this module. 186 187 """ 188 pass
189
190 191 -class GanetiApiError(Error):
192 """Generic error raised from Ganeti API. 193 194 """
195 - def __init__(self, msg, code=None):
196 Error.__init__(self, msg) 197 self.code = code
198
199 200 -class CertificateError(GanetiApiError):
201 """Raised when a problem is found with the SSL certificate. 202 203 """ 204 pass
205
206 207 -def _AppendIf(container, condition, value):
208 """Appends to a list if a condition evaluates to truth. 209 210 """ 211 if condition: 212 container.append(value) 213 214 return condition
215
216 217 -def _AppendDryRunIf(container, condition):
218 """Appends a "dry-run" parameter if a condition evaluates to truth. 219 220 """ 221 return _AppendIf(container, condition, (_QPARAM_DRY_RUN, 1))
222
223 224 -def _AppendForceIf(container, condition):
225 """Appends a "force" parameter if a condition evaluates to truth. 226 227 """ 228 return _AppendIf(container, condition, (_QPARAM_FORCE, 1))
229
230 231 -def _AppendReason(container, reason):
232 """Appends an element to the reason trail. 233 234 If the user provided a reason, it is added to the reason trail. 235 236 """ 237 return _AppendIf(container, reason, ("reason", reason))
238
239 240 -def _SetItemIf(container, condition, item, value):
241 """Sets an item if a condition evaluates to truth. 242 243 """ 244 if condition: 245 container[item] = value 246 247 return condition
248
249 250 -def UsesRapiClient(fn):
251 """Decorator for code using RAPI client to initialize pycURL. 252 253 """ 254 def wrapper(*args, **kwargs): 255 # curl_global_init(3) and curl_global_cleanup(3) must be called with only 256 # one thread running. This check is just a safety measure -- it doesn't 257 # cover all cases. 258 assert threading.activeCount() == 1, \ 259 "Found active threads when initializing pycURL" 260 261 pycurl.global_init(pycurl.GLOBAL_ALL) 262 try: 263 return fn(*args, **kwargs) 264 finally: 265 pycurl.global_cleanup()
266 267 return wrapper 268
269 270 -def GenericCurlConfig(verbose=False, use_signal=False, 271 use_curl_cabundle=False, cafile=None, capath=None, 272 proxy=None, verify_hostname=False, 273 connect_timeout=None, timeout=None, 274 _pycurl_version_fn=pycurl.version_info):
275 """Curl configuration function generator. 276 277 @type verbose: bool 278 @param verbose: Whether to set cURL to verbose mode 279 @type use_signal: bool 280 @param use_signal: Whether to allow cURL to use signals 281 @type use_curl_cabundle: bool 282 @param use_curl_cabundle: Whether to use cURL's default CA bundle 283 @type cafile: string 284 @param cafile: In which file we can find the certificates 285 @type capath: string 286 @param capath: In which directory we can find the certificates 287 @type proxy: string 288 @param proxy: Proxy to use, None for default behaviour and empty string for 289 disabling proxies (see curl_easy_setopt(3)) 290 @type verify_hostname: bool 291 @param verify_hostname: Whether to verify the remote peer certificate's 292 commonName 293 @type connect_timeout: number 294 @param connect_timeout: Timeout for establishing connection in seconds 295 @type timeout: number 296 @param timeout: Timeout for complete transfer in seconds (see 297 curl_easy_setopt(3)). 298 299 """ 300 if use_curl_cabundle and (cafile or capath): 301 raise Error("Can not use default CA bundle when CA file or path is set") 302 303 def _ConfigCurl(curl, logger): 304 """Configures a cURL object 305 306 @type curl: pycurl.Curl 307 @param curl: cURL object 308 309 """ 310 logger.debug("Using cURL version %s", pycurl.version) 311 312 # pycurl.version_info returns a tuple with information about the used 313 # version of libcurl. Item 5 is the SSL library linked to it. 314 # e.g.: (3, '7.18.0', 463360, 'x86_64-pc-linux-gnu', 1581, 'GnuTLS/2.0.4', 315 # 0, '1.2.3.3', ...) 316 sslver = _pycurl_version_fn()[5] 317 if not sslver: 318 raise Error("No SSL support in cURL") 319 320 lcsslver = sslver.lower() 321 if lcsslver.startswith("openssl/"): 322 pass 323 elif lcsslver.startswith("nss/"): 324 # TODO: investigate compatibility beyond a simple test 325 pass 326 elif lcsslver.startswith("gnutls/"): 327 if capath: 328 raise Error("cURL linked against GnuTLS has no support for a" 329 " CA path (%s)" % (pycurl.version, )) 330 else: 331 raise NotImplementedError("cURL uses unsupported SSL version '%s'" % 332 sslver) 333 334 curl.setopt(pycurl.VERBOSE, verbose) 335 curl.setopt(pycurl.NOSIGNAL, not use_signal) 336 337 # Whether to verify remote peer's CN 338 if verify_hostname: 339 # curl_easy_setopt(3): "When CURLOPT_SSL_VERIFYHOST is 2, that 340 # certificate must indicate that the server is the server to which you 341 # meant to connect, or the connection fails. [...] When the value is 1, 342 # the certificate must contain a Common Name field, but it doesn't matter 343 # what name it says. [...]" 344 curl.setopt(pycurl.SSL_VERIFYHOST, 2) 345 else: 346 curl.setopt(pycurl.SSL_VERIFYHOST, 0) 347 348 if cafile or capath or use_curl_cabundle: 349 # Require certificates to be checked 350 curl.setopt(pycurl.SSL_VERIFYPEER, True) 351 if cafile: 352 curl.setopt(pycurl.CAINFO, str(cafile)) 353 if capath: 354 curl.setopt(pycurl.CAPATH, str(capath)) 355 # Not changing anything for using default CA bundle 356 else: 357 # Disable SSL certificate verification 358 curl.setopt(pycurl.SSL_VERIFYPEER, False) 359 360 if proxy is not None: 361 curl.setopt(pycurl.PROXY, str(proxy)) 362 363 # Timeouts 364 if connect_timeout is not None: 365 curl.setopt(pycurl.CONNECTTIMEOUT, connect_timeout) 366 if timeout is not None: 367 curl.setopt(pycurl.TIMEOUT, timeout)
368 369 return _ConfigCurl 370
371 372 -class GanetiRapiClient(object): # pylint: disable=R0904
373 """Ganeti RAPI client. 374 375 """ 376 USER_AGENT = "Ganeti RAPI Client" 377 _json_encoder = simplejson.JSONEncoder(sort_keys=True) 378
379 - def __init__(self, host, port=GANETI_RAPI_PORT, 380 username=None, password=None, logger=logging, 381 curl_config_fn=None, curl_factory=None):
382 """Initializes this class. 383 384 @type host: string 385 @param host: the ganeti cluster master to interact with 386 @type port: int 387 @param port: the port on which the RAPI is running (default is 5080) 388 @type username: string 389 @param username: the username to connect with 390 @type password: string 391 @param password: the password to connect with 392 @type curl_config_fn: callable 393 @param curl_config_fn: Function to configure C{pycurl.Curl} object 394 @param logger: Logging object 395 396 """ 397 self._username = username 398 self._password = password 399 self._logger = logger 400 self._curl_config_fn = curl_config_fn 401 self._curl_factory = curl_factory 402 403 try: 404 socket.inet_pton(socket.AF_INET6, host) 405 address = "[%s]:%s" % (host, port) 406 except socket.error: 407 address = "%s:%s" % (host, port) 408 409 self._base_url = "https://%s" % address 410 411 if username is not None: 412 if password is None: 413 raise Error("Password not specified") 414 elif password: 415 raise Error("Specified password without username")
416
417 - def _CreateCurl(self):
418 """Creates a cURL object. 419 420 """ 421 # Create pycURL object if no factory is provided 422 if self._curl_factory: 423 curl = self._curl_factory() 424 else: 425 curl = pycurl.Curl() 426 427 # Default cURL settings 428 curl.setopt(pycurl.VERBOSE, False) 429 curl.setopt(pycurl.FOLLOWLOCATION, False) 430 curl.setopt(pycurl.MAXREDIRS, 5) 431 curl.setopt(pycurl.NOSIGNAL, True) 432 curl.setopt(pycurl.USERAGENT, self.USER_AGENT) 433 curl.setopt(pycurl.SSL_VERIFYHOST, 0) 434 curl.setopt(pycurl.SSL_VERIFYPEER, False) 435 curl.setopt(pycurl.HTTPHEADER, [ 436 "Accept: %s" % HTTP_APP_JSON, 437 "Content-type: %s" % HTTP_APP_JSON, 438 ]) 439 440 assert ((self._username is None and self._password is None) ^ 441 (self._username is not None and self._password is not None)) 442 443 if self._username: 444 # Setup authentication 445 curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_BASIC) 446 curl.setopt(pycurl.USERPWD, 447 str("%s:%s" % (self._username, self._password))) 448 449 # Call external configuration function 450 if self._curl_config_fn: 451 self._curl_config_fn(curl, self._logger) 452 453 return curl
454 455 @staticmethod
456 - def _EncodeQuery(query):
457 """Encode query values for RAPI URL. 458 459 @type query: list of two-tuples 460 @param query: Query arguments 461 @rtype: list 462 @return: Query list with encoded values 463 464 """ 465 result = [] 466 467 for name, value in query: 468 if value is None: 469 result.append((name, "")) 470 471 elif isinstance(value, bool): 472 # Boolean values must be encoded as 0 or 1 473 result.append((name, int(value))) 474 475 elif isinstance(value, (list, tuple, dict)): 476 raise ValueError("Invalid query data type %r" % type(value).__name__) 477 478 else: 479 result.append((name, value)) 480 481 return result
482
483 - def _SendRequest(self, method, path, query, content):
484 """Sends an HTTP request. 485 486 This constructs a full URL, encodes and decodes HTTP bodies, and 487 handles invalid responses in a pythonic way. 488 489 @type method: string 490 @param method: HTTP method to use 491 @type path: string 492 @param path: HTTP URL path 493 @type query: list of two-tuples 494 @param query: query arguments to pass to urllib.urlencode 495 @type content: str or None 496 @param content: HTTP body content 497 498 @rtype: str 499 @return: JSON-Decoded response 500 501 @raises CertificateError: If an invalid SSL certificate is found 502 @raises GanetiApiError: If an invalid response is returned 503 504 """ 505 assert path.startswith("/") 506 507 curl = self._CreateCurl() 508 509 if content is not None: 510 encoded_content = self._json_encoder.encode(content) 511 else: 512 encoded_content = "" 513 514 # Build URL 515 urlparts = [self._base_url, path] 516 if query: 517 urlparts.append("?") 518 urlparts.append(urllib.urlencode(self._EncodeQuery(query))) 519 520 url = "".join(urlparts) 521 522 self._logger.debug("Sending request %s %s (content=%r)", 523 method, url, encoded_content) 524 525 # Buffer for response 526 encoded_resp_body = StringIO() 527 528 # Configure cURL 529 curl.setopt(pycurl.CUSTOMREQUEST, str(method)) 530 curl.setopt(pycurl.URL, str(url)) 531 curl.setopt(pycurl.POSTFIELDS, str(encoded_content)) 532 curl.setopt(pycurl.WRITEFUNCTION, encoded_resp_body.write) 533 534 try: 535 # Send request and wait for response 536 try: 537 curl.perform() 538 except pycurl.error, err: 539 if err.args[0] in _CURL_SSL_CERT_ERRORS: 540 raise CertificateError("SSL certificate error %s" % err, 541 code=err.args[0]) 542 543 raise GanetiApiError(str(err), code=err.args[0]) 544 finally: 545 # Reset settings to not keep references to large objects in memory 546 # between requests 547 curl.setopt(pycurl.POSTFIELDS, "") 548 curl.setopt(pycurl.WRITEFUNCTION, lambda _: None) 549 550 # Get HTTP response code 551 http_code = curl.getinfo(pycurl.RESPONSE_CODE) 552 553 # Was anything written to the response buffer? 554 if encoded_resp_body.tell(): 555 response_content = simplejson.loads(encoded_resp_body.getvalue()) 556 else: 557 response_content = None 558 559 if http_code != HTTP_OK: 560 if isinstance(response_content, dict): 561 msg = ("%s %s: %s" % 562 (response_content["code"], 563 response_content["message"], 564 response_content["explain"])) 565 else: 566 msg = str(response_content) 567 568 raise GanetiApiError(msg, code=http_code) 569 570 return response_content
571
572 - def GetVersion(self):
573 """Gets the Remote API version running on the cluster. 574 575 @rtype: int 576 @return: Ganeti Remote API version 577 578 """ 579 return self._SendRequest(HTTP_GET, "/version", None, None)
580
581 - def GetFeatures(self):
582 """Gets the list of optional features supported by RAPI server. 583 584 @rtype: list 585 @return: List of optional features 586 587 """ 588 try: 589 return self._SendRequest(HTTP_GET, "/%s/features" % GANETI_RAPI_VERSION, 590 None, None) 591 except GanetiApiError, err: 592 # Older RAPI servers don't support this resource 593 if err.code == HTTP_NOT_FOUND: 594 return [] 595 596 raise
597
598 - def GetOperatingSystems(self, reason=None):
599 """Gets the Operating Systems running in the Ganeti cluster. 600 601 @rtype: list of str 602 @return: operating systems 603 @type reason: string 604 @param reason: the reason for executing this operation 605 606 """ 607 query = [] 608 _AppendReason(query, reason) 609 return self._SendRequest(HTTP_GET, "/%s/os" % GANETI_RAPI_VERSION, 610 query, None)
611
612 - def GetInfo(self, reason=None):
613 """Gets info about the cluster. 614 615 @type reason: string 616 @param reason: the reason for executing this operation 617 @rtype: dict 618 @return: information about the cluster 619 620 """ 621 query = [] 622 _AppendReason(query, reason) 623 return self._SendRequest(HTTP_GET, "/%s/info" % GANETI_RAPI_VERSION, 624 query, None)
625
626 - def RedistributeConfig(self, reason=None):
627 """Tells the cluster to redistribute its configuration files. 628 629 @type reason: string 630 @param reason: the reason for executing this operation 631 @rtype: string 632 @return: job id 633 634 """ 635 query = [] 636 _AppendReason(query, reason) 637 return self._SendRequest(HTTP_PUT, 638 "/%s/redistribute-config" % GANETI_RAPI_VERSION, 639 query, None)
640
641 - def ModifyCluster(self, reason=None, **kwargs):
642 """Modifies cluster parameters. 643 644 More details for parameters can be found in the RAPI documentation. 645 646 @type reason: string 647 @param reason: the reason for executing this operation 648 @rtype: string 649 @return: job id 650 651 """ 652 query = [] 653 _AppendReason(query, reason) 654 655 body = kwargs 656 657 return self._SendRequest(HTTP_PUT, 658 "/%s/modify" % GANETI_RAPI_VERSION, query, body)
659
660 - def GetClusterTags(self, reason=None):
661 """Gets the cluster tags. 662 663 @type reason: string 664 @param reason: the reason for executing this operation 665 @rtype: list of str 666 @return: cluster tags 667 668 """ 669 query = [] 670 _AppendReason(query, reason) 671 return self._SendRequest(HTTP_GET, "/%s/tags" % GANETI_RAPI_VERSION, 672 query, None)
673
674 - def AddClusterTags(self, tags, dry_run=False, reason=None):
675 """Adds tags to the cluster. 676 677 @type tags: list of str 678 @param tags: tags to add to the cluster 679 @type dry_run: bool 680 @param dry_run: whether to perform a dry run 681 @type reason: string 682 @param reason: the reason for executing this operation 683 684 @rtype: string 685 @return: job id 686 687 """ 688 query = [("tag", t) for t in tags] 689 _AppendDryRunIf(query, dry_run) 690 _AppendReason(query, reason) 691 692 return self._SendRequest(HTTP_PUT, "/%s/tags" % GANETI_RAPI_VERSION, 693 query, None)
694
695 - def DeleteClusterTags(self, tags, dry_run=False, reason=None):
696 """Deletes tags from the cluster. 697 698 @type tags: list of str 699 @param tags: tags to delete 700 @type dry_run: bool 701 @param dry_run: whether to perform a dry run 702 @type reason: string 703 @param reason: the reason for executing this operation 704 @rtype: string 705 @return: job id 706 707 """ 708 query = [("tag", t) for t in tags] 709 _AppendDryRunIf(query, dry_run) 710 _AppendReason(query, reason) 711 712 return self._SendRequest(HTTP_DELETE, "/%s/tags" % GANETI_RAPI_VERSION, 713 query, None)
714
715 - def GetInstances(self, bulk=False, reason=None):
716 """Gets information about instances on the cluster. 717 718 @type bulk: bool 719 @param bulk: whether to return all information about all instances 720 @type reason: string 721 @param reason: the reason for executing this operation 722 723 @rtype: list of dict or list of str 724 @return: if bulk is True, info about the instances, else a list of instances 725 726 """ 727 query = [] 728 _AppendIf(query, bulk, ("bulk", 1)) 729 _AppendReason(query, reason) 730 731 instances = self._SendRequest(HTTP_GET, 732 "/%s/instances" % GANETI_RAPI_VERSION, 733 query, None) 734 if bulk: 735 return instances 736 else: 737 return [i["id"] for i in instances]
738
739 - def GetInstance(self, instance, reason=None):
740 """Gets information about an instance. 741 742 @type instance: str 743 @param instance: instance whose info to return 744 @type reason: string 745 @param reason: the reason for executing this operation 746 747 @rtype: dict 748 @return: info about the instance 749 750 """ 751 query = [] 752 _AppendReason(query, reason) 753 754 return self._SendRequest(HTTP_GET, 755 ("/%s/instances/%s" % 756 (GANETI_RAPI_VERSION, instance)), query, None)
757
758 - def GetInstanceInfo(self, instance, static=None, reason=None):
759 """Gets information about an instance. 760 761 @type instance: string 762 @param instance: Instance name 763 @type reason: string 764 @param reason: the reason for executing this operation 765 @rtype: string 766 @return: Job ID 767 768 """ 769 query = [] 770 if static is not None: 771 query.append(("static", static)) 772 _AppendReason(query, reason) 773 774 return self._SendRequest(HTTP_GET, 775 ("/%s/instances/%s/info" % 776 (GANETI_RAPI_VERSION, instance)), query, None)
777 778 @staticmethod
779 - def _UpdateWithKwargs(base, **kwargs):
780 """Updates the base with params from kwargs. 781 782 @param base: The base dict, filled with required fields 783 784 @note: This is an inplace update of base 785 786 """ 787 conflicts = set(kwargs.iterkeys()) & set(base.iterkeys()) 788 if conflicts: 789 raise GanetiApiError("Required fields can not be specified as" 790 " keywords: %s" % ", ".join(conflicts)) 791 792 base.update((key, value) for key, value in kwargs.iteritems() 793 if key != "dry_run")
794
795 - def InstanceAllocation(self, mode, name, disk_template, disks, nics, 796 **kwargs):
797 """Generates an instance allocation as used by multiallocate. 798 799 More details for parameters can be found in the RAPI documentation. 800 It is the same as used by CreateInstance. 801 802 @type mode: string 803 @param mode: Instance creation mode 804 @type name: string 805 @param name: Hostname of the instance to create 806 @type disk_template: string 807 @param disk_template: Disk template for instance (e.g. plain, diskless, 808 file, or drbd) 809 @type disks: list of dicts 810 @param disks: List of disk definitions 811 @type nics: list of dicts 812 @param nics: List of NIC definitions 813 814 @return: A dict with the generated entry 815 816 """ 817 # All required fields for request data version 1 818 alloc = { 819 "mode": mode, 820 "name": name, 821 "disk_template": disk_template, 822 "disks": disks, 823 "nics": nics, 824 } 825 826 self._UpdateWithKwargs(alloc, **kwargs) 827 828 return alloc
829
830 - def InstancesMultiAlloc(self, instances, reason=None, **kwargs):
831 """Tries to allocate multiple instances. 832 833 More details for parameters can be found in the RAPI documentation. 834 835 @param instances: A list of L{InstanceAllocation} results 836 837 """ 838 query = [] 839 body = { 840 "instances": instances, 841 } 842 self._UpdateWithKwargs(body, **kwargs) 843 844 _AppendDryRunIf(query, kwargs.get("dry_run")) 845 _AppendReason(query, reason) 846 847 return self._SendRequest(HTTP_POST, 848 "/%s/instances-multi-alloc" % GANETI_RAPI_VERSION, 849 query, body)
850
851 - def CreateInstance(self, mode, name, disk_template, disks, nics, 852 reason=None, **kwargs):
853 """Creates a new instance. 854 855 More details for parameters can be found in the RAPI documentation. 856 857 @type mode: string 858 @param mode: Instance creation mode 859 @type name: string 860 @param name: Hostname of the instance to create 861 @type disk_template: string 862 @param disk_template: Disk template for instance (e.g. plain, diskless, 863 file, or drbd) 864 @type disks: list of dicts 865 @param disks: List of disk definitions 866 @type nics: list of dicts 867 @param nics: List of NIC definitions 868 @type dry_run: bool 869 @keyword dry_run: whether to perform a dry run 870 @type reason: string 871 @param reason: the reason for executing this operation 872 873 @rtype: string 874 @return: job id 875 876 """ 877 query = [] 878 879 _AppendDryRunIf(query, kwargs.get("dry_run")) 880 _AppendReason(query, reason) 881 882 if _INST_CREATE_REQV1 in self.GetFeatures(): 883 body = self.InstanceAllocation(mode, name, disk_template, disks, nics, 884 **kwargs) 885 body[_REQ_DATA_VERSION_FIELD] = 1 886 else: 887 raise GanetiApiError("Server does not support new-style (version 1)" 888 " instance creation requests") 889 890 return self._SendRequest(HTTP_POST, "/%s/instances" % GANETI_RAPI_VERSION, 891 query, body)
892
893 - def DeleteInstance(self, instance, dry_run=False, reason=None, **kwargs):
894 """Deletes an instance. 895 896 @type instance: str 897 @param instance: the instance to delete 898 @type reason: string 899 @param reason: the reason for executing this operation 900 901 @rtype: string 902 @return: job id 903 904 """ 905 query = [] 906 body = kwargs 907 908 _AppendDryRunIf(query, dry_run) 909 _AppendReason(query, reason) 910 911 return self._SendRequest(HTTP_DELETE, 912 ("/%s/instances/%s" % 913 (GANETI_RAPI_VERSION, instance)), query, body)
914
915 - def ModifyInstance(self, instance, reason=None, **kwargs):
916 """Modifies an instance. 917 918 More details for parameters can be found in the RAPI documentation. 919 920 @type instance: string 921 @param instance: Instance name 922 @type reason: string 923 @param reason: the reason for executing this operation 924 @rtype: string 925 @return: job id 926 927 """ 928 body = kwargs 929 query = [] 930 _AppendReason(query, reason) 931 932 return self._SendRequest(HTTP_PUT, 933 ("/%s/instances/%s/modify" % 934 (GANETI_RAPI_VERSION, instance)), query, body)
935
936 - def ActivateInstanceDisks(self, instance, ignore_size=None, reason=None):
937 """Activates an instance's disks. 938 939 @type instance: string 940 @param instance: Instance name 941 @type ignore_size: bool 942 @param ignore_size: Whether to ignore recorded size 943 @type reason: string 944 @param reason: the reason for executing this operation 945 @rtype: string 946 @return: job id 947 948 """ 949 query = [] 950 _AppendIf(query, ignore_size, ("ignore_size", 1)) 951 _AppendReason(query, reason) 952 953 return self._SendRequest(HTTP_PUT, 954 ("/%s/instances/%s/activate-disks" % 955 (GANETI_RAPI_VERSION, instance)), query, None)
956
957 - def DeactivateInstanceDisks(self, instance, reason=None):
958 """Deactivates an instance's disks. 959 960 @type instance: string 961 @param instance: Instance name 962 @type reason: string 963 @param reason: the reason for executing this operation 964 @rtype: string 965 @return: job id 966 967 """ 968 query = [] 969 _AppendReason(query, reason) 970 return self._SendRequest(HTTP_PUT, 971 ("/%s/instances/%s/deactivate-disks" % 972 (GANETI_RAPI_VERSION, instance)), query, None)
973
974 - def RecreateInstanceDisks(self, instance, disks=None, nodes=None, 975 reason=None):
976 """Recreate an instance's disks. 977 978 @type instance: string 979 @param instance: Instance name 980 @type disks: list of int 981 @param disks: List of disk indexes 982 @type nodes: list of string 983 @param nodes: New instance nodes, if relocation is desired 984 @type reason: string 985 @param reason: the reason for executing this operation 986 @rtype: string 987 @return: job id 988 989 """ 990 body = {} 991 _SetItemIf(body, disks is not None, "disks", disks) 992 _SetItemIf(body, nodes is not None, "nodes", nodes) 993 994 query = [] 995 _AppendReason(query, reason) 996 997 return self._SendRequest(HTTP_POST, 998 ("/%s/instances/%s/recreate-disks" % 999 (GANETI_RAPI_VERSION, instance)), query, body)
1000
1001 - def GrowInstanceDisk(self, instance, disk, amount, wait_for_sync=None, 1002 reason=None):
1003 """Grows a disk of an instance. 1004 1005 More details for parameters can be found in the RAPI documentation. 1006 1007 @type instance: string 1008 @param instance: Instance name 1009 @type disk: integer 1010 @param disk: Disk index 1011 @type amount: integer 1012 @param amount: Grow disk by this amount (MiB) 1013 @type wait_for_sync: bool 1014 @param wait_for_sync: Wait for disk to synchronize 1015 @type reason: string 1016 @param reason: the reason for executing this operation 1017 @rtype: string 1018 @return: job id 1019 1020 """ 1021 body = { 1022 "amount": amount, 1023 } 1024 1025 _SetItemIf(body, wait_for_sync is not None, "wait_for_sync", wait_for_sync) 1026 1027 query = [] 1028 _AppendReason(query, reason) 1029 1030 return self._SendRequest(HTTP_POST, 1031 ("/%s/instances/%s/disk/%s/grow" % 1032 (GANETI_RAPI_VERSION, instance, disk)), 1033 query, body)
1034
1035 - def GetInstanceTags(self, instance, reason=None):
1036 """Gets tags for an instance. 1037 1038 @type instance: str 1039 @param instance: instance whose tags to return 1040 @type reason: string 1041 @param reason: the reason for executing this operation 1042 1043 @rtype: list of str 1044 @return: tags for the instance 1045 1046 """ 1047 query = [] 1048 _AppendReason(query, reason) 1049 return self._SendRequest(HTTP_GET, 1050 ("/%s/instances/%s/tags" % 1051 (GANETI_RAPI_VERSION, instance)), query, None)
1052
1053 - def AddInstanceTags(self, instance, tags, dry_run=False, reason=None):
1054 """Adds tags to an instance. 1055 1056 @type instance: str 1057 @param instance: instance to add tags to 1058 @type tags: list of str 1059 @param tags: tags to add to the instance 1060 @type dry_run: bool 1061 @param dry_run: whether to perform a dry run 1062 @type reason: string 1063 @param reason: the reason for executing this operation 1064 1065 @rtype: string 1066 @return: job id 1067 1068 """ 1069 query = [("tag", t) for t in tags] 1070 _AppendDryRunIf(query, dry_run) 1071 _AppendReason(query, reason) 1072 1073 return self._SendRequest(HTTP_PUT, 1074 ("/%s/instances/%s/tags" % 1075 (GANETI_RAPI_VERSION, instance)), query, None)
1076
1077 - def DeleteInstanceTags(self, instance, tags, dry_run=False, reason=None):
1078 """Deletes tags from an instance. 1079 1080 @type instance: str 1081 @param instance: instance to delete tags from 1082 @type tags: list of str 1083 @param tags: tags to delete 1084 @type dry_run: bool 1085 @param dry_run: whether to perform a dry run 1086 @type reason: string 1087 @param reason: the reason for executing this operation 1088 @rtype: string 1089 @return: job id 1090 1091 """ 1092 query = [("tag", t) for t in tags] 1093 _AppendDryRunIf(query, dry_run) 1094 _AppendReason(query, reason) 1095 1096 return self._SendRequest(HTTP_DELETE, 1097 ("/%s/instances/%s/tags" % 1098 (GANETI_RAPI_VERSION, instance)), query, None)
1099
1100 - def RebootInstance(self, instance, reboot_type=None, ignore_secondaries=None, 1101 dry_run=False, reason=None, **kwargs):
1102 """Reboots an instance. 1103 1104 @type instance: str 1105 @param instance: instance to reboot 1106 @type reboot_type: str 1107 @param reboot_type: one of: hard, soft, full 1108 @type ignore_secondaries: bool 1109 @param ignore_secondaries: if True, ignores errors for the secondary node 1110 while re-assembling disks (in hard-reboot mode only) 1111 @type dry_run: bool 1112 @param dry_run: whether to perform a dry run 1113 @type reason: string 1114 @param reason: the reason for the reboot 1115 @rtype: string 1116 @return: job id 1117 1118 """ 1119 query = [] 1120 body = kwargs 1121 1122 _AppendDryRunIf(query, dry_run) 1123 _AppendIf(query, reboot_type, ("type", reboot_type)) 1124 _AppendIf(query, ignore_secondaries is not None, 1125 ("ignore_secondaries", ignore_secondaries)) 1126 _AppendReason(query, reason) 1127 1128 return self._SendRequest(HTTP_POST, 1129 ("/%s/instances/%s/reboot" % 1130 (GANETI_RAPI_VERSION, instance)), query, body)
1131
1132 - def ShutdownInstance(self, instance, dry_run=False, no_remember=False, 1133 reason=None, **kwargs):
1134 """Shuts down an instance. 1135 1136 @type instance: str 1137 @param instance: the instance to shut down 1138 @type dry_run: bool 1139 @param dry_run: whether to perform a dry run 1140 @type no_remember: bool 1141 @param no_remember: if true, will not record the state change 1142 @type reason: string 1143 @param reason: the reason for the shutdown 1144 @rtype: string 1145 @return: job id 1146 1147 """ 1148 query = [] 1149 body = kwargs 1150 1151 _AppendDryRunIf(query, dry_run) 1152 _AppendIf(query, no_remember, ("no_remember", 1)) 1153 _AppendReason(query, reason) 1154 1155 return self._SendRequest(HTTP_PUT, 1156 ("/%s/instances/%s/shutdown" % 1157 (GANETI_RAPI_VERSION, instance)), query, body)
1158
1159 - def StartupInstance(self, instance, dry_run=False, no_remember=False, 1160 reason=None):
1161 """Starts up an instance. 1162 1163 @type instance: str 1164 @param instance: the instance to start up 1165 @type dry_run: bool 1166 @param dry_run: whether to perform a dry run 1167 @type no_remember: bool 1168 @param no_remember: if true, will not record the state change 1169 @type reason: string 1170 @param reason: the reason for the startup 1171 @rtype: string 1172 @return: job id 1173 1174 """ 1175 query = [] 1176 _AppendDryRunIf(query, dry_run) 1177 _AppendIf(query, no_remember, ("no_remember", 1)) 1178 _AppendReason(query, reason) 1179 1180 return self._SendRequest(HTTP_PUT, 1181 ("/%s/instances/%s/startup" % 1182 (GANETI_RAPI_VERSION, instance)), query, None)
1183
1184 - def ReinstallInstance(self, instance, os=None, no_startup=False, 1185 osparams=None, reason=None):
1186 """Reinstalls an instance. 1187 1188 @type instance: str 1189 @param instance: The instance to reinstall 1190 @type os: str or None 1191 @param os: The operating system to reinstall. If None, the instance's 1192 current operating system will be installed again 1193 @type no_startup: bool 1194 @param no_startup: Whether to start the instance automatically 1195 @type reason: string 1196 @param reason: the reason for executing this operation 1197 @rtype: string 1198 @return: job id 1199 1200 """ 1201 query = [] 1202 _AppendReason(query, reason) 1203 1204 if _INST_REINSTALL_REQV1 in self.GetFeatures(): 1205 body = { 1206 "start": not no_startup, 1207 } 1208 _SetItemIf(body, os is not None, "os", os) 1209 _SetItemIf(body, osparams is not None, "osparams", osparams) 1210 return self._SendRequest(HTTP_POST, 1211 ("/%s/instances/%s/reinstall" % 1212 (GANETI_RAPI_VERSION, instance)), query, body) 1213 1214 # Use old request format 1215 if osparams: 1216 raise GanetiApiError("Server does not support specifying OS parameters" 1217 " for instance reinstallation") 1218 1219 query = [] 1220 _AppendIf(query, os, ("os", os)) 1221 _AppendIf(query, no_startup, ("nostartup", 1)) 1222 1223 return self._SendRequest(HTTP_POST, 1224 ("/%s/instances/%s/reinstall" % 1225 (GANETI_RAPI_VERSION, instance)), query, None)
1226
1227 - def ReplaceInstanceDisks(self, instance, disks=None, mode=REPLACE_DISK_AUTO, 1228 remote_node=None, iallocator=None, reason=None, 1229 early_release=None):
1230 """Replaces disks on an instance. 1231 1232 @type instance: str 1233 @param instance: instance whose disks to replace 1234 @type disks: list of ints 1235 @param disks: Indexes of disks to replace 1236 @type mode: str 1237 @param mode: replacement mode to use (defaults to replace_auto) 1238 @type remote_node: str or None 1239 @param remote_node: new secondary node to use (for use with 1240 replace_new_secondary mode) 1241 @type iallocator: str or None 1242 @param iallocator: instance allocator plugin to use (for use with 1243 replace_auto mode) 1244 @type reason: string 1245 @param reason: the reason for executing this operation 1246 @type early_release: bool 1247 @param early_release: whether to release locks as soon as possible 1248 1249 @rtype: string 1250 @return: job id 1251 1252 """ 1253 query = [ 1254 ("mode", mode), 1255 ] 1256 1257 # TODO: Convert to body parameters 1258 1259 if disks is not None: 1260 _AppendIf(query, True, 1261 ("disks", ",".join(str(idx) for idx in disks))) 1262 1263 _AppendIf(query, remote_node is not None, ("remote_node", remote_node)) 1264 _AppendIf(query, iallocator is not None, ("iallocator", iallocator)) 1265 _AppendReason(query, reason) 1266 _AppendIf(query, early_release is not None, 1267 ("early_release", early_release)) 1268 1269 return self._SendRequest(HTTP_POST, 1270 ("/%s/instances/%s/replace-disks" % 1271 (GANETI_RAPI_VERSION, instance)), query, None)
1272
1273 - def PrepareExport(self, instance, mode, reason=None):
1274 """Prepares an instance for an export. 1275 1276 @type instance: string 1277 @param instance: Instance name 1278 @type mode: string 1279 @param mode: Export mode 1280 @type reason: string 1281 @param reason: the reason for executing this operation 1282 @rtype: string 1283 @return: Job ID 1284 1285 """ 1286 query = [("mode", mode)] 1287 _AppendReason(query, reason) 1288 return self._SendRequest(HTTP_PUT, 1289 ("/%s/instances/%s/prepare-export" % 1290 (GANETI_RAPI_VERSION, instance)), query, None)
1291
1292 - def ExportInstance(self, instance, mode, destination, shutdown=None, 1293 remove_instance=None, 1294 x509_key_name=None, destination_x509_ca=None, reason=None):
1295 """Exports an instance. 1296 1297 @type instance: string 1298 @param instance: Instance name 1299 @type mode: string 1300 @param mode: Export mode 1301 @type reason: string 1302 @param reason: the reason for executing this operation 1303 @rtype: string 1304 @return: Job ID 1305 1306 """ 1307 body = { 1308 "destination": destination, 1309 "mode": mode, 1310 } 1311 1312 _SetItemIf(body, shutdown is not None, "shutdown", shutdown) 1313 _SetItemIf(body, remove_instance is not None, 1314 "remove_instance", remove_instance) 1315 _SetItemIf(body, x509_key_name is not None, "x509_key_name", x509_key_name) 1316 _SetItemIf(body, destination_x509_ca is not None, 1317 "destination_x509_ca", destination_x509_ca) 1318 1319 query = [] 1320 _AppendReason(query, reason) 1321 1322 return self._SendRequest(HTTP_PUT, 1323 ("/%s/instances/%s/export" % 1324 (GANETI_RAPI_VERSION, instance)), query, body)
1325
1326 - def MigrateInstance(self, instance, mode=None, cleanup=None, 1327 target_node=None, reason=None):
1328 """Migrates an instance. 1329 1330 @type instance: string 1331 @param instance: Instance name 1332 @type mode: string 1333 @param mode: Migration mode 1334 @type cleanup: bool 1335 @param cleanup: Whether to clean up a previously failed migration 1336 @type target_node: string 1337 @param target_node: Target Node for externally mirrored instances 1338 @type reason: string 1339 @param reason: the reason for executing this operation 1340 @rtype: string 1341 @return: job id 1342 1343 """ 1344 body = {} 1345 _SetItemIf(body, mode is not None, "mode", mode) 1346 _SetItemIf(body, cleanup is not None, "cleanup", cleanup) 1347 _SetItemIf(body, target_node is not None, "target_node", target_node) 1348 1349 query = [] 1350 _AppendReason(query, reason) 1351 1352 return self._SendRequest(HTTP_PUT, 1353 ("/%s/instances/%s/migrate" % 1354 (GANETI_RAPI_VERSION, instance)), query, body)
1355
1356 - def FailoverInstance(self, instance, iallocator=None, 1357 ignore_consistency=None, target_node=None, reason=None):
1358 """Does a failover of an instance. 1359 1360 @type instance: string 1361 @param instance: Instance name 1362 @type iallocator: string 1363 @param iallocator: Iallocator for deciding the target node for 1364 shared-storage instances 1365 @type ignore_consistency: bool 1366 @param ignore_consistency: Whether to ignore disk consistency 1367 @type target_node: string 1368 @param target_node: Target node for shared-storage instances 1369 @type reason: string 1370 @param reason: the reason for executing this operation 1371 @rtype: string 1372 @return: job id 1373 1374 """ 1375 body = {} 1376 _SetItemIf(body, iallocator is not None, "iallocator", iallocator) 1377 _SetItemIf(body, ignore_consistency is not None, 1378 "ignore_consistency", ignore_consistency) 1379 _SetItemIf(body, target_node is not None, "target_node", target_node) 1380 1381 query = [] 1382 _AppendReason(query, reason) 1383 1384 return self._SendRequest(HTTP_PUT, 1385 ("/%s/instances/%s/failover" % 1386 (GANETI_RAPI_VERSION, instance)), query, body)
1387
1388 - def RenameInstance(self, instance, new_name, ip_check=None, name_check=None, 1389 reason=None):
1390 """Changes the name of an instance. 1391 1392 @type instance: string 1393 @param instance: Instance name 1394 @type new_name: string 1395 @param new_name: New instance name 1396 @type ip_check: bool 1397 @param ip_check: Whether to ensure instance's IP address is inactive 1398 @type name_check: bool 1399 @param name_check: Whether to ensure instance's name is resolvable 1400 @type reason: string 1401 @param reason: the reason for executing this operation 1402 @rtype: string 1403 @return: job id 1404 1405 """ 1406 body = { 1407 "new_name": new_name, 1408 } 1409 1410 _SetItemIf(body, ip_check is not None, "ip_check", ip_check) 1411 _SetItemIf(body, name_check is not None, "name_check", name_check) 1412 1413 query = [] 1414 _AppendReason(query, reason) 1415 1416 return self._SendRequest(HTTP_PUT, 1417 ("/%s/instances/%s/rename" % 1418 (GANETI_RAPI_VERSION, instance)), query, body)
1419
1420 - def GetInstanceConsole(self, instance, reason=None):
1421 """Request information for connecting to instance's console. 1422 1423 @type instance: string 1424 @param instance: Instance name 1425 @type reason: string 1426 @param reason: the reason for executing this operation 1427 @rtype: dict 1428 @return: dictionary containing information about instance's console 1429 1430 """ 1431 query = [] 1432 _AppendReason(query, reason) 1433 return self._SendRequest(HTTP_GET, 1434 ("/%s/instances/%s/console" % 1435 (GANETI_RAPI_VERSION, instance)), query, None)
1436
1437 - def GetJobs(self, bulk=False):
1438 """Gets all jobs for the cluster. 1439 1440 @type bulk: bool 1441 @param bulk: Whether to return detailed information about jobs. 1442 @rtype: list of int 1443 @return: List of job ids for the cluster or list of dicts with detailed 1444 information about the jobs if bulk parameter was true. 1445 1446 """ 1447 query = [] 1448 _AppendIf(query, bulk, ("bulk", 1)) 1449 1450 if bulk: 1451 return self._SendRequest(HTTP_GET, 1452 "/%s/jobs" % GANETI_RAPI_VERSION, 1453 query, None) 1454 else: 1455 return [int(j["id"]) 1456 for j in self._SendRequest(HTTP_GET, 1457 "/%s/jobs" % GANETI_RAPI_VERSION, 1458 None, None)]
1459
1460 - def GetJobStatus(self, job_id):
1461 """Gets the status of a job. 1462 1463 @type job_id: string 1464 @param job_id: job id whose status to query 1465 1466 @rtype: dict 1467 @return: job status 1468 1469 """ 1470 return self._SendRequest(HTTP_GET, 1471 "/%s/jobs/%s" % (GANETI_RAPI_VERSION, job_id), 1472 None, None)
1473
1474 - def WaitForJobCompletion(self, job_id, period=5, retries=-1):
1475 """Polls cluster for job status until completion. 1476 1477 Completion is defined as any of the following states listed in 1478 L{JOB_STATUS_FINALIZED}. 1479 1480 @type job_id: string 1481 @param job_id: job id to watch 1482 @type period: int 1483 @param period: how often to poll for status (optional, default 5s) 1484 @type retries: int 1485 @param retries: how many time to poll before giving up 1486 (optional, default -1 means unlimited) 1487 1488 @rtype: bool 1489 @return: C{True} if job succeeded or C{False} if failed/status timeout 1490 @deprecated: It is recommended to use L{WaitForJobChange} wherever 1491 possible; L{WaitForJobChange} returns immediately after a job changed and 1492 does not use polling 1493 1494 """ 1495 while retries != 0: 1496 job_result = self.GetJobStatus(job_id) 1497 1498 if job_result and job_result["status"] == JOB_STATUS_SUCCESS: 1499 return True 1500 elif not job_result or job_result["status"] in JOB_STATUS_FINALIZED: 1501 return False 1502 1503 if period: 1504 time.sleep(period) 1505 1506 if retries > 0: 1507 retries -= 1 1508 1509 return False
1510
1511 - def WaitForJobChange(self, job_id, fields, prev_job_info, prev_log_serial):
1512 """Waits for job changes. 1513 1514 @type job_id: string 1515 @param job_id: Job ID for which to wait 1516 @return: C{None} if no changes have been detected and a dict with two keys, 1517 C{job_info} and C{log_entries} otherwise. 1518 @rtype: dict 1519 1520 """ 1521 body = { 1522 "fields": fields, 1523 "previous_job_info": prev_job_info, 1524 "previous_log_serial": prev_log_serial, 1525 } 1526 1527 return self._SendRequest(HTTP_GET, 1528 "/%s/jobs/%s/wait" % (GANETI_RAPI_VERSION, job_id), 1529 None, body)
1530
1531 - def CancelJob(self, job_id, dry_run=False):
1532 """Cancels a job. 1533 1534 @type job_id: string 1535 @param job_id: id of the job to delete 1536 @type dry_run: bool 1537 @param dry_run: whether to perform a dry run 1538 @rtype: tuple 1539 @return: tuple containing the result, and a message (bool, string) 1540 1541 """ 1542 query = [] 1543 _AppendDryRunIf(query, dry_run) 1544 1545 return self._SendRequest(HTTP_DELETE, 1546 "/%s/jobs/%s" % (GANETI_RAPI_VERSION, job_id), 1547 query, None)
1548
1549 - def GetNodes(self, bulk=False, reason=None):
1550 """Gets all nodes in the cluster. 1551 1552 @type bulk: bool 1553 @param bulk: whether to return all information about all instances 1554 @type reason: string 1555 @param reason: the reason for executing this operation 1556 1557 @rtype: list of dict or str 1558 @return: if bulk is true, info about nodes in the cluster, 1559 else list of nodes in the cluster 1560 1561 """ 1562 query = [] 1563 _AppendIf(query, bulk, ("bulk", 1)) 1564 _AppendReason(query, reason) 1565 1566 nodes = self._SendRequest(HTTP_GET, "/%s/nodes" % GANETI_RAPI_VERSION, 1567 query, None) 1568 if bulk: 1569 return nodes 1570 else: 1571 return [n["id"] for n in nodes]
1572
1573 - def GetNode(self, node, reason=None):
1574 """Gets information about a node. 1575 1576 @type node: str 1577 @param node: node whose info to return 1578 @type reason: string 1579 @param reason: the reason for executing this operation 1580 1581 @rtype: dict 1582 @return: info about the node 1583 1584 """ 1585 query = [] 1586 _AppendReason(query, reason) 1587 1588 return self._SendRequest(HTTP_GET, 1589 "/%s/nodes/%s" % (GANETI_RAPI_VERSION, node), 1590 query, None)
1591
1592 - def EvacuateNode(self, node, iallocator=None, remote_node=None, 1593 dry_run=False, early_release=None, 1594 mode=None, accept_old=False, reason=None):
1595 """Evacuates instances from a Ganeti node. 1596 1597 @type node: str 1598 @param node: node to evacuate 1599 @type iallocator: str or None 1600 @param iallocator: instance allocator to use 1601 @type remote_node: str 1602 @param remote_node: node to evaucate to 1603 @type dry_run: bool 1604 @param dry_run: whether to perform a dry run 1605 @type early_release: bool 1606 @param early_release: whether to enable parallelization 1607 @type mode: string 1608 @param mode: Node evacuation mode 1609 @type accept_old: bool 1610 @param accept_old: Whether caller is ready to accept old-style (pre-2.5) 1611 results 1612 @type reason: string 1613 @param reason: the reason for executing this operation 1614 1615 @rtype: string, or a list for pre-2.5 results 1616 @return: Job ID or, if C{accept_old} is set and server is pre-2.5, 1617 list of (job ID, instance name, new secondary node); if dry_run was 1618 specified, then the actual move jobs were not submitted and the job IDs 1619 will be C{None} 1620 1621 @raises GanetiApiError: if an iallocator and remote_node are both 1622 specified 1623 1624 """ 1625 if iallocator and remote_node: 1626 raise GanetiApiError("Only one of iallocator or remote_node can be used") 1627 1628 query = [] 1629 _AppendDryRunIf(query, dry_run) 1630 _AppendReason(query, reason) 1631 1632 if _NODE_EVAC_RES1 in self.GetFeatures(): 1633 # Server supports body parameters 1634 body = {} 1635 1636 _SetItemIf(body, iallocator is not None, "iallocator", iallocator) 1637 _SetItemIf(body, remote_node is not None, "remote_node", remote_node) 1638 _SetItemIf(body, early_release is not None, 1639 "early_release", early_release) 1640 _SetItemIf(body, mode is not None, "mode", mode) 1641 else: 1642 # Pre-2.5 request format 1643 body = None 1644 1645 if not accept_old: 1646 raise GanetiApiError("Server is version 2.4 or earlier and caller does" 1647 " not accept old-style results (parameter" 1648 " accept_old)") 1649 1650 # Pre-2.5 servers can only evacuate secondaries 1651 if mode is not None and mode != NODE_EVAC_SEC: 1652 raise GanetiApiError("Server can only evacuate secondary instances") 1653 1654 _AppendIf(query, iallocator, ("iallocator", iallocator)) 1655 _AppendIf(query, remote_node, ("remote_node", remote_node)) 1656 _AppendIf(query, early_release, ("early_release", 1)) 1657 1658 return self._SendRequest(HTTP_POST, 1659 ("/%s/nodes/%s/evacuate" % 1660 (GANETI_RAPI_VERSION, node)), query, body)
1661
1662 - def MigrateNode(self, node, mode=None, dry_run=False, iallocator=None, 1663 target_node=None, reason=None):
1664 """Migrates all primary instances from a node. 1665 1666 @type node: str 1667 @param node: node to migrate 1668 @type mode: string 1669 @param mode: if passed, it will overwrite the live migration type, 1670 otherwise the hypervisor default will be used 1671 @type dry_run: bool 1672 @param dry_run: whether to perform a dry run 1673 @type iallocator: string 1674 @param iallocator: instance allocator to use 1675 @type target_node: string 1676 @param target_node: Target node for shared-storage instances 1677 @type reason: string 1678 @param reason: the reason for executing this operation 1679 1680 @rtype: string 1681 @return: job id 1682 1683 """ 1684 query = [] 1685 _AppendDryRunIf(query, dry_run) 1686 _AppendReason(query, reason) 1687 1688 if _NODE_MIGRATE_REQV1 in self.GetFeatures(): 1689 body = {} 1690 1691 _SetItemIf(body, mode is not None, "mode", mode) 1692 _SetItemIf(body, iallocator is not None, "iallocator", iallocator) 1693 _SetItemIf(body, target_node is not None, "target_node", target_node) 1694 1695 assert len(query) <= 1 1696 1697 return self._SendRequest(HTTP_POST, 1698 ("/%s/nodes/%s/migrate" % 1699 (GANETI_RAPI_VERSION, node)), query, body) 1700 else: 1701 # Use old request format 1702 if target_node is not None: 1703 raise GanetiApiError("Server does not support specifying target node" 1704 " for node migration") 1705 1706 _AppendIf(query, mode is not None, ("mode", mode)) 1707 1708 return self._SendRequest(HTTP_POST, 1709 ("/%s/nodes/%s/migrate" % 1710 (GANETI_RAPI_VERSION, node)), query, None)
1711
1712 - def GetNodeRole(self, node, reason=None):
1713 """Gets the current role for a node. 1714 1715 @type node: str 1716 @param node: node whose role to return 1717 @type reason: string 1718 @param reason: the reason for executing this operation 1719 1720 @rtype: str 1721 @return: the current role for a node 1722 1723 """ 1724 query = [] 1725 _AppendReason(query, reason) 1726 1727 return self._SendRequest(HTTP_GET, 1728 ("/%s/nodes/%s/role" % 1729 (GANETI_RAPI_VERSION, node)), query, None)
1730
1731 - def SetNodeRole(self, node, role, force=False, auto_promote=None, 1732 reason=None):
1733 """Sets the role for a node. 1734 1735 @type node: str 1736 @param node: the node whose role to set 1737 @type role: str 1738 @param role: the role to set for the node 1739 @type force: bool 1740 @param force: whether to force the role change 1741 @type auto_promote: bool 1742 @param auto_promote: Whether node(s) should be promoted to master candidate 1743 if necessary 1744 @type reason: string 1745 @param reason: the reason for executing this operation 1746 1747 @rtype: string 1748 @return: job id 1749 1750 """ 1751 query = [] 1752 _AppendForceIf(query, force) 1753 _AppendIf(query, auto_promote is not None, ("auto-promote", auto_promote)) 1754 _AppendReason(query, reason) 1755 1756 return self._SendRequest(HTTP_PUT, 1757 ("/%s/nodes/%s/role" % 1758 (GANETI_RAPI_VERSION, node)), query, role)
1759
1760 - def PowercycleNode(self, node, force=False, reason=None):
1761 """Powercycles a node. 1762 1763 @type node: string 1764 @param node: Node name 1765 @type force: bool 1766 @param force: Whether to force the operation 1767 @type reason: string 1768 @param reason: the reason for executing this operation 1769 @rtype: string 1770 @return: job id 1771 1772 """ 1773 query = [] 1774 _AppendForceIf(query, force) 1775 _AppendReason(query, reason) 1776 1777 return self._SendRequest(HTTP_POST, 1778 ("/%s/nodes/%s/powercycle" % 1779 (GANETI_RAPI_VERSION, node)), query, None)
1780
1781 - def ModifyNode(self, node, reason=None, **kwargs):
1782 """Modifies a node. 1783 1784 More details for parameters can be found in the RAPI documentation. 1785 1786 @type node: string 1787 @param node: Node name 1788 @type reason: string 1789 @param reason: the reason for executing this operation 1790 @rtype: string 1791 @return: job id 1792 1793 """ 1794 query = [] 1795 _AppendReason(query, reason) 1796 1797 return self._SendRequest(HTTP_POST, 1798 ("/%s/nodes/%s/modify" % 1799 (GANETI_RAPI_VERSION, node)), query, kwargs)
1800
1801 - def GetNodeStorageUnits(self, node, storage_type, output_fields, reason=None):
1802 """Gets the storage units for a node. 1803 1804 @type node: str 1805 @param node: the node whose storage units to return 1806 @type storage_type: str 1807 @param storage_type: storage type whose units to return 1808 @type output_fields: str 1809 @param output_fields: storage type fields to return 1810 @type reason: string 1811 @param reason: the reason for executing this operation 1812 1813 @rtype: string 1814 @return: job id where results can be retrieved 1815 1816 """ 1817 query = [ 1818 ("storage_type", storage_type), 1819 ("output_fields", output_fields), 1820 ] 1821 _AppendReason(query, reason) 1822 1823 return self._SendRequest(HTTP_GET, 1824 ("/%s/nodes/%s/storage" % 1825 (GANETI_RAPI_VERSION, node)), query, None)
1826
1827 - def ModifyNodeStorageUnits(self, node, storage_type, name, allocatable=None, 1828 reason=None):
1829 """Modifies parameters of storage units on the node. 1830 1831 @type node: str 1832 @param node: node whose storage units to modify 1833 @type storage_type: str 1834 @param storage_type: storage type whose units to modify 1835 @type name: str 1836 @param name: name of the storage unit 1837 @type allocatable: bool or None 1838 @param allocatable: Whether to set the "allocatable" flag on the storage 1839 unit (None=no modification, True=set, False=unset) 1840 @type reason: string 1841 @param reason: the reason for executing this operation 1842 1843 @rtype: string 1844 @return: job id 1845 1846 """ 1847 query = [ 1848 ("storage_type", storage_type), 1849 ("name", name), 1850 ] 1851 1852 _AppendIf(query, allocatable is not None, ("allocatable", allocatable)) 1853 _AppendReason(query, reason) 1854 1855 return self._SendRequest(HTTP_PUT, 1856 ("/%s/nodes/%s/storage/modify" % 1857 (GANETI_RAPI_VERSION, node)), query, None)
1858
1859 - def RepairNodeStorageUnits(self, node, storage_type, name, reason=None):
1860 """Repairs a storage unit on the node. 1861 1862 @type node: str 1863 @param node: node whose storage units to repair 1864 @type storage_type: str 1865 @param storage_type: storage type to repair 1866 @type name: str 1867 @param name: name of the storage unit to repair 1868 @type reason: string 1869 @param reason: the reason for executing this operation 1870 1871 @rtype: string 1872 @return: job id 1873 1874 """ 1875 query = [ 1876 ("storage_type", storage_type), 1877 ("name", name), 1878 ] 1879 _AppendReason(query, reason) 1880 1881 return self._SendRequest(HTTP_PUT, 1882 ("/%s/nodes/%s/storage/repair" % 1883 (GANETI_RAPI_VERSION, node)), query, None)
1884
1885 - def GetNodeTags(self, node, reason=None):
1886 """Gets the tags for a node. 1887 1888 @type node: str 1889 @param node: node whose tags to return 1890 @type reason: string 1891 @param reason: the reason for executing this operation 1892 1893 @rtype: list of str 1894 @return: tags for the node 1895 1896 """ 1897 query = [] 1898 _AppendReason(query, reason) 1899 1900 return self._SendRequest(HTTP_GET, 1901 ("/%s/nodes/%s/tags" % 1902 (GANETI_RAPI_VERSION, node)), query, None)
1903
1904 - def AddNodeTags(self, node, tags, dry_run=False, reason=None):
1905 """Adds tags to a node. 1906 1907 @type node: str 1908 @param node: node to add tags to 1909 @type tags: list of str 1910 @param tags: tags to add to the node 1911 @type dry_run: bool 1912 @param dry_run: whether to perform a dry run 1913 @type reason: string 1914 @param reason: the reason for executing this operation 1915 1916 @rtype: string 1917 @return: job id 1918 1919 """ 1920 query = [("tag", t) for t in tags] 1921 _AppendDryRunIf(query, dry_run) 1922 _AppendReason(query, reason) 1923 1924 return self._SendRequest(HTTP_PUT, 1925 ("/%s/nodes/%s/tags" % 1926 (GANETI_RAPI_VERSION, node)), query, tags)
1927
1928 - def DeleteNodeTags(self, node, tags, dry_run=False, reason=None):
1929 """Delete tags from a node. 1930 1931 @type node: str 1932 @param node: node to remove tags from 1933 @type tags: list of str 1934 @param tags: tags to remove from the node 1935 @type dry_run: bool 1936 @param dry_run: whether to perform a dry run 1937 @type reason: string 1938 @param reason: the reason for executing this operation 1939 1940 @rtype: string 1941 @return: job id 1942 1943 """ 1944 query = [("tag", t) for t in tags] 1945 _AppendDryRunIf(query, dry_run) 1946 _AppendReason(query, reason) 1947 1948 return self._SendRequest(HTTP_DELETE, 1949 ("/%s/nodes/%s/tags" % 1950 (GANETI_RAPI_VERSION, node)), query, None)
1951
1952 - def GetNetworks(self, bulk=False, reason=None):
1953 """Gets all networks in the cluster. 1954 1955 @type bulk: bool 1956 @param bulk: whether to return all information about the networks 1957 1958 @rtype: list of dict or str 1959 @return: if bulk is true, a list of dictionaries with info about all 1960 networks in the cluster, else a list of names of those networks 1961 1962 """ 1963 query = [] 1964 _AppendIf(query, bulk, ("bulk", 1)) 1965 _AppendReason(query, reason) 1966 1967 networks = self._SendRequest(HTTP_GET, "/%s/networks" % GANETI_RAPI_VERSION, 1968 query, None) 1969 if bulk: 1970 return networks 1971 else: 1972 return [n["name"] for n in networks]
1973
1974 - def GetNetwork(self, network, reason=None):
1975 """Gets information about a network. 1976 1977 @type network: str 1978 @param network: name of the network whose info to return 1979 @type reason: string 1980 @param reason: the reason for executing this operation 1981 1982 @rtype: dict 1983 @return: info about the network 1984 1985 """ 1986 query = [] 1987 _AppendReason(query, reason) 1988 1989 return self._SendRequest(HTTP_GET, 1990 "/%s/networks/%s" % (GANETI_RAPI_VERSION, network), 1991 query, None)
1992
1993 - def CreateNetwork(self, network_name, network, gateway=None, network6=None, 1994 gateway6=None, mac_prefix=None, 1995 add_reserved_ips=None, tags=None, dry_run=False, 1996 reason=None):
1997 """Creates a new network. 1998 1999 @type network_name: str 2000 @param network_name: the name of network to create 2001 @type dry_run: bool 2002 @param dry_run: whether to peform a dry run 2003 @type reason: string 2004 @param reason: the reason for executing this operation 2005 2006 @rtype: string 2007 @return: job id 2008 2009 """ 2010 query = [] 2011 _AppendDryRunIf(query, dry_run) 2012 _AppendReason(query, reason) 2013 2014 if add_reserved_ips: 2015 add_reserved_ips = add_reserved_ips.split(",") 2016 2017 if tags: 2018 tags = tags.split(",") 2019 2020 body = { 2021 "network_name": network_name, 2022 "gateway": gateway, 2023 "network": network, 2024 "gateway6": gateway6, 2025 "network6": network6, 2026 "mac_prefix": mac_prefix, 2027 "add_reserved_ips": add_reserved_ips, 2028 "tags": tags, 2029 } 2030 2031 return self._SendRequest(HTTP_POST, "/%s/networks" % GANETI_RAPI_VERSION, 2032 query, body)
2033
2034 - def ConnectNetwork(self, network_name, group_name, mode, link, 2035 vlan="", dry_run=False, reason=None):
2036 """Connects a Network to a NodeGroup with the given netparams 2037 2038 """ 2039 body = { 2040 "group_name": group_name, 2041 "network_mode": mode, 2042 "network_link": link, 2043 "network_vlan": vlan, 2044 } 2045 2046 query = [] 2047 _AppendDryRunIf(query, dry_run) 2048 _AppendReason(query, reason) 2049 2050 return self._SendRequest(HTTP_PUT, 2051 ("/%s/networks/%s/connect" % 2052 (GANETI_RAPI_VERSION, network_name)), query, body)
2053
2054 - def DisconnectNetwork(self, network_name, group_name, dry_run=False, 2055 reason=None):
2056 """Connects a Network to a NodeGroup with the given netparams 2057 2058 """ 2059 body = { 2060 "group_name": group_name, 2061 } 2062 2063 query = [] 2064 _AppendDryRunIf(query, dry_run) 2065 _AppendReason(query, reason) 2066 2067 return self._SendRequest(HTTP_PUT, 2068 ("/%s/networks/%s/disconnect" % 2069 (GANETI_RAPI_VERSION, network_name)), query, body)
2070
2071 - def ModifyNetwork(self, network, reason=None, **kwargs):
2072 """Modifies a network. 2073 2074 More details for parameters can be found in the RAPI documentation. 2075 2076 @type network: string 2077 @param network: Network name 2078 @type reason: string 2079 @param reason: the reason for executing this operation 2080 @rtype: string 2081 @return: job id 2082 2083 """ 2084 query = [] 2085 _AppendReason(query, reason) 2086 2087 return self._SendRequest(HTTP_PUT, 2088 ("/%s/networks/%s/modify" % 2089 (GANETI_RAPI_VERSION, network)), None, kwargs)
2090
2091 - def DeleteNetwork(self, network, dry_run=False, reason=None):
2092 """Deletes a network. 2093 2094 @type network: str 2095 @param network: the network to delete 2096 @type dry_run: bool 2097 @param dry_run: whether to peform a dry run 2098 @type reason: string 2099 @param reason: the reason for executing this operation 2100 2101 @rtype: string 2102 @return: job id 2103 2104 """ 2105 query = [] 2106 _AppendDryRunIf(query, dry_run) 2107 _AppendReason(query, reason) 2108 2109 return self._SendRequest(HTTP_DELETE, 2110 ("/%s/networks/%s" % 2111 (GANETI_RAPI_VERSION, network)), query, None)
2112
2113 - def GetNetworkTags(self, network, reason=None):
2114 """Gets tags for a network. 2115 2116 @type network: string 2117 @param network: Node group whose tags to return 2118 @type reason: string 2119 @param reason: the reason for executing this operation 2120 2121 @rtype: list of strings 2122 @return: tags for the network 2123 2124 """ 2125 query = [] 2126 _AppendReason(query, reason) 2127 2128 return self._SendRequest(HTTP_GET, 2129 ("/%s/networks/%s/tags" % 2130 (GANETI_RAPI_VERSION, network)), query, None)
2131
2132 - def AddNetworkTags(self, network, tags, dry_run=False, reason=None):
2133 """Adds tags to a network. 2134 2135 @type network: str 2136 @param network: network to add tags to 2137 @type tags: list of string 2138 @param tags: tags to add to the network 2139 @type dry_run: bool 2140 @param dry_run: whether to perform a dry run 2141 @type reason: string 2142 @param reason: the reason for executing this operation 2143 2144 @rtype: string 2145 @return: job id 2146 2147 """ 2148 query = [("tag", t) for t in tags] 2149 _AppendDryRunIf(query, dry_run) 2150 _AppendReason(query, reason) 2151 2152 return self._SendRequest(HTTP_PUT, 2153 ("/%s/networks/%s/tags" % 2154 (GANETI_RAPI_VERSION, network)), query, None)
2155
2156 - def DeleteNetworkTags(self, network, tags, dry_run=False, reason=None):
2157 """Deletes tags from a network. 2158 2159 @type network: str 2160 @param network: network to delete tags from 2161 @type tags: list of string 2162 @param tags: tags to delete 2163 @type dry_run: bool 2164 @param dry_run: whether to perform a dry run 2165 @type reason: string 2166 @param reason: the reason for executing this operation 2167 @rtype: string 2168 @return: job id 2169 2170 """ 2171 query = [("tag", t) for t in tags] 2172 _AppendDryRunIf(query, dry_run) 2173 _AppendReason(query, reason) 2174 2175 return self._SendRequest(HTTP_DELETE, 2176 ("/%s/networks/%s/tags" % 2177 (GANETI_RAPI_VERSION, network)), query, None)
2178
2179 - def GetGroups(self, bulk=False, reason=None):
2180 """Gets all node groups in the cluster. 2181 2182 @type bulk: bool 2183 @param bulk: whether to return all information about the groups 2184 @type reason: string 2185 @param reason: the reason for executing this operation 2186 2187 @rtype: list of dict or str 2188 @return: if bulk is true, a list of dictionaries with info about all node 2189 groups in the cluster, else a list of names of those node groups 2190 2191 """ 2192 query = [] 2193 _AppendIf(query, bulk, ("bulk", 1)) 2194 _AppendReason(query, reason) 2195 2196 groups = self._SendRequest(HTTP_GET, "/%s/groups" % GANETI_RAPI_VERSION, 2197 query, None) 2198 if bulk: 2199 return groups 2200 else: 2201 return [g["name"] for g in groups]
2202
2203 - def GetGroup(self, group, reason=None):
2204 """Gets information about a node group. 2205 2206 @type group: str 2207 @param group: name of the node group whose info to return 2208 @type reason: string 2209 @param reason: the reason for executing this operation 2210 2211 @rtype: dict 2212 @return: info about the node group 2213 2214 """ 2215 query = [] 2216 _AppendReason(query, reason) 2217 2218 return self._SendRequest(HTTP_GET, 2219 "/%s/groups/%s" % (GANETI_RAPI_VERSION, group), 2220 query, None)
2221
2222 - def CreateGroup(self, name, alloc_policy=None, dry_run=False, reason=None):
2223 """Creates a new node group. 2224 2225 @type name: str 2226 @param name: the name of node group to create 2227 @type alloc_policy: str 2228 @param alloc_policy: the desired allocation policy for the group, if any 2229 @type dry_run: bool 2230 @param dry_run: whether to peform a dry run 2231 @type reason: string 2232 @param reason: the reason for executing this operation 2233 2234 @rtype: string 2235 @return: job id 2236 2237 """ 2238 query = [] 2239 _AppendDryRunIf(query, dry_run) 2240 _AppendReason(query, reason) 2241 2242 body = { 2243 "name": name, 2244 "alloc_policy": alloc_policy, 2245 } 2246 2247 return self._SendRequest(HTTP_POST, "/%s/groups" % GANETI_RAPI_VERSION, 2248 query, body)
2249
2250 - def ModifyGroup(self, group, reason=None, **kwargs):
2251 """Modifies a node group. 2252 2253 More details for parameters can be found in the RAPI documentation. 2254 2255 @type group: string 2256 @param group: Node group name 2257 @type reason: string 2258 @param reason: the reason for executing this operation 2259 @rtype: string 2260 @return: job id 2261 2262 """ 2263 query = [] 2264 _AppendReason(query, reason) 2265 2266 return self._SendRequest(HTTP_PUT, 2267 ("/%s/groups/%s/modify" % 2268 (GANETI_RAPI_VERSION, group)), query, kwargs)
2269
2270 - def DeleteGroup(self, group, dry_run=False, reason=None):
2271 """Deletes a node group. 2272 2273 @type group: str 2274 @param group: the node group to delete 2275 @type dry_run: bool 2276 @param dry_run: whether to peform a dry run 2277 @type reason: string 2278 @param reason: the reason for executing this operation 2279 2280 @rtype: string 2281 @return: job id 2282 2283 """ 2284 query = [] 2285 _AppendDryRunIf(query, dry_run) 2286 _AppendReason(query, reason) 2287 2288 return self._SendRequest(HTTP_DELETE, 2289 ("/%s/groups/%s" % 2290 (GANETI_RAPI_VERSION, group)), query, None)
2291
2292 - def RenameGroup(self, group, new_name, reason=None):
2293 """Changes the name of a node group. 2294 2295 @type group: string 2296 @param group: Node group name 2297 @type new_name: string 2298 @param new_name: New node group name 2299 @type reason: string 2300 @param reason: the reason for executing this operation 2301 2302 @rtype: string 2303 @return: job id 2304 2305 """ 2306 body = { 2307 "new_name": new_name, 2308 } 2309 2310 query = [] 2311 _AppendReason(query, reason) 2312 2313 return self._SendRequest(HTTP_PUT, 2314 ("/%s/groups/%s/rename" % 2315 (GANETI_RAPI_VERSION, group)), query, body)
2316
2317 - def AssignGroupNodes(self, group, nodes, force=False, dry_run=False, 2318 reason=None):
2319 """Assigns nodes to a group. 2320 2321 @type group: string 2322 @param group: Node group name 2323 @type nodes: list of strings 2324 @param nodes: List of nodes to assign to the group 2325 @type reason: string 2326 @param reason: the reason for executing this operation 2327 2328 @rtype: string 2329 @return: job id 2330 2331 """ 2332 query = [] 2333 _AppendForceIf(query, force) 2334 _AppendDryRunIf(query, dry_run) 2335 _AppendReason(query, reason) 2336 2337 body = { 2338 "nodes": nodes, 2339 } 2340 2341 return self._SendRequest(HTTP_PUT, 2342 ("/%s/groups/%s/assign-nodes" % 2343 (GANETI_RAPI_VERSION, group)), query, body)
2344
2345 - def GetGroupTags(self, group, reason=None):
2346 """Gets tags for a node group. 2347 2348 @type group: string 2349 @param group: Node group whose tags to return 2350 @type reason: string 2351 @param reason: the reason for executing this operation 2352 2353 @rtype: list of strings 2354 @return: tags for the group 2355 2356 """ 2357 query = [] 2358 _AppendReason(query, reason) 2359 2360 return self._SendRequest(HTTP_GET, 2361 ("/%s/groups/%s/tags" % 2362 (GANETI_RAPI_VERSION, group)), query, None)
2363
2364 - def AddGroupTags(self, group, tags, dry_run=False, reason=None):
2365 """Adds tags to a node group. 2366 2367 @type group: str 2368 @param group: group to add tags to 2369 @type tags: list of string 2370 @param tags: tags to add to the group 2371 @type dry_run: bool 2372 @param dry_run: whether to perform a dry run 2373 @type reason: string 2374 @param reason: the reason for executing this operation 2375 2376 @rtype: string 2377 @return: job id 2378 2379 """ 2380 query = [("tag", t) for t in tags] 2381 _AppendDryRunIf(query, dry_run) 2382 _AppendReason(query, reason) 2383 2384 return self._SendRequest(HTTP_PUT, 2385 ("/%s/groups/%s/tags" % 2386 (GANETI_RAPI_VERSION, group)), query, None)
2387
2388 - def DeleteGroupTags(self, group, tags, dry_run=False, reason=None):
2389 """Deletes tags from a node group. 2390 2391 @type group: str 2392 @param group: group to delete tags from 2393 @type tags: list of string 2394 @param tags: tags to delete 2395 @type dry_run: bool 2396 @param dry_run: whether to perform a dry run 2397 @type reason: string 2398 @param reason: the reason for executing this operation 2399 @rtype: string 2400 @return: job id 2401 2402 """ 2403 query = [("tag", t) for t in tags] 2404 _AppendDryRunIf(query, dry_run) 2405 _AppendReason(query, reason) 2406 2407 return self._SendRequest(HTTP_DELETE, 2408 ("/%s/groups/%s/tags" % 2409 (GANETI_RAPI_VERSION, group)), query, None)
2410
2411 - def Query(self, what, fields, qfilter=None, reason=None):
2412 """Retrieves information about resources. 2413 2414 @type what: string 2415 @param what: Resource name, one of L{constants.QR_VIA_RAPI} 2416 @type fields: list of string 2417 @param fields: Requested fields 2418 @type qfilter: None or list 2419 @param qfilter: Query filter 2420 @type reason: string 2421 @param reason: the reason for executing this operation 2422 2423 @rtype: string 2424 @return: job id 2425 2426 """ 2427 query = [] 2428 _AppendReason(query, reason) 2429 2430 body = { 2431 "fields": fields, 2432 } 2433 2434 _SetItemIf(body, qfilter is not None, "qfilter", qfilter) 2435 # TODO: remove "filter" after 2.7 2436 _SetItemIf(body, qfilter is not None, "filter", qfilter) 2437 2438 return self._SendRequest(HTTP_PUT, 2439 ("/%s/query/%s" % 2440 (GANETI_RAPI_VERSION, what)), query, body)
2441
2442 - def QueryFields(self, what, fields=None, reason=None):
2443 """Retrieves available fields for a resource. 2444 2445 @type what: string 2446 @param what: Resource name, one of L{constants.QR_VIA_RAPI} 2447 @type fields: list of string 2448 @param fields: Requested fields 2449 @type reason: string 2450 @param reason: the reason for executing this operation 2451 2452 @rtype: string 2453 @return: job id 2454 2455 """ 2456 query = [] 2457 _AppendReason(query, reason) 2458 2459 if fields is not None: 2460 _AppendIf(query, True, ("fields", ",".join(fields))) 2461 2462 return self._SendRequest(HTTP_GET, 2463 ("/%s/query/%s/fields" % 2464 (GANETI_RAPI_VERSION, what)), query, None)
2465