utils: Add function generating regex for DNS name globbing
authorMichael Hanselmann <hansmi@google.com>
Wed, 30 Mar 2011 15:54:21 +0000 (17:54 +0200)
committerMichael Hanselmann <hansmi@google.com>
Wed, 6 Apr 2011 11:05:52 +0000 (13:05 +0200)
The intent of this function is to be able to provide a globbing operator
or query filters. One should be able to say, for example, something to
the effect of “gnt-instance shutdown '*.site'”.

Also rename a variable in MatchNameComponent.

Signed-off-by: Michael Hanselmann <hansmi@google.com>
Reviewed-by: Iustin Pop <iustin@google.com>

lib/utils/text.py
test/ganeti.utils.text_unittest.py

index e55baaa..d8e0984 100644 (file)
@@ -74,11 +74,13 @@ def MatchNameComponent(key, name_list, case_sensitive=True):
   if not case_sensitive:
     re_flags |= re.IGNORECASE
     key = key.upper()
-  mo = re.compile("^%s(\..*)?$" % re.escape(key), re_flags)
+
+  name_re = re.compile(r"^%s(\..*)?$" % re.escape(key), re_flags)
+
   names_filtered = []
   string_matches = []
   for name in name_list:
-    if mo.match(name) is not None:
+    if name_re.match(name) is not None:
       names_filtered.append(name)
       if not case_sensitive and key == name.upper():
         string_matches.append(name)
@@ -87,9 +89,45 @@ def MatchNameComponent(key, name_list, case_sensitive=True):
     return string_matches[0]
   if len(names_filtered) == 1:
     return names_filtered[0]
+
   return None
 
 
+def _DnsNameGlobHelper(match):
+  """Helper function for L{DnsNameGlobPattern}.
+
+  Returns regular expression pattern for parts of the pattern.
+
+  """
+  text = match.group(0)
+
+  if text == "*":
+    return "[^.]*"
+  elif text == "?":
+    return "[^.]"
+  else:
+    return re.escape(text)
+
+
+def DnsNameGlobPattern(pattern):
+  """Generates regular expression from DNS name globbing pattern.
+
+  A DNS name globbing pattern (e.g. C{*.site}) is converted to a regular
+  expression. Escape sequences or ranges (e.g. [a-z]) are not supported.
+
+  Matching always starts at the leftmost part. An asterisk (*) matches all
+  characters except the dot (.) separating DNS name parts. A question mark (?)
+  matches a single character except the dot (.).
+
+  @type pattern: string
+  @param pattern: DNS name globbing pattern
+  @rtype: string
+  @return: Regular expression
+
+  """
+  return r"^%s(\..*)?$" % re.sub(r"\*|\?|[^*?]*", _DnsNameGlobHelper, pattern)
+
+
 def FormatUnit(value, units):
   """Formats an incoming number of MiB with the appropriate unit.
 
index 417a001..5181db4 100755 (executable)
@@ -106,6 +106,58 @@ class TestMatchNameComponent(unittest.TestCase):
                      None)
 
 
+class TestDnsNameGlobPattern(unittest.TestCase):
+  def setUp(self):
+    self.names = [
+      "node1.example.com",
+      "node2-0.example.com",
+      "node2-1.example.com",
+      "node1.example.net",
+      "web1.example.com",
+      "web2.example.com",
+      "sub.site.example.com",
+      ]
+
+  def _Test(self, pattern):
+    re_pat = utils.DnsNameGlobPattern(pattern)
+
+    return filter(re.compile(re_pat).match, self.names)
+
+  def test(self):
+    for pattern in ["xyz", "node", " ", "example.net", "x*.example.*",
+                    "x*.example.com"]:
+      self.assertEqual(self._Test(pattern), [])
+
+    for pattern in ["*", "???*"]:
+      self.assertEqual(self._Test(pattern), self.names)
+
+    self.assertEqual(self._Test("node1.*.net"), ["node1.example.net"])
+    self.assertEqual(self._Test("*.example.net"), ["node1.example.net"])
+    self.assertEqual(self._Test("web1.example.com"), ["web1.example.com"])
+
+    for pattern in ["*.*.*.*", "???", "*.site"]:
+      self.assertEqual(self._Test(pattern), ["sub.site.example.com"])
+
+    self.assertEqual(self._Test("node1"), [
+      "node1.example.com",
+      "node1.example.net",
+      ])
+    self.assertEqual(self._Test("node?*.example.*"), [
+      "node1.example.com",
+      "node2-0.example.com",
+      "node2-1.example.com",
+      "node1.example.net",
+      ])
+    self.assertEqual(self._Test("*-?"), [
+      "node2-0.example.com",
+      "node2-1.example.com",
+      ])
+    self.assertEqual(self._Test("node2-?.example.com"), [
+      "node2-0.example.com",
+      "node2-1.example.com",
+      ])
+
+
 class TestFormatUnit(unittest.TestCase):
   """Test case for the FormatUnit function"""