commit 7341571946d86252eb3f43c0f9957e1ba4d92336
Author: Iustin Pop <iustin@google.com>
Date:   Sat Nov 3 06:55:50 2007 +0000

    Implement tag searching
    
    This patch adds a search command for locating tags on all objects of the
    cluster using a regex pattern.
    
    Reviewed-by: aat

diff --git a/lib/cmdlib.py b/lib/cmdlib.py
index 829c019..5864ee1 100644
--- a/lib/cmdlib.py
+++ b/lib/cmdlib.py
@@ -4141,6 +4141,42 @@ class LUGetTags(TagsLU):
     return self.target.GetTags()
 
 
+class LUSearchTags(NoHooksLU):
+  """Searches the tags for a given pattern.
+
+  """
+  _OP_REQP = ["pattern"]
+
+  def CheckPrereq(self):
+    """Check prerequisites.
+
+    This checks the pattern passed for validity by compiling it.
+
+    """
+    try:
+      self.re = re.compile(self.op.pattern)
+    except re.error, err:
+      raise errors.OpPrereqError("Invalid search pattern '%s': %s" %
+                                 (self.op.pattern, err))
+
+  def Exec(self, feedback_fn):
+    """Returns the tag list.
+
+    """
+    cfg = self.cfg
+    tgts = [("/cluster", cfg.GetClusterInfo())]
+    ilist = [cfg.GetInstanceInfo(name) for name in cfg.GetInstanceList()]
+    tgts.extend([("/instances/%s" % i.name, i) for i in ilist])
+    nlist = [cfg.GetNodeInfo(name) for name in cfg.GetNodeList()]
+    tgts.extend([("/nodes/%s" % n.name, n) for n in nlist])
+    results = []
+    for path, target in tgts:
+      for tag in target.GetTags():
+        if self.re.search(tag):
+          results.append((path, tag))
+    return results
+
+
 class LUAddTags(TagsLU):
   """Sets a tag on a given object.
 
diff --git a/lib/mcpu.py b/lib/mcpu.py
index 139a3bd..5226fd7 100644
--- a/lib/mcpu.py
+++ b/lib/mcpu.py
@@ -80,6 +80,7 @@ class Processor(object):
     opcodes.OpExportInstance: cmdlib.LUExportInstance,
     # tags lu
     opcodes.OpGetTags: cmdlib.LUGetTags,
+    opcodes.OpSearchTags: cmdlib.LUSearchTags,
     opcodes.OpAddTags: cmdlib.LUAddTags,
     opcodes.OpDelTags: cmdlib.LUDelTags,
     }
diff --git a/lib/opcodes.py b/lib/opcodes.py
index 43cefae..99f4439 100644
--- a/lib/opcodes.py
+++ b/lib/opcodes.py
@@ -263,6 +263,12 @@ class OpGetTags(OpCode):
   __slots__ = ["kind", "name"]
 
 
+class OpSearchTags(OpCode):
+  """Searches the tags in the cluster for a given pattern."""
+  OP_ID = "OP_TAGS_SEARCH"
+  __slots__ = ["pattern"]
+
+
 class OpAddTags(OpCode):
   """Add a list of tags on a given object."""
   OP_ID = "OP_TAGS_SET"
diff --git a/man/gnt-cluster.sgml b/man/gnt-cluster.sgml
index 5781c09..4f55602 100644
--- a/man/gnt-cluster.sgml
+++ b/man/gnt-cluster.sgml
@@ -302,6 +302,42 @@
     </refsect2>
 
     <refsect2>
+      <title>SEARCH-TAGS</title>
+
+      <cmdsynopsis>
+        <command>search-tags</command>
+        <arg choice="req"><replaceable>pattern</replaceable></arg>
+      </cmdsynopsis>
+
+      <para>
+        Searches the tags on all objects in the cluster (the cluster
+        itself, the nodes and the instances) for a given pattern. The
+        pattern is interpreted as a regular expression and a search
+        will be done on it (i.e. the given pattern is not anchored to
+        the beggining of the string; if you want that, prefix the
+        pattern with <literal>^</literal>).
+      </para>
+
+      <para>
+        If no tags are matching the pattern, the exit code of the
+        command will be one. If there is at least one match, the exit
+        code will be zero. Each match is listed on one line, the
+        object and the tag separated by a space. The cluster will be
+        listed as <filename>/cluster</filename>, a node will be listed
+        as
+        <filename>/nodes/<replaceable>name</replaceable></filename>,
+        and an instance as
+        <filename>/instances/<replaceable>name</replaceable></filename>.
+        Example:
+      </para>
+<screen>
+# gnt-cluster search time
+/cluster ctime:2007-09-01
+/nodes/node1.example.com mtime:2007-10-04
+</screen>
+    </refsect2>
+
+    <refsect2>
       <title>VERIFY</title>
 
       <cmdsynopsis>
diff --git a/scripts/gnt-cluster b/scripts/gnt-cluster
index 9be9904..42451a5 100755
--- a/scripts/gnt-cluster
+++ b/scripts/gnt-cluster
@@ -193,6 +193,20 @@ def MasterFailover(opts, args):
   SubmitOpCode(op)
 
 
+def SearchTags(opts, args):
+  """Searches the tags on all the cluster.
+
+  """
+  op = opcodes.OpSearchTags(pattern=args[0])
+  result = SubmitOpCode(op)
+  if not result:
+    return 1
+  result = list(result)
+  result.sort()
+  for path, tag in result:
+    print "%s %s" % (path, tag)
+
+
 # this is an option common to more than one command, so we declare
 # it here and reuse it
 node_option = make_option("-n", "--node", action="append", dest="nodes",
@@ -269,6 +283,9 @@ commands = {
                "tag...", "Add tags to the cluster"),
   'remove-tags': (RemoveTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
                   "tag...", "Remove tags from the cluster"),
+  'search-tags': (SearchTags, ARGS_ONE,
+                  [DEBUG_OPT], "", "Searches the tags on all objects on"
+                  " the cluster for a given pattern (regex)"),
   }
 
 if __name__ == '__main__':
