Merge branch 'devel-2.5'
[ganeti-local] / test / ganeti.qlang_unittest.py
1 #!/usr/bin/python
2 #
3
4 # Copyright (C) 2010, 2011 Google Inc.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 # General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 # 02110-1301, USA.
20
21
22 """Script for testing ganeti.qlang"""
23
24 import unittest
25 import string
26
27 from ganeti import utils
28 from ganeti import errors
29 from ganeti import qlang
30 from ganeti import query
31
32 import testutils
33
34
35 class TestMakeSimpleFilter(unittest.TestCase):
36   def _Test(self, field, names, expected, parse_exp=None):
37     if parse_exp is None:
38       parse_exp = names
39
40     qfilter = qlang.MakeSimpleFilter(field, names)
41     self.assertEqual(qfilter, expected)
42
43   def test(self):
44     self._Test("name", None, None, parse_exp=[])
45     self._Test("name", [], None)
46     self._Test("name", ["node1.example.com"],
47                ["|", ["=", "name", "node1.example.com"]])
48     self._Test("xyz", ["a", "b", "c"],
49                ["|", ["=", "xyz", "a"], ["=", "xyz", "b"], ["=", "xyz", "c"]])
50
51
52 class TestParseFilter(unittest.TestCase):
53   def setUp(self):
54     self.parser = qlang.BuildFilterParser()
55
56   def _Test(self, qfilter, expected, expect_filter=True):
57     self.assertEqual(qlang.MakeFilter([qfilter], not expect_filter), expected)
58     self.assertEqual(qlang.ParseFilter(qfilter, parser=self.parser), expected)
59
60   def test(self):
61     self._Test("name==\"foobar\"", [qlang.OP_EQUAL, "name", "foobar"])
62     self._Test("name=='foobar'", [qlang.OP_EQUAL, "name", "foobar"])
63
64     self._Test("valA==1 and valB==2 or valC==3",
65                [qlang.OP_OR,
66                 [qlang.OP_AND, [qlang.OP_EQUAL, "valA", 1],
67                                [qlang.OP_EQUAL, "valB", 2]],
68                 [qlang.OP_EQUAL, "valC", 3]])
69
70     self._Test(("(name\n==\"foobar\") and (xyz==\"va)ue\" and k == 256 or"
71                 " x ==\t\"y\"\n) and mc"),
72                [qlang.OP_AND,
73                 [qlang.OP_EQUAL, "name", "foobar"],
74                 [qlang.OP_OR,
75                  [qlang.OP_AND, [qlang.OP_EQUAL, "xyz", "va)ue"],
76                                 [qlang.OP_EQUAL, "k", 256]],
77                  [qlang.OP_EQUAL, "x", "y"]],
78                 [qlang.OP_TRUE, "mc"]])
79
80     self._Test("(xyz==\"v\" or k == 256 and x == \"y\")",
81                [qlang.OP_OR,
82                 [qlang.OP_EQUAL, "xyz", "v"],
83                 [qlang.OP_AND, [qlang.OP_EQUAL, "k", 256],
84                                [qlang.OP_EQUAL, "x", "y"]]])
85
86     self._Test("valA==1 and valB==2 and valC==3",
87                [qlang.OP_AND, [qlang.OP_EQUAL, "valA", 1],
88                               [qlang.OP_EQUAL, "valB", 2],
89                               [qlang.OP_EQUAL, "valC", 3]])
90     self._Test("master or field",
91                [qlang.OP_OR, [qlang.OP_TRUE, "master"],
92                              [qlang.OP_TRUE, "field"]])
93     self._Test("mem == 128", [qlang.OP_EQUAL, "mem", 128])
94     self._Test("negfield != -1", [qlang.OP_NOT_EQUAL, "negfield", -1])
95     self._Test("master", [qlang.OP_TRUE, "master"],
96                expect_filter=False)
97     self._Test("not master", [qlang.OP_NOT, [qlang.OP_TRUE, "master"]])
98     for op in ["not", "and", "or"]:
99       self._Test("%sxyz" % op, [qlang.OP_TRUE, "%sxyz" % op],
100                  expect_filter=False)
101       self._Test("not %sxyz" % op,
102                  [qlang.OP_NOT, [qlang.OP_TRUE, "%sxyz" % op]])
103       self._Test("  not \t%sfoo" % op,
104                  [qlang.OP_NOT, [qlang.OP_TRUE, "%sfoo" % op]])
105       self._Test("%sname =~ m/abc/" % op,
106                  [qlang.OP_REGEXP, "%sname" % op, "abc"])
107     self._Test("master and not other",
108                [qlang.OP_AND, [qlang.OP_TRUE, "master"],
109                               [qlang.OP_NOT, [qlang.OP_TRUE, "other"]]])
110     self._Test("not (master or other == 4)",
111                [qlang.OP_NOT,
112                 [qlang.OP_OR, [qlang.OP_TRUE, "master"],
113                               [qlang.OP_EQUAL, "other", 4]]])
114     self._Test("some==\"val\\\"ue\"", [qlang.OP_EQUAL, "some", "val\\\"ue"])
115     self._Test("123 in ips", [qlang.OP_CONTAINS, "ips", 123])
116     self._Test("99 not in ips", [qlang.OP_NOT, [qlang.OP_CONTAINS, "ips", 99]])
117     self._Test("\"a\" in valA and \"b\" not in valB",
118                [qlang.OP_AND, [qlang.OP_CONTAINS, "valA", "a"],
119                               [qlang.OP_NOT, [qlang.OP_CONTAINS, "valB", "b"]]])
120
121     self._Test("name =~ m/test/", [qlang.OP_REGEXP, "name", "test"])
122     self._Test("name =~ m/^node.*example.com$/i",
123                [qlang.OP_REGEXP, "name", "(?i)^node.*example.com$"])
124     self._Test("(name =~ m/^node.*example.com$/s and master) or pip =~ |^3.*|",
125                [qlang.OP_OR,
126                 [qlang.OP_AND,
127                  [qlang.OP_REGEXP, "name", "(?s)^node.*example.com$"],
128                  [qlang.OP_TRUE, "master"]],
129                 [qlang.OP_REGEXP, "pip", "^3.*"]])
130     for flags in ["si", "is", "ssss", "iiiisiii"]:
131       self._Test("name =~ m/gi/%s" % flags,
132                  [qlang.OP_REGEXP, "name", "(?%s)gi" % "".join(sorted(flags))])
133
134     for i in qlang._KNOWN_REGEXP_DELIM:
135       self._Test("name =~ m%stest%s" % (i, i),
136                  [qlang.OP_REGEXP, "name", "test"])
137       self._Test("name !~ m%stest%s" % (i, i),
138                  [qlang.OP_NOT, [qlang.OP_REGEXP, "name", "test"]])
139       self._Test("not\tname =~ m%stest%s" % (i, i),
140                  [qlang.OP_NOT, [qlang.OP_REGEXP, "name", "test"]])
141       self._Test("notname =~ m%stest%s" % (i, i),
142                  [qlang.OP_REGEXP, "notname", "test"])
143
144     self._Test("name =* '*.site'",
145                [qlang.OP_REGEXP, "name", utils.DnsNameGlobPattern("*.site")])
146     self._Test("field !* '*.example.*'",
147                [qlang.OP_NOT, [qlang.OP_REGEXP, "field",
148                                utils.DnsNameGlobPattern("*.example.*")]])
149
150   def testAllFields(self):
151     for name in frozenset(i for d in query.ALL_FIELD_LISTS for i in d.keys()):
152       self._Test("%s == \"value\"" % name, [qlang.OP_EQUAL, name, "value"])
153
154   def testError(self):
155     # Invalid field names, meaning no boolean check is done
156     tests = ["#invalid!filter#", "m/x/,"]
157
158     # Unknown regexp flag
159     tests.append("name=~m#a#g")
160
161     # Incomplete regexp group
162     tests.append("name=~^[^")
163
164     # Valid flag, but in uppercase
165     tests.append("asdf =~ m|abc|I")
166
167     # Non-matching regexp delimiters
168     tests.append("name =~ /foobarbaz#")
169
170     for qfilter in tests:
171       try:
172         qlang.ParseFilter(qfilter, parser=self.parser)
173       except errors.QueryFilterParseError, err:
174         self.assertEqual(len(err.GetDetails()), 3)
175       else:
176         self.fail("Invalid filter '%s' did not raise exception" % qfilter)
177
178
179 class TestMakeFilter(unittest.TestCase):
180   def testNoNames(self):
181     self.assertEqual(qlang.MakeFilter([], False), None)
182     self.assertEqual(qlang.MakeFilter(None, False), None)
183
184   def testPlainNames(self):
185     self.assertEqual(qlang.MakeFilter(["web1", "web2"], False),
186                      [qlang.OP_OR, [qlang.OP_EQUAL, "name", "web1"],
187                                    [qlang.OP_EQUAL, "name", "web2"]])
188
189   def testForcedFilter(self):
190     for i in [None, [], ["1", "2"], ["", "", ""], ["a", "b", "c", "d"]]:
191       self.assertRaises(errors.OpPrereqError, qlang.MakeFilter, i, True)
192
193     # Glob pattern shouldn't parse as filter
194     self.assertRaises(errors.QueryFilterParseError,
195                       qlang.MakeFilter, ["*.site"], True)
196
197     # Plain name parses as boolean filter
198     self.assertEqual(qlang.MakeFilter(["web1"], True), [qlang.OP_TRUE, "web1"])
199
200   def testFilter(self):
201     self.assertEqual(qlang.MakeFilter(["foo/bar"], False),
202                      [qlang.OP_TRUE, "foo/bar"])
203     self.assertEqual(qlang.MakeFilter(["foo=='bar'"], False),
204                      [qlang.OP_EQUAL, "foo", "bar"])
205     self.assertEqual(qlang.MakeFilter(["field=*'*.site'"], False),
206                      [qlang.OP_REGEXP, "field",
207                       utils.DnsNameGlobPattern("*.site")])
208
209     # Plain name parses as name filter, not boolean
210     for name in ["node1", "n-o-d-e", "n_o_d_e", "node1.example.com",
211                  "node1.example.com."]:
212       self.assertEqual(qlang.MakeFilter([name], False),
213                        [qlang.OP_OR, [qlang.OP_EQUAL, "name", name]])
214
215     # Invalid filters
216     for i in ["foo==bar", "foo+=1"]:
217       self.assertRaises(errors.QueryFilterParseError,
218                         qlang.MakeFilter, [i], False)
219
220   def testGlob(self):
221     self.assertEqual(qlang.MakeFilter(["*.site"], False),
222                      [qlang.OP_OR, [qlang.OP_REGEXP, "name",
223                                     utils.DnsNameGlobPattern("*.site")]])
224     self.assertEqual(qlang.MakeFilter(["web?.example"], False),
225                      [qlang.OP_OR, [qlang.OP_REGEXP, "name",
226                                     utils.DnsNameGlobPattern("web?.example")]])
227     self.assertEqual(qlang.MakeFilter(["*.a", "*.b", "?.c"], False),
228                      [qlang.OP_OR,
229                       [qlang.OP_REGEXP, "name",
230                        utils.DnsNameGlobPattern("*.a")],
231                       [qlang.OP_REGEXP, "name",
232                        utils.DnsNameGlobPattern("*.b")],
233                       [qlang.OP_REGEXP, "name",
234                        utils.DnsNameGlobPattern("?.c")]])
235
236
237 if __name__ == "__main__":
238   testutils.GanetiTestProgram()