KVM: Allow the HV_VNC_BIND_ADDRESS parameter
[ganeti-local] / lib / rapi / rlib2.py
1 #
2 #
3
4 # Copyright (C) 2006, 2007, 2008 Google Inc.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 # General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 # 02110-1301, USA.
20
21
22 """Remote API version 2 baserlib.library.
23
24 """
25
26 import ganeti.opcodes
27 from ganeti import http
28 from ganeti import luxi
29 from ganeti import constants
30 from ganeti.rapi import baserlib
31
32
33 I_FIELDS = ["name", "os", "pnode", "snodes", "admin_state", "disk_template",
34             "ip", "mac", "bridge", "sda_size", "sdb_size", "beparams",
35             "oper_state", "status", "tags"]
36
37 N_FIELDS = ["name", "dtotal", "dfree",
38             "mtotal", "mnode", "mfree",
39             "pinst_cnt", "sinst_cnt", "tags"]
40
41
42 class R_version(baserlib.R_Generic):
43   """/version resource.
44
45   This resource should be used to determine the remote API version and
46   to adapt clients accordingly.
47
48   """
49   DOC_URI = "/version"
50
51   def GET(self):
52     """Returns the remote API version.
53
54     """
55     return constants.RAPI_VERSION
56
57
58 class R_2_info(baserlib.R_Generic):
59   """Cluster info.
60
61   """
62   DOC_URI = "/2/info"
63
64   def GET(self):
65     """Returns cluster information.
66
67     Example::
68
69       {
70         "config_version": 3,
71         "name": "cluster1.example.com",
72         "software_version": "1.2.4",
73         "os_api_version": 5,
74         "export_version": 0,
75         "master": "node1.example.com",
76         "architecture": [
77           "64bit",
78           "x86_64"
79         ],
80         "hypervisor_type": "xen-pvm",
81         "protocol_version": 12
82       }
83
84     """
85     op = ganeti.opcodes.OpQueryClusterInfo()
86     return ganeti.cli.SubmitOpCode(op)
87
88
89 class R_2_os(baserlib.R_Generic):
90   """/2/os resource.
91
92   """
93   DOC_URI = "/2/os"
94
95   def GET(self):
96     """Return a list of all OSes.
97
98     Can return error 500 in case of a problem.
99
100     Example: ["debian-etch"]
101
102     """
103     op = ganeti.opcodes.OpDiagnoseOS(output_fields=["name", "valid"],
104                                      names=[])
105     diagnose_data = ganeti.cli.SubmitOpCode(op)
106
107     if not isinstance(diagnose_data, list):
108       raise http.HttpInternalServerError(message="Can't get OS list")
109
110     return [row[0] for row in diagnose_data if row[1]]
111
112
113 class R_2_jobs(baserlib.R_Generic):
114   """/2/jobs resource.
115
116   """
117   DOC_URI = "/2/jobs"
118
119   def GET(self):
120     """Returns a dictionary of jobs.
121
122     @return: a dictionary with jobs id and uri.
123
124     """
125     fields = ["id"]
126     # Convert the list of lists to the list of ids
127     result = [job_id for [job_id] in luxi.Client().QueryJobs(None, fields)]
128     return baserlib.BuildUriList(result, "/2/jobs/%s", uri_fields=("id", "uri"))
129
130
131 class R_2_jobs_id(baserlib.R_Generic):
132   """/2/jobs/[job_id] resource.
133
134   """
135   DOC_URI = "/2/jobs/[job_id]"
136
137   def GET(self):
138     """Returns a job status.
139
140     @return: a dictionary with job parameters.
141         The result includes:
142             - id: job ID as a number
143             - status: current job status as a string
144             - ops: involved OpCodes as a list of dictionaries for each
145               opcodes in the job
146             - opstatus: OpCodes status as a list
147             - opresult: OpCodes results as a list of lists
148
149     """
150     fields = ["id", "ops", "status", "opstatus", "opresult"]
151     job_id = self.items[0]
152     result = luxi.Client().QueryJobs([job_id, ], fields)[0]
153     return baserlib.MapFields(fields, result)
154
155   def DELETE(self):
156     """Cancel not-yet-started job.
157
158     """
159     job_id = self.items[0]
160     result = luxi.Client().CancelJob(job_id)
161     return result
162
163
164 class R_2_nodes(baserlib.R_Generic):
165   """/2/nodes resource.
166
167   """
168   DOC_URI = "/2/nodes"
169
170   def GET(self):
171     """Returns a list of all nodes.
172
173     Example::
174
175       [
176         {
177           "id": "node1.example.com",
178           "uri": "\/instances\/node1.example.com"
179         },
180         {
181           "id": "node2.example.com",
182           "uri": "\/instances\/node2.example.com"
183         }
184       ]
185
186     If the optional 'bulk' argument is provided and set to 'true'
187     value (i.e '?bulk=1'), the output contains detailed
188     information about nodes as a list.
189
190     Example::
191
192       [
193         {
194           "pinst_cnt": 1,
195           "mfree": 31280,
196           "mtotal": 32763,
197           "name": "www.example.com",
198           "tags": [],
199           "mnode": 512,
200           "dtotal": 5246208,
201           "sinst_cnt": 2,
202           "dfree": 5171712
203         },
204         ...
205       ]
206
207     @return: a dictionary with 'name' and 'uri' keys for each of them
208
209     """
210     client = luxi.Client()
211     nodesdata = client.QueryNodes([], ["name"])
212     nodeslist = [row[0] for row in nodesdata]
213
214     if 'bulk' in self.queryargs:
215       bulkdata = client.QueryNodes(nodeslist, N_FIELDS)
216       return baserlib.MapBulkFields(bulkdata, N_FIELDS)
217
218     return baserlib.BuildUriList(nodeslist, "/2/nodes/%s",
219                                  uri_fields=("id", "uri"))
220
221
222 class R_2_nodes_name(baserlib.R_Generic):
223   """/2/nodes/[node_name] resources.
224
225   """
226   DOC_URI = "/nodes/[node_name]"
227
228   def GET(self):
229     """Send information about a node.
230
231     """
232     node_name = self.items[0]
233     op = ganeti.opcodes.OpQueryNodes(output_fields=N_FIELDS,
234                                      names=[node_name])
235     result = ganeti.cli.SubmitOpCode(op)
236
237     return baserlib.MapFields(N_FIELDS, result[0])
238
239
240 class R_2_instances(baserlib.R_Generic):
241   """/2/instances resource.
242
243   """
244   DOC_URI = "/2/instances"
245
246   def GET(self):
247     """Returns a list of all available instances.
248
249
250     Example::
251
252       [
253         {
254           "name": "web.example.com",
255           "uri": "\/instances\/web.example.com"
256         },
257         {
258           "name": "mail.example.com",
259           "uri": "\/instances\/mail.example.com"
260         }
261       ]
262
263     If the optional 'bulk' argument is provided and set to 'true'
264     value (i.e '?bulk=1'), the output contains detailed
265     information about instances as a list.
266
267     Example::
268
269       [
270         {
271            "status": "running",
272            "bridge": "xen-br0",
273            "name": "web.example.com",
274            "tags": ["tag1", "tag2"],
275            "admin_ram": 512,
276            "sda_size": 20480,
277            "pnode": "node1.example.com",
278            "mac": "01:23:45:67:89:01",
279            "sdb_size": 4096,
280            "snodes": ["node2.example.com"],
281            "disk_template": "drbd",
282            "ip": null,
283            "admin_state": true,
284            "os": "debian-etch",
285            "vcpus": 2,
286            "oper_state": true
287         },
288         ...
289       ]
290
291     @returns: a dictionary with 'name' and 'uri' keys for each of them.
292
293     """
294     client = luxi.Client()
295     instancesdata = client.QueryInstances([], ["name"])
296     instanceslist = [row[0] for row in instancesdata]
297
298
299     if 'bulk' in self.queryargs:
300       bulkdata = client.QueryInstances(instanceslist, I_FIELDS)
301       return baserlib.MapBulkFields(bulkdata, I_FIELDS)
302
303     else:
304       return baserlib.BuildUriList(instanceslist, "/2/instances/%s",
305                                    uri_fields=("id", "uri"))
306
307   def POST(self):
308     """Create an instance.
309
310     @returns: a job id
311
312     """
313     opts = self.req.request_post_data
314
315     beparams = baserlib.MakeParamsDict(opts, constants.BES_PARAMETERS)
316     hvparams = baserlib.MakeParamsDict(opts, constants.HVS_PARAMETERS)
317
318     op = ganeti.opcodes.OpCreateInstance(
319         instance_name=opts.get('name'),
320         disk_size=opts.get('size', 20 * 1024),
321         swap_size=opts.get('swap', 4 * 1024),
322         disk_template=opts.get('disk_template', None),
323         mode=constants.INSTANCE_CREATE,
324         os_type=opts.get('os'),
325         pnode=opts.get('pnode'),
326         snode=opts.get('snode'),
327         ip=opts.get('ip', 'none'),
328         bridge=opts.get('bridge', None),
329         start=opts.get('start', True),
330         ip_check=opts.get('ip_check', True),
331         wait_for_sync=opts.get('wait_for_sync', True),
332         mac=opts.get('mac', 'auto'),
333         hypervisor=opts.get('hypervisor', None),
334         hvparams=hvparams,
335         beparams=beparams,
336         iallocator=opts.get('iallocator', None),
337         file_storage_dir=opts.get('file_storage_dir', None),
338         file_driver=opts.get('file_driver', 'loop'),
339         )
340
341     job_id = ganeti.cli.SendJob([op])
342     return job_id
343
344
345 class R_2_instances_name(baserlib.R_Generic):
346   """/2/instances/[instance_name] resources.
347
348   """
349   DOC_URI = "/2/instances/[instance_name]"
350
351   def GET(self):
352     """Send information about an instance.
353
354     """
355     instance_name = self.items[0]
356     op = ganeti.opcodes.OpQueryInstances(output_fields=I_FIELDS,
357                                          names=[instance_name])
358     result = ganeti.cli.SubmitOpCode(op)
359
360     return baserlib.MapFields(I_FIELDS, result[0])
361
362
363 class R_2_instances_name_reboot(baserlib.R_Generic):
364   """/2/instances/[instance_name]/reboot resource.
365
366   Implements an instance reboot.
367
368   """
369
370   DOC_URI = "/2/instances/[instance_name]/reboot"
371
372   def POST(self):
373     """Reboot an instance.
374
375     The URI takes type=[hard|soft|full] and
376     ignore_secondaries=[False|True] parameters.
377
378     """
379     instance_name = self.items[0]
380     reboot_type = self.queryargs.get('type',
381                                      [constants.INSTANCE_REBOOT_HARD])[0]
382     ignore_secondaries = bool(self.queryargs.get('ignore_secondaries',
383                                                  [False])[0])
384     op = ganeti.opcodes.OpRebootInstance(
385         instance_name=instance_name,
386         reboot_type=reboot_type,
387         ignore_secondaries=ignore_secondaries)
388
389     job_id = ganeti.cli.SendJob([op])
390
391     return job_id
392
393
394 class R_2_instances_name_startup(baserlib.R_Generic):
395   """/2/instances/[instance_name]/startup resource.
396
397   Implements an instance startup.
398
399   """
400
401   DOC_URI = "/2/instances/[instance_name]/startup"
402
403   def PUT(self):
404     """Startup an instance.
405
406     The URI takes force=[False|True] parameter to start the instance
407     if even if secondary disks are failing.
408
409     """
410     instance_name = self.items[0]
411     force_startup = bool(self.queryargs.get('force', [False])[0])
412     op = ganeti.opcodes.OpStartupInstance(instance_name=instance_name,
413                                           force=force_startup)
414
415     job_id = ganeti.cli.SendJob([op])
416
417     return job_id
418
419
420 class R_2_instances_name_shutdown(baserlib.R_Generic):
421   """/2/instances/[instance_name]/shutdown resource.
422
423   Implements an instance shutdown.
424
425   """
426
427   DOC_URI = "/2/instances/[instance_name]/shutdown"
428
429   def PUT(self):
430     """Shutdown an instance.
431
432     """
433     instance_name = self.items[0]
434     op = ganeti.opcodes.OpShutdownInstance(instance_name=instance_name)
435
436     job_id = ganeti.cli.SendJob([op])
437
438     return job_id
439
440
441 class _R_Tags(baserlib.R_Generic):
442   """ Quasiclass for tagging resources
443
444   Manages tags. Inheriting this class you suppose to define DOC_URI and
445   TAG_LEVEL for it.
446
447   """
448
449   def __init__(self, items, queryargs, req):
450     """A tag resource constructor.
451
452     We have to override the default to sort out cluster naming case.
453
454     """
455     baserlib.R_Generic.__init__(self, items, queryargs, req)
456
457     if self.TAG_LEVEL != constants.TAG_CLUSTER:
458       self.name = items[0]
459     else:
460       self.name = ""
461
462   def GET(self):
463     """Returns a list of tags.
464
465     Example: ["tag1", "tag2", "tag3"]
466
467     """
468     return baserlib._Tags_GET(self.TAG_LEVEL, name=self.name)
469
470   def PUT(self):
471     """Add a set of tags.
472
473     The request as a list of strings should be PUT to this URI. And
474     you'll have back a job id.
475
476     """
477     return baserlib._Tags_PUT(self.TAG_LEVEL,
478                               self.req.request_post_data, name=self.name)
479
480   def DELETE(self):
481     """Delete a tag.
482
483     In order to delete a set of tags, the DELETE
484     request should be addressed to URI like:
485     /tags?tag=[tag]&tag=[tag]
486
487     """
488     if 'tag' not in self.queryargs:
489       # no we not gonna delete all tags
490       raise http.HttpNotImplemented()
491     return baserlib._Tags_DELETE(self.TAG_LEVEL,
492                                  self.queryargs['tag'],
493                                  name=self.name)
494
495
496 class R_2_instances_name_tags(_R_Tags):
497   """ /2/instances/[instance_name]/tags resource.
498
499   Manages per-instance tags.
500
501   """
502   DOC_URI = "/2/instances/[instance_name]/tags"
503   TAG_LEVEL = constants.TAG_INSTANCE
504
505
506 class R_2_nodes_name_tags(_R_Tags):
507   """ /2/nodes/[node_name]/tags resource.
508
509   Manages per-node tags.
510
511   """
512   DOC_URI = "/2/nodes/[node_name]/tags"
513   TAG_LEVEL = constants.TAG_NODE
514
515
516 class R_2_tags(_R_Tags):
517   """ /2/instances/tags resource.
518
519   Manages cluster tags.
520
521   """
522   DOC_URI = "/2/tags"
523   TAG_LEVEL = constants.TAG_CLUSTER