4 # Copyright (C) 2006, 2007, 2008 Google Inc.
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.
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.
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
22 """Remote API resources.
33 from ganeti import constants
34 from ganeti import utils
35 from ganeti.rapi import httperror
38 # Initialized at the end of this file.
42 def BuildUriList(names, uri_format):
43 """Builds a URI list as used by index resources.
46 - names: List of names as strings
47 - uri_format: Format to be applied for URI
51 return { "name": name, "uri": uri_format % name, }
53 # Make sure the result is sorted, makes it nicer to look at and simplifies
57 return map(_MapName, names)
60 def ExtractField(sequence, index):
61 """Creates a list containing one column out of a list of lists.
64 - sequence: Sequence of lists
65 - index: Index of field
68 return map(lambda item: item[index], sequence)
71 def MapFields(names, data):
72 """Maps two lists into one dictionary.
75 - names: Field names (list of strings)
76 - data: Field data (list)
79 >>> MapFields(["a", "b"], ["foo", 123])
80 {'a': 'foo', 'b': 123}
83 if len(names) != len(data):
84 raise AttributeError("Names and data must have the same length")
85 return dict([(names[i], data[i]) for i in range(len(names))])
88 def RequireLock(name='cmd'):
89 """Function decorator to automatically acquire locks.
91 PEP-318 style function decorator.
95 def new_f(*args, **kwargs):
97 utils.Lock(name, max_retries=15)
100 return fn(*args, **kwargs)
104 except ganeti.errors.LockError, err:
105 raise httperror.HTTPServiceUnavailable(message=str(err))
107 # Override function metadata
108 new_f.func_name = fn.func_name
109 new_f.func_doc = fn.func_doc
116 def _Tags_GET(kind, name=None):
117 """Helper function to retrieve tags.
121 # Do not cause "missing parameter" error, which happens if a parameter
124 op = ganeti.opcodes.OpGetTags(kind=kind, name=name)
125 tags = ganeti.cli.SubmitOpCode(op)
130 """Map resource to method.
133 def __init__(self, connector=_CONNECTOR):
134 """Resource mapper constructor.
137 con: a dictionary, mapping method name with URL path regexp
140 self._connector = connector
142 def getController(self, uri):
143 """Find method for a given URI.
149 None if no method is found or a tuple containing the following fields:
150 methd: name of method mapped to URI
151 items: a list of variable intems in the path
152 args: a dictionary with additional parameters from URL
156 (path, query) = uri.split('?', 1)
157 args = cgi.parse_qs(query)
165 for key, handler in self._connector.iteritems():
167 if hasattr(key, "match"):
170 result = (handler, list(m.groups()), args)
175 result = (handler, [], args)
178 if result is not None:
181 raise httperror.HTTPNotFound()
184 class R_Generic(object):
185 """Generic class for resources.
188 def __init__(self, request, items, queryargs):
189 """Generic resource constructor.
192 request: HTTPRequestHandler object
193 items: a list with variables encoded in the URL
194 queryargs: a dictionary with additional options from URL
197 self.request = request
199 self.queryargs = queryargs
202 class R_root(R_Generic):
209 """Show the list of mapped resources.
212 A dictionary with 'name' and 'uri' keys for each of them.
215 root_pattern = re.compile('^R_([a-zA-Z0-9]+)$')
218 for handler in _CONNECTOR.values():
219 m = root_pattern.match(handler.__name__)
223 rootlist.append(name)
225 return BuildUriList(rootlist, "/%s")
228 class R_version(R_Generic):
229 """/version resource.
231 This resource should be used to determine the remote API version and to adapt
238 """Returns the remote API version.
241 return constants.RAPI_VERSION
244 class R_tags(R_Generic):
247 Manages cluster tags.
253 """Returns a list of all cluster tags.
255 Example: ["tag1", "tag2", "tag3"]
258 return _Tags_GET(constants.TAG_CLUSTER)
261 class R_info(R_Generic):
268 """Returns cluster information.
272 "name": "cluster1.example.com",
273 "software_version": "1.2.4",
276 "master": "node1.example.com",
281 "hypervisor_type": "xen-3.0",
282 "protocol_version": 12
286 op = ganeti.opcodes.OpQueryClusterInfo()
287 return ganeti.cli.SubmitOpCode(op)
290 class R_nodes(R_Generic):
297 def _GetDetails(self, nodeslist):
298 """Returns detailed instance data for bulk output.
301 instance: A list of nodes names.
304 A list of nodes properties
307 fields = ["name","dtotal", "dfree",
308 "mtotal", "mnode", "mfree",
309 "pinst_cnt", "sinst_cnt", "tags"]
311 op = ganeti.opcodes.OpQueryNodes(output_fields=fields,
313 result = ganeti.cli.SubmitOpCode(op)
317 mapped = MapFields(fields, node)
318 nodes_details.append(mapped)
322 """Returns a list of all nodes.
325 A dictionary with 'name' and 'uri' keys for each of them.
329 "name": "node1.example.com",
330 "uri": "\/instances\/node1.example.com"
333 "name": "node2.example.com",
334 "uri": "\/instances\/node2.example.com"
337 If the optional 'bulk' argument is provided and set to 'true'
338 value (i.e '?bulk=1'), the output contains detailed
339 information about nodes as a list. Note: Lock required.
346 "name": "www.example.com",
357 op = ganeti.opcodes.OpQueryNodes(output_fields=["name"], names=[])
358 nodeslist = ExtractField(ganeti.cli.SubmitOpCode(op), 0)
360 if 'bulk' in self.queryargs:
361 return self._GetDetails(nodeslist)
363 return BuildUriList(nodeslist, "/nodes/%s")
366 class R_nodes_name(R_Generic):
367 """/nodes/[node_name] resources.
370 DOC_URI = "/nodes/[node_name]"
374 """Send information about a node.
377 node_name = self.items[0]
378 fields = ["name","dtotal", "dfree",
379 "mtotal", "mnode", "mfree",
380 "pinst_cnt", "sinst_cnt", "tags"]
382 op = ganeti.opcodes.OpQueryNodes(output_fields=fields,
384 result = ganeti.cli.SubmitOpCode(op)
386 return MapFields(fields, result[0])
389 class R_nodes_name_tags(R_Generic):
390 """/nodes/[node_name]/tags resource.
392 Manages per-node tags.
395 DOC_URI = "/nodes/[node_name]/tags"
398 """Returns a list of node tags.
400 Example: ["tag1", "tag2", "tag3"]
403 return _Tags_GET(constants.TAG_NODE, name=self.items[0])
406 class R_instances(R_Generic):
407 """/instances resource.
410 DOC_URI = "/instances"
413 def _GetDetails(self, instanceslist):
414 """Returns detailed instance data for bulk output.
417 instance: A list of instances names.
420 A list with instances properties.
423 fields = ["name", "os", "pnode", "snodes",
424 "admin_state", "admin_ram",
425 "disk_template", "ip", "mac", "bridge",
426 "sda_size", "sdb_size", "vcpus",
427 "oper_state", "status", "tags"]
429 op = ganeti.opcodes.OpQueryInstances(output_fields=fields,
431 result = ganeti.cli.SubmitOpCode(op)
433 instances_details = []
434 for instance in result:
435 mapped = MapFields(fields, instance)
436 instances_details.append(mapped)
437 return instances_details
440 """Returns a list of all available instances.
443 A dictionary with 'name' and 'uri' keys for each of them.
447 "name": "web.example.com",
448 "uri": "\/instances\/web.example.com"
451 "name": "mail.example.com",
452 "uri": "\/instances\/mail.example.com"
455 If the optional 'bulk' argument is provided and set to 'true'
456 value (i.e '?bulk=1'), the output contains detailed
457 information about instances as a list. Note: Lock required.
463 "name": "web.example.com",
464 "tags": ["tag1", "tag2"],
467 "pnode": "node1.example.com",
468 "mac": "01:23:45:67:89:01",
470 "snodes": ["node2.example.com"],
471 "disk_template": "drbd",
482 op = ganeti.opcodes.OpQueryInstances(output_fields=["name"], names=[])
483 instanceslist = ExtractField(ganeti.cli.SubmitOpCode(op), 0)
485 if 'bulk' in self.queryargs:
486 return self._GetDetails(instanceslist)
489 return BuildUriList(instanceslist, "/instances/%s")
492 class R_instances_name(R_Generic):
493 """/instances/[instance_name] resources.
496 DOC_URI = "/instances/[instance_name]"
500 """Send information about an instance.
503 instance_name = self.items[0]
504 fields = ["name", "os", "pnode", "snodes",
505 "admin_state", "admin_ram",
506 "disk_template", "ip", "mac", "bridge",
507 "sda_size", "sdb_size", "vcpus",
508 "oper_state", "status", "tags"]
510 op = ganeti.opcodes.OpQueryInstances(output_fields=fields,
511 names=[instance_name])
512 result = ganeti.cli.SubmitOpCode(op)
514 return MapFields(fields, result[0])
517 class R_instances_name_tags(R_Generic):
518 """/instances/[instance_name]/tags resource.
520 Manages per-instance tags.
523 DOC_URI = "/instances/[instance_name]/tags"
526 """Returns a list of instance tags.
528 Example: ["tag1", "tag2", "tag3"]
531 return _Tags_GET(constants.TAG_INSTANCE, name=self.items[0])
534 class R_os(R_Generic):
542 """Return a list of all OSes.
544 Can return error 500 in case of a problem.
546 Example: ["debian-etch"]
549 op = ganeti.opcodes.OpDiagnoseOS(output_fields=["name", "valid"],
551 diagnose_data = ganeti.cli.SubmitOpCode(op)
553 if not isinstance(diagnose_data, list):
554 raise httperror.HTTPInternalError(message="Can't get OS list")
556 return [row[0] for row in diagnose_data if row[1]]
562 "/version": R_version,
568 re.compile(r'^/nodes/([\w\._-]+)$'): R_nodes_name,
569 re.compile(r'^/nodes/([\w\._-]+)/tags$'): R_nodes_name_tags,
571 "/instances": R_instances,
572 re.compile(r'^/instances/([\w\._-]+)$'): R_instances_name,
573 re.compile(r'^/instances/([\w\._-]+)/tags$'): R_instances_name_tags,