| Trees | Indices | Help |
|
|---|
|
|
1 #
2 #
3
4 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 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 """Remote API resource implementations.
32
33 PUT or POST?
34 ============
35
36 According to RFC2616 the main difference between PUT and POST is that
37 POST can create new resources but PUT can only create the resource the
38 URI was pointing to on the PUT request.
39
40 In the context of this module POST on ``/2/instances`` to change an existing
41 entity is legitimate, while PUT would not be. PUT creates a new entity (e.g. a
42 new instance) with a name specified in the request.
43
44 Quoting from RFC2616, section 9.6::
45
46 The fundamental difference between the POST and PUT requests is reflected in
47 the different meaning of the Request-URI. The URI in a POST request
48 identifies the resource that will handle the enclosed entity. That resource
49 might be a data-accepting process, a gateway to some other protocol, or a
50 separate entity that accepts annotations. In contrast, the URI in a PUT
51 request identifies the entity enclosed with the request -- the user agent
52 knows what URI is intended and the server MUST NOT attempt to apply the
53 request to some other resource. If the server desires that the request be
54 applied to a different URI, it MUST send a 301 (Moved Permanently) response;
55 the user agent MAY then make its own decision regarding whether or not to
56 redirect the request.
57
58 So when adding new methods, if they are operating on the URI entity itself,
59 PUT should be prefered over POST.
60
61 """
62
63 # pylint: disable=C0103
64
65 # C0103: Invalid name, since the R_* names are not conforming
66
67 from ganeti import opcodes
68 from ganeti import objects
69 from ganeti import http
70 from ganeti import constants
71 from ganeti import cli
72 from ganeti import rapi
73 from ganeti import ht
74 from ganeti import compat
75 from ganeti.rapi import baserlib
76
77
78 _COMMON_FIELDS = ["ctime", "mtime", "uuid", "serial_no", "tags"]
79 I_FIELDS = ["name", "admin_state", "os",
80 "pnode", "snodes",
81 "disk_template",
82 "nic.ips", "nic.macs", "nic.modes", "nic.uuids", "nic.names",
83 "nic.links", "nic.networks", "nic.networks.names", "nic.bridges",
84 "network_port",
85 "disk.sizes", "disk.spindles", "disk_usage", "disk.uuids",
86 "disk.names",
87 "beparams", "hvparams",
88 "oper_state", "oper_ram", "oper_vcpus", "status",
89 "custom_hvparams", "custom_beparams", "custom_nicparams",
90 ] + _COMMON_FIELDS
91
92 N_FIELDS = ["name", "offline", "master_candidate", "drained",
93 "dtotal", "dfree", "sptotal", "spfree",
94 "mtotal", "mnode", "mfree", "hv_state",
95 "pinst_cnt", "sinst_cnt",
96 "ctotal", "cnos", "cnodes", "csockets",
97 "pip", "sip", "role",
98 "pinst_list", "sinst_list",
99 "master_capable", "vm_capable",
100 "ndparams",
101 "group.uuid",
102 ] + _COMMON_FIELDS
103
104 NET_FIELDS = ["name", "network", "gateway",
105 "network6", "gateway6",
106 "mac_prefix",
107 "free_count", "reserved_count",
108 "map", "group_list", "inst_list",
109 "external_reservations",
110 ] + _COMMON_FIELDS
111
112 G_FIELDS = [
113 "alloc_policy",
114 "name",
115 "node_cnt",
116 "node_list",
117 "ipolicy",
118 "custom_ipolicy",
119 "diskparams",
120 "custom_diskparams",
121 "ndparams",
122 "custom_ndparams"
123 ] + _COMMON_FIELDS
124
125 FILTER_RULE_FIELDS = [
126 "watermark",
127 "priority",
128 "predicates",
129 "action",
130 "reason_trail",
131 "uuid",
132 ]
133
134 J_FIELDS_BULK = [
135 "id", "ops", "status", "summary",
136 "opstatus",
137 "received_ts", "start_ts", "end_ts",
138 ]
139
140 J_FIELDS = J_FIELDS_BULK + [
141 "oplog",
142 "opresult",
143 ]
144
145 _NR_DRAINED = "drained"
146 _NR_MASTER_CANDIDATE = "master-candidate"
147 _NR_MASTER = "master"
148 _NR_OFFLINE = "offline"
149 _NR_REGULAR = "regular"
150
151 _NR_MAP = {
152 constants.NR_MASTER: _NR_MASTER,
153 constants.NR_MCANDIDATE: _NR_MASTER_CANDIDATE,
154 constants.NR_DRAINED: _NR_DRAINED,
155 constants.NR_OFFLINE: _NR_OFFLINE,
156 constants.NR_REGULAR: _NR_REGULAR,
157 }
158
159 assert frozenset(_NR_MAP.keys()) == constants.NR_ALL
160
161 # Request data version field
162 _REQ_DATA_VERSION = "__version__"
163
164 # Feature string for instance creation request data version 1
165 _INST_CREATE_REQV1 = "instance-create-reqv1"
166
167 # Feature string for instance reinstall request version 1
168 _INST_REINSTALL_REQV1 = "instance-reinstall-reqv1"
169
170 # Feature string for node migration version 1
171 _NODE_MIGRATE_REQV1 = "node-migrate-reqv1"
172
173 # Feature string for node evacuation with LU-generated jobs
174 _NODE_EVAC_RES1 = "node-evac-res1"
175
176 ALL_FEATURES = compat.UniqueFrozenset([
177 _INST_CREATE_REQV1,
178 _INST_REINSTALL_REQV1,
179 _NODE_MIGRATE_REQV1,
180 _NODE_EVAC_RES1,
181 ])
182
183 # Timeout for /2/jobs/[job_id]/wait. Gives job up to 10 seconds to change.
184 _WFJC_TIMEOUT = 10
185
186
187 # FIXME: For compatibility we update the beparams/memory field. Needs to be
188 # removed in Ganeti 2.8
189 -def _UpdateBeparams(inst):
190 """Updates the beparams dict of inst to support the memory field.
191
192 @param inst: Inst dict
193 @return: Updated inst dict
194
195 """
196 beparams = inst["beparams"]
197 beparams[constants.BE_MEMORY] = beparams[constants.BE_MAXMEM]
198
199 return inst
200
212
218
221 """/version resource.
222
223 This resource should be used to determine the remote API version and
224 to adapt clients accordingly.
225
226 """
227 @staticmethod
233
236 """/2/info resource.
237
238 """
239 GET_OPCODE = opcodes.OpClusterQuery
240 GET_ALIASES = {
241 "volume_group_name": "vg_name",
242 "drbd_usermode_helper": "drbd_helper",
243 }
244
246 """Returns cluster information.
247
248 """
249 client = self.GetClient()
250 return client.QueryClusterInfo()
251
254 """/2/features resource.
255
256 """
257 @staticmethod
259 """Returns list of optional RAPI features implemented.
260
261 """
262 return list(ALL_FEATURES)
263
266 """/2/os resource.
267
268 """
269 GET_OPCODE = opcodes.OpOsDiagnose
270
272 """Return a list of all OSes.
273
274 Can return error 500 in case of a problem.
275
276 Example: ["debian-etch"]
277
278 """
279 cl = self.GetClient()
280 op = opcodes.OpOsDiagnose(output_fields=["name", "variants"], names=[])
281 job_id = self.SubmitJob([op], cl=cl)
282 # we use custom feedback function, instead of print we log the status
283 result = cli.PollJob(job_id, cl, feedback_fn=baserlib.FeedbackFn)
284 diagnose_data = result[0]
285
286 if not isinstance(diagnose_data, list):
287 raise http.HttpBadGateway(message="Can't get OS list")
288
289 os_names = []
290 for (name, variants) in diagnose_data:
291 os_names.extend(cli.CalculateOSNames(name, variants))
292
293 return os_names
294
301
304 """/2/modify resource.
305
306 """
307 PUT_OPCODE = opcodes.OpClusterSetParams
308 PUT_FORBIDDEN = [
309 "compression_tools",
310 ]
311
314 """Checks and extracts filter rule parameters from a request body.
315
316 @return: the checked parameters: (priority, predicates, action).
317
318 """
319
320 if not isinstance(data, dict):
321 raise http.HttpBadRequest("Invalid body contents, not a dictionary")
322
323 # Forbid unknown parameters
324 allowed_params = set(["priority", "predicates", "action", "reason"])
325 for param in data:
326 if param not in allowed_params:
327 raise http.HttpBadRequest("Invalid body parameters: filter rule doesn't"
328 " support the parameter '%s'" % param)
329
330 priority = baserlib.CheckParameter(
331 data, "priority", exptype=int, default=0)
332
333 # We leave the deeper check into the predicates list to the server.
334 predicates = baserlib.CheckParameter(
335 data, "predicates", exptype=list, default=[])
336
337 # The action can be a string or a list; we leave the check to the server.
338 action = baserlib.CheckParameter(data, "action", default="CONTINUE")
339
340 reason = baserlib.CheckParameter(data, "reason", exptype=list, default=[])
341
342 return (priority, predicates, action, reason)
343
346 """/2/filters resource.
347
348 """
349
351 """Returns a list of all filter rules.
352
353 @return: a dictionary with filter rule UUID and uri.
354
355 """
356 client = self.GetClient()
357
358 if self.useBulk():
359 bulkdata = client.QueryFilters(None, FILTER_RULE_FIELDS)
360 return baserlib.MapBulkFields(bulkdata, FILTER_RULE_FIELDS)
361 else:
362 jobdata = map(compat.fst, client.QueryFilters(None, ["uuid"]))
363 return baserlib.BuildUriList(jobdata, "/2/filters/%s",
364 uri_fields=("uuid", "uri"))
365
367 """Adds a filter rule.
368
369 @return: the UUID of the newly created filter rule.
370
371 """
372 priority, predicates, action, reason = \
373 checkFilterParameters(self.request_body)
374
375 # ReplaceFilter(None, ...) inserts a new filter.
376 return self.GetClient().ReplaceFilter(None, priority, predicates, action,
377 reason)
378
381 """/2/filters/[filter_uuid] resource.
382
383 """
385 """Returns a filter rule.
386
387 @return: a dictionary with job parameters.
388 The result includes:
389 - uuid: unique filter ID string
390 - watermark: highest job ID ever used as a number
391 - priority: filter priority as a non-negative number
392 - predicates: filter predicates, each one being a list
393 with the first element being the name of the predicate
394 and the rest being parameters suitable for that predicate
395 - action: effect of the filter as a string
396 - reason_trail: reasons for the addition of this filter as a
397 list of lists
398
399 """
400 uuid = self.items[0]
401
402 result = baserlib.HandleItemQueryErrors(self.GetClient().QueryFilters,
403 uuids=[uuid],
404 fields=FILTER_RULE_FIELDS)
405
406 return baserlib.MapFields(FILTER_RULE_FIELDS, result[0])
407
409 """Replaces an existing filter rule, or creates one if it doesn't
410 exist already.
411
412 @return: the UUID of the changed or created filter rule.
413
414 """
415 uuid = self.items[0]
416
417 priority, predicates, action, reason = \
418 checkFilterParameters(self.request_body)
419
420 return self.GetClient().ReplaceFilter(uuid, priority, predicates, action,
421 reason)
422
424 """Deletes a filter rule.
425
426 """
427 uuid = self.items[0]
428 return self.GetClient().DeleteFilter(uuid)
429
432 """/2/jobs resource.
433
434 """
436 """Returns a dictionary of jobs.
437
438 @return: a dictionary with jobs id and uri.
439
440 """
441 client = self.GetClient()
442
443 if self.useBulk():
444 bulkdata = client.QueryJobs(None, J_FIELDS_BULK)
445 return baserlib.MapBulkFields(bulkdata, J_FIELDS_BULK)
446 else:
447 jobdata = map(compat.fst, client.QueryJobs(None, ["id"]))
448 return baserlib.BuildUriList(jobdata, "/2/jobs/%s",
449 uri_fields=("id", "uri"))
450
453 """/2/jobs/[job_id] resource.
454
455 """
457 """Returns a job status.
458
459 @return: a dictionary with job parameters.
460 The result includes:
461 - id: job ID as a number
462 - status: current job status as a string
463 - ops: involved OpCodes as a list of dictionaries for each
464 opcodes in the job
465 - opstatus: OpCodes status as a list
466 - opresult: OpCodes results as a list of lists
467
468 """
469 job_id = self.items[0]
470 result = self.GetClient().QueryJobs([job_id, ], J_FIELDS)[0]
471 if result is None:
472 raise http.HttpNotFound()
473 return baserlib.MapFields(J_FIELDS, result)
474
482
485 """/2/jobs/[job_id]/wait resource.
486
487 """
488 # WaitForJobChange provides access to sensitive information and blocks
489 # machine resources (it's a blocking RAPI call), hence restricting access.
490 GET_ACCESS = [rapi.RAPI_ACCESS_WRITE]
491
493 """Waits for job changes.
494
495 """
496 job_id = self.items[0]
497
498 fields = self.getBodyParameter("fields")
499 prev_job_info = self.getBodyParameter("previous_job_info", None)
500 prev_log_serial = self.getBodyParameter("previous_log_serial", None)
501
502 if not isinstance(fields, list):
503 raise http.HttpBadRequest("The 'fields' parameter should be a list")
504
505 if not (prev_job_info is None or isinstance(prev_job_info, list)):
506 raise http.HttpBadRequest("The 'previous_job_info' parameter should"
507 " be a list")
508
509 if not (prev_log_serial is None or
510 isinstance(prev_log_serial, (int, long))):
511 raise http.HttpBadRequest("The 'previous_log_serial' parameter should"
512 " be a number")
513
514 client = self.GetClient()
515 result = client.WaitForJobChangeOnce(job_id, fields,
516 prev_job_info, prev_log_serial,
517 timeout=_WFJC_TIMEOUT)
518 if not result:
519 raise http.HttpNotFound()
520
521 if result == constants.JOB_NOTCHANGED:
522 # No changes
523 return None
524
525 (job_info, log_entries) = result
526
527 return {
528 "job_info": job_info,
529 "log_entries": log_entries,
530 }
531
534 """/2/nodes resource.
535
536 """
537
539 """Returns a list of all nodes.
540
541 """
542 client = self.GetClient()
543
544 if self.useBulk():
545 bulkdata = client.QueryNodes([], N_FIELDS, False)
546 return baserlib.MapBulkFields(bulkdata, N_FIELDS)
547 else:
548 nodesdata = client.QueryNodes([], ["name"], False)
549 nodeslist = [row[0] for row in nodesdata]
550 return baserlib.BuildUriList(nodeslist, "/2/nodes/%s",
551 uri_fields=("id", "uri"))
552
555 """/2/nodes/[node_name] resource.
556
557 """
558 GET_ALIASES = {
559 "sip": "secondary_ip",
560 }
561
563 """Send information about a node.
564
565 """
566 node_name = self.items[0]
567 client = self.GetClient()
568
569 result = baserlib.HandleItemQueryErrors(client.QueryNodes,
570 names=[node_name], fields=N_FIELDS,
571 use_locking=self.useLocking())
572
573 return baserlib.MapFields(N_FIELDS, result[0])
574
577 """/2/nodes/[node_name]/powercycle resource.
578
579 """
580 POST_OPCODE = opcodes.OpNodePowercycle
581
583 """Tries to powercycle a node.
584
585 """
586 return (self.request_body, {
587 "node_name": self.items[0],
588 "force": self.useForce(),
589 })
590
593 """/2/nodes/[node_name]/role resource.
594
595 """
596 PUT_OPCODE = opcodes.OpNodeSetParams
597
599 """Returns the current node role.
600
601 @return: Node role
602
603 """
604 node_name = self.items[0]
605 client = self.GetClient()
606 result = client.QueryNodes(names=[node_name], fields=["role"],
607 use_locking=self.useLocking())
608
609 return _NR_MAP[result[0][0]]
610
612 """Sets the node role.
613
614 """
615 baserlib.CheckType(self.request_body, basestring, "Body contents")
616
617 role = self.request_body
618
619 if role == _NR_REGULAR:
620 candidate = False
621 offline = False
622 drained = False
623
624 elif role == _NR_MASTER_CANDIDATE:
625 candidate = True
626 offline = drained = None
627
628 elif role == _NR_DRAINED:
629 drained = True
630 candidate = offline = None
631
632 elif role == _NR_OFFLINE:
633 offline = True
634 candidate = drained = None
635
636 else:
637 raise http.HttpBadRequest("Can't set '%s' role" % role)
638
639 assert len(self.items) == 1
640
641 return ({}, {
642 "node_name": self.items[0],
643 "master_candidate": candidate,
644 "offline": offline,
645 "drained": drained,
646 "force": self.useForce(),
647 "auto_promote": bool(self._checkIntVariable("auto-promote", default=0)),
648 })
649
652 """/2/nodes/[node_name]/evacuate resource.
653
654 """
655 POST_OPCODE = opcodes.OpNodeEvacuate
656
658 """Evacuate all instances off a node.
659
660 """
661 return (self.request_body, {
662 "node_name": self.items[0],
663 "dry_run": self.dryRun(),
664 })
665
668 """/2/nodes/[node_name]/migrate resource.
669
670 """
671 POST_OPCODE = opcodes.OpNodeMigrate
672
674 """Migrate all primary instances from a node.
675
676 """
677 if self.queryargs:
678 # Support old-style requests
679 if "live" in self.queryargs and "mode" in self.queryargs:
680 raise http.HttpBadRequest("Only one of 'live' and 'mode' should"
681 " be passed")
682
683 if "live" in self.queryargs:
684 if self._checkIntVariable("live", default=1):
685 mode = constants.HT_MIGRATION_LIVE
686 else:
687 mode = constants.HT_MIGRATION_NONLIVE
688 else:
689 mode = self._checkStringVariable("mode", default=None)
690
691 data = {
692 "mode": mode,
693 }
694 else:
695 data = self.request_body
696
697 return (data, {
698 "node_name": self.items[0],
699 })
700
703 """/2/nodes/[node_name]/modify resource.
704
705 """
706 POST_OPCODE = opcodes.OpNodeSetParams
707
709 """Changes parameters of a node.
710
711 """
712 assert len(self.items) == 1
713
714 return (self.request_body, {
715 "node_name": self.items[0],
716 })
717
720 """/2/nodes/[node_name]/storage resource.
721
722 """
723 # LUNodeQueryStorage acquires locks, hence restricting access to GET
724 GET_ACCESS = [rapi.RAPI_ACCESS_WRITE]
725 GET_OPCODE = opcodes.OpNodeQueryStorage
726
728 """List storage available on a node.
729
730 """
731 storage_type = self._checkStringVariable("storage_type", None)
732 output_fields = self._checkStringVariable("output_fields", None)
733
734 if not output_fields:
735 raise http.HttpBadRequest("Missing the required 'output_fields'"
736 " parameter")
737
738 return ({}, {
739 "nodes": [self.items[0]],
740 "storage_type": storage_type,
741 "output_fields": output_fields.split(","),
742 })
743
746 """/2/nodes/[node_name]/storage/modify resource.
747
748 """
749 PUT_OPCODE = opcodes.OpNodeModifyStorage
750
752 """Modifies a storage volume on a node.
753
754 """
755 storage_type = self._checkStringVariable("storage_type", None)
756 name = self._checkStringVariable("name", None)
757
758 if not name:
759 raise http.HttpBadRequest("Missing the required 'name'"
760 " parameter")
761
762 changes = {}
763
764 if "allocatable" in self.queryargs:
765 changes[constants.SF_ALLOCATABLE] = \
766 bool(self._checkIntVariable("allocatable", default=1))
767
768 return ({}, {
769 "node_name": self.items[0],
770 "storage_type": storage_type,
771 "name": name,
772 "changes": changes,
773 })
774
777 """/2/nodes/[node_name]/storage/repair resource.
778
779 """
780 PUT_OPCODE = opcodes.OpRepairNodeStorage
781
783 """Repairs a storage volume on a node.
784
785 """
786 storage_type = self._checkStringVariable("storage_type", None)
787 name = self._checkStringVariable("name", None)
788 if not name:
789 raise http.HttpBadRequest("Missing the required 'name'"
790 " parameter")
791
792 return ({}, {
793 "node_name": self.items[0],
794 "storage_type": storage_type,
795 "name": name,
796 })
797
800 """/2/networks resource.
801
802 """
803 POST_OPCODE = opcodes.OpNetworkAdd
804 POST_RENAME = {
805 "name": "network_name",
806 }
807
809 """Create a network.
810
811 """
812 assert not self.items
813 return (self.request_body, {
814 "dry_run": self.dryRun(),
815 })
816
818 """Returns a list of all networks.
819
820 """
821 client = self.GetClient()
822
823 if self.useBulk():
824 bulkdata = client.QueryNetworks([], NET_FIELDS, False)
825 return baserlib.MapBulkFields(bulkdata, NET_FIELDS)
826 else:
827 data = client.QueryNetworks([], ["name"], False)
828 networknames = [row[0] for row in data]
829 return baserlib.BuildUriList(networknames, "/2/networks/%s",
830 uri_fields=("name", "uri"))
831
834 """/2/networks/[network_name] resource.
835
836 """
837 DELETE_OPCODE = opcodes.OpNetworkRemove
838
840 """Send information about a network.
841
842 """
843 network_name = self.items[0]
844 client = self.GetClient()
845
846 result = baserlib.HandleItemQueryErrors(client.QueryNetworks,
847 names=[network_name],
848 fields=NET_FIELDS,
849 use_locking=self.useLocking())
850
851 return baserlib.MapFields(NET_FIELDS, result[0])
852
854 """Delete a network.
855
856 """
857 assert len(self.items) == 1
858 return (self.request_body, {
859 "network_name": self.items[0],
860 "dry_run": self.dryRun(),
861 })
862
865 """/2/networks/[network_name]/connect resource.
866
867 """
868 PUT_OPCODE = opcodes.OpNetworkConnect
869
871 """Changes some parameters of node group.
872
873 """
874 assert self.items
875 return (self.request_body, {
876 "network_name": self.items[0],
877 "dry_run": self.dryRun(),
878 })
879
882 """/2/networks/[network_name]/disconnect resource.
883
884 """
885 PUT_OPCODE = opcodes.OpNetworkDisconnect
886
888 """Changes some parameters of node group.
889
890 """
891 assert self.items
892 return (self.request_body, {
893 "network_name": self.items[0],
894 "dry_run": self.dryRun(),
895 })
896
899 """/2/networks/[network_name]/modify resource.
900
901 """
902 PUT_OPCODE = opcodes.OpNetworkSetParams
903
905 """Changes some parameters of network.
906
907 """
908 assert self.items
909 return (self.request_body, {
910 "network_name": self.items[0],
911 })
912
915 """/2/groups resource.
916
917 """
918 POST_OPCODE = opcodes.OpGroupAdd
919 POST_RENAME = {
920 "name": "group_name",
921 }
922
924 """Create a node group.
925
926
927 """
928 assert not self.items
929 return (self.request_body, {
930 "dry_run": self.dryRun(),
931 })
932
934 """Returns a list of all node groups.
935
936 """
937 client = self.GetClient()
938
939 if self.useBulk():
940 bulkdata = client.QueryGroups([], G_FIELDS, False)
941 return baserlib.MapBulkFields(bulkdata, G_FIELDS)
942 else:
943 data = client.QueryGroups([], ["name"], False)
944 groupnames = [row[0] for row in data]
945 return baserlib.BuildUriList(groupnames, "/2/groups/%s",
946 uri_fields=("name", "uri"))
947
950 """/2/groups/[group_name] resource.
951
952 """
953 DELETE_OPCODE = opcodes.OpGroupRemove
954
956 """Send information about a node group.
957
958 """
959 group_name = self.items[0]
960 client = self.GetClient()
961
962 result = baserlib.HandleItemQueryErrors(client.QueryGroups,
963 names=[group_name], fields=G_FIELDS,
964 use_locking=self.useLocking())
965
966 return baserlib.MapFields(G_FIELDS, result[0])
967
969 """Delete a node group.
970
971 """
972 assert len(self.items) == 1
973 return ({}, {
974 "group_name": self.items[0],
975 "dry_run": self.dryRun(),
976 })
977
980 """/2/groups/[group_name]/modify resource.
981
982 """
983 PUT_OPCODE = opcodes.OpGroupSetParams
984 PUT_RENAME = {
985 "custom_ndparams": "ndparams",
986 "custom_ipolicy": "ipolicy",
987 "custom_diskparams": "diskparams",
988 }
989
991 """Changes some parameters of node group.
992
993 """
994 assert self.items
995 return (self.request_body, {
996 "group_name": self.items[0],
997 })
998
1001 """/2/groups/[group_name]/rename resource.
1002
1003 """
1004 PUT_OPCODE = opcodes.OpGroupRename
1005
1007 """Changes the name of a node group.
1008
1009 """
1010 assert len(self.items) == 1
1011 return (self.request_body, {
1012 "group_name": self.items[0],
1013 "dry_run": self.dryRun(),
1014 })
1015
1018 """/2/groups/[group_name]/assign-nodes resource.
1019
1020 """
1021 PUT_OPCODE = opcodes.OpGroupAssignNodes
1022
1024 """Assigns nodes to a group.
1025
1026 """
1027 assert len(self.items) == 1
1028 return (self.request_body, {
1029 "group_name": self.items[0],
1030 "dry_run": self.dryRun(),
1031 "force": self.useForce(),
1032 })
1033
1036 """Convert in place the usb_devices string to the proper format.
1037
1038 In Ganeti 2.8.4 the separator for the usb_devices hvparam was changed from
1039 comma to space because commas cannot be accepted on the command line
1040 (they already act as the separator between different hvparams). RAPI
1041 should be able to accept commas for backwards compatibility, but we want
1042 it to also accept the new space separator. Therefore, we convert
1043 spaces into commas here and keep the old parsing logic elsewhere.
1044
1045 """
1046 try:
1047 hvparams = data["hvparams"]
1048 usb_devices = hvparams[constants.HV_USB_DEVICES]
1049 hvparams[constants.HV_USB_DEVICES] = usb_devices.replace(" ", ",")
1050 data["hvparams"] = hvparams
1051 except KeyError:
1052 #No usb_devices, no modification required
1053 pass
1054
1057 """/2/instances resource.
1058
1059 """
1060 POST_OPCODE = opcodes.OpInstanceCreate
1061 POST_RENAME = {
1062 "os": "os_type",
1063 "name": "instance_name",
1064 }
1065
1067 """Returns a list of all available instances.
1068
1069 """
1070 client = self.GetClient()
1071
1072 use_locking = self.useLocking()
1073 if self.useBulk():
1074 bulkdata = client.QueryInstances([], I_FIELDS, use_locking)
1075 return map(_UpdateBeparams, baserlib.MapBulkFields(bulkdata, I_FIELDS))
1076 else:
1077 instancesdata = client.QueryInstances([], ["name"], use_locking)
1078 instanceslist = [row[0] for row in instancesdata]
1079 return baserlib.BuildUriList(instanceslist, "/2/instances/%s",
1080 uri_fields=("id", "uri"))
1081
1083 """Create an instance.
1084
1085 @return: a job id
1086
1087 """
1088 baserlib.CheckType(self.request_body, dict, "Body contents")
1089
1090 # Default to request data version 0
1091 data_version = self.getBodyParameter(_REQ_DATA_VERSION, 0)
1092
1093 if data_version == 0:
1094 raise http.HttpBadRequest("Instance creation request version 0 is no"
1095 " longer supported")
1096 elif data_version != 1:
1097 raise http.HttpBadRequest("Unsupported request data version %s" %
1098 data_version)
1099
1100 data = self.request_body.copy()
1101 # Remove "__version__"
1102 data.pop(_REQ_DATA_VERSION, None)
1103
1104 _ConvertUsbDevices(data)
1105
1106 return (data, {
1107 "dry_run": self.dryRun(),
1108 })
1109
1112 """/2/instances-multi-alloc resource.
1113
1114 """
1115 POST_OPCODE = opcodes.OpInstanceMultiAlloc
1116
1118 """Try to allocate multiple instances.
1119
1120 @return: A dict with submitted jobs, allocatable instances and failed
1121 allocations
1122
1123 """
1124 if "instances" not in self.request_body:
1125 raise http.HttpBadRequest("Request is missing required 'instances' field"
1126 " in body")
1127
1128 # Unlike most other RAPI calls, this one is composed of individual opcodes,
1129 # and we have to do the filling ourselves
1130 OPCODE_RENAME = {
1131 "os": "os_type",
1132 "name": "instance_name",
1133 }
1134
1135 body = objects.FillDict(self.request_body, {
1136 "instances": [
1137 baserlib.FillOpcode(opcodes.OpInstanceCreate, inst, {},
1138 rename=OPCODE_RENAME)
1139 for inst in self.request_body["instances"]
1140 ],
1141 })
1142
1143 return (body, {
1144 "dry_run": self.dryRun(),
1145 })
1146
1149 """/2/instances/[instance_name] resource.
1150
1151 """
1152 DELETE_OPCODE = opcodes.OpInstanceRemove
1153
1155 """Send information about an instance.
1156
1157 """
1158 client = self.GetClient()
1159 instance_name = self.items[0]
1160
1161 result = baserlib.HandleItemQueryErrors(client.QueryInstances,
1162 names=[instance_name],
1163 fields=I_FIELDS,
1164 use_locking=self.useLocking())
1165
1166 return _UpdateBeparams(baserlib.MapFields(I_FIELDS, result[0]))
1167
1169 """Delete an instance.
1170
1171 """
1172 assert len(self.items) == 1
1173 return (self.request_body, {
1174 "instance_name": self.items[0],
1175 "ignore_failures": False,
1176 "dry_run": self.dryRun(),
1177 })
1178
1181 """/2/instances/[instance_name]/info resource.
1182
1183 """
1184 GET_OPCODE = opcodes.OpInstanceQueryData
1185
1187 """Request detailed instance information.
1188
1189 """
1190 assert len(self.items) == 1
1191 return ({}, {
1192 "instances": [self.items[0]],
1193 "static": bool(self._checkIntVariable("static", default=0)),
1194 })
1195
1198 """/2/instances/[instance_name]/reboot resource.
1199
1200 Implements an instance reboot.
1201
1202 """
1203 POST_OPCODE = opcodes.OpInstanceReboot
1204
1206 """Reboot an instance.
1207
1208 The URI takes type=[hard|soft|full] and
1209 ignore_secondaries=[False|True] parameters.
1210
1211 """
1212 return (self.request_body, {
1213 "instance_name": self.items[0],
1214 "reboot_type":
1215 self.queryargs.get("type", [constants.INSTANCE_REBOOT_HARD])[0],
1216 "ignore_secondaries": bool(self._checkIntVariable("ignore_secondaries")),
1217 "dry_run": self.dryRun(),
1218 })
1219
1222 """/2/instances/[instance_name]/startup resource.
1223
1224 Implements an instance startup.
1225
1226 """
1227 PUT_OPCODE = opcodes.OpInstanceStartup
1228
1230 """Startup an instance.
1231
1232 The URI takes force=[False|True] parameter to start the instance
1233 if even if secondary disks are failing.
1234
1235 """
1236 return ({}, {
1237 "instance_name": self.items[0],
1238 "force": self.useForce(),
1239 "dry_run": self.dryRun(),
1240 "no_remember": bool(self._checkIntVariable("no_remember")),
1241 })
1242
1245 """/2/instances/[instance_name]/shutdown resource.
1246
1247 Implements an instance shutdown.
1248
1249 """
1250 PUT_OPCODE = opcodes.OpInstanceShutdown
1251
1253 """Shutdown an instance.
1254
1255 """
1256 return (self.request_body, {
1257 "instance_name": self.items[0],
1258 "no_remember": bool(self._checkIntVariable("no_remember")),
1259 "dry_run": self.dryRun(),
1260 })
1261
1264 """Parses a request for reinstalling an instance.
1265
1266 """
1267 if not isinstance(data, dict):
1268 raise http.HttpBadRequest("Invalid body contents, not a dictionary")
1269
1270 ostype = baserlib.CheckParameter(data, "os", default=None)
1271 start = baserlib.CheckParameter(data, "start", exptype=bool,
1272 default=True)
1273 osparams = baserlib.CheckParameter(data, "osparams", default=None)
1274
1275 ops = [
1276 opcodes.OpInstanceShutdown(instance_name=name),
1277 opcodes.OpInstanceReinstall(instance_name=name, os_type=ostype,
1278 osparams=osparams),
1279 ]
1280
1281 if start:
1282 ops.append(opcodes.OpInstanceStartup(instance_name=name, force=False))
1283
1284 return ops
1285
1288 """/2/instances/[instance_name]/reinstall resource.
1289
1290 Implements an instance reinstall.
1291
1292 """
1293 POST_OPCODE = opcodes.OpInstanceReinstall
1294
1296 """Reinstall an instance.
1297
1298 The URI takes os=name and nostartup=[0|1] optional
1299 parameters. By default, the instance will be started
1300 automatically.
1301
1302 """
1303 if self.request_body:
1304 if self.queryargs:
1305 raise http.HttpBadRequest("Can't combine query and body parameters")
1306
1307 body = self.request_body
1308 elif self.queryargs:
1309 # Legacy interface, do not modify/extend
1310 body = {
1311 "os": self._checkStringVariable("os"),
1312 "start": not self._checkIntVariable("nostartup"),
1313 }
1314 else:
1315 body = {}
1316
1317 ops = _ParseInstanceReinstallRequest(self.items[0], body)
1318
1319 return self.SubmitJob(ops)
1320
1323 """/2/instances/[instance_name]/replace-disks resource.
1324
1325 """
1326 POST_OPCODE = opcodes.OpInstanceReplaceDisks
1327
1329 """Replaces disks on an instance.
1330
1331 """
1332 static = {
1333 "instance_name": self.items[0],
1334 }
1335
1336 if self.request_body:
1337 data = self.request_body
1338 elif self.queryargs:
1339 # Legacy interface, do not modify/extend
1340 data = {
1341 "remote_node": self._checkStringVariable("remote_node", default=None),
1342 "mode": self._checkStringVariable("mode", default=None),
1343 "disks": self._checkStringVariable("disks", default=None),
1344 "iallocator": self._checkStringVariable("iallocator", default=None),
1345 }
1346 else:
1347 data = {}
1348
1349 # Parse disks
1350 try:
1351 raw_disks = data.pop("disks")
1352 except KeyError:
1353 pass
1354 else:
1355 if raw_disks:
1356 if ht.TListOf(ht.TInt)(raw_disks): # pylint: disable=E1102
1357 data["disks"] = raw_disks
1358 else:
1359 # Backwards compatibility for strings of the format "1, 2, 3"
1360 try:
1361 data["disks"] = [int(part) for part in raw_disks.split(",")]
1362 except (TypeError, ValueError), err:
1363 raise http.HttpBadRequest("Invalid disk index passed: %s" % err)
1364
1365 return (data, static)
1366
1369 """/2/instances/[instance_name]/activate-disks resource.
1370
1371 """
1372 PUT_OPCODE = opcodes.OpInstanceActivateDisks
1373
1375 """Activate disks for an instance.
1376
1377 The URI might contain ignore_size to ignore current recorded size.
1378
1379 """
1380 return ({}, {
1381 "instance_name": self.items[0],
1382 "ignore_size": bool(self._checkIntVariable("ignore_size")),
1383 })
1384
1387 """/2/instances/[instance_name]/deactivate-disks resource.
1388
1389 """
1390 PUT_OPCODE = opcodes.OpInstanceDeactivateDisks
1391
1393 """Deactivate disks for an instance.
1394
1395 """
1396 return ({}, {
1397 "instance_name": self.items[0],
1398 "force": self.useForce(),
1399 })
1400
1403 """/2/instances/[instance_name]/recreate-disks resource.
1404
1405 """
1406 POST_OPCODE = opcodes.OpInstanceRecreateDisks
1407
1409 """Recreate disks for an instance.
1410
1411 """
1412 return (self.request_body, {
1413 "instance_name": self.items[0],
1414 })
1415
1418 """/2/instances/[instance_name]/prepare-export resource.
1419
1420 """
1421 PUT_OPCODE = opcodes.OpBackupPrepare
1422
1424 """Prepares an export for an instance.
1425
1426 """
1427 return ({}, {
1428 "instance_name": self.items[0],
1429 "mode": self._checkStringVariable("mode"),
1430 })
1431
1434 """/2/instances/[instance_name]/export resource.
1435
1436 """
1437 PUT_OPCODE = opcodes.OpBackupExport
1438 PUT_RENAME = {
1439 "destination": "target_node",
1440 }
1441
1443 """Exports an instance.
1444
1445 """
1446 return (self.request_body, {
1447 "instance_name": self.items[0],
1448 })
1449
1452 """/2/instances/[instance_name]/migrate resource.
1453
1454 """
1455 PUT_OPCODE = opcodes.OpInstanceMigrate
1456
1458 """Migrates an instance.
1459
1460 """
1461 return (self.request_body, {
1462 "instance_name": self.items[0],
1463 })
1464
1467 """/2/instances/[instance_name]/failover resource.
1468
1469 """
1470 PUT_OPCODE = opcodes.OpInstanceFailover
1471
1473 """Does a failover of an instance.
1474
1475 """
1476 return (self.request_body, {
1477 "instance_name": self.items[0],
1478 })
1479
1482 """/2/instances/[instance_name]/rename resource.
1483
1484 """
1485 PUT_OPCODE = opcodes.OpInstanceRename
1486
1488 """Changes the name of an instance.
1489
1490 """
1491 return (self.request_body, {
1492 "instance_name": self.items[0],
1493 })
1494
1497 """/2/instances/[instance_name]/modify resource.
1498
1499 """
1500 PUT_OPCODE = opcodes.OpInstanceSetParams
1501 PUT_RENAME = {
1502 "custom_beparams": "beparams",
1503 "custom_hvparams": "hvparams",
1504 }
1505
1507 """Changes parameters of an instance.
1508
1509 """
1510 data = self.request_body.copy()
1511 _ConvertUsbDevices(data)
1512
1513 return (data, {
1514 "instance_name": self.items[0],
1515 })
1516
1519 """/2/instances/[instance_name]/disk/[disk_index]/grow resource.
1520
1521 """
1522 POST_OPCODE = opcodes.OpInstanceGrowDisk
1523
1525 """Increases the size of an instance disk.
1526
1527 """
1528 return (self.request_body, {
1529 "instance_name": self.items[0],
1530 "disk": int(self.items[1]),
1531 })
1532
1535 """/2/instances/[instance_name]/console resource.
1536
1537 """
1538 GET_ACCESS = [rapi.RAPI_ACCESS_WRITE, rapi.RAPI_ACCESS_READ]
1539 GET_OPCODE = opcodes.OpInstanceConsole
1540
1542 """Request information for connecting to instance's console.
1543
1544 @return: Serialized instance console description, see
1545 L{objects.InstanceConsole}
1546
1547 """
1548 instance_name = self.items[0]
1549 client = self.GetClient()
1550
1551 ((console, oper_state), ) = \
1552 client.QueryInstances([instance_name], ["console", "oper_state"], False)
1553
1554 if not oper_state:
1555 raise http.HttpServiceUnavailable("Instance console unavailable")
1556
1557 assert isinstance(console, dict)
1558 return console
1559
1562 """Tries to extract C{fields} query parameter.
1563
1564 @type args: dictionary
1565 @rtype: list of string
1566 @raise http.HttpBadRequest: When parameter can't be found
1567
1568 """
1569 try:
1570 fields = args["fields"]
1571 except KeyError:
1572 raise http.HttpBadRequest("Missing 'fields' query argument")
1573
1574 return _SplitQueryFields(fields[0])
1575
1578 """Splits fields as given for a query request.
1579
1580 @type fields: string
1581 @rtype: list of string
1582
1583 """
1584 return [i.strip() for i in fields.split(",")]
1585
1588 """/2/query/[resource] resource.
1589
1590 """
1591 # Results might contain sensitive information
1592 GET_ACCESS = [rapi.RAPI_ACCESS_WRITE, rapi.RAPI_ACCESS_READ]
1593 PUT_ACCESS = GET_ACCESS
1594 GET_OPCODE = opcodes.OpQuery
1595 PUT_OPCODE = opcodes.OpQuery
1596
1600
1602 """Returns resource information.
1603
1604 @return: Query result, see L{objects.QueryResponse}
1605
1606 """
1607 return self._Query(_GetQueryFields(self.queryargs), None)
1608
1610 """Submits job querying for resources.
1611
1612 @return: Query result, see L{objects.QueryResponse}
1613
1614 """
1615 body = self.request_body
1616
1617 baserlib.CheckType(body, dict, "Body contents")
1618
1619 try:
1620 fields = body["fields"]
1621 except KeyError:
1622 fields = _GetQueryFields(self.queryargs)
1623
1624 qfilter = body.get("qfilter", None)
1625 # TODO: remove this after 2.7
1626 if qfilter is None:
1627 qfilter = body.get("filter", None)
1628
1629 return self._Query(fields, qfilter)
1630
1633 """/2/query/[resource]/fields resource.
1634
1635 """
1636 GET_OPCODE = opcodes.OpQueryFields
1637
1639 """Retrieves list of available fields for a resource.
1640
1641 @return: List of serialized L{objects.QueryFieldDefinition}
1642
1643 """
1644 try:
1645 raw_fields = self.queryargs["fields"]
1646 except KeyError:
1647 fields = None
1648 else:
1649 fields = _SplitQueryFields(raw_fields[0])
1650
1651 return self.GetClient().QueryFields(self.items[0], fields).ToDict()
1652
1655 """Quasiclass for tagging resources.
1656
1657 Manages tags. When inheriting this class you must define the
1658 TAG_LEVEL for it.
1659
1660 """
1661 TAG_LEVEL = None
1662 GET_OPCODE = opcodes.OpTagsGet
1663 PUT_OPCODE = opcodes.OpTagsSet
1664 DELETE_OPCODE = opcodes.OpTagsDel
1665
1667 """A tag resource constructor.
1668
1669 We have to override the default to sort out cluster naming case.
1670
1671 """
1672 baserlib.OpcodeResource.__init__(self, items, queryargs, req, **kwargs)
1673
1674 if self.TAG_LEVEL == constants.TAG_CLUSTER:
1675 self.name = None
1676 else:
1677 self.name = items[0]
1678
1680 """Returns a list of tags.
1681
1682 Example: ["tag1", "tag2", "tag3"]
1683
1684 """
1685 kind = self.TAG_LEVEL
1686
1687 if kind in constants.VALID_TAG_TYPES:
1688 cl = self.GetClient()
1689 if kind == constants.TAG_CLUSTER:
1690 if self.name:
1691 raise http.HttpBadRequest("Can't specify a name"
1692 " for cluster tag request")
1693 tags = list(cl.QueryTags(kind, ""))
1694 else:
1695 if not self.name:
1696 raise http.HttpBadRequest("Missing name on tag request")
1697 tags = list(cl.QueryTags(kind, self.name))
1698
1699 else:
1700 raise http.HttpBadRequest("Unhandled tag type!")
1701
1702 return list(tags)
1703
1705 """Add a set of tags.
1706
1707 The request as a list of strings should be PUT to this URI. And
1708 you'll have back a job id.
1709
1710 """
1711 return ({}, {
1712 "kind": self.TAG_LEVEL,
1713 "name": self.name,
1714 "tags": self.queryargs.get("tag", []),
1715 "dry_run": self.dryRun(),
1716 })
1717
1719 """Delete a tag.
1720
1721 In order to delete a set of tags, the DELETE
1722 request should be addressed to URI like:
1723 /tags?tag=[tag]&tag=[tag]
1724
1725 """
1726 # Re-use code
1727 return self.GetPutOpInput()
1728
1737
1746
1755
1764
1773
| Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Mon Apr 4 13:44:13 2016 | http://epydoc.sourceforge.net |