Merge branch 'stable-2.6'
[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 testPlainNamesOtherNamefield(self):
190     self.assertEqual(qlang.MakeFilter(["mailA", "mailB"], False,
191                                       namefield="id"),
192                      [qlang.OP_OR, [qlang.OP_EQUAL, "id", "mailA"],
193                                    [qlang.OP_EQUAL, "id", "mailB"]])
194
195   def testForcedFilter(self):
196     for i in [None, [], ["1", "2"], ["", "", ""], ["a", "b", "c", "d"]]:
197       self.assertRaises(errors.OpPrereqError, qlang.MakeFilter, i, True)
198
199     # Glob pattern shouldn't parse as filter
200     self.assertRaises(errors.QueryFilterParseError,
201                       qlang.MakeFilter, ["*.site"], True)
202
203     # Plain name parses as boolean filter
204     self.assertEqual(qlang.MakeFilter(["web1"], True), [qlang.OP_TRUE, "web1"])
205
206   def testFilter(self):
207     self.assertEqual(qlang.MakeFilter(["foo/bar"], False),
208                      [qlang.OP_TRUE, "foo/bar"])
209     self.assertEqual(qlang.MakeFilter(["foo=='bar'"], False),
210                      [qlang.OP_EQUAL, "foo", "bar"])
211     self.assertEqual(qlang.MakeFilter(["field=*'*.site'"], False),
212                      [qlang.OP_REGEXP, "field",
213                       utils.DnsNameGlobPattern("*.site")])
214
215     # Plain name parses as name filter, not boolean
216     for name in ["node1", "n-o-d-e", "n_o_d_e", "node1.example.com",
217                  "node1.example.com."]:
218       self.assertEqual(qlang.MakeFilter([name], False),
219                        [qlang.OP_OR, [qlang.OP_EQUAL, "name", name]])
220
221     # Invalid filters
222     for i in ["foo==bar", "foo+=1"]:
223       self.assertRaises(errors.QueryFilterParseError,
224                         qlang.MakeFilter, [i], False)
225
226   def testGlob(self):
227     self.assertEqual(qlang.MakeFilter(["*.site"], False),
228                      [qlang.OP_OR, [qlang.OP_REGEXP, "name",
229                                     utils.DnsNameGlobPattern("*.site")]])
230     self.assertEqual(qlang.MakeFilter(["web?.example"], False),
231                      [qlang.OP_OR, [qlang.OP_REGEXP, "name",
232                                     utils.DnsNameGlobPattern("web?.example")]])
233     self.assertEqual(qlang.MakeFilter(["*.a", "*.b", "?.c"], False),
234                      [qlang.OP_OR,
235                       [qlang.OP_REGEXP, "name",
236                        utils.DnsNameGlobPattern("*.a")],
237                       [qlang.OP_REGEXP, "name",
238                        utils.DnsNameGlobPattern("*.b")],
239                       [qlang.OP_REGEXP, "name",
240                        utils.DnsNameGlobPattern("?.c")]])
241
242
243 if __name__ == "__main__":
244   testutils.GanetiTestProgram()