diff --git a/daemons/ganeti-rapi b/daemons/ganeti-rapi index c8e8953d39714be721320f5a9d4c1ca68557bc17..78da6dccdfb3404eb3c8bdacf6301fc1a4d78188 100755 --- a/daemons/ganeti-rapi +++ b/daemons/ganeti-rapi @@ -22,15 +22,16 @@ """ import glob +import logging import optparse import sys import os import signal +from ganeti import logger from ganeti import constants from ganeti import errors from ganeti import http -from ganeti import rpc from ganeti import ssconf from ganeti import utils from ganeti.rapi import connector @@ -49,7 +50,7 @@ class RESTRequestHandler(http.HTTPRequestHandler): """ (HandlerClass, items, args) = self._resmap.getController(self.path) - handler = HandlerClass(self, items, args) + handler = HandlerClass(self, items, args, self.post_data) command = self.command.upper() try: @@ -58,7 +59,11 @@ class RESTRequestHandler(http.HTTPRequestHandler): raise http.HTTPBadRequest() try: - result = fn() + try: + result = fn() + except: + logging.exception("Error while handling the %s request", command) + raise except errors.OpPrereqError, err: # TODO: "Not found" is not always the correct error. Ganeti's core must @@ -135,6 +140,9 @@ def main(): if options.fork: utils.Daemonize(logfile=constants.LOG_RAPISERVER) + logger.SetupLogging(constants.LOG_RAPISERVER, debug=options.debug, + stderr_logging=not options.fork) + utils.WritePidFile(constants.RAPI_PID) log_fd = open(constants.LOG_RAPIACCESS, 'a') @@ -155,4 +163,5 @@ def main(): if __name__ == '__main__': + main() diff --git a/lib/rapi/baserlib.py b/lib/rapi/baserlib.py index 080fcd8599b149a6530c134da629aada57c5b2c8..0de15c9325a9331aa3892738b7f87548010d4c60 100644 --- a/lib/rapi/baserlib.py +++ b/lib/rapi/baserlib.py @@ -26,6 +26,8 @@ import ganeti.cli import ganeti.opcodes +from ganeti import luxi + def BuildUriList(ids, uri_format, uri_fields=("name", "uri")): """Builds a URI list as used by index resources. @@ -89,6 +91,19 @@ def _Tags_GET(kind, name=None): return list(tags) +def _Tags_POST(kind, tags, name=None): + """Helper function to set tags. + + """ + if name is None: + # Do not cause "missing parameter" error, which happens if a parameter + # is None. + name = "" + cl = luxi.Client() + return cl.SubmitJob([ganeti.opcodes.OpAddTags(kind=kind, name=name, + tags=tags)]) + + def MapBulkFields(itemslist, fields): """Map value to field name in to one dictionary. @@ -110,7 +125,7 @@ class R_Generic(object): """Generic class for resources. """ - def __init__(self, request, items, queryargs): + def __init__(self, request, items, queryargs, post_data): """Generic resource constructor. Args: @@ -122,3 +137,4 @@ class R_Generic(object): self.request = request self.items = items self.queryargs = queryargs + self.post_data = post_data diff --git a/lib/rapi/connector.py b/lib/rapi/connector.py index a80250cce11793636fccce7a7c9552bb0639d0c1..1b0fea482b9fc66b834f2f378bd7269082072c33 100644 --- a/lib/rapi/connector.py +++ b/lib/rapi/connector.py @@ -137,5 +137,8 @@ CONNECTOR.update({ "/2/jobs": rlib2.R_2_jobs, "/2/nodes": rlib2.R_2_nodes, + "/2/instances": rlib2.R_2_instances, + re.compile(r'^/2/instances/([\w\._-]+)$'): rlib1.R_instances_name, + re.compile(r'^/2/instances/([\w\._-]+)/tags$'): rlib2.R_2_instances_name_tags, re.compile(r'/2/jobs/(%s)$' % constants.JOB_ID_TEMPLATE): rlib2.R_2_jobs_id, }) diff --git a/lib/rapi/rlib2.py b/lib/rapi/rlib2.py index 0a777cc02e493c65ca93a5191fa80f2727bb4391..9c4863b409724d205b6915fe5fd9f5f0de5270ea 100644 --- a/lib/rapi/rlib2.py +++ b/lib/rapi/rlib2.py @@ -129,5 +129,97 @@ class R_2_nodes(baserlib.R_Generic): result = ganeti.cli.SubmitOpCode(op) return baserlib.MapBulkFields(result, N_FIELDS) - return baserlib.BuildUriList(nodeslist, "/nodes/%s", + return baserlib.BuildUriList(nodeslist, "/2/nodes/%s", uri_fields=("id", "uri")) + + +class R_2_instances(baserlib.R_Generic): + """/2/instances resource. + + """ + DOC_URI = "/2/instances" + + + def GET(self): + """Returns a list of all available instances. + + Returns: + A dictionary with 'name' and 'uri' keys for each of them. + + Example: [ + { + "name": "web.example.com", + "uri": "\/instances\/web.example.com" + }, + { + "name": "mail.example.com", + "uri": "\/instances\/mail.example.com" + }] + + If the optional 'bulk' argument is provided and set to 'true' + value (i.e '?bulk=1'), the output contains detailed + information about instances as a list. + + Example: [ + { + "status": "running", + "bridge": "xen-br0", + "name": "web.example.com", + "tags": ["tag1", "tag2"], + "admin_ram": 512, + "sda_size": 20480, + "pnode": "node1.example.com", + "mac": "01:23:45:67:89:01", + "sdb_size": 4096, + "snodes": ["node2.example.com"], + "disk_template": "drbd", + "ip": null, + "admin_state": true, + "os": "debian-etch", + "vcpus": 2, + "oper_state": true + }, + ... + ] + + """ + op = ganeti.opcodes.OpQueryInstances(output_fields=["name"], names=[]) + instanceslist = baserlib.ExtractField(ganeti.cli.SubmitOpCode(op), 0) + + if 'bulk' in self.queryargs: + op = ganeti.opcodes.OpQueryInstances(output_fields=I_FIELDS, + names=instanceslist) + result = ganeti.cli.SubmitOpCode(op) + return baserlib.MapBulkFields(result, I_FIELDS) + + + else: + return baserlib.BuildUriList(instanceslist, "/2/instances/%s", + uri_fields=("id", "uri")) + + +class R_2_instances_name_tags(baserlib.R_Generic): + """/2/instances/[instance_name]/tags resource. + + Manages per-instance tags. + + """ + DOC_URI = "/2/instances/[instance_name]/tags" + + def GET(self): + """Returns a list of instance tags. + + Example: ["tag1", "tag2", "tag3"] + + """ + return baserlib._Tags_GET(constants.TAG_INSTANCE, name=self.items[0]) + + def POST(self): + """Add a set of tags to the instance. + + The reqest as a list of strings should be POST to this URI. And you'll have + back a job id. + + """ + return baserlib._Tags_POST(constants.TAG_INSTANCE, + self.post_data, name=self.items[0]) diff --git a/test/ganeti.rapi.resources_unittest.py b/test/ganeti.rapi.resources_unittest.py index a2401b8c505eab8ce358953d47034532498d8e83..c75cf171e7044962090442e4ceb93308173b0ed9 100755 --- a/test/ganeti.rapi.resources_unittest.py +++ b/test/ganeti.rapi.resources_unittest.py @@ -28,8 +28,8 @@ import tempfile from ganeti import errors from ganeti import http -from ganeti.rapi import connector -from ganeti.rapi import rlib1 +from ganeti.rapi import connector +from ganeti.rapi import rlib1 class MapperTests(unittest.TestCase): @@ -70,7 +70,7 @@ class R_RootTests(unittest.TestCase): """Testing for R_root class.""" def setUp(self): - self.root = connector.R_root(None, None, None) + self.root = connector.R_root(None, None, None, None) def testGet(self): expected = [