1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 """Remote API version 2 baserlib.library.
23
24 PUT or POST?
25 ============
26
27 According to RFC2616 the main difference between PUT and POST is that
28 POST can create new resources but PUT can only create the resource the
29 URI was pointing to on the PUT request.
30
31 To be in context of this module for instance creation POST on
32 /2/instances is legitim while PUT would be not, due to it does create a
33 new entity and not just replace /2/instances with it.
34
35 So when adding new methods, if they are operating on the URI entity itself,
36 PUT should be prefered over POST.
37
38 """
39
40
41
42
43
44 from ganeti import opcodes
45 from ganeti import http
46 from ganeti import constants
47 from ganeti import cli
48 from ganeti import utils
49 from ganeti import rapi
50 from ganeti.rapi import baserlib
51
52
53 _COMMON_FIELDS = ["ctime", "mtime", "uuid", "serial_no", "tags"]
54 I_FIELDS = ["name", "admin_state", "os",
55 "pnode", "snodes",
56 "disk_template",
57 "nic.ips", "nic.macs", "nic.modes", "nic.links", "nic.bridges",
58 "network_port",
59 "disk.sizes", "disk_usage",
60 "beparams", "hvparams",
61 "oper_state", "oper_ram", "oper_vcpus", "status",
62 "custom_hvparams", "custom_beparams", "custom_nicparams",
63 ] + _COMMON_FIELDS
64
65 N_FIELDS = ["name", "offline", "master_candidate", "drained",
66 "dtotal", "dfree",
67 "mtotal", "mnode", "mfree",
68 "pinst_cnt", "sinst_cnt",
69 "ctotal", "cnodes", "csockets",
70 "pip", "sip", "role",
71 "pinst_list", "sinst_list",
72 "master_capable", "vm_capable",
73 ] + _COMMON_FIELDS
74
75 _NR_DRAINED = "drained"
76 _NR_MASTER_CANDIATE = "master-candidate"
77 _NR_MASTER = "master"
78 _NR_OFFLINE = "offline"
79 _NR_REGULAR = "regular"
80
81 _NR_MAP = {
82 "M": _NR_MASTER,
83 "C": _NR_MASTER_CANDIATE,
84 "D": _NR_DRAINED,
85 "O": _NR_OFFLINE,
86 "R": _NR_REGULAR,
87 }
88
89
90 _REQ_DATA_VERSION = "__version__"
91
92
93 _INST_CREATE_REQV1 = "instance-create-reqv1"
94
95
96 _WFJC_TIMEOUT = 10
100 """/version resource.
101
102 This resource should be used to determine the remote API version and
103 to adapt clients accordingly.
104
105 """
106 @staticmethod
112
115 """Cluster info.
116
117 """
118 @staticmethod
125
128 """/2/features resource.
129
130 """
131 @staticmethod
133 """Returns list of optional RAPI features implemented.
134
135 """
136 return [_INST_CREATE_REQV1]
137
138
139 -class R_2_os(baserlib.R_Generic):
140 """/2/os resource.
141
142 """
143 @staticmethod
145 """Return a list of all OSes.
146
147 Can return error 500 in case of a problem.
148
149 Example: ["debian-etch"]
150
151 """
152 cl = baserlib.GetClient()
153 op = opcodes.OpDiagnoseOS(output_fields=["name", "variants"], names=[])
154 job_id = baserlib.SubmitJob([op], cl)
155
156 result = cli.PollJob(job_id, cl, feedback_fn=baserlib.FeedbackFn)
157 diagnose_data = result[0]
158
159 if not isinstance(diagnose_data, list):
160 raise http.HttpBadGateway(message="Can't get OS list")
161
162 os_names = []
163 for (name, variants) in diagnose_data:
164 os_names.extend(cli.CalculateOSNames(name, variants))
165
166 return os_names
167
170 """/2/redistribute-config resource.
171
172 """
173 @staticmethod
179
182 """/2/jobs resource.
183
184 """
185 @staticmethod
187 """Returns a dictionary of jobs.
188
189 @return: a dictionary with jobs id and uri.
190
191 """
192 fields = ["id"]
193 cl = baserlib.GetClient()
194
195 result = [job_id for [job_id] in cl.QueryJobs(None, fields)]
196 return baserlib.BuildUriList(result, "/2/jobs/%s",
197 uri_fields=("id", "uri"))
198
201 """/2/jobs/[job_id] resource.
202
203 """
205 """Returns a job status.
206
207 @return: a dictionary with job parameters.
208 The result includes:
209 - id: job ID as a number
210 - status: current job status as a string
211 - ops: involved OpCodes as a list of dictionaries for each
212 opcodes in the job
213 - opstatus: OpCodes status as a list
214 - opresult: OpCodes results as a list of lists
215
216 """
217 fields = ["id", "ops", "status", "summary",
218 "opstatus", "opresult", "oplog",
219 "received_ts", "start_ts", "end_ts",
220 ]
221 job_id = self.items[0]
222 result = baserlib.GetClient().QueryJobs([job_id, ], fields)[0]
223 if result is None:
224 raise http.HttpNotFound()
225 return baserlib.MapFields(fields, result)
226
228 """Cancel not-yet-started job.
229
230 """
231 job_id = self.items[0]
232 result = baserlib.GetClient().CancelJob(job_id)
233 return result
234
237 """/2/jobs/[job_id]/wait resource.
238
239 """
240
241
242 GET_ACCESS = [rapi.RAPI_ACCESS_WRITE]
243
245 """Waits for job changes.
246
247 """
248 job_id = self.items[0]
249
250 fields = self.getBodyParameter("fields")
251 prev_job_info = self.getBodyParameter("previous_job_info", None)
252 prev_log_serial = self.getBodyParameter("previous_log_serial", None)
253
254 if not isinstance(fields, list):
255 raise http.HttpBadRequest("The 'fields' parameter should be a list")
256
257 if not (prev_job_info is None or isinstance(prev_job_info, list)):
258 raise http.HttpBadRequest("The 'previous_job_info' parameter should"
259 " be a list")
260
261 if not (prev_log_serial is None or
262 isinstance(prev_log_serial, (int, long))):
263 raise http.HttpBadRequest("The 'previous_log_serial' parameter should"
264 " be a number")
265
266 client = baserlib.GetClient()
267 result = client.WaitForJobChangeOnce(job_id, fields,
268 prev_job_info, prev_log_serial,
269 timeout=_WFJC_TIMEOUT)
270 if not result:
271 raise http.HttpNotFound()
272
273 if result == constants.JOB_NOTCHANGED:
274
275 return None
276
277 (job_info, log_entries) = result
278
279 return {
280 "job_info": job_info,
281 "log_entries": log_entries,
282 }
283
286 """/2/nodes resource.
287
288 """
303
306 """/2/nodes/[node_name] resources.
307
308 """
321
324 """ /2/nodes/[node_name]/role resource.
325
326 """
339
341 """Sets the node role.
342
343 @return: a job id
344
345 """
346 if not isinstance(self.request_body, basestring):
347 raise http.HttpBadRequest("Invalid body contents, not a string")
348
349 node_name = self.items[0]
350 role = self.request_body
351
352 if role == _NR_REGULAR:
353 candidate = False
354 offline = False
355 drained = False
356
357 elif role == _NR_MASTER_CANDIATE:
358 candidate = True
359 offline = drained = None
360
361 elif role == _NR_DRAINED:
362 drained = True
363 candidate = offline = None
364
365 elif role == _NR_OFFLINE:
366 offline = True
367 candidate = drained = None
368
369 else:
370 raise http.HttpBadRequest("Can't set '%s' role" % role)
371
372 op = opcodes.OpSetNodeParams(node_name=node_name,
373 master_candidate=candidate,
374 offline=offline,
375 drained=drained,
376 force=bool(self.useForce()))
377
378 return baserlib.SubmitJob([op])
379
382 """/2/nodes/[node_name]/evacuate resource.
383
384 """
386 """Evacuate all secondary instances off a node.
387
388 """
389 node_name = self.items[0]
390 remote_node = self._checkStringVariable("remote_node", default=None)
391 iallocator = self._checkStringVariable("iallocator", default=None)
392 early_r = bool(self._checkIntVariable("early_release", default=0))
393 dry_run = bool(self.dryRun())
394
395 cl = baserlib.GetClient()
396
397 op = opcodes.OpNodeEvacuationStrategy(nodes=[node_name],
398 iallocator=iallocator,
399 remote_node=remote_node)
400
401 job_id = baserlib.SubmitJob([op], cl)
402
403 result = cli.PollJob(job_id, cl, feedback_fn=baserlib.FeedbackFn)
404
405 jobs = []
406 for iname, node in result:
407 if dry_run:
408 jid = None
409 else:
410 op = opcodes.OpReplaceDisks(instance_name=iname,
411 remote_node=node, disks=[],
412 mode=constants.REPLACE_DISK_CHG,
413 early_release=early_r)
414 jid = baserlib.SubmitJob([op])
415 jobs.append((jid, iname, node))
416
417 return jobs
418
444
470
473 """/2/nodes/[node_name]/storage/modify ressource.
474
475 """
500
503 """/2/nodes/[node_name]/storage/repair ressource.
504
505 """
523
526 """Parses an instance creation request version 1.
527
528 @rtype: L{opcodes.OpCreateInstance}
529 @return: Instance creation opcode
530
531 """
532
533 disks_input = baserlib.CheckParameter(data, "disks", exptype=list)
534
535 disks = []
536 for idx, i in enumerate(disks_input):
537 baserlib.CheckType(i, dict, "Disk %d specification" % idx)
538
539
540 try:
541 size = i[constants.IDISK_SIZE]
542 except KeyError:
543 raise http.HttpBadRequest("Disk %d specification wrong: missing disk"
544 " size" % idx)
545
546 disk = {
547 constants.IDISK_SIZE: size,
548 }
549
550
551 try:
552 disk_access = i[constants.IDISK_MODE]
553 except KeyError:
554 pass
555 else:
556 disk[constants.IDISK_MODE] = disk_access
557
558 disks.append(disk)
559
560 assert len(disks_input) == len(disks)
561
562
563 nics_input = baserlib.CheckParameter(data, "nics", exptype=list)
564
565 nics = []
566 for idx, i in enumerate(nics_input):
567 baserlib.CheckType(i, dict, "NIC %d specification" % idx)
568
569 nic = {}
570
571 for field in constants.INIC_PARAMS:
572 try:
573 value = i[field]
574 except KeyError:
575 continue
576
577 nic[field] = value
578
579 nics.append(nic)
580
581 assert len(nics_input) == len(nics)
582
583
584 hvparams = baserlib.CheckParameter(data, "hvparams", default={})
585 utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
586
587 beparams = baserlib.CheckParameter(data, "beparams", default={})
588 utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
589
590 return opcodes.OpCreateInstance(
591 mode=baserlib.CheckParameter(data, "mode"),
592 instance_name=baserlib.CheckParameter(data, "name"),
593 os_type=baserlib.CheckParameter(data, "os"),
594 osparams=baserlib.CheckParameter(data, "osparams", default={}),
595 force_variant=baserlib.CheckParameter(data, "force_variant",
596 default=False),
597 pnode=baserlib.CheckParameter(data, "pnode", default=None),
598 snode=baserlib.CheckParameter(data, "snode", default=None),
599 disk_template=baserlib.CheckParameter(data, "disk_template"),
600 disks=disks,
601 nics=nics,
602 src_node=baserlib.CheckParameter(data, "src_node", default=None),
603 src_path=baserlib.CheckParameter(data, "src_path", default=None),
604 start=baserlib.CheckParameter(data, "start", default=True),
605 wait_for_sync=True,
606 ip_check=baserlib.CheckParameter(data, "ip_check", default=True),
607 name_check=baserlib.CheckParameter(data, "name_check", default=True),
608 file_storage_dir=baserlib.CheckParameter(data, "file_storage_dir",
609 default=None),
610 file_driver=baserlib.CheckParameter(data, "file_driver",
611 default=constants.FD_LOOP),
612 source_handshake=baserlib.CheckParameter(data, "source_handshake",
613 default=None),
614 source_x509_ca=baserlib.CheckParameter(data, "source_x509_ca",
615 default=None),
616 source_instance_name=baserlib.CheckParameter(data, "source_instance_name",
617 default=None),
618 iallocator=baserlib.CheckParameter(data, "iallocator", default=None),
619 hypervisor=baserlib.CheckParameter(data, "hypervisor", default=None),
620 hvparams=hvparams,
621 beparams=beparams,
622 dry_run=dry_run,
623 )
624
627 """/2/instances resource.
628
629 """
645
647 """Parses an instance creation request version 0.
648
649 Request data version 0 is deprecated and should not be used anymore.
650
651 @rtype: L{opcodes.OpCreateInstance}
652 @return: Instance creation opcode
653
654 """
655
656 beparams = baserlib.MakeParamsDict(self.request_body,
657 constants.BES_PARAMETERS)
658 hvparams = baserlib.MakeParamsDict(self.request_body,
659 constants.HVS_PARAMETERS)
660 fn = self.getBodyParameter
661
662
663 disk_data = fn('disks')
664 if not isinstance(disk_data, list):
665 raise http.HttpBadRequest("The 'disks' parameter should be a list")
666 disks = []
667 for idx, d in enumerate(disk_data):
668 if not isinstance(d, int):
669 raise http.HttpBadRequest("Disk %d specification wrong: should"
670 " be an integer" % idx)
671 disks.append({"size": d})
672
673
674 nics = [{"mac": fn("mac", constants.VALUE_AUTO)}]
675 if fn("ip", None) is not None:
676 nics[0]["ip"] = fn("ip")
677 if fn("mode", None) is not None:
678 nics[0]["mode"] = fn("mode")
679 if fn("link", None) is not None:
680 nics[0]["link"] = fn("link")
681 if fn("bridge", None) is not None:
682 nics[0]["bridge"] = fn("bridge")
683
684
685 return opcodes.OpCreateInstance(
686 mode=constants.INSTANCE_CREATE,
687 instance_name=fn('name'),
688 disks=disks,
689 disk_template=fn('disk_template'),
690 os_type=fn('os'),
691 pnode=fn('pnode', None),
692 snode=fn('snode', None),
693 iallocator=fn('iallocator', None),
694 nics=nics,
695 start=fn('start', True),
696 ip_check=fn('ip_check', True),
697 name_check=fn('name_check', True),
698 wait_for_sync=True,
699 hypervisor=fn('hypervisor', None),
700 hvparams=hvparams,
701 beparams=beparams,
702 file_storage_dir=fn('file_storage_dir', None),
703 file_driver=fn('file_driver', constants.FD_LOOP),
704 dry_run=bool(self.dryRun()),
705 )
706
708 """Create an instance.
709
710 @return: a job id
711
712 """
713 if not isinstance(self.request_body, dict):
714 raise http.HttpBadRequest("Invalid body contents, not a dictionary")
715
716
717 data_version = self.getBodyParameter(_REQ_DATA_VERSION, 0)
718
719 if data_version == 0:
720 op = self._ParseVersion0CreateRequest()
721 elif data_version == 1:
722 op = _ParseInstanceCreateRequestVersion1(self.request_body,
723 self.dryRun())
724 else:
725 raise http.HttpBadRequest("Unsupported request data version %s" %
726 data_version)
727
728 return baserlib.SubmitJob([op])
729
732 """/2/instances/[instance_name] resources.
733
734 """
748
757
760 """/2/instances/[instance_name]/info resource.
761
762 """
773
776 """/2/instances/[instance_name]/reboot resource.
777
778 Implements an instance reboot.
779
780 """
782 """Reboot an instance.
783
784 The URI takes type=[hard|soft|full] and
785 ignore_secondaries=[False|True] parameters.
786
787 """
788 instance_name = self.items[0]
789 reboot_type = self.queryargs.get('type',
790 [constants.INSTANCE_REBOOT_HARD])[0]
791 ignore_secondaries = bool(self._checkIntVariable('ignore_secondaries'))
792 op = opcodes.OpRebootInstance(instance_name=instance_name,
793 reboot_type=reboot_type,
794 ignore_secondaries=ignore_secondaries,
795 dry_run=bool(self.dryRun()))
796
797 return baserlib.SubmitJob([op])
798
801 """/2/instances/[instance_name]/startup resource.
802
803 Implements an instance startup.
804
805 """
807 """Startup an instance.
808
809 The URI takes force=[False|True] parameter to start the instance
810 if even if secondary disks are failing.
811
812 """
813 instance_name = self.items[0]
814 force_startup = bool(self._checkIntVariable('force'))
815 op = opcodes.OpStartupInstance(instance_name=instance_name,
816 force=force_startup,
817 dry_run=bool(self.dryRun()))
818
819 return baserlib.SubmitJob([op])
820
823 """/2/instances/[instance_name]/shutdown resource.
824
825 Implements an instance shutdown.
826
827 """
837
840 """/2/instances/[instance_name]/reinstall resource.
841
842 Implements an instance reinstall.
843
844 """
846 """Reinstall an instance.
847
848 The URI takes os=name and nostartup=[0|1] optional
849 parameters. By default, the instance will be started
850 automatically.
851
852 """
853 instance_name = self.items[0]
854 ostype = self._checkStringVariable('os')
855 nostartup = self._checkIntVariable('nostartup')
856 ops = [
857 opcodes.OpShutdownInstance(instance_name=instance_name),
858 opcodes.OpReinstallInstance(instance_name=instance_name, os_type=ostype),
859 ]
860 if not nostartup:
861 ops.append(opcodes.OpStartupInstance(instance_name=instance_name,
862 force=False))
863 return baserlib.SubmitJob(ops)
864
867 """/2/instances/[instance_name]/replace-disks resource.
868
869 """
871 """Replaces disks on an instance.
872
873 """
874 instance_name = self.items[0]
875 remote_node = self._checkStringVariable("remote_node", default=None)
876 mode = self._checkStringVariable("mode", default=None)
877 raw_disks = self._checkStringVariable("disks", default=None)
878 iallocator = self._checkStringVariable("iallocator", default=None)
879
880 if raw_disks:
881 try:
882 disks = [int(part) for part in raw_disks.split(",")]
883 except ValueError, err:
884 raise http.HttpBadRequest("Invalid disk index passed: %s" % str(err))
885 else:
886 disks = []
887
888 op = opcodes.OpReplaceDisks(instance_name=instance_name,
889 remote_node=remote_node,
890 mode=mode,
891 disks=disks,
892 iallocator=iallocator)
893
894 return baserlib.SubmitJob([op])
895
898 """/2/instances/[instance_name]/activate-disks resource.
899
900 """
902 """Activate disks for an instance.
903
904 The URI might contain ignore_size to ignore current recorded size.
905
906 """
907 instance_name = self.items[0]
908 ignore_size = bool(self._checkIntVariable('ignore_size'))
909
910 op = opcodes.OpActivateInstanceDisks(instance_name=instance_name,
911 ignore_size=ignore_size)
912
913 return baserlib.SubmitJob([op])
914
917 """/2/instances/[instance_name]/deactivate-disks resource.
918
919 """
929
932 """/2/instances/[instance_name]/prepare-export resource.
933
934 """
948
951 """Parses a request for an instance export.
952
953 @rtype: L{opcodes.OpExportInstance}
954 @return: Instance export opcode
955
956 """
957 mode = baserlib.CheckParameter(data, "mode",
958 default=constants.EXPORT_MODE_LOCAL)
959 target_node = baserlib.CheckParameter(data, "destination")
960 shutdown = baserlib.CheckParameter(data, "shutdown", exptype=bool)
961 remove_instance = baserlib.CheckParameter(data, "remove_instance",
962 exptype=bool, default=False)
963 x509_key_name = baserlib.CheckParameter(data, "x509_key_name", default=None)
964 destination_x509_ca = baserlib.CheckParameter(data, "destination_x509_ca",
965 default=None)
966
967 return opcodes.OpExportInstance(instance_name=name,
968 mode=mode,
969 target_node=target_node,
970 shutdown=shutdown,
971 remove_instance=remove_instance,
972 x509_key_name=x509_key_name,
973 destination_x509_ca=destination_x509_ca)
974
977 """/2/instances/[instance_name]/export resource.
978
979 """
992
995 """Parses a request for an instance migration.
996
997 @rtype: L{opcodes.OpMigrateInstance}
998 @return: Instance migration opcode
999
1000 """
1001 mode = baserlib.CheckParameter(data, "mode", default=None)
1002 cleanup = baserlib.CheckParameter(data, "cleanup", exptype=bool,
1003 default=False)
1004
1005 return opcodes.OpMigrateInstance(instance_name=name, mode=mode,
1006 cleanup=cleanup)
1007
1010 """/2/instances/[instance_name]/migrate resource.
1011
1012 """
1024
1027 """Parses a request for renaming an instance.
1028
1029 @rtype: L{opcodes.OpRenameInstance}
1030 @return: Instance rename opcode
1031
1032 """
1033 new_name = baserlib.CheckParameter(data, "new_name")
1034 ip_check = baserlib.CheckParameter(data, "ip_check", default=True)
1035 name_check = baserlib.CheckParameter(data, "name_check", default=True)
1036
1037 return opcodes.OpRenameInstance(instance_name=name, new_name=new_name,
1038 name_check=name_check, ip_check=ip_check)
1039
1042 """/2/instances/[instance_name]/rename resource.
1043
1044 """
1056
1059 """Parses a request for modifying an instance.
1060
1061 @rtype: L{opcodes.OpSetInstanceParams}
1062 @return: Instance modify opcode
1063
1064 """
1065 osparams = baserlib.CheckParameter(data, "osparams", default={})
1066 force = baserlib.CheckParameter(data, "force", default=False)
1067 nics = baserlib.CheckParameter(data, "nics", default=[])
1068 disks = baserlib.CheckParameter(data, "disks", default=[])
1069 disk_template = baserlib.CheckParameter(data, "disk_template", default=None)
1070 remote_node = baserlib.CheckParameter(data, "remote_node", default=None)
1071 os_name = baserlib.CheckParameter(data, "os_name", default=None)
1072 force_variant = baserlib.CheckParameter(data, "force_variant", default=False)
1073
1074
1075 hvparams = baserlib.CheckParameter(data, "hvparams", default={})
1076 utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES,
1077 allowed_values=[constants.VALUE_DEFAULT])
1078
1079 beparams = baserlib.CheckParameter(data, "beparams", default={})
1080 utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES,
1081 allowed_values=[constants.VALUE_DEFAULT])
1082
1083 return opcodes.OpSetInstanceParams(instance_name=name, hvparams=hvparams,
1084 beparams=beparams, osparams=osparams,
1085 force=force, nics=nics, disks=disks,
1086 disk_template=disk_template,
1087 remote_node=remote_node, os_name=os_name,
1088 force_variant=force_variant)
1089
1092 """/2/instances/[instance_name]/modify resource.
1093
1094 """
1106
1171
1180
1189
1198