From 441e7cfd9cadc50ff7df27a98e1d5b2db169e15d Mon Sep 17 00:00:00 2001 From: Oleksiy Mishchenko <oleksiy@google.com> Date: Thu, 31 Jul 2008 09:06:37 +0000 Subject: [PATCH] First write operation (add tag) for Ganeti RAPI Add instance tag handling, improved error logging. ...oh, yes adopt instance listing for RAPI2! Reviewed-by: iustinp --- daemons/ganeti-rapi | 15 +++- lib/rapi/baserlib.py | 18 ++++- lib/rapi/connector.py | 3 + lib/rapi/rlib2.py | 94 +++++++++++++++++++++++++- test/ganeti.rapi.resources_unittest.py | 6 +- 5 files changed, 128 insertions(+), 8 deletions(-) diff --git a/daemons/ganeti-rapi b/daemons/ganeti-rapi index c8e8953d3..78da6dccd 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 080fcd859..0de15c932 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 a80250cce..1b0fea482 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 0a777cc02..9c4863b40 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 a2401b8c5..c75cf171e 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 = [ -- GitLab