1 # Copyright (C) 2010, 2011 Google Inc.
2 # Copyright (c) 2012 Oregon State University Open Source Lab
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 2 of the License, or
7 # (at your option) any later version.
9 # This program is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 # General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
24 # No Ganeti-specific modules should be imported. The RAPI client is supposed
28 import simplejson
as json
34 GANETI_RAPI_PORT
= 5080
35 GANETI_RAPI_VERSION
= 2
37 REPLACE_DISK_PRI
= "replace_on_primary"
38 REPLACE_DISK_SECONDARY
= "replace_on_secondary"
39 REPLACE_DISK_CHG
= "replace_new_secondary"
40 REPLACE_DISK_AUTO
= "replace_auto"
42 NODE_EVAC_PRI
= "primary-only"
43 NODE_EVAC_SEC
= "secondary-only"
46 NODE_ROLE_DRAINED
= "drained"
47 NODE_ROLE_MASTER_CANDIATE
= "master-candidate"
48 NODE_ROLE_MASTER
= "master"
49 NODE_ROLE_OFFLINE
= "offline"
50 NODE_ROLE_REGULAR
= "regular"
52 JOB_STATUS_QUEUED
= "queued"
53 JOB_STATUS_WAITING
= "waiting"
54 JOB_STATUS_CANCELING
= "canceling"
55 JOB_STATUS_RUNNING
= "running"
56 JOB_STATUS_CANCELED
= "canceled"
57 JOB_STATUS_SUCCESS
= "success"
58 JOB_STATUS_ERROR
= "error"
59 JOB_STATUS_FINALIZED
= frozenset([
64 JOB_STATUS_ALL
= frozenset([
69 ]) | JOB_STATUS_FINALIZED
72 JOB_STATUS_WAITLOCK
= JOB_STATUS_WAITING
75 _REQ_DATA_VERSION_FIELD
= "__version__"
76 _INST_NIC_PARAMS
= frozenset(["mac", "ip", "mode", "link"])
77 _INST_CREATE_V0_DISK_PARAMS
= frozenset(["size"])
78 _INST_CREATE_V0_PARAMS
= frozenset([
79 "os", "pnode", "snode", "iallocator", "start", "ip_check", "name_check",
80 "hypervisor", "file_storage_dir", "file_driver", "dry_run",
82 _INST_CREATE_V0_DPARAMS
= frozenset(["beparams", "hvparams"])
85 INST_CREATE_REQV1
= "instance-create-reqv1"
86 INST_REINSTALL_REQV1
= "instance-reinstall-reqv1"
87 NODE_MIGRATE_REQV1
= "node-migrate-reqv1"
88 NODE_EVAC_RES1
= "node-evac-res1"
90 # Old feature constant names in case they're references by users of this module
91 _INST_CREATE_REQV1
= INST_CREATE_REQV1
92 _INST_REINSTALL_REQV1
= INST_REINSTALL_REQV1
93 _NODE_MIGRATE_REQV1
= NODE_MIGRATE_REQV1
94 _NODE_EVAC_RES1
= NODE_EVAC_RES1
97 "accept": "application/json",
98 "content-type": "application/json",
99 "user-agent": "Ganeti RAPI Client",
103 class ClientError(Exception):
105 Base error class for this module.
109 class CertificateError(ClientError
):
111 Raised when a problem is found with the SSL certificate.
115 class GanetiApiError(ClientError
):
117 Generic error raised from Ganeti API.
120 def __init__(self
, msg
, code
=None):
121 ClientError
.__init
__(self
, msg
)
125 def prepare_query(query
):
127 Prepare a query object for the RAPI.
129 RAPI has lots of curious rules for coercing values.
131 This function operates on dicts in-place and has no return value.
134 :param query: Query arguments
140 # None is sent as an empty string.
144 # Booleans are sent as 0 or 1.
145 elif isinstance(value
, bool):
146 query
[name
] = int(value
)
148 # XXX shouldn't this just check for basestring instead?
149 elif isinstance(value
, dict):
150 raise ValueError("Invalid query data type %r" %
151 type(value
).__name
__)
154 class GanetiRapiClient(object): # pylint: disable-msg=R0904
159 _json_encoder
= json
.JSONEncoder(sort_keys
=True)
161 def __init__(self
, host
, port
=GANETI_RAPI_PORT
, username
=None,
162 password
=None, timeout
=60, logger
=logging
):
164 Initializes this class.
167 :param host: the ganeti cluster master to interact with
169 :param port: the port on which the RAPI is running (default is 5080)
170 :type username: string
171 :param username: the username to connect with
172 :type password: string
173 :param password: the password to connect with
174 :param logger: Logging object
177 if username
is not None and password
is None:
178 raise ClientError("Password not specified")
179 elif password
is not None and username
is None:
180 raise ClientError("Specified password without username")
182 self
.username
= username
183 self
.password
= password
184 self
.timeout
= timeout
185 self
._logger
= logger
188 socket
.inet_pton(socket
.AF_INET6
, host
)
189 address
= "[%s]:%s" % (host
, port
)
190 # ValueError can happen too, so catch it as well for the IPv4
192 except (socket
.error
, ValueError):
193 address
= "%s:%s" % (host
, port
)
195 self
._base
_url
= "https://%s" % address
197 def _SendRequest(self
, method
, path
, query
=None, content
=None):
199 Sends an HTTP request.
201 This constructs a full URL, encodes and decodes HTTP bodies, and
202 handles invalid responses in a pythonic way.
205 :param method: HTTP method to use
207 :param path: HTTP URL path
208 :type query: list of two-tuples
209 :param query: query arguments to pass to urllib.urlencode
210 :type content: str or None
211 :param content: HTTP body content
214 :return: JSON-Decoded response
216 :raises GanetiApiError: If an invalid response is returned
219 if not path
.startswith("/"):
220 raise ClientError("Implementation error: Called with bad path %s"
225 "timeout": self
.timeout
,
229 if self
.username
and self
.password
:
230 kwargs
["auth"] = self
.username
, self
.password
232 if content
is not None:
233 kwargs
["data"] = self
._json
_encoder
.encode(content
)
237 kwargs
["params"] = query
239 url
= self
._base
_url
+ path
241 self
._logger
.debug("Sending request to %s %s", url
, kwargs
)
242 # print "Sending request to %s %s" % (url, kwargs)
245 r
= requests
.request(method
, url
, **kwargs
)
246 except requests
.ConnectionError
:
247 raise GanetiApiError("Couldn't connect to %s" % self
._base
_url
)
248 except requests
.Timeout
:
249 raise GanetiApiError("Timed out connecting to %s" %
252 if r
.status_code
!= requests
.codes
.ok
:
253 raise GanetiApiError(str(r
.status_code
), code
=r
.status_code
)
256 return json
.loads(r
.content
)
260 def GetVersion(self
):
262 Gets the Remote API version running on the cluster.
265 :return: Ganeti Remote API version
268 return self
._SendRequest
("get", "/version")
270 def GetFeatures(self
):
272 Gets the list of optional features supported by RAPI server.
275 :return: List of optional features
279 return self
._SendRequest
("get",
280 "/%s/features" % GANETI_RAPI_VERSION
)
281 except GanetiApiError
, err
:
282 # Older RAPI servers don't support this resource. Just return an
284 if err
.code
== requests
.codes
.not_found
:
289 def GetOperatingSystems(self
):
291 Gets the Operating Systems running in the Ganeti cluster.
294 :return: operating systems
297 return self
._SendRequest
("get", "/%s/os" % GANETI_RAPI_VERSION
)
301 Gets info about the cluster.
304 :return: information about the cluster
307 return self
._SendRequest
("get", "/%s/info" % GANETI_RAPI_VERSION
,
310 def RedistributeConfig(self
):
312 Tells the cluster to redistribute its configuration files.
317 return self
._SendRequest
("put", "/%s/redistribute-config" %
320 def ModifyCluster(self
, **kwargs
):
322 Modifies cluster parameters.
324 More details for parameters can be found in the RAPI documentation.
330 return self
._SendRequest
("put", "/%s/modify" % GANETI_RAPI_VERSION
,
333 def GetClusterTags(self
):
335 Gets the cluster tags.
338 :return: cluster tags
341 return self
._SendRequest
("get", "/%s/tags" % GANETI_RAPI_VERSION
)
343 def AddClusterTags(self
, tags
, dry_run
=False):
345 Adds tags to the cluster.
347 :type tags: list of str
348 :param tags: tags to add to the cluster
350 :param dry_run: whether to perform a dry run
361 return self
._SendRequest
("put", "/%s/tags" % GANETI_RAPI_VERSION
,
364 def DeleteClusterTags(self
, tags
, dry_run
=False):
366 Deletes tags from the cluster.
368 :type tags: list of str
369 :param tags: tags to delete
371 :param dry_run: whether to perform a dry run
379 return self
._SendRequest
("delete", "/%s/tags" % GANETI_RAPI_VERSION
,
382 def GetInstances(self
, bulk
=False):
384 Gets information about instances on the cluster.
387 :param bulk: whether to return all information about all instances
389 :rtype: list of dict or list of str
390 :return: if bulk is True, info about the instances,
391 else a list of instances
395 return self
._SendRequest
("get", "/%s/instances" %
396 GANETI_RAPI_VERSION
, query
={"bulk": 1})
398 instances
= self
._SendRequest
("get", "/%s/instances" %
400 return [i
["id"] for i
in instances
]
402 def GetInstance(self
, instance
):
404 Gets information about an instance.
407 :param instance: instance whose info to return
410 :return: info about the instance
413 return self
._SendRequest
("get", ("/%s/instances/%s" %
414 (GANETI_RAPI_VERSION
, instance
)))
416 def GetInstanceInfo(self
, instance
, static
=None):
418 Gets information about an instance.
420 :type instance: string
421 :param instance: Instance name
427 return self
._SendRequest
("get", ("/%s/instances/%s/info" %
428 (GANETI_RAPI_VERSION
, instance
)))
430 return self
._SendRequest
("get", ("/%s/instances/%s/info" %
431 (GANETI_RAPI_VERSION
, instance
)),
432 query
={"static": static
})
434 def CreateInstance(self
, mode
, name
, disk_template
, disks
, nics
,
437 Creates a new instance.
439 More details for parameters can be found in the RAPI documentation.
442 :param mode: Instance creation mode
444 :param name: Hostname of the instance to create
445 :type disk_template: string
446 :param disk_template: Disk template for instance (e.g. plain, diskless,
448 :type disks: list of dicts
449 :param disks: List of disk definitions
450 :type nics: list of dicts
451 :param nics: List of NIC definitions
453 :keyword dry_run: whether to perform a dry run
454 :type no_install: bool
455 :keyword no_install: whether to create
456 without installing OS(true=don't install)
462 if _INST_CREATE_REQV1
not in self
.GetFeatures():
463 raise GanetiApiError("Cannot create Ganeti 2.1-style instances")
467 if kwargs
.get("dry_run"):
469 if kwargs
.get("no_install"):
470 query
["no-install"] = 1
472 # Make a version 1 request.
474 _REQ_DATA_VERSION_FIELD
: 1,
477 "disk_template": disk_template
,
482 conflicts
= set(kwargs
.iterkeys()) & set(body
.iterkeys())
484 raise GanetiApiError("Required fields can not be specified as"
485 " keywords: %s" % ", ".join(conflicts
))
487 kwargs
.pop("dry_run", None)
490 return self
._SendRequest
("post", "/%s/instances" %
491 GANETI_RAPI_VERSION
, query
=query
,
494 def DeleteInstance(self
, instance
, dry_run
=False):
499 :param instance: the instance to delete
505 return self
._SendRequest
("delete", ("/%s/instances/%s" %
506 (GANETI_RAPI_VERSION
, instance
)),
507 query
={"dry-run": dry_run
})
509 def ModifyInstance(self
, instance
, **kwargs
):
511 Modifies an instance.
513 More details for parameters can be found in the RAPI documentation.
515 :type instance: string
516 :param instance: Instance name
521 return self
._SendRequest
("put", ("/%s/instances/%s/modify" %
522 (GANETI_RAPI_VERSION
, instance
)),
525 def ActivateInstanceDisks(self
, instance
, ignore_size
=False):
527 Activates an instance's disks.
529 :type instance: string
530 :param instance: Instance name
531 :type ignore_size: bool
532 :param ignore_size: Whether to ignore recorded size
536 return self
._SendRequest
("put", ("/%s/instances/%s/activate-disks" %
537 (GANETI_RAPI_VERSION
, instance
)),
538 query
={"ignore_size": ignore_size
})
540 def DeactivateInstanceDisks(self
, instance
):
542 Deactivates an instance's disks.
544 :type instance: string
545 :param instance: Instance name
549 return self
._SendRequest
("put", ("/%s/instances/%s/deactivate-disks" %
550 (GANETI_RAPI_VERSION
, instance
)))
552 def RecreateInstanceDisks(self
, instance
, disks
=None, nodes
=None):
553 """Recreate an instance's disks.
555 :type instance: string
556 :param instance: Instance name
557 :type disks: list of int
558 :param disks: List of disk indexes
559 :type nodes: list of string
560 :param nodes: New instance nodes, if relocation is desired
567 if disks
is not None:
568 body
["disks"] = disks
569 if nodes
is not None:
570 body
["nodes"] = nodes
572 return self
._SendRequest
("post", ("/%s/instances/%s/recreate-disks" %
573 (GANETI_RAPI_VERSION
, instance
)),
576 def GrowInstanceDisk(self
, instance
, disk
, amount
, wait_for_sync
=False):
578 Grows a disk of an instance.
580 More details for parameters can be found in the RAPI documentation.
582 :type instance: string
583 :param instance: Instance name
585 :param disk: Disk index
586 :type amount: integer
587 :param amount: Grow disk by this amount (MiB)
588 :type wait_for_sync: bool
589 :param wait_for_sync: Wait for disk to synchronize
596 "wait_for_sync": wait_for_sync
,
599 return self
._SendRequest
("post", ("/%s/instances/%s/disk/%s/grow" %
600 (GANETI_RAPI_VERSION
, instance
,
601 disk
)), content
=body
)
603 def GetInstanceTags(self
, instance
):
605 Gets tags for an instance.
608 :param instance: instance whose tags to return
611 :return: tags for the instance
614 return self
._SendRequest
("get", ("/%s/instances/%s/tags" %
615 (GANETI_RAPI_VERSION
, instance
)))
617 def AddInstanceTags(self
, instance
, tags
, dry_run
=False):
619 Adds tags to an instance.
622 :param instance: instance to add tags to
623 :type tags: list of str
624 :param tags: tags to add to the instance
626 :param dry_run: whether to perform a dry run
637 return self
._SendRequest
("put", ("/%s/instances/%s/tags" %
638 (GANETI_RAPI_VERSION
, instance
)),
641 def DeleteInstanceTags(self
, instance
, tags
, dry_run
=False):
643 Deletes tags from an instance.
646 :param instance: instance to delete tags from
647 :type tags: list of str
648 :param tags: tags to delete
650 :param dry_run: whether to perform a dry run
658 return self
._SendRequest
("delete", ("/%s/instances/%s/tags" %
659 (GANETI_RAPI_VERSION
, instance
)),
662 def RebootInstance(self
, instance
, reboot_type
=None,
663 ignore_secondaries
=False, dry_run
=False):
668 :param instance: instance to rebot
669 :type reboot_type: str
670 :param reboot_type: one of: hard, soft, full
671 :type ignore_secondaries: bool
672 :param ignore_secondaries: if True, ignores errors
673 for the secondary node
674 while re-assembling disks (in hard-reboot mode only)
676 :param dry_run: whether to perform a dry run
680 "ignore_secondaries": ignore_secondaries
,
685 if reboot_type
not in ("hard", "soft", "full"):
686 raise GanetiApiError("reboot_type must be one of 'hard',"
687 " 'soft', or 'full'")
688 query
["type"] = reboot_type
690 return self
._SendRequest
("post", ("/%s/instances/%s/reboot" %
691 (GANETI_RAPI_VERSION
, instance
)),
694 def ShutdownInstance(self
, instance
, dry_run
=False, no_remember
=False,
697 Shuts down an instance.
700 :param instance: the instance to shut down
702 :param dry_run: whether to perform a dry run
703 :type no_remember: bool
704 :param no_remember: if true, will not record the state change
711 "no-remember": no_remember
,
718 return self
._SendRequest
("put", ("/%s/instances/%s/shutdown" %
719 (GANETI_RAPI_VERSION
, instance
)),
720 query
=query
, content
=content
)
722 def StartupInstance(self
, instance
, dry_run
=False, no_remember
=False):
724 Starts up an instance.
727 :param instance: the instance to start up
729 :param dry_run: whether to perform a dry run
730 :type no_remember: bool
731 :param no_remember: if true, will not record the state change
738 "no-remember": no_remember
,
741 return self
._SendRequest
("put", ("/%s/instances/%s/startup" %
742 (GANETI_RAPI_VERSION
, instance
)),
745 def ReinstallInstance(self
, instance
, os
=None, no_startup
=False,
748 Reinstalls an instance.
751 :param instance: The instance to reinstall
752 :type os: str or None
753 :param os: The operating system to reinstall. If None, the instance's
754 current operating system will be installed again
755 :type no_startup: bool
756 :param no_startup: Whether to start the instance automatically
759 if _INST_REINSTALL_REQV1
in self
.GetFeatures():
761 "start": not no_startup
,
765 if osparams
is not None:
766 body
["osparams"] = osparams
767 return self
._SendRequest
("post", ("/%s/instances/%s/reinstall" %
768 (GANETI_RAPI_VERSION
,
769 instance
)), content
=body
)
771 # Use old request format
773 raise GanetiApiError("Server does not support specifying OS"
774 " parameters for instance reinstallation")
777 "nostartup": no_startup
,
783 return self
._SendRequest
("post", ("/%s/instances/%s/reinstall" %
784 (GANETI_RAPI_VERSION
, instance
)),
787 def ReplaceInstanceDisks(self
, instance
, disks
=None,
788 mode
=REPLACE_DISK_AUTO
, remote_node
=None,
789 iallocator
=None, dry_run
=False):
791 Replaces disks on an instance.
794 :param instance: instance whose disks to replace
795 :type disks: list of ints
796 :param disks: Indexes of disks to replace
798 :param mode: replacement mode to use (defaults to replace_auto)
799 :type remote_node: str or None
800 :param remote_node: new secondary node to use (for use with
801 replace_new_secondary mode)
802 :type iallocator: str or None
803 :param iallocator: instance allocator plugin to use (for use with
806 :param dry_run: whether to perform a dry run
818 query
["disks"] = ",".join(str(idx
) for idx
in disks
)
821 query
["remote_node"] = remote_node
824 query
["iallocator"] = iallocator
826 return self
._SendRequest
("post", ("/%s/instances/%s/replace-disks" %
827 (GANETI_RAPI_VERSION
, instance
)),
830 def PrepareExport(self
, instance
, mode
):
832 Prepares an instance for an export.
834 :type instance: string
835 :param instance: Instance name
837 :param mode: Export mode
842 return self
._SendRequest
("put", ("/%s/instances/%s/prepare-export" %
843 (GANETI_RAPI_VERSION
, instance
)),
844 query
={"mode": mode
})
846 def ExportInstance(self
, instance
, mode
, destination
, shutdown
=None,
847 remove_instance
=None, x509_key_name
=None,
848 destination_x509_ca
=None):
852 :type instance: string
853 :param instance: Instance name
855 :param mode: Export mode
861 "destination": destination
,
865 if shutdown
is not None:
866 body
["shutdown"] = shutdown
868 if remove_instance
is not None:
869 body
["remove_instance"] = remove_instance
871 if x509_key_name
is not None:
872 body
["x509_key_name"] = x509_key_name
874 if destination_x509_ca
is not None:
875 body
["destination_x509_ca"] = destination_x509_ca
877 return self
._SendRequest
("put", ("/%s/instances/%s/export" %
878 (GANETI_RAPI_VERSION
, instance
)),
881 def MigrateInstance(self
, instance
, mode
=None, cleanup
=None):
883 Migrates an instance.
885 :type instance: string
886 :param instance: Instance name
888 :param mode: Migration mode
890 :param cleanup: Whether to clean up a previously failed migration
898 if cleanup
is not None:
899 body
["cleanup"] = cleanup
901 return self
._SendRequest
("put", ("/%s/instances/%s/migrate" %
902 (GANETI_RAPI_VERSION
, instance
)),
905 def FailoverInstance(self
, instance
, iallocator
=None,
906 ignore_consistency
=False, target_node
=None):
907 """Does a failover of an instance.
909 :type instance: string
910 :param instance: Instance name
911 :type iallocator: string
912 :param iallocator: Iallocator for deciding the target node for
913 shared-storage instances
914 :type ignore_consistency: bool
915 :param ignore_consistency: Whether to ignore disk consistency
916 :type target_node: string
917 :param target_node: Target node for shared-storage instances
923 "ignore_consistency": ignore_consistency
,
926 if iallocator
is not None:
927 body
["iallocator"] = iallocator
928 if target_node
is not None:
929 body
["target_node"] = target_node
931 return self
._SendRequest
("put", ("/%s/instances/%s/failover" %
932 (GANETI_RAPI_VERSION
, instance
)),
935 def RenameInstance(self
, instance
, new_name
, ip_check
,
938 Changes the name of an instance.
940 :type instance: string
941 :param instance: Instance name
942 :type new_name: string
943 :param new_name: New instance name
945 :param ip_check: Whether to ensure instance's IP address is inactive
946 :type name_check: bool
947 :param name_check: Whether to ensure instance's name is resolvable
951 "ip_check": ip_check
,
952 "new_name": new_name
,
955 if name_check
is not None:
956 body
["name_check"] = name_check
958 return self
._SendRequest
("put", ("/%s/instances/%s/rename" %
959 (GANETI_RAPI_VERSION
, instance
)),
962 def GetInstanceConsole(self
, instance
):
964 Request information for connecting to instance's console.
966 :type instance: string
967 :param instance: Instance name
970 return self
._SendRequest
("get", ("/%s/instances/%s/console" %
971 (GANETI_RAPI_VERSION
, instance
)))
975 Gets all jobs for the cluster.
978 :return: job ids for the cluster
981 jobs
= self
._SendRequest
("get", "/%s/jobs" % GANETI_RAPI_VERSION
)
983 return [int(job
["id"]) for job
in jobs
]
985 def GetJobStatus(self
, job_id
):
987 Gets the status of a job.
990 :param job_id: job id whose status to query
996 return self
._SendRequest
("get", "/%s/jobs/%s" % (GANETI_RAPI_VERSION
,
999 def WaitForJobChange(self
, job_id
, fields
, prev_job_info
, prev_log_serial
):
1001 Waits for job changes.
1004 :param job_id: Job ID for which to wait
1009 "previous_job_info": prev_job_info
,
1010 "previous_log_serial": prev_log_serial
,
1013 return self
._SendRequest
("get", "/%s/jobs/%s/wait" %
1014 (GANETI_RAPI_VERSION
, job_id
), content
=body
)
1016 def CancelJob(self
, job_id
, dry_run
=False):
1021 :param job_id: id of the job to delete
1023 :param dry_run: whether to perform a dry run
1026 return self
._SendRequest
("delete", "/%s/jobs/%s" %
1027 (GANETI_RAPI_VERSION
, job_id
),
1028 query
={"dry-run": dry_run
})
1030 def GetNodes(self
, bulk
=False):
1032 Gets all nodes in the cluster.
1035 :param bulk: whether to return all information about all instances
1037 :rtype: list of dict or str
1038 :return: if bulk is true, info about nodes in the cluster,
1039 else list of nodes in the cluster
1043 return self
._SendRequest
("get", "/%s/nodes" % GANETI_RAPI_VERSION
,
1046 nodes
= self
._SendRequest
("get", "/%s/nodes" %
1047 GANETI_RAPI_VERSION
)
1048 return [n
["id"] for n
in nodes
]
1050 def GetNode(self
, node
):
1052 Gets information about a node.
1055 :param node: node whose info to return
1058 :return: info about the node
1061 return self
._SendRequest
("get", "/%s/nodes/%s" % (GANETI_RAPI_VERSION
,
1064 def EvacuateNode(self
, node
, iallocator
=None, remote_node
=None,
1065 dry_run
=False, early_release
=False, mode
=None,
1068 Evacuates instances from a Ganeti node.
1071 :param node: node to evacuate
1072 :type iallocator: str or None
1073 :param iallocator: instance allocator to use
1074 :type remote_node: str
1075 :param remote_node: node to evaucate to
1077 :param dry_run: whether to perform a dry run
1078 :type early_release: bool
1079 :param early_release: whether to enable parallelization
1080 :type accept_old: bool
1081 :param accept_old: Whether caller is ready to accept old-style
1084 :rtype: string, or a list for pre-2.5 results
1085 :return: Job ID or, if C{accept_old} is set and server is pre-2.5,
1086 list of (job ID, instance name, new secondary node); if dry_run
1087 was specified, then the actual move jobs were not submitted and
1088 the job IDs will be C{None}
1090 :raises GanetiApiError: if an iallocator and remote_node are both
1094 if iallocator
and remote_node
:
1095 raise GanetiApiError("Only one of iallocator or remote_node can"
1103 query
["iallocator"] = iallocator
1105 query
["remote_node"] = remote_node
1107 if _NODE_EVAC_RES1
in self
.GetFeatures():
1108 # Server supports body parameters
1110 "early_release": early_release
,
1113 if iallocator
is not None:
1114 body
["iallocator"] = iallocator
1115 if remote_node
is not None:
1116 body
["remote_node"] = remote_node
1117 if mode
is not None:
1120 # Pre-2.5 request format
1124 raise GanetiApiError("Server is version 2.4 or earlier and"
1125 " caller does not accept old-style"
1126 " results (parameter accept_old)")
1128 # Pre-2.5 servers can only evacuate secondaries
1129 if mode
is not None and mode
!= NODE_EVAC_SEC
:
1130 raise GanetiApiError("Server can only evacuate "
1131 "secondary instances")
1133 if iallocator
is not None:
1134 query
["iallocator"] = iallocator
1135 if remote_node
is not None:
1136 query
["remote_node"] = remote_node
1138 query
["early_release"] = 1
1140 return self
._SendRequest
("post", ("/%s/nodes/%s/evacuate" %
1141 (GANETI_RAPI_VERSION
, node
)),
1142 query
=query
, content
=body
)
1144 def MigrateNode(self
, node
, mode
=None, dry_run
=False, iallocator
=None,
1147 Migrates all primary instances from a node.
1150 :param node: node to migrate
1152 :param mode: if passed, it will overwrite the live migration type,
1153 otherwise the hypervisor default will be used
1155 :param dry_run: whether to perform a dry run
1156 :type iallocator: string
1157 :param iallocator: instance allocator to use
1158 :type target_node: string
1159 :param target_node: Target node for shared-storage instances
1169 if _NODE_MIGRATE_REQV1
in self
.GetFeatures():
1172 if mode
is not None:
1174 if iallocator
is not None:
1175 body
["iallocator"] = iallocator
1176 if target_node
is not None:
1177 body
["target_node"] = target_node
1180 # Use old request format
1181 if target_node
is not None:
1182 raise GanetiApiError("Server does not support specifying"
1183 " target node for node migration")
1187 if mode
is not None:
1188 query
["mode"] = mode
1190 return self
._SendRequest
("post", ("/%s/nodes/%s/migrate" %
1191 (GANETI_RAPI_VERSION
, node
)),
1192 query
=query
, content
=body
)
1194 def GetNodeRole(self
, node
):
1196 Gets the current role for a node.
1199 :param node: node whose role to return
1202 :return: the current role for a node
1205 return self
._SendRequest
("get", ("/%s/nodes/%s/role" %
1206 (GANETI_RAPI_VERSION
, node
)))
1208 def SetNodeRole(self
, node
, role
, force
=False, auto_promote
=False):
1210 Sets the role for a node.
1213 :param node: the node whose role to set
1215 :param role: the role to set for the node
1217 :param force: whether to force the role change
1218 :type auto_promote: bool
1219 :param auto_promote: Whether node(s) should be promoted to master
1220 candidate if necessary
1228 "auto_promote": auto_promote
,
1231 return self
._SendRequest
("put", ("/%s/nodes/%s/role" %
1232 (GANETI_RAPI_VERSION
, node
)),
1233 query
=query
, content
=role
)
1235 def PowercycleNode(self
, node
, force
=False):
1240 :param node: Node name
1242 :param force: Whether to force the operation
1251 return self
._SendRequest
("post", ("/%s/nodes/%s/powercycle" %
1252 (GANETI_RAPI_VERSION
, node
)),
1255 def ModifyNode(self
, node
, **kwargs
):
1259 More details for parameters can be found in the RAPI documentation.
1262 :param node: Node name
1267 return self
._SendRequest
("post", ("/%s/nodes/%s/modify" %
1268 (GANETI_RAPI_VERSION
, node
)),
1271 def GetNodeStorageUnits(self
, node
, storage_type
, output_fields
):
1273 Gets the storage units for a node.
1276 :param node: the node whose storage units to return
1277 :type storage_type: str
1278 :param storage_type: storage type whose units to return
1279 :type output_fields: str
1280 :param output_fields: storage type fields to return
1283 :return: job id where results can be retrieved
1287 "storage_type": storage_type
,
1288 "output_fields": output_fields
,
1291 return self
._SendRequest
("get", ("/%s/nodes/%s/storage" %
1292 (GANETI_RAPI_VERSION
, node
)),
1295 def ModifyNodeStorageUnits(self
, node
, storage_type
, name
,
1298 Modifies parameters of storage units on the node.
1301 :param node: node whose storage units to modify
1302 :type storage_type: str
1303 :param storage_type: storage type whose units to modify
1305 :param name: name of the storage unit
1306 :type allocatable: bool or None
1307 :param allocatable: Whether to set the "allocatable"
1309 unit (None=no modification, True=set, False=unset)
1316 "storage_type": storage_type
,
1320 if allocatable
is not None:
1321 query
["allocatable"] = allocatable
1323 return self
._SendRequest
("put", ("/%s/nodes/%s/storage/modify" %
1324 (GANETI_RAPI_VERSION
, node
)),
1327 def RepairNodeStorageUnits(self
, node
, storage_type
, name
):
1329 Repairs a storage unit on the node.
1332 :param node: node whose storage units to repair
1333 :type storage_type: str
1334 :param storage_type: storage type to repair
1336 :param name: name of the storage unit to repair
1343 "storage_type": storage_type
,
1347 return self
._SendRequest
("put", ("/%s/nodes/%s/storage/repair" %
1348 (GANETI_RAPI_VERSION
, node
)),
1351 def GetNodeTags(self
, node
):
1353 Gets the tags for a node.
1356 :param node: node whose tags to return
1359 :return: tags for the node
1362 return self
._SendRequest
("get", ("/%s/nodes/%s/tags" %
1363 (GANETI_RAPI_VERSION
, node
)))
1365 def AddNodeTags(self
, node
, tags
, dry_run
=False):
1367 Adds tags to a node.
1370 :param node: node to add tags to
1371 :type tags: list of str
1372 :param tags: tags to add to the node
1374 :param dry_run: whether to perform a dry run
1385 return self
._SendRequest
("put", ("/%s/nodes/%s/tags" %
1386 (GANETI_RAPI_VERSION
, node
)),
1387 query
=query
, content
=tags
)
1389 def DeleteNodeTags(self
, node
, tags
, dry_run
=False):
1391 Delete tags from a node.
1394 :param node: node to remove tags from
1395 :type tags: list of str
1396 :param tags: tags to remove from the node
1398 :param dry_run: whether to perform a dry run
1409 return self
._SendRequest
("delete", ("/%s/nodes/%s/tags" %
1410 (GANETI_RAPI_VERSION
, node
)),
1413 def GetGroups(self
, bulk
=False):
1415 Gets all node groups in the cluster.
1418 :param bulk: whether to return all information about the groups
1420 :rtype: list of dict or str
1421 :return: if bulk is true, a list of dictionaries
1422 with info about all node groups
1423 in the cluster, else a list of names of those node groups
1427 return self
._SendRequest
("get", "/%s/groups" %
1428 GANETI_RAPI_VERSION
, query
={"bulk": 1})
1430 groups
= self
._SendRequest
("get", "/%s/groups" %
1431 GANETI_RAPI_VERSION
)
1432 return [g
["name"] for g
in groups
]
1434 def GetGroup(self
, group
):
1436 Gets information about a node group.
1439 :param group: name of the node group whose info to return
1442 :return: info about the node group
1445 return self
._SendRequest
("get", "/%s/groups/%s" %
1446 (GANETI_RAPI_VERSION
, group
))
1448 def CreateGroup(self
, name
, alloc_policy
=None, dry_run
=False):
1450 Creates a new node group.
1453 :param name: the name of node group to create
1454 :type alloc_policy: str
1455 :param alloc_policy: the desired allocation
1456 policy for the group, if any
1458 :param dry_run: whether to peform a dry run
1470 "alloc_policy": alloc_policy
1473 return self
._SendRequest
("post", "/%s/groups" % GANETI_RAPI_VERSION
,
1474 query
=query
, content
=body
)
1476 def ModifyGroup(self
, group
, **kwargs
):
1478 Modifies a node group.
1480 More details for parameters can be found in the RAPI documentation.
1483 :param group: Node group name
1488 return self
._SendRequest
("put", ("/%s/groups/%s/modify" %
1489 (GANETI_RAPI_VERSION
, group
)),
1492 def DeleteGroup(self
, group
, dry_run
=False):
1494 Deletes a node group.
1497 :param group: the node group to delete
1499 :param dry_run: whether to peform a dry run
1509 return self
._SendRequest
("delete", ("/%s/groups/%s" %
1510 (GANETI_RAPI_VERSION
, group
)),
1513 def RenameGroup(self
, group
, new_name
):
1515 Changes the name of a node group.
1518 :param group: Node group name
1519 :type new_name: string
1520 :param new_name: New node group name
1527 "new_name": new_name
,
1530 return self
._SendRequest
("put", ("/%s/groups/%s/rename" %
1531 (GANETI_RAPI_VERSION
, group
)),
1534 def AssignGroupNodes(self
, group
, nodes
, force
=False, dry_run
=False):
1536 Assigns nodes to a group.
1539 :param group: Node gropu name
1540 :type nodes: list of strings
1541 :param nodes: List of nodes to assign to the group
1557 return self
._SendRequest
("put", ("/%s/groups/%s/assign-nodes" %
1558 (GANETI_RAPI_VERSION
, group
)),
1559 query
=query
, content
=body
)
1561 def GetGroupTags(self
, group
):
1563 Gets tags for a node group.
1566 :param group: Node group whose tags to return
1568 :rtype: list of strings
1569 :return: tags for the group
1572 return self
._SendRequest
("get", ("/%s/groups/%s/tags" %
1573 (GANETI_RAPI_VERSION
, group
)))
1575 def AddGroupTags(self
, group
, tags
, dry_run
=False):
1577 Adds tags to a node group.
1580 :param group: group to add tags to
1581 :type tags: list of string
1582 :param tags: tags to add to the group
1584 :param dry_run: whether to perform a dry run
1595 return self
._SendRequest
("put", ("/%s/groups/%s/tags" %
1596 (GANETI_RAPI_VERSION
, group
)),
1599 def DeleteGroupTags(self
, group
, tags
, dry_run
=False):
1601 Deletes tags from a node group.
1604 :param group: group to delete tags from
1605 :type tags: list of string
1606 :param tags: tags to delete
1608 :param dry_run: whether to perform a dry run
1618 return self
._SendRequest
("delete", ("/%s/groups/%s/tags" %
1619 (GANETI_RAPI_VERSION
, group
)),
1622 def Query(self
, what
, fields
, qfilter
=None):
1624 Retrieves information about resources.
1627 :param what: Resource name, one of L{constants.QR_VIA_RAPI}
1628 :type fields: list of string
1629 :param fields: Requested fields
1630 :type qfilter: None or list
1631 :param qfilter: Query filter
1641 if qfilter
is not None:
1642 body
["qfilter"] = body
["filter"] = qfilter
1644 return self
._SendRequest
("put", ("/%s/query/%s" %
1645 (GANETI_RAPI_VERSION
, what
)),
1648 def QueryFields(self
, what
, fields
=None):
1650 Retrieves available fields for a resource.
1653 :param what: Resource name, one of L{constants.QR_VIA_RAPI}
1654 :type fields: list of string
1655 :param fields: Requested fields
1663 if fields
is not None:
1664 query
["fields"] = ",".join(fields
)
1666 return self
._SendRequest
("get", ("/%s/query/%s/fields" %
1667 (GANETI_RAPI_VERSION
, what
)),