commit bf968b7ffc2fd7aea5400814648a58275d7c5dec
Author: Michael Hanselmann <hansmi@google.com>
Date:   Thu Sep 3 12:28:14 2009 +0200

    Add simple unittest for remote API docs
    
    Signed-off-by: Michael Hanselmann <hansmi@google.com>
    Reviewed-by: Luca Bigliardi <shammash@google.com>

diff --git a/lib/rapi/connector.py b/lib/rapi/connector.py
index 168d960..eeae01e 100644
--- a/lib/rapi/connector.py
+++ b/lib/rapi/connector.py
@@ -31,6 +31,9 @@ from ganeti import http
 from ganeti.rapi import baserlib
 from ganeti.rapi import rlib2
 
+
+_NAME_PATTERN = r"[\w\._-]+"
+
 # the connection map is created at the end of this file
 CONNECTOR = {}
 
@@ -144,44 +147,62 @@ class R_2(baserlib.R_Generic):
     return baserlib.BuildUriList(_getResources("2"), "/2/%s")
 
 
-CONNECTOR.update({
-  "/": R_root,
+def GetHandlers(node_name_pattern, instance_name_pattern, job_id_pattern):
+  """Returns all supported resources and their handlers.
+
+  """
+  return {
+    "/": R_root,
+
+    "/version": rlib2.R_version,
 
-  "/version": rlib2.R_version,
+    "/2": R_2,
 
-  "/2": R_2,
-  "/2/jobs": rlib2.R_2_jobs,
-  "/2/nodes": rlib2.R_2_nodes,
-  re.compile(r'^/2/nodes/([\w\._-]+)$'): rlib2.R_2_nodes_name,
-  re.compile(r'^/2/nodes/([\w\._-]+)/tags$'): rlib2.R_2_nodes_name_tags,
-  re.compile(r'^/2/nodes/([\w\._-]+)/role$'): rlib2.R_2_nodes_name_role,
-  re.compile(r'^/2/nodes/([\w\._-]+)/evacuate$'):
+    "/2/nodes": rlib2.R_2_nodes,
+    re.compile(r'^/2/nodes/(%s)$' % node_name_pattern):
+      rlib2.R_2_nodes_name,
+    re.compile(r'^/2/nodes/(%s)/tags$' % node_name_pattern):
+      rlib2.R_2_nodes_name_tags,
+    re.compile(r'^/2/nodes/(%s)/role$' % node_name_pattern):
+      rlib2.R_2_nodes_name_role,
+    re.compile(r'^/2/nodes/(%s)/evacuate$' % node_name_pattern):
       rlib2.R_2_nodes_name_evacuate,
-  re.compile(r'^/2/nodes/([\w\._-]+)/migrate$'):
+    re.compile(r'^/2/nodes/(%s)/migrate$' % node_name_pattern):
       rlib2.R_2_nodes_name_migrate,
-  re.compile(r'^/2/nodes/([\w\._-]+)/storage$'):
+    re.compile(r'^/2/nodes/(%s)/storage$' % node_name_pattern):
       rlib2.R_2_nodes_name_storage,
-  re.compile(r'^/2/nodes/([\w\._-]+)/storage/modify$'):
+    re.compile(r'^/2/nodes/(%s)/storage/modify$' % node_name_pattern):
       rlib2.R_2_nodes_name_storage_modify,
-  re.compile(r'^/2/nodes/([\w\._-]+)/storage/repair$'):
+    re.compile(r'^/2/nodes/(%s)/storage/repair$' % node_name_pattern):
       rlib2.R_2_nodes_name_storage_repair,
-  "/2/instances": rlib2.R_2_instances,
-  re.compile(r'^/2/instances/([\w\._-]+)$'): rlib2.R_2_instances_name,
-  re.compile(r'^/2/instances/([\w\._-]+)/info$'):
+
+    "/2/instances": rlib2.R_2_instances,
+    re.compile(r'^/2/instances/(%s)$' % instance_name_pattern):
+      rlib2.R_2_instances_name,
+    re.compile(r'^/2/instances/(%s)/info$' % instance_name_pattern):
       rlib2.R_2_instances_name_info,
-  re.compile(r'^/2/instances/([\w\._-]+)/tags$'): rlib2.R_2_instances_name_tags,
-  re.compile(r'^/2/instances/([\w\._-]+)/reboot$'):
+    re.compile(r'^/2/instances/(%s)/tags$' % instance_name_pattern):
+      rlib2.R_2_instances_name_tags,
+    re.compile(r'^/2/instances/(%s)/reboot$' % instance_name_pattern):
       rlib2.R_2_instances_name_reboot,
-  re.compile(r'^/2/instances/([\w\._-]+)/reinstall$'):
+    re.compile(r'^/2/instances/(%s)/reinstall$' % instance_name_pattern):
       rlib2.R_2_instances_name_reinstall,
-  re.compile(r'^/2/instances/([\w\._-]+)/replace-disks$'):
+    re.compile(r'^/2/instances/(%s)/replace-disks$' % instance_name_pattern):
       rlib2.R_2_instances_name_replace_disks,
-  re.compile(r'^/2/instances/([\w\._-]+)/shutdown$'):
+    re.compile(r'^/2/instances/(%s)/shutdown$' % instance_name_pattern):
       rlib2.R_2_instances_name_shutdown,
-  re.compile(r'^/2/instances/([\w\._-]+)/startup$'):
+    re.compile(r'^/2/instances/(%s)/startup$' % instance_name_pattern):
       rlib2.R_2_instances_name_startup,
-  re.compile(r'/2/jobs/(%s)$' % constants.JOB_ID_TEMPLATE): rlib2.R_2_jobs_id,
-  "/2/tags": rlib2.R_2_tags,
-  "/2/info": rlib2.R_2_info,
-  "/2/os": rlib2.R_2_os,
-  })
+
+    "/2/jobs": rlib2.R_2_jobs,
+    re.compile(r'/2/jobs/(%s)$' % job_id_pattern):
+      rlib2.R_2_jobs_id,
+
+    "/2/tags": rlib2.R_2_tags,
+    "/2/info": rlib2.R_2_info,
+    "/2/os": rlib2.R_2_os,
+    }
+
+
+CONNECTOR.update(GetHandlers(_NAME_PATTERN, _NAME_PATTERN,
+                             constants.JOB_ID_TEMPLATE))
diff --git a/test/docs_unittest.py b/test/docs_unittest.py
index 1021c35..0ede50b 100755
--- a/test/docs_unittest.py
+++ b/test/docs_unittest.py
@@ -26,6 +26,7 @@ import re
 
 from ganeti import utils
 from ganeti import cmdlib
+from ganeti.rapi import connector
 
 import testutils
 
@@ -66,5 +67,53 @@ class TestDocs(unittest.TestCase):
                       (lucls.HTYPE, lucls.HPATH)))
 
 
+  def testRapiDocs(self):
+    """Check whether all RAPI resources are documented.
+
+    """
+    rapidoc = self._ReadDocFile("rapi.rst")
+
+    node_name = "[node_name]"
+    instance_name = "[instance_name]"
+    job_id = "[job_id]"
+
+    resources = connector.GetHandlers(re.escape(node_name),
+                                      re.escape(instance_name),
+                                      re.escape(job_id))
+
+    titles = []
+
+    prevline = None
+    for line in rapidoc.splitlines():
+      if re.match(r"^\++$", line):
+        titles.append(prevline)
+
+      prevline = line
+
+    undocumented = []
+
+    for key, handler in resources.iteritems():
+      # Regex objects
+      if hasattr(key, "match"):
+        found = False
+        for title in titles:
+          if (title.startswith("``") and
+              title.endswith("``") and
+              key.match(title[2:-2])):
+            found = True
+            break
+
+        if not found:
+          # TODO: Find better way of identifying resource
+          undocumented.append(str(handler))
+
+      elif ("``%s``" % key) not in titles:
+        undocumented.append(key)
+
+    self.failIf(undocumented,
+                msg=("Missing RAPI resource documentation for %s" %
+                     utils.CommaJoin(undocumented)))
+
+
 if __name__ == "__main__":
   unittest.main()
