Add simple query filter parser
authorMichael Hanselmann <hansmi@google.com>
Fri, 19 Nov 2010 19:41:26 +0000 (20:41 +0100)
committerMichael Hanselmann <hansmi@google.com>
Mon, 29 Nov 2010 20:00:24 +0000 (21:00 +0100)
This parser reads only the format described by the query2
design document: either an empty filter or an OR operator
with equality checks as operands.

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

Makefile.am
lib/qlang.py [new file with mode: 0644]
test/ganeti.qlang_unittest.py [new file with mode: 0755]

index b51685a..4bfd094 100644 (file)
@@ -138,6 +138,7 @@ pkgpython_PYTHON = \
        lib/netutils.py \
        lib/objects.py \
        lib/opcodes.py \
+       lib/qlang.py \
        lib/query.py \
        lib/rpc.py \
        lib/runtime.py \
@@ -443,6 +444,7 @@ python_tests = \
        test/ganeti.netutils_unittest.py \
        test/ganeti.objects_unittest.py \
        test/ganeti.opcodes_unittest.py \
+       test/ganeti.qlang_unittest.py \
        test/ganeti.query_unittest.py \
        test/ganeti.rapi.client_unittest.py \
        test/ganeti.rapi.resources_unittest.py \
diff --git a/lib/qlang.py b/lib/qlang.py
new file mode 100644 (file)
index 0000000..ec4f41b
--- /dev/null
@@ -0,0 +1,75 @@
+#
+#
+
+# Copyright (C) 2010 Google Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+
+
+"""Module for a simple query language"""
+
+from ganeti import errors
+
+
+OP_OR = "|"
+OP_EQUAL = "="
+
+
+def ReadSimpleFilter(namefield, filter_):
+  """Function extracting wanted names from restricted filter.
+
+  This should only be used until proper filtering is implemented. The filter
+  must either be empty or of the format C{["|", ["=", field, "name1"], ["=",
+  field, "name2"], ...]}.
+
+  """
+  if filter_ is None:
+    return []
+
+  if not isinstance(filter_, list):
+    raise errors.ParameterError("Filter should be list")
+
+  if not filter_ or filter_[0] != OP_OR:
+    raise errors.ParameterError("Filter should start with OR operator")
+
+  if len(filter_) < 2:
+    raise errors.ParameterError("Invalid filter, OR operator should have"
+                                " operands")
+
+  result = []
+
+  for idx, item in enumerate(filter_[1:]):
+    if not isinstance(item, list):
+      raise errors.ParameterError("Invalid OR operator, operand %s not a"
+                                  " list" % idx)
+
+    if len(item) != 3 or item[0] != OP_EQUAL:
+      raise errors.ParameterError("Invalid OR operator, operand %s is not an"
+                                  " equality filter" % idx)
+
+    (_, name, value) = item
+
+    if not isinstance(value, basestring):
+      raise errors.ParameterError("Operand %s for OR should compare against a"
+                                  " string" % idx)
+
+    if name != namefield:
+      raise errors.ParameterError("Operand %s for OR should filter field '%s',"
+                                  " not '%s'" % (idx, namefield, name))
+
+    result.append(value)
+
+  return result
diff --git a/test/ganeti.qlang_unittest.py b/test/ganeti.qlang_unittest.py
new file mode 100755 (executable)
index 0000000..c0ab03d
--- /dev/null
@@ -0,0 +1,60 @@
+#!/usr/bin/python
+#
+
+# Copyright (C) 2010 Google Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+
+
+"""Script for testing ganeti.qlang"""
+
+import unittest
+
+from ganeti import utils
+from ganeti import errors
+from ganeti import qlang
+
+import testutils
+
+
+class TestReadSimpleFilter(unittest.TestCase):
+  def _Test(self, filter_, expected):
+    self.assertEqual(qlang.ReadSimpleFilter("name", filter_), expected)
+
+  def test(self):
+    self._Test(None, [])
+    self._Test(["|", ["=", "name", "xyz"]], ["xyz"])
+
+    for i in [1, 3, 10, 25, 140]:
+      self._Test(["|"] + [["=", "name", "node%s" % j] for j in range(i)],
+                 ["node%s" % j for j in range(i)])
+
+  def testErrors(self):
+    for i in [123, True, False, "", "Hello World", "a==b",
+              [], ["x"], ["x", "y", "z"], ["|"],
+              ["|", ["="]], ["|", "x"], ["|", 123],
+              ["|", ["=", "otherfield", "xyz"]],
+              ["|", ["=", "name", "xyz"], "abc"],
+              ["|", ["=", "name", "xyz", "too", "long"]],
+              ["|", ["=", "name", []]],
+              ["|", ["=", "name", 999]],
+              ["|", ["=", "name", "abc"], ["=", "otherfield", "xyz"]]]:
+      self.assertRaises(errors.ParameterError, qlang.ReadSimpleFilter,
+                        "name", i)
+
+
+if __name__ == "__main__":
+  testutils.GanetiTestProgram()