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