Check DRBD status in cluster verify
[ganeti-local] / lib / rapi / connector.py
index 4f932b0..46b89da 100644 (file)
 
 """
 
-import cgi
+# pylint: disable=C0103
+
+# C0103: Invalid name, since the R_* names are not conforming
+
 import re
+import urlparse
 
 from ganeti import constants
 from ganeti import http
+from ganeti import utils
 
-from ganeti.rapi import baserlib
 from ganeti.rapi import rlib2
 
 
 _NAME_PATTERN = r"[\w\._-]+"
+_DISK_PATTERN = r"\d+"
 
 # the connection map is created at the end of this file
 CONNECTOR = {}
@@ -42,12 +47,14 @@ class Mapper:
   """Map resource to method.
 
   """
-  def __init__(self, connector=CONNECTOR):
+  def __init__(self, connector=None):
     """Resource mapper constructor.
 
     @param connector: a dictionary, mapping method name with URL path regexp
 
     """
+    if connector is None:
+      connector = CONNECTOR
     self._connector = connector
 
   def getController(self, uri):
@@ -62,148 +69,209 @@ class Mapper:
             - args: a dictionary with additional parameters from URL
 
     """
-    if '?' in uri:
-      (path, query) = uri.split('?', 1)
-      args = cgi.parse_qs(query)
+    if "?" in uri:
+      (path, query) = uri.split("?", 1)
+      args = urlparse.parse_qs(query)
     else:
       path = uri
       query = None
       args = {}
 
-    result = None
-
-    for key, handler in self._connector.iteritems():
-      # Regex objects
-      if hasattr(key, "match"):
-        m = key.match(path)
-        if m:
-          result = (handler, list(m.groups()), args)
-          break
+    # Try to find handler for request path
+    result = utils.FindMatch(self._connector, path)
 
-      # String objects
-      elif key == path:
-        result = (handler, [], args)
-        break
-
-    if result:
-      return result
-    else:
+    if result is None:
       raise http.HttpNotFound()
 
+    (handler, groups) = result
 
-class R_root(baserlib.R_Generic):
-  """/ resource.
+    return (handler, groups, args)
 
-  """
-  def GET(self):
-    """Show the list of mapped resources.
-
-    @return: a dictionary with 'name' and 'uri' keys for each of them.
 
-    """
-    root_pattern = re.compile('^R_([a-zA-Z0-9]+)$')
+def _ConvertPattern(value):
+  """Converts URI pattern into a regular expression group.
 
-    rootlist = []
-    for handler in CONNECTOR.values():
-      m = root_pattern.match(handler.__name__)
-      if m:
-        name = m.group(1)
-        if name != 'root':
-          rootlist.append(name)
-
-    return baserlib.BuildUriList(rootlist, "/%s")
+  Used by L{_CompileHandlerPath}.
 
+  """
+  if isinstance(value, UriPattern):
+    return "(%s)" % value.content
+  else:
+    return value
 
-def _getResources(id):
-  """Return a list of resources underneath given id.
 
-  This is to generalize querying of version resources lists.
+def _CompileHandlerPath(*args):
+  """Compiles path for RAPI resource into regular expression.
 
-  @return: a list of resources names.
+  @return: Compiled regular expression object
 
   """
-  r_pattern = re.compile('^R_%s_([a-zA-Z0-9]+)$' % id)
+  return re.compile("^%s$" % "".join(map(_ConvertPattern, args)))
 
-  rlist = []
-  for handler in CONNECTOR.values():
-    m = r_pattern.match(handler.__name__)
-    if m:
-      name = m.group(1)
-      rlist.append(name)
 
-  return rlist
+class UriPattern(object):
+  __slots__ = [
+    "content",
+    ]
 
+  def __init__(self, content):
+    self.content = content
 
-class R_2(baserlib.R_Generic):
-  """ /2 resource, the root of the version 2 API.
 
-  """
-  def GET(self):
-    """Show the list of mapped resources.
+def GetHandlers(node_name_pattern, instance_name_pattern,
+                group_name_pattern, network_name_pattern,
+                job_id_pattern, disk_pattern,
+                query_res_pattern,
+                translate=None):
+  """Returns all supported resources and their handlers.
 
-    @return: a dictionary with 'name' and 'uri' keys for each of them.
+  C{node_name_pattern} and the other C{*_pattern} parameters are wrapped in
+  L{UriPattern} and, if used in a URI, passed to the function specified using
+  C{translate}. C{translate} receives 1..N parameters which are either plain
+  strings or instances of L{UriPattern} and returns a dictionary key suitable
+  for the caller of C{GetHandlers}. The default implementation in
+  L{_CompileHandlerPath} returns a compiled regular expression in which each
+  pattern is a group.
 
-    """
-    return baserlib.BuildUriList(_getResources("2"), "/2/%s")
-
-
-def GetHandlers(node_name_pattern, instance_name_pattern, job_id_pattern):
-  """Returns all supported resources and their handlers.
+  @rtype: dict
 
   """
+  if translate is None:
+    translate_fn = _CompileHandlerPath
+  else:
+    translate_fn = translate
+
+  node_name = UriPattern(node_name_pattern)
+  instance_name = UriPattern(instance_name_pattern)
+  group_name = UriPattern(group_name_pattern)
+  network_name = UriPattern(network_name_pattern)
+  job_id = UriPattern(job_id_pattern)
+  disk = UriPattern(disk_pattern)
+  query_res = UriPattern(query_res_pattern)
+
+  # Important note: New resources should always be added under /2. During a
+  # discussion in July 2010 it was decided that having per-resource versions
+  # is more flexible and future-compatible than versioning the whole remote
+  # API.
+  # TODO: Consider a different data structure where all keys are of the same
+  # type. Strings are faster to look up in a dictionary than iterating and
+  # matching regular expressions, therefore maybe two separate dictionaries
+  # should be used.
   return {
-    "/": R_root,
+    "/": rlib2.R_root,
+    "/2": rlib2.R_2,
 
     "/version": rlib2.R_version,
 
-    "/2": R_2,
-
     "/2/nodes": rlib2.R_2_nodes,
-    re.compile(r'^/2/nodes/(%s)$' % node_name_pattern):
+
+    translate_fn("/2/nodes/", node_name):
       rlib2.R_2_nodes_name,
-    re.compile(r'^/2/nodes/(%s)/tags$' % node_name_pattern):
+    translate_fn("/2/nodes/", node_name, "/powercycle"):
+      rlib2.R_2_nodes_name_powercycle,
+    translate_fn("/2/nodes/", node_name, "/tags"):
       rlib2.R_2_nodes_name_tags,
-    re.compile(r'^/2/nodes/(%s)/role$' % node_name_pattern):
+    translate_fn("/2/nodes/", node_name, "/role"):
       rlib2.R_2_nodes_name_role,
-    re.compile(r'^/2/nodes/(%s)/evacuate$' % node_name_pattern):
+    translate_fn("/2/nodes/", node_name, "/evacuate"):
       rlib2.R_2_nodes_name_evacuate,
-    re.compile(r'^/2/nodes/(%s)/migrate$' % node_name_pattern):
+    translate_fn("/2/nodes/", node_name, "/migrate"):
       rlib2.R_2_nodes_name_migrate,
-    re.compile(r'^/2/nodes/(%s)/storage$' % node_name_pattern):
+    translate_fn("/2/nodes/", node_name, "/modify"):
+      rlib2.R_2_nodes_name_modify,
+    translate_fn("/2/nodes/", node_name, "/storage"):
       rlib2.R_2_nodes_name_storage,
-    re.compile(r'^/2/nodes/(%s)/storage/modify$' % node_name_pattern):
+    translate_fn("/2/nodes/", node_name, "/storage/modify"):
       rlib2.R_2_nodes_name_storage_modify,
-    re.compile(r'^/2/nodes/(%s)/storage/repair$' % node_name_pattern):
+    translate_fn("/2/nodes/", node_name, "/storage/repair"):
       rlib2.R_2_nodes_name_storage_repair,
 
     "/2/instances": rlib2.R_2_instances,
-    re.compile(r'^/2/instances/(%s)$' % instance_name_pattern):
+    translate_fn("/2/instances/", instance_name):
       rlib2.R_2_instances_name,
-    re.compile(r'^/2/instances/(%s)/info$' % instance_name_pattern):
+    translate_fn("/2/instances/", instance_name, "/info"):
       rlib2.R_2_instances_name_info,
-    re.compile(r'^/2/instances/(%s)/tags$' % instance_name_pattern):
+    translate_fn("/2/instances/", instance_name, "/tags"):
       rlib2.R_2_instances_name_tags,
-    re.compile(r'^/2/instances/(%s)/reboot$' % instance_name_pattern):
+    translate_fn("/2/instances/", instance_name, "/reboot"):
       rlib2.R_2_instances_name_reboot,
-    re.compile(r'^/2/instances/(%s)/reinstall$' % instance_name_pattern):
+    translate_fn("/2/instances/", instance_name, "/reinstall"):
       rlib2.R_2_instances_name_reinstall,
-    re.compile(r'^/2/instances/(%s)/replace-disks$' % instance_name_pattern):
+    translate_fn("/2/instances/", instance_name, "/replace-disks"):
       rlib2.R_2_instances_name_replace_disks,
-    re.compile(r'^/2/instances/(%s)/shutdown$' % instance_name_pattern):
+    translate_fn("/2/instances/", instance_name, "/shutdown"):
       rlib2.R_2_instances_name_shutdown,
-    re.compile(r'^/2/instances/(%s)/startup$' % instance_name_pattern):
+    translate_fn("/2/instances/", instance_name, "/startup"):
       rlib2.R_2_instances_name_startup,
+    translate_fn("/2/instances/", instance_name, "/activate-disks"):
+      rlib2.R_2_instances_name_activate_disks,
+    translate_fn("/2/instances/", instance_name, "/deactivate-disks"):
+      rlib2.R_2_instances_name_deactivate_disks,
+    translate_fn("/2/instances/", instance_name, "/recreate-disks"):
+      rlib2.R_2_instances_name_recreate_disks,
+    translate_fn("/2/instances/", instance_name, "/prepare-export"):
+      rlib2.R_2_instances_name_prepare_export,
+    translate_fn("/2/instances/", instance_name, "/export"):
+      rlib2.R_2_instances_name_export,
+    translate_fn("/2/instances/", instance_name, "/migrate"):
+      rlib2.R_2_instances_name_migrate,
+    translate_fn("/2/instances/", instance_name, "/failover"):
+      rlib2.R_2_instances_name_failover,
+    translate_fn("/2/instances/", instance_name, "/rename"):
+      rlib2.R_2_instances_name_rename,
+    translate_fn("/2/instances/", instance_name, "/modify"):
+      rlib2.R_2_instances_name_modify,
+    translate_fn("/2/instances/", instance_name, "/disk/", disk, "/grow"):
+      rlib2.R_2_instances_name_disk_grow,
+    translate_fn("/2/instances/", instance_name, "/console"):
+      rlib2.R_2_instances_name_console,
+
+    "/2/networks": rlib2.R_2_networks,
+    translate_fn("/2/networks/", network_name):
+      rlib2.R_2_networks_name,
+    translate_fn("/2/networks/", network_name, "/connect"):
+      rlib2.R_2_networks_name_connect,
+    translate_fn("/2/networks/", network_name, "/disconnect"):
+      rlib2.R_2_networks_name_disconnect,
+    translate_fn("/2/networks/", network_name, "/modify"):
+      rlib2.R_2_networks_name_modify,
+    translate_fn("/2/networks/", network_name, "/tags"):
+      rlib2.R_2_networks_name_tags,
+
+    "/2/groups": rlib2.R_2_groups,
+    translate_fn("/2/groups/", group_name):
+      rlib2.R_2_groups_name,
+    translate_fn("/2/groups/", group_name, "/modify"):
+      rlib2.R_2_groups_name_modify,
+    translate_fn("/2/groups/", group_name, "/rename"):
+      rlib2.R_2_groups_name_rename,
+    translate_fn("/2/groups/", group_name, "/assign-nodes"):
+      rlib2.R_2_groups_name_assign_nodes,
+    translate_fn("/2/groups/", group_name, "/tags"):
+      rlib2.R_2_groups_name_tags,
 
     "/2/jobs": rlib2.R_2_jobs,
-    re.compile(r'/2/jobs/(%s)$' % job_id_pattern):
+    translate_fn("/2/jobs/", job_id):
       rlib2.R_2_jobs_id,
+    translate_fn("/2/jobs/", job_id, "/wait"):
+      rlib2.R_2_jobs_id_wait,
 
+    "/2/instances-multi-alloc": rlib2.R_2_instances_multi_alloc,
     "/2/tags": rlib2.R_2_tags,
     "/2/info": rlib2.R_2_info,
     "/2/os": rlib2.R_2_os,
     "/2/redistribute-config": rlib2.R_2_redist_config,
+    "/2/features": rlib2.R_2_features,
+    "/2/modify": rlib2.R_2_cluster_modify,
+
+    translate_fn("/2/query/", query_res):
+      rlib2.R_2_query,
+    translate_fn("/2/query/", query_res, "/fields"):
+      rlib2.R_2_query_fields,
     }
 
 
 CONNECTOR.update(GetHandlers(_NAME_PATTERN, _NAME_PATTERN,
-                             constants.JOB_ID_TEMPLATE))
+                             _NAME_PATTERN, _NAME_PATTERN,
+                             constants.JOB_ID_TEMPLATE, _DISK_PATTERN,
+                             _NAME_PATTERN))