Migrate lib/cli.py from constants to pathutils
[ganeti-local] / test / ganeti.query_unittest.py
1 #!/usr/bin/python
2 #
3
4 # Copyright (C) 2010, 2011, 2012 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.query"""
23
24 import re
25 import unittest
26 import random
27
28 from ganeti import constants
29 from ganeti import utils
30 from ganeti import compat
31 from ganeti import errors
32 from ganeti import query
33 from ganeti import objects
34 from ganeti import cmdlib
35
36 import ganeti.masterd.instance as gmi
37
38 import testutils
39
40
41 class TestConstants(unittest.TestCase):
42   def test(self):
43     self.assertEqual(set(query._VERIFY_FN.keys()),
44                      constants.QFT_ALL)
45
46
47 class _QueryData:
48   def __init__(self, data, **kwargs):
49     self.data = data
50
51     for name, value in kwargs.items():
52       setattr(self, name, value)
53
54   def __iter__(self):
55     return iter(self.data)
56
57
58 def _GetDiskSize(nr, ctx, item):
59   disks = item["disks"]
60   try:
61     return disks[nr]
62   except IndexError:
63     return query._FS_UNAVAIL
64
65
66 class TestQuery(unittest.TestCase):
67   def test(self):
68     (STATIC, DISK) = range(10, 12)
69
70     fielddef = query._PrepareFieldList([
71       (query._MakeField("name", "Name", constants.QFT_TEXT, "Name"),
72        STATIC, 0, lambda ctx, item: item["name"]),
73       (query._MakeField("master", "Master", constants.QFT_BOOL, "Master"),
74        STATIC, 0, lambda ctx, item: ctx.mastername == item["name"]),
75       ] +
76       [(query._MakeField("disk%s.size" % i, "DiskSize%s" % i,
77                          constants.QFT_UNIT, "Disk size %s" % i),
78         DISK, 0, compat.partial(_GetDiskSize, i))
79        for i in range(4)], [])
80
81     q = query.Query(fielddef, ["name"])
82     self.assertEqual(q.RequestedData(), set([STATIC]))
83     self.assertEqual(len(q._fields), 1)
84     self.assertEqual(len(q.GetFields()), 1)
85     self.assertEqual(q.GetFields()[0].ToDict(),
86       objects.QueryFieldDefinition(name="name",
87                                    title="Name",
88                                    kind=constants.QFT_TEXT,
89                                    doc="Name").ToDict())
90
91     # Create data only once query has been prepared
92     data = [
93       { "name": "node1", "disks": [0, 1, 2], },
94       { "name": "node2", "disks": [3, 4], },
95       { "name": "node3", "disks": [5, 6, 7], },
96       ]
97
98     self.assertEqual(q.Query(_QueryData(data, mastername="node3")),
99                      [[(constants.RS_NORMAL, "node1")],
100                       [(constants.RS_NORMAL, "node2")],
101                       [(constants.RS_NORMAL, "node3")]])
102     self.assertEqual(q.OldStyleQuery(_QueryData(data, mastername="node3")),
103                      [["node1"], ["node2"], ["node3"]])
104
105     q = query.Query(fielddef, ["name", "master"])
106     self.assertEqual(q.RequestedData(), set([STATIC]))
107     self.assertEqual(len(q._fields), 2)
108     self.assertEqual(q.Query(_QueryData(data, mastername="node3")),
109                      [[(constants.RS_NORMAL, "node1"),
110                        (constants.RS_NORMAL, False)],
111                       [(constants.RS_NORMAL, "node2"),
112                        (constants.RS_NORMAL, False)],
113                       [(constants.RS_NORMAL, "node3"),
114                        (constants.RS_NORMAL, True)],
115                      ])
116
117     q = query.Query(fielddef, ["name", "master", "disk0.size"])
118     self.assertEqual(q.RequestedData(), set([STATIC, DISK]))
119     self.assertEqual(len(q._fields), 3)
120     self.assertEqual(q.Query(_QueryData(data, mastername="node2")),
121                      [[(constants.RS_NORMAL, "node1"),
122                        (constants.RS_NORMAL, False),
123                        (constants.RS_NORMAL, 0)],
124                       [(constants.RS_NORMAL, "node2"),
125                        (constants.RS_NORMAL, True),
126                        (constants.RS_NORMAL, 3)],
127                       [(constants.RS_NORMAL, "node3"),
128                        (constants.RS_NORMAL, False),
129                        (constants.RS_NORMAL, 5)],
130                      ])
131
132     # With unknown column
133     q = query.Query(fielddef, ["disk2.size", "disk1.size", "disk99.size",
134                                "disk0.size"])
135     self.assertEqual(q.RequestedData(), set([DISK]))
136     self.assertEqual(len(q._fields), 4)
137     self.assertEqual(q.Query(_QueryData(data, mastername="node2")),
138                      [[(constants.RS_NORMAL, 2),
139                        (constants.RS_NORMAL, 1),
140                        (constants.RS_UNKNOWN, None),
141                        (constants.RS_NORMAL, 0)],
142                       [(constants.RS_UNAVAIL, None),
143                        (constants.RS_NORMAL, 4),
144                        (constants.RS_UNKNOWN, None),
145                        (constants.RS_NORMAL, 3)],
146                       [(constants.RS_NORMAL, 7),
147                        (constants.RS_NORMAL, 6),
148                        (constants.RS_UNKNOWN, None),
149                        (constants.RS_NORMAL, 5)],
150                      ])
151     self.assertRaises(errors.OpPrereqError, q.OldStyleQuery,
152                       _QueryData(data, mastername="node2"))
153     self.assertEqual([fdef.ToDict() for fdef in q.GetFields()], [
154                      { "name": "disk2.size", "title": "DiskSize2",
155                        "kind": constants.QFT_UNIT, "doc": "Disk size 2", },
156                      { "name": "disk1.size", "title": "DiskSize1",
157                        "kind": constants.QFT_UNIT, "doc": "Disk size 1", },
158                      { "name": "disk99.size", "title": "disk99.size",
159                        "kind": constants.QFT_UNKNOWN,
160                        "doc": "Unknown field 'disk99.size'", },
161                      { "name": "disk0.size", "title": "DiskSize0",
162                        "kind": constants.QFT_UNIT, "doc": "Disk size 0", },
163                      ])
164
165     # Empty query
166     q = query.Query(fielddef, [])
167     self.assertEqual(q.RequestedData(), set([]))
168     self.assertEqual(len(q._fields), 0)
169     self.assertEqual(q.Query(_QueryData(data, mastername="node2")),
170                      [[], [], []])
171     self.assertEqual(q.OldStyleQuery(_QueryData(data, mastername="node2")),
172                      [[], [], []])
173     self.assertEqual(q.GetFields(), [])
174
175   def testPrepareFieldList(self):
176     # Duplicate titles
177     for (a, b) in [("name", "name"), ("NAME", "name")]:
178       self.assertRaises(AssertionError, query._PrepareFieldList, [
179         (query._MakeField("name", b, constants.QFT_TEXT, "Name"), None, 0,
180          lambda *args: None),
181         (query._MakeField("other", a, constants.QFT_TEXT, "Other"), None, 0,
182          lambda *args: None),
183         ], [])
184
185     # Non-lowercase names
186     self.assertRaises(AssertionError, query._PrepareFieldList, [
187       (query._MakeField("NAME", "Name", constants.QFT_TEXT, "Name"), None, 0,
188        lambda *args: None),
189       ], [])
190     self.assertRaises(AssertionError, query._PrepareFieldList, [
191       (query._MakeField("Name", "Name", constants.QFT_TEXT, "Name"), None, 0,
192        lambda *args: None),
193       ], [])
194
195     # Empty name
196     self.assertRaises(AssertionError, query._PrepareFieldList, [
197       (query._MakeField("", "Name", constants.QFT_TEXT, "Name"), None, 0,
198        lambda *args: None),
199       ], [])
200
201     # Empty title
202     self.assertRaises(AssertionError, query._PrepareFieldList, [
203       (query._MakeField("name", "", constants.QFT_TEXT, "Name"), None, 0,
204        lambda *args: None),
205       ], [])
206
207     # Whitespace in title
208     self.assertRaises(AssertionError, query._PrepareFieldList, [
209       (query._MakeField("name", "Co lu mn", constants.QFT_TEXT, "Name"),
210        None, 0, lambda *args: None),
211       ], [])
212
213     # No callable function
214     self.assertRaises(AssertionError, query._PrepareFieldList, [
215       (query._MakeField("name", "Name", constants.QFT_TEXT, "Name"),
216        None, 0, None),
217       ], [])
218
219     # Invalid documentation
220     for doc in ["", ".", "Hello world\n", "Hello\nWo\nrld", "Hello World!",
221                 "HelloWorld.", "only lowercase", ",", " x y z .\t", "  "]:
222       self.assertRaises(AssertionError, query._PrepareFieldList, [
223         (query._MakeField("name", "Name", constants.QFT_TEXT, doc),
224         None, 0, lambda *args: None),
225         ], [])
226
227     # Duplicate field name
228     self.assertRaises(ValueError, query._PrepareFieldList, [
229       (query._MakeField("name", "Name", constants.QFT_TEXT, "Name"),
230        None, 0, lambda *args: None),
231       (query._MakeField("name", "Other", constants.QFT_OTHER, "Other"),
232        None, 0, lambda *args: None),
233       ], [])
234
235   def testUnknown(self):
236     fielddef = query._PrepareFieldList([
237       (query._MakeField("name", "Name", constants.QFT_TEXT, "Name"),
238        None, 0, lambda _, item: "name%s" % item),
239       (query._MakeField("other0", "Other0", constants.QFT_TIMESTAMP, "Other"),
240        None, 0, lambda *args: 1234),
241       (query._MakeField("nodata", "NoData", constants.QFT_NUMBER, "No data"),
242        None, 0, lambda *args: query._FS_NODATA ),
243       (query._MakeField("unavail", "Unavail", constants.QFT_BOOL, "Unavail"),
244        None, 0, lambda *args: query._FS_UNAVAIL),
245       ], [])
246
247     for selected in [["foo"], ["Hello", "World"],
248                      ["name1", "other", "foo"]]:
249       q = query.Query(fielddef, selected)
250       self.assertEqual(len(q._fields), len(selected))
251       self.assert_(compat.all(len(row) == len(selected)
252                               for row in q.Query(_QueryData(range(1, 10)))))
253       self.assertEqual(q.Query(_QueryData(range(1, 10))),
254                        [[(constants.RS_UNKNOWN, None)] * len(selected)
255                         for i in range(1, 10)])
256       self.assertEqual([fdef.ToDict() for fdef in q.GetFields()],
257                        [{ "name": name, "title": name,
258                           "kind": constants.QFT_UNKNOWN,
259                           "doc": "Unknown field '%s'" % name}
260                         for name in selected])
261
262     q = query.Query(fielddef, ["name", "other0", "nodata", "unavail"])
263     self.assertEqual(len(q._fields), 4)
264     self.assertEqual(q.OldStyleQuery(_QueryData(range(1, 10))), [
265                      ["name%s" % i, 1234, None, None]
266                      for i in range(1, 10)
267                      ])
268
269     q = query.Query(fielddef, ["name", "other0", "nodata", "unavail", "unk"])
270     self.assertEqual(len(q._fields), 5)
271     self.assertEqual(q.Query(_QueryData(range(1, 10))),
272                      [[(constants.RS_NORMAL, "name%s" % i),
273                        (constants.RS_NORMAL, 1234),
274                        (constants.RS_NODATA, None),
275                        (constants.RS_UNAVAIL, None),
276                        (constants.RS_UNKNOWN, None)]
277                       for i in range(1, 10)])
278
279   def testAliases(self):
280     fields = [
281       (query._MakeField("a", "a-title", constants.QFT_TEXT, "Field A"),
282        None, 0, lambda *args: None),
283       (query._MakeField("b", "b-title", constants.QFT_TEXT, "Field B"),
284        None, 0, lambda *args: None),
285       ]
286     # duplicate field
287     self.assertRaises(AssertionError, query._PrepareFieldList, fields,
288                       [("b", "a")])
289     self.assertRaises(AssertionError, query._PrepareFieldList, fields,
290                       [("c", "b"), ("c", "a")])
291     # missing target
292     self.assertRaises(AssertionError, query._PrepareFieldList, fields,
293                       [("c", "d")])
294     fdefs = query._PrepareFieldList(fields, [("c", "b")])
295     self.assertEqual(len(fdefs), 3)
296     self.assertEqual(fdefs["b"][1:], fdefs["c"][1:])
297
298
299 class TestGetNodeRole(unittest.TestCase):
300   def test(self):
301     tested_role = set()
302
303     checks = [
304       (constants.NR_MASTER, "node1", objects.Node(name="node1")),
305       (constants.NR_MCANDIDATE, "master",
306        objects.Node(name="node1", master_candidate=True)),
307       (constants.NR_REGULAR, "master", objects.Node(name="node1")),
308       (constants.NR_DRAINED, "master",
309        objects.Node(name="node1", drained=True)),
310       (constants.NR_OFFLINE,
311        "master", objects.Node(name="node1", offline=True)),
312       ]
313
314     for (role, master_name, node) in checks:
315       result = query._GetNodeRole(node, master_name)
316       self.assertEqual(result, role)
317       tested_role.add(result)
318
319     self.assertEqual(tested_role, constants.NR_ALL)
320
321
322 class TestNodeQuery(unittest.TestCase):
323   def _Create(self, selected):
324     return query.Query(query.NODE_FIELDS, selected)
325
326   def testSimple(self):
327     cluster = objects.Cluster(cluster_name="testcluster",
328                               ndparams=constants.NDC_DEFAULTS.copy())
329     grp1 = objects.NodeGroup(name="default",
330                              uuid="c0e89160-18e7-11e0-a46e-001d0904baeb",
331                              alloc_policy=constants.ALLOC_POLICY_PREFERRED,
332                              ipolicy=objects.MakeEmptyIPolicy(),
333                              ndparams={},
334                              )
335     grp2 = objects.NodeGroup(name="group2",
336                              uuid="c0e89160-18e7-11e0-a46e-001d0904babe",
337                              alloc_policy=constants.ALLOC_POLICY_PREFERRED,
338                              ipolicy=objects.MakeEmptyIPolicy(),
339                              ndparams={constants.ND_SPINDLE_COUNT: 2},
340                              )
341     groups = {grp1.uuid: grp1, grp2.uuid: grp2}
342     nodes = [
343       objects.Node(name="node1", drained=False, group=grp1.uuid, ndparams={}),
344       objects.Node(name="node2", drained=True, group=grp2.uuid, ndparams={}),
345       objects.Node(name="node3", drained=False, group=grp1.uuid,
346                    ndparams={constants.ND_SPINDLE_COUNT: 4}),
347       ]
348     for live_data in [None, dict.fromkeys([node.name for node in nodes], {})]:
349       nqd = query.NodeQueryData(nodes, live_data, None, None, None,
350                                 groups, None, cluster)
351
352       q = self._Create(["name", "drained"])
353       self.assertEqual(q.RequestedData(), set([query.NQ_CONFIG]))
354       self.assertEqual(q.Query(nqd),
355                        [[(constants.RS_NORMAL, "node1"),
356                          (constants.RS_NORMAL, False)],
357                         [(constants.RS_NORMAL, "node2"),
358                          (constants.RS_NORMAL, True)],
359                         [(constants.RS_NORMAL, "node3"),
360                          (constants.RS_NORMAL, False)],
361                        ])
362       self.assertEqual(q.OldStyleQuery(nqd),
363                        [["node1", False],
364                         ["node2", True],
365                         ["node3", False]])
366       q = self._Create(["ndp/spindle_count"])
367       self.assertEqual(q.RequestedData(), set([query.NQ_GROUP]))
368       self.assertEqual(q.Query(nqd),
369                        [[(constants.RS_NORMAL,
370                           constants.NDC_DEFAULTS[constants.ND_SPINDLE_COUNT])],
371                         [(constants.RS_NORMAL,
372                           grp2.ndparams[constants.ND_SPINDLE_COUNT])],
373                         [(constants.RS_NORMAL,
374                           nodes[2].ndparams[constants.ND_SPINDLE_COUNT])],
375                        ])
376
377   def test(self):
378     selected = query.NODE_FIELDS.keys()
379     field_index = dict((field, idx) for idx, field in enumerate(selected))
380
381     q = self._Create(selected)
382     self.assertEqual(q.RequestedData(),
383                      set([query.NQ_CONFIG, query.NQ_LIVE, query.NQ_INST,
384                           query.NQ_GROUP, query.NQ_OOB]))
385
386     cluster = objects.Cluster(cluster_name="testcluster",
387       hvparams=constants.HVC_DEFAULTS,
388       beparams={
389         constants.PP_DEFAULT: constants.BEC_DEFAULTS,
390         },
391       nicparams={
392         constants.PP_DEFAULT: constants.NICC_DEFAULTS,
393         },
394       ndparams=constants.NDC_DEFAULTS,
395         )
396
397     node_names = ["node%s" % i for i in range(20)]
398     master_name = node_names[3]
399     nodes = [
400       objects.Node(name=name,
401                    primary_ip="192.0.2.%s" % idx,
402                    secondary_ip="192.0.100.%s" % idx,
403                    serial_no=7789 * idx,
404                    master_candidate=(name != master_name and idx % 3 == 0),
405                    offline=False,
406                    drained=False,
407                    powered=True,
408                    vm_capable=True,
409                    master_capable=False,
410                    ndparams={},
411                    group="default",
412                    ctime=1290006900,
413                    mtime=1290006913,
414                    uuid="fd9ccebe-6339-43c9-a82e-94bbe575%04d" % idx)
415       for idx, name in enumerate(node_names)
416       ]
417
418     master_node = nodes[3]
419     master_node.AddTag("masternode")
420     master_node.AddTag("another")
421     master_node.AddTag("tag")
422     master_node.ctime = None
423     master_node.mtime = None
424     assert master_node.name == master_name
425
426     live_data_name = node_names[4]
427     assert live_data_name != master_name
428
429     fake_live_data = {
430       "bootid": "a2504766-498e-4b25-b21e-d23098dc3af4",
431       "cnodes": 4,
432       "csockets": 4,
433       "ctotal": 8,
434       "mnode": 128,
435       "mfree": 100,
436       "mtotal": 4096,
437       "dfree": 5 * 1024 * 1024,
438       "dtotal": 100 * 1024 * 1024,
439       }
440
441     assert (sorted(query._NODE_LIVE_FIELDS.keys()) ==
442             sorted(fake_live_data.keys()))
443
444     live_data = dict.fromkeys(node_names, {})
445     live_data[live_data_name] = \
446       dict((query._NODE_LIVE_FIELDS[name][2], value)
447            for name, value in fake_live_data.items())
448
449     node_to_primary = dict((name, set()) for name in node_names)
450     node_to_primary[master_name].update(["inst1", "inst2"])
451
452     node_to_secondary = dict((name, set()) for name in node_names)
453     node_to_secondary[live_data_name].update(["instX", "instY", "instZ"])
454
455     ng_uuid = "492b4b74-8670-478a-b98d-4c53a76238e6"
456     groups = {
457       ng_uuid: objects.NodeGroup(name="ng1", uuid=ng_uuid, ndparams={}),
458       }
459
460     oob_not_powered_node = node_names[0]
461     nodes[0].powered = False
462     oob_support = dict((name, False) for name in node_names)
463     oob_support[master_name] = True
464     oob_support[oob_not_powered_node] = True
465
466     master_node.group = ng_uuid
467
468     nqd = query.NodeQueryData(nodes, live_data, master_name,
469                               node_to_primary, node_to_secondary, groups,
470                               oob_support, cluster)
471     result = q.Query(nqd)
472     self.assert_(compat.all(len(row) == len(selected) for row in result))
473     self.assertEqual([row[field_index["name"]] for row in result],
474                      [(constants.RS_NORMAL, name) for name in node_names])
475
476     node_to_row = dict((row[field_index["name"]][1], idx)
477                        for idx, row in enumerate(result))
478
479     master_row = result[node_to_row[master_name]]
480     self.assert_(master_row[field_index["master"]])
481     self.assert_(master_row[field_index["role"]], "M")
482     self.assertEqual(master_row[field_index["group"]],
483                      (constants.RS_NORMAL, "ng1"))
484     self.assertEqual(master_row[field_index["group.uuid"]],
485                      (constants.RS_NORMAL, ng_uuid))
486     self.assertEqual(master_row[field_index["ctime"]],
487                      (constants.RS_UNAVAIL, None))
488     self.assertEqual(master_row[field_index["mtime"]],
489                      (constants.RS_UNAVAIL, None))
490
491     self.assert_(row[field_index["pip"]] == node.primary_ip and
492                  row[field_index["sip"]] == node.secondary_ip and
493                  set(row[field_index["tags"]]) == node.GetTags() and
494                  row[field_index["serial_no"]] == node.serial_no and
495                  row[field_index["role"]] == query._GetNodeRole(node,
496                                                                 master_name) and
497                  (node.name == master_name or
498                   (row[field_index["group"]] == "<unknown>" and
499                    row[field_index["group.uuid"]] is None and
500                    row[field_index["ctime"]] == (constants.RS_NORMAL,
501                                                  node.ctime) and
502                    row[field_index["mtime"]] == (constants.RS_NORMAL,
503                                                  node.mtime) and
504                    row[field_index["powered"]] == (constants.RS_NORMAL,
505                                                    True))) or
506                  (node.name == oob_not_powered_node and
507                   row[field_index["powered"]] == (constants.RS_NORMAL,
508                                                   False)) or
509                  row[field_index["powered"]] == (constants.RS_UNAVAIL, None)
510                  for row, node in zip(result, nodes))
511
512     live_data_row = result[node_to_row[live_data_name]]
513
514     for (field, value) in fake_live_data.items():
515       self.assertEqual(live_data_row[field_index[field]],
516                        (constants.RS_NORMAL, value))
517
518     self.assertEqual(master_row[field_index["pinst_cnt"]],
519                      (constants.RS_NORMAL, 2))
520     self.assertEqual(live_data_row[field_index["sinst_cnt"]],
521                      (constants.RS_NORMAL, 3))
522     self.assertEqual(master_row[field_index["pinst_list"]],
523                      (constants.RS_NORMAL,
524                       list(node_to_primary[master_name])))
525     self.assertEqual(live_data_row[field_index["sinst_list"]],
526                      (constants.RS_NORMAL,
527                       list(node_to_secondary[live_data_name])))
528
529   def testGetLiveNodeField(self):
530     nodes = [
531       objects.Node(name="node1", drained=False, offline=False,
532                    vm_capable=True),
533       objects.Node(name="node2", drained=True, offline=False,
534                    vm_capable=True),
535       objects.Node(name="node3", drained=False, offline=False,
536                    vm_capable=True),
537       objects.Node(name="node4", drained=False, offline=True,
538                    vm_capable=True),
539       objects.Node(name="node5", drained=False, offline=False,
540                    vm_capable=False),
541       ]
542     live_data = dict.fromkeys([node.name for node in nodes], {})
543
544     # No data
545     nqd = query.NodeQueryData(None, None, None, None, None, None, None, None)
546     self.assertEqual(query._GetLiveNodeField("hello", constants.QFT_NUMBER,
547                                              nqd, nodes[0]),
548                      query._FS_NODATA)
549
550     # Missing field
551     ctx = _QueryData(None, curlive_data={
552       "some": 1,
553       "other": 2,
554       })
555     self.assertEqual(query._GetLiveNodeField("hello", constants.QFT_NUMBER,
556                                              ctx, nodes[0]),
557                      query._FS_UNAVAIL)
558
559     # Wrong format/datatype
560     ctx = _QueryData(None, curlive_data={
561       "hello": ["Hello World"],
562       "other": 2,
563       })
564     self.assertEqual(query._GetLiveNodeField("hello", constants.QFT_NUMBER,
565                                              ctx, nodes[0]),
566                      query._FS_UNAVAIL)
567
568     # Offline node
569     assert nodes[3].offline
570     ctx = _QueryData(None, curlive_data={})
571     self.assertEqual(query._GetLiveNodeField("hello", constants.QFT_NUMBER,
572                                              ctx, nodes[3]),
573                      query._FS_OFFLINE, None)
574
575     # Wrong field type
576     ctx = _QueryData(None, curlive_data={"hello": 123})
577     self.assertRaises(AssertionError, query._GetLiveNodeField,
578                       "hello", constants.QFT_BOOL, ctx, nodes[0])
579
580     # Non-vm_capable node
581     assert not nodes[4].vm_capable
582     ctx = _QueryData(None, curlive_data={})
583     self.assertEqual(query._GetLiveNodeField("hello", constants.QFT_NUMBER,
584                                              ctx, nodes[4]),
585                      query._FS_UNAVAIL, None)
586
587
588 class TestInstanceQuery(unittest.TestCase):
589   def _Create(self, selected):
590     return query.Query(query.INSTANCE_FIELDS, selected)
591
592   def testSimple(self):
593     q = self._Create(["name", "be/maxmem", "ip"])
594     self.assertEqual(q.RequestedData(), set([query.IQ_CONFIG]))
595
596     cluster = objects.Cluster(cluster_name="testcluster",
597       hvparams=constants.HVC_DEFAULTS,
598       beparams={
599         constants.PP_DEFAULT: constants.BEC_DEFAULTS,
600         },
601       nicparams={
602         constants.PP_DEFAULT: constants.NICC_DEFAULTS,
603         },
604       os_hvp={},
605       osparams={})
606
607     instances = [
608       objects.Instance(name="inst1", hvparams={}, beparams={}, osparams={},
609                        nics=[], os="deb1"),
610       objects.Instance(name="inst2", hvparams={}, nics=[], osparams={},
611         os="foomoo",
612         beparams={
613           constants.BE_MAXMEM: 512,
614         }),
615       objects.Instance(name="inst3", hvparams={}, beparams={}, osparams={},
616         os="dos", nics=[objects.NIC(ip="192.0.2.99", nicparams={})]),
617       ]
618
619     iqd = query.InstanceQueryData(instances, cluster, None, [], [], {},
620                                   set(), {}, None, None)
621     self.assertEqual(q.Query(iqd),
622       [[(constants.RS_NORMAL, "inst1"),
623         (constants.RS_NORMAL, 128),
624         (constants.RS_UNAVAIL, None),
625        ],
626        [(constants.RS_NORMAL, "inst2"),
627         (constants.RS_NORMAL, 512),
628         (constants.RS_UNAVAIL, None),
629        ],
630        [(constants.RS_NORMAL, "inst3"),
631         (constants.RS_NORMAL, 128),
632         (constants.RS_NORMAL, "192.0.2.99"),
633        ]])
634     self.assertEqual(q.OldStyleQuery(iqd),
635       [["inst1", 128, None],
636        ["inst2", 512, None],
637        ["inst3", 128, "192.0.2.99"]])
638
639   def test(self):
640     selected = query.INSTANCE_FIELDS.keys()
641     fieldidx = dict((field, idx) for idx, field in enumerate(selected))
642
643     macs = ["00:11:22:%02x:%02x:%02x" % (i % 255, i % 3, (i * 123) % 255)
644             for i in range(20)]
645
646     q = self._Create(selected)
647     self.assertEqual(q.RequestedData(),
648                      set([query.IQ_CONFIG, query.IQ_LIVE, query.IQ_DISKUSAGE,
649                           query.IQ_CONSOLE, query.IQ_NODES]))
650
651     cluster = objects.Cluster(cluster_name="testcluster",
652       hvparams=constants.HVC_DEFAULTS,
653       beparams={
654         constants.PP_DEFAULT: constants.BEC_DEFAULTS,
655         },
656       nicparams={
657         constants.PP_DEFAULT: constants.NICC_DEFAULTS,
658         },
659       os_hvp={},
660       tcpudp_port_pool=set(),
661       osparams={
662         "deb99": {
663           "clean_install": "yes",
664           },
665         })
666
667     offline_nodes = ["nodeoff1", "nodeoff2"]
668     bad_nodes = ["nodebad1", "nodebad2", "nodebad3"] + offline_nodes
669     nodes = ["node%s" % i for i in range(10)] + bad_nodes
670
671     instances = [
672       objects.Instance(name="inst1", hvparams={}, beparams={}, nics=[],
673         uuid="f90eccb3-e227-4e3c-bf2a-94a21ca8f9cd",
674         ctime=1291244000, mtime=1291244400, serial_no=30,
675         admin_state=constants.ADMINST_UP, hypervisor=constants.HT_XEN_PVM,
676         os="linux1",
677         primary_node="node1",
678         disk_template=constants.DT_PLAIN,
679         disks=[],
680         osparams={}),
681       objects.Instance(name="inst2", hvparams={}, nics=[],
682         uuid="73a0f8a7-068c-4630-ada2-c3440015ab1a",
683         ctime=1291211000, mtime=1291211077, serial_no=1,
684         admin_state=constants.ADMINST_UP, hypervisor=constants.HT_XEN_HVM,
685         os="deb99",
686         primary_node="node5",
687         disk_template=constants.DT_DISKLESS,
688         disks=[],
689         beparams={
690           constants.BE_MAXMEM: 512,
691           constants.BE_MINMEM: 256,
692         },
693         osparams={}),
694       objects.Instance(name="inst3", hvparams={}, beparams={},
695         uuid="11ec8dff-fb61-4850-bfe0-baa1803ff280",
696         ctime=1291011000, mtime=1291013000, serial_no=1923,
697         admin_state=constants.ADMINST_DOWN, hypervisor=constants.HT_KVM,
698         os="busybox",
699         primary_node="node6",
700         disk_template=constants.DT_DRBD8,
701         disks=[],
702         nics=[
703           objects.NIC(ip="192.0.2.99", mac=macs.pop(),
704                       nicparams={
705                         constants.NIC_LINK: constants.DEFAULT_BRIDGE,
706                         }),
707           objects.NIC(ip=None, mac=macs.pop(), nicparams={}),
708           ],
709         osparams={}),
710       objects.Instance(name="inst4", hvparams={}, beparams={},
711         uuid="68dab168-3ef5-4c9d-b4d3-801e0672068c",
712         ctime=1291244390, mtime=1291244395, serial_no=25,
713         admin_state=constants.ADMINST_DOWN, hypervisor=constants.HT_XEN_PVM,
714         os="linux1",
715         primary_node="nodeoff2",
716         disk_template=constants.DT_DRBD8,
717         disks=[],
718         nics=[
719           objects.NIC(ip="192.0.2.1", mac=macs.pop(),
720                       nicparams={
721                         constants.NIC_LINK: constants.DEFAULT_BRIDGE,
722                         }),
723           objects.NIC(ip="192.0.2.2", mac=macs.pop(), nicparams={}),
724           objects.NIC(ip="192.0.2.3", mac=macs.pop(),
725                       nicparams={
726                         constants.NIC_MODE: constants.NIC_MODE_ROUTED,
727                         }),
728           objects.NIC(ip="192.0.2.4", mac=macs.pop(),
729                       nicparams={
730                         constants.NIC_MODE: constants.NIC_MODE_BRIDGED,
731                         constants.NIC_LINK: "eth123",
732                         }),
733           ],
734         osparams={}),
735       objects.Instance(name="inst5", hvparams={}, nics=[],
736         uuid="0e3dca12-5b42-4e24-98a2-415267545bd0",
737         ctime=1231211000, mtime=1261200000, serial_no=3,
738         admin_state=constants.ADMINST_UP, hypervisor=constants.HT_XEN_HVM,
739         os="deb99",
740         primary_node="nodebad2",
741         disk_template=constants.DT_DISKLESS,
742         disks=[],
743         beparams={
744           constants.BE_MAXMEM: 512,
745           constants.BE_MINMEM: 512,
746         },
747         osparams={}),
748       objects.Instance(name="inst6", hvparams={}, nics=[],
749         uuid="72de6580-c8d5-4661-b902-38b5785bb8b3",
750         ctime=7513, mtime=11501, serial_no=13390,
751         admin_state=constants.ADMINST_DOWN, hypervisor=constants.HT_XEN_HVM,
752         os="deb99",
753         primary_node="node7",
754         disk_template=constants.DT_DISKLESS,
755         disks=[],
756         beparams={
757           constants.BE_MAXMEM: 768,
758           constants.BE_MINMEM: 256,
759         },
760         osparams={
761           "clean_install": "no",
762           }),
763       objects.Instance(name="inst7", hvparams={}, nics=[],
764         uuid="ceec5dc4-b729-4f42-ae28-69b3cd24920e",
765         ctime=None, mtime=None, serial_no=1947,
766         admin_state=constants.ADMINST_DOWN, hypervisor=constants.HT_XEN_HVM,
767         os="deb99",
768         primary_node="node6",
769         disk_template=constants.DT_DISKLESS,
770         disks=[],
771         beparams={},
772         osparams={}),
773       objects.Instance(name="inst8", hvparams={}, nics=[],
774         uuid="ceec5dc4-b729-4f42-ae28-69b3cd24920f",
775         ctime=None, mtime=None, serial_no=19478,
776         admin_state=constants.ADMINST_OFFLINE, hypervisor=constants.HT_XEN_HVM,
777         os="deb99",
778         primary_node="node6",
779         disk_template=constants.DT_DISKLESS,
780         disks=[],
781         beparams={},
782         osparams={}),
783       ]
784
785     assert not utils.FindDuplicates(inst.name for inst in instances)
786
787     instbyname = dict((inst.name, inst) for inst in instances)
788
789     disk_usage = dict((inst.name,
790                        gmi.ComputeDiskSize(inst.disk_template,
791                                            [{"size": disk.size}
792                                            for disk in inst.disks]))
793                       for inst in instances)
794
795     inst_bridges = {
796       "inst3": [constants.DEFAULT_BRIDGE, constants.DEFAULT_BRIDGE],
797       "inst4": [constants.DEFAULT_BRIDGE, constants.DEFAULT_BRIDGE,
798                 None, "eth123"],
799       }
800
801     live_data = {
802       "inst2": {
803         "vcpus": 3,
804         },
805       "inst4": {
806         "memory": 123,
807         },
808       "inst6": {
809         "memory": 768,
810         },
811       "inst7": {
812         "vcpus": 3,
813         },
814       }
815     wrongnode_inst = set(["inst7"])
816
817     consinfo = dict((inst.name, None) for inst in instances)
818     consinfo["inst7"] = \
819       objects.InstanceConsole(instance="inst7", kind=constants.CONS_SSH,
820                               host=instbyname["inst7"].primary_node,
821                               user=constants.GANETI_RUNAS,
822                               command=["hostname"]).ToDict()
823
824     iqd = query.InstanceQueryData(instances, cluster, disk_usage,
825                                   offline_nodes, bad_nodes, live_data,
826                                   wrongnode_inst, consinfo, {}, {})
827     result = q.Query(iqd)
828     self.assertEqual(len(result), len(instances))
829     self.assert_(compat.all(len(row) == len(selected)
830                             for row in result))
831
832     assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
833            "Offline nodes not included in bad nodes"
834
835     tested_status = set()
836
837     for (inst, row) in zip(instances, result):
838       assert inst.primary_node in nodes
839
840       self.assertEqual(row[fieldidx["name"]],
841                        (constants.RS_NORMAL, inst.name))
842
843       if inst.primary_node in offline_nodes:
844         exp_status = constants.INSTST_NODEOFFLINE
845       elif inst.primary_node in bad_nodes:
846         exp_status = constants.INSTST_NODEDOWN
847       elif inst.name in live_data:
848         if inst.name in wrongnode_inst:
849           exp_status = constants.INSTST_WRONGNODE
850         elif inst.admin_state == constants.ADMINST_UP:
851           exp_status = constants.INSTST_RUNNING
852         else:
853           exp_status = constants.INSTST_ERRORUP
854       elif inst.admin_state == constants.ADMINST_UP:
855         exp_status = constants.INSTST_ERRORDOWN
856       elif inst.admin_state == constants.ADMINST_DOWN:
857         exp_status = constants.INSTST_ADMINDOWN
858       else:
859         exp_status = constants.INSTST_ADMINOFFLINE
860
861       self.assertEqual(row[fieldidx["status"]],
862                        (constants.RS_NORMAL, exp_status))
863
864       (_, status) = row[fieldidx["status"]]
865       tested_status.add(status)
866
867       #FIXME(dynmem): check oper_ram vs min/max mem
868       for (field, livefield) in [("oper_vcpus", "vcpus")]:
869         if inst.primary_node in bad_nodes:
870           exp = (constants.RS_NODATA, None)
871         elif inst.name in live_data:
872           value = live_data[inst.name].get(livefield, None)
873           if value is None:
874             exp = (constants.RS_UNAVAIL, None)
875           else:
876             exp = (constants.RS_NORMAL, value)
877         else:
878           exp = (constants.RS_UNAVAIL, None)
879
880         self.assertEqual(row[fieldidx[field]], exp)
881
882       bridges = inst_bridges.get(inst.name, [])
883       self.assertEqual(row[fieldidx["nic.bridges"]],
884                        (constants.RS_NORMAL, bridges))
885       if bridges:
886         self.assertEqual(row[fieldidx["bridge"]],
887                          (constants.RS_NORMAL, bridges[0]))
888       else:
889         self.assertEqual(row[fieldidx["bridge"]],
890                          (constants.RS_UNAVAIL, None))
891
892       for i in range(constants.MAX_NICS):
893         if i < len(bridges) and bridges[i] is not None:
894           exp = (constants.RS_NORMAL, bridges[i])
895         else:
896           exp = (constants.RS_UNAVAIL, None)
897         self.assertEqual(row[fieldidx["nic.bridge/%s" % i]], exp)
898
899       if inst.primary_node in bad_nodes:
900         exp = (constants.RS_NODATA, None)
901       else:
902         exp = (constants.RS_NORMAL, inst.name in live_data)
903       self.assertEqual(row[fieldidx["oper_state"]], exp)
904
905       cust_exp = (constants.RS_NORMAL, {})
906       if inst.os == "deb99":
907         if inst.name == "inst6":
908           exp = (constants.RS_NORMAL, {"clean_install": "no"})
909           cust_exp = exp
910         else:
911           exp = (constants.RS_NORMAL, {"clean_install": "yes"})
912       else:
913         exp = (constants.RS_NORMAL, {})
914       self.assertEqual(row[fieldidx["osparams"]], exp)
915       self.assertEqual(row[fieldidx["custom_osparams"]], cust_exp)
916
917       usage = disk_usage[inst.name]
918       if usage is None:
919         usage = 0
920       self.assertEqual(row[fieldidx["disk_usage"]],
921                        (constants.RS_NORMAL, usage))
922
923       for alias, target in [("sda_size", "disk.size/0"),
924                             ("sdb_size", "disk.size/1"),
925                             ("vcpus", "be/vcpus"),
926                             ("ip", "nic.ip/0"),
927                             ("mac", "nic.mac/0"),
928                             ("bridge", "nic.bridge/0"),
929                             ("nic_mode", "nic.mode/0"),
930                             ("nic_link", "nic.link/0"),
931                             ]:
932         self.assertEqual(row[fieldidx[alias]], row[fieldidx[target]])
933
934       for field in ["ctime", "mtime"]:
935         if getattr(inst, field) is None:
936           # No ctime/mtime
937           exp = (constants.RS_UNAVAIL, None)
938         else:
939           exp = (constants.RS_NORMAL, getattr(inst, field))
940         self.assertEqual(row[fieldidx[field]], exp)
941
942       self._CheckInstanceConsole(inst, row[fieldidx["console"]])
943
944     # Ensure all possible status' have been tested
945     self.assertEqual(tested_status, constants.INSTST_ALL)
946
947   def _CheckInstanceConsole(self, instance, (status, consdata)):
948     if instance.name == "inst7":
949       self.assertEqual(status, constants.RS_NORMAL)
950       console = objects.InstanceConsole.FromDict(consdata)
951       self.assertTrue(console.Validate())
952       self.assertEqual(console.host, instance.primary_node)
953     else:
954       self.assertEqual(status, constants.RS_UNAVAIL)
955
956
957 class TestGroupQuery(unittest.TestCase):
958
959   def setUp(self):
960     self.custom_diskparams = {
961       constants.DT_DRBD8: {
962         constants.DRBD_DEFAULT_METAVG: "foobar",
963       },
964     }
965
966     self.groups = [
967       objects.NodeGroup(name="default",
968                         uuid="c0e89160-18e7-11e0-a46e-001d0904baeb",
969                         alloc_policy=constants.ALLOC_POLICY_PREFERRED,
970                         ipolicy=objects.MakeEmptyIPolicy(),
971                         ndparams={},
972                         diskparams={},
973                         ),
974       objects.NodeGroup(name="restricted",
975                         uuid="d2a40a74-18e7-11e0-9143-001d0904baeb",
976                         alloc_policy=constants.ALLOC_POLICY_LAST_RESORT,
977                         ipolicy=objects.MakeEmptyIPolicy(),
978                         ndparams={},
979                         diskparams=self.custom_diskparams,
980                         ),
981       ]
982     self.cluster = objects.Cluster(cluster_name="testcluster",
983       hvparams=constants.HVC_DEFAULTS,
984       beparams={
985         constants.PP_DEFAULT: constants.BEC_DEFAULTS,
986         },
987       nicparams={
988         constants.PP_DEFAULT: constants.NICC_DEFAULTS,
989         },
990       ndparams=constants.NDC_DEFAULTS,
991       ipolicy=constants.IPOLICY_DEFAULTS,
992       diskparams=constants.DISK_DT_DEFAULTS,
993       )
994
995   def _Create(self, selected):
996     return query.Query(query.GROUP_FIELDS, selected)
997
998   def testSimple(self):
999     q = self._Create(["name", "uuid", "alloc_policy"])
1000     gqd = query.GroupQueryData(self.cluster, self.groups, None, None, False)
1001
1002     self.assertEqual(q.RequestedData(), set([query.GQ_CONFIG]))
1003
1004     self.assertEqual(q.Query(gqd),
1005       [[(constants.RS_NORMAL, "default"),
1006         (constants.RS_NORMAL, "c0e89160-18e7-11e0-a46e-001d0904baeb"),
1007         (constants.RS_NORMAL, constants.ALLOC_POLICY_PREFERRED)
1008         ],
1009        [(constants.RS_NORMAL, "restricted"),
1010         (constants.RS_NORMAL, "d2a40a74-18e7-11e0-9143-001d0904baeb"),
1011         (constants.RS_NORMAL, constants.ALLOC_POLICY_LAST_RESORT)
1012         ],
1013        ])
1014
1015   def testNodes(self):
1016     groups_to_nodes = {
1017       "c0e89160-18e7-11e0-a46e-001d0904baeb": ["node1", "node2"],
1018       "d2a40a74-18e7-11e0-9143-001d0904baeb": ["node1", "node10", "node9"],
1019       }
1020
1021     q = self._Create(["name", "node_cnt", "node_list"])
1022     gqd = query.GroupQueryData(self.cluster, self.groups, groups_to_nodes, None,
1023                                False)
1024
1025     self.assertEqual(q.RequestedData(), set([query.GQ_CONFIG, query.GQ_NODE]))
1026
1027     self.assertEqual(q.Query(gqd),
1028                      [[(constants.RS_NORMAL, "default"),
1029                        (constants.RS_NORMAL, 2),
1030                        (constants.RS_NORMAL, ["node1", "node2"]),
1031                        ],
1032                       [(constants.RS_NORMAL, "restricted"),
1033                        (constants.RS_NORMAL, 3),
1034                        (constants.RS_NORMAL, ["node1", "node9", "node10"]),
1035                        ],
1036                       ])
1037
1038   def testInstances(self):
1039     groups_to_instances = {
1040       "c0e89160-18e7-11e0-a46e-001d0904baeb": ["inst1", "inst2"],
1041       "d2a40a74-18e7-11e0-9143-001d0904baeb": ["inst1", "inst10", "inst9"],
1042       }
1043
1044     q = self._Create(["pinst_cnt", "pinst_list"])
1045     gqd = query.GroupQueryData(self.cluster, self.groups, None,
1046       groups_to_instances, False)
1047
1048     self.assertEqual(q.RequestedData(), set([query.GQ_INST]))
1049
1050     self.assertEqual(q.Query(gqd),
1051                      [[(constants.RS_NORMAL, 2),
1052                        (constants.RS_NORMAL, ["inst1", "inst2"]),
1053                        ],
1054                       [(constants.RS_NORMAL, 3),
1055                        (constants.RS_NORMAL, ["inst1", "inst9", "inst10"]),
1056                        ],
1057                       ])
1058
1059   def testDiskparams(self):
1060     q = self._Create(["name", "uuid", "diskparams", "custom_diskparams"])
1061     gqd = query.GroupQueryData(self.cluster, self.groups, None, None, True)
1062
1063     self.assertEqual(q.RequestedData(),
1064                      set([query.GQ_CONFIG, query.GQ_DISKPARAMS]))
1065
1066     self.assertEqual(q.Query(gqd),
1067       [[(constants.RS_NORMAL, "default"),
1068         (constants.RS_NORMAL, "c0e89160-18e7-11e0-a46e-001d0904baeb"),
1069         (constants.RS_NORMAL, constants.DISK_DT_DEFAULTS),
1070         (constants.RS_NORMAL, {}),
1071         ],
1072        [(constants.RS_NORMAL, "restricted"),
1073         (constants.RS_NORMAL, "d2a40a74-18e7-11e0-9143-001d0904baeb"),
1074         (constants.RS_NORMAL, objects.FillDiskParams(constants.DISK_DT_DEFAULTS,
1075                                                      self.custom_diskparams)),
1076         (constants.RS_NORMAL, self.custom_diskparams),
1077         ],
1078        ])
1079
1080
1081 class TestOsQuery(unittest.TestCase):
1082   def _Create(self, selected):
1083     return query.Query(query.OS_FIELDS, selected)
1084
1085   def test(self):
1086     variants = ["v00", "plain", "v3", "var0", "v33", "v20"]
1087     api_versions = [10, 0, 15, 5]
1088     parameters = ["zpar3", "apar9"]
1089
1090     assert variants != sorted(variants) and variants != utils.NiceSort(variants)
1091     assert (api_versions != sorted(api_versions) and
1092             api_versions != utils.NiceSort(variants))
1093     assert (parameters != sorted(parameters) and
1094             parameters != utils.NiceSort(parameters))
1095
1096     data = [
1097       query.OsInfo(name="debian", valid=False, hidden=False, blacklisted=False,
1098                    variants=set(), api_versions=set(), parameters=set(),
1099                    node_status={ "some": "status", }),
1100       query.OsInfo(name="dos", valid=True, hidden=False, blacklisted=True,
1101                    variants=set(variants),
1102                    api_versions=set(api_versions),
1103                    parameters=set(parameters),
1104                    node_status={ "some": "other", "status": None, }),
1105       ]
1106
1107
1108     q = self._Create(["name", "valid", "hidden", "blacklisted", "variants",
1109                       "api_versions", "parameters", "node_status"])
1110     self.assertEqual(q.RequestedData(), set([]))
1111     self.assertEqual(q.Query(data),
1112                      [[(constants.RS_NORMAL, "debian"),
1113                        (constants.RS_NORMAL, False),
1114                        (constants.RS_NORMAL, False),
1115                        (constants.RS_NORMAL, False),
1116                        (constants.RS_NORMAL, []),
1117                        (constants.RS_NORMAL, []),
1118                        (constants.RS_NORMAL, []),
1119                        (constants.RS_NORMAL, {"some": "status"})],
1120                       [(constants.RS_NORMAL, "dos"),
1121                        (constants.RS_NORMAL, True),
1122                        (constants.RS_NORMAL, False),
1123                        (constants.RS_NORMAL, True),
1124                        (constants.RS_NORMAL,
1125                         ["plain", "v00", "v3", "v20", "v33", "var0"]),
1126                        (constants.RS_NORMAL, [0, 5, 10, 15]),
1127                        (constants.RS_NORMAL, ["apar9", "zpar3"]),
1128                        (constants.RS_NORMAL,
1129                         { "some": "other", "status": None, })
1130                        ]])
1131
1132
1133 class TestQueryFields(unittest.TestCase):
1134   def testAllFields(self):
1135     for fielddefs in query.ALL_FIELD_LISTS:
1136       result = query.QueryFields(fielddefs, None)
1137       self.assert_(isinstance(result, dict))
1138       response = objects.QueryFieldsResponse.FromDict(result)
1139       self.assertEqual([(fdef.name, fdef.title) for fdef in response.fields],
1140         [(fdef2.name, fdef2.title)
1141          for (fdef2, _, _, _) in utils.NiceSort(fielddefs.values(),
1142                                                 key=lambda x: x[0].name)])
1143
1144   def testSomeFields(self):
1145     rnd = random.Random(5357)
1146
1147     for _ in range(10):
1148       for fielddefs in query.ALL_FIELD_LISTS:
1149         if len(fielddefs) > 20:
1150           sample_size = rnd.randint(5, 20)
1151         else:
1152           sample_size = rnd.randint(1, max(1, len(fielddefs) - 1))
1153         fields = [fdef for (fdef, _, _, _) in rnd.sample(fielddefs.values(),
1154                                                          sample_size)]
1155         result = query.QueryFields(fielddefs, [fdef.name for fdef in fields])
1156         self.assert_(isinstance(result, dict))
1157         response = objects.QueryFieldsResponse.FromDict(result)
1158         self.assertEqual([(fdef.name, fdef.title) for fdef in response.fields],
1159                          [(fdef2.name, fdef2.title) for fdef2 in fields])
1160
1161
1162 class TestQueryFilter(unittest.TestCase):
1163   def testRequestedNames(self):
1164     for (what, fielddefs) in query.ALL_FIELDS.items():
1165       if what == constants.QR_JOB:
1166         namefield = "id"
1167         nameval = 123
1168         namevalempty = 0
1169         genval = lambda i: i * 10
1170         randvals = [17361, 22015, 13193, 15215]
1171       else:
1172         nameval = "abc"
1173         namevalempty = ""
1174         genval = lambda i: "x%s" % i
1175         randvals = ["x17361", "x22015", "x13193", "x15215"]
1176         if what == constants.QR_EXPORT:
1177           namefield = "export"
1178         else:
1179           namefield = "name"
1180
1181       assert namefield in fielddefs
1182
1183       reqnames = [genval(i) for i in range(4)]
1184       innerfilter = [["=", namefield, v] for v in reqnames]
1185
1186       # No name field
1187       q = query.Query(fielddefs, [namefield],
1188                       qfilter=["=", namefield, nameval], namefield=None)
1189       self.assertEqual(q.RequestedNames(), None)
1190
1191       # No filter
1192       q = query.Query(fielddefs, [namefield], qfilter=None, namefield=namefield)
1193       self.assertEqual(q.RequestedNames(), None)
1194
1195       # Check empty query
1196       q = query.Query(fielddefs, [namefield], qfilter=["|"],
1197                       namefield=namefield)
1198       self.assertEqual(q.RequestedNames(), None)
1199
1200       # Check order
1201       q = query.Query(fielddefs, [namefield], qfilter=["|"] + innerfilter,
1202                       namefield=namefield)
1203       self.assertEqual(q.RequestedNames(), reqnames)
1204
1205       # Check reverse order
1206       q = query.Query(fielddefs, [namefield],
1207                       qfilter=["|"] + list(reversed(innerfilter)),
1208                       namefield=namefield)
1209       self.assertEqual(q.RequestedNames(), list(reversed(reqnames)))
1210
1211       # Duplicates
1212       q = query.Query(fielddefs, [namefield],
1213                       qfilter=["|"] + innerfilter + list(reversed(innerfilter)),
1214                       namefield=namefield)
1215       self.assertEqual(q.RequestedNames(), reqnames)
1216
1217       # Unknown name field
1218       self.assertRaises(AssertionError, query.Query, fielddefs, [namefield],
1219                         namefield="_unknown_field_")
1220
1221       # Filter with AND
1222       q = query.Query(fielddefs, [namefield],
1223                       qfilter=["|", ["=", namefield, nameval],
1224                                     ["&", ["=", namefield, namevalempty]]],
1225                       namefield=namefield)
1226       self.assertTrue(q.RequestedNames() is None)
1227
1228       # Filter with NOT
1229       q = query.Query(fielddefs, [namefield],
1230                       qfilter=["|", ["=", namefield, nameval],
1231                                     ["!", ["=", namefield, namevalempty]]],
1232                       namefield=namefield)
1233       self.assertTrue(q.RequestedNames() is None)
1234
1235       # Filter with only OR (names must be in correct order)
1236       q = query.Query(fielddefs, [namefield],
1237                       qfilter=["|", ["=", namefield, randvals[0]],
1238                                     ["|", ["=", namefield, randvals[1]]],
1239                                     ["|", ["|", ["=", namefield, randvals[2]]]],
1240                                     ["=", namefield, randvals[3]]],
1241                       namefield=namefield)
1242       self.assertEqual(q.RequestedNames(), randvals)
1243
1244   @staticmethod
1245   def _GenNestedFilter(namefield, op, depth, nameval):
1246     nested = ["=", namefield, nameval]
1247     for i in range(depth):
1248       nested = [op, nested]
1249     return nested
1250
1251   def testCompileFilter(self):
1252     levels_max = query._FilterCompilerHelper._LEVELS_MAX
1253
1254     for (what, fielddefs) in query.ALL_FIELDS.items():
1255       if what == constants.QR_JOB:
1256         namefield = "id"
1257         nameval = 123
1258       elif what == constants.QR_EXPORT:
1259         namefield = "export"
1260         nameval = "value"
1261       else:
1262         namefield = "name"
1263         nameval = "value"
1264
1265       checks = [
1266         [], ["="], ["=", "foo"], ["unknownop"], ["!"],
1267         ["=", "_unknown_field", "value"],
1268         self._GenNestedFilter(namefield, "|", levels_max, nameval),
1269         self._GenNestedFilter(namefield, "|", levels_max * 3, nameval),
1270         self._GenNestedFilter(namefield, "!", levels_max, nameval),
1271         ]
1272
1273       for qfilter in checks:
1274         self.assertRaises(errors.ParameterError, query._CompileFilter,
1275                           fielddefs, None, qfilter)
1276
1277       for op in ["|", "!"]:
1278         qfilter = self._GenNestedFilter(namefield, op, levels_max - 1, nameval)
1279         self.assertTrue(callable(query._CompileFilter(fielddefs, None,
1280                                                       qfilter)))
1281
1282   def testQueryInputOrder(self):
1283     fielddefs = query._PrepareFieldList([
1284       (query._MakeField("pnode", "PNode", constants.QFT_TEXT, "Primary"),
1285        None, 0, lambda ctx, item: item["pnode"]),
1286       (query._MakeField("snode", "SNode", constants.QFT_TEXT, "Secondary"),
1287        None, 0, lambda ctx, item: item["snode"]),
1288       ], [])
1289
1290     data = [
1291       { "pnode": "node1", "snode": "node44", },
1292       { "pnode": "node30", "snode": "node90", },
1293       { "pnode": "node25", "snode": "node1", },
1294       { "pnode": "node20", "snode": "node1", },
1295       ]
1296
1297     qfilter = ["|", ["=", "pnode", "node1"], ["=", "snode", "node1"]]
1298
1299     q = query.Query(fielddefs, ["pnode", "snode"], namefield="pnode",
1300                     qfilter=qfilter)
1301     self.assertTrue(q.RequestedNames() is None)
1302     self.assertFalse(q.RequestedData())
1303     self.assertEqual(q.Query(data),
1304       [[(constants.RS_NORMAL, "node1"), (constants.RS_NORMAL, "node44")],
1305        [(constants.RS_NORMAL, "node20"), (constants.RS_NORMAL, "node1")],
1306        [(constants.RS_NORMAL, "node25"), (constants.RS_NORMAL, "node1")]])
1307
1308     # Try again with reversed input data
1309     self.assertEqual(q.Query(reversed(data)),
1310       [[(constants.RS_NORMAL, "node1"), (constants.RS_NORMAL, "node44")],
1311        [(constants.RS_NORMAL, "node20"), (constants.RS_NORMAL, "node1")],
1312        [(constants.RS_NORMAL, "node25"), (constants.RS_NORMAL, "node1")]])
1313
1314     # No name field, result must be in incoming order
1315     q = query.Query(fielddefs, ["pnode", "snode"], namefield=None,
1316                     qfilter=qfilter)
1317     self.assertFalse(q.RequestedData())
1318     self.assertEqual(q.Query(data),
1319       [[(constants.RS_NORMAL, "node1"), (constants.RS_NORMAL, "node44")],
1320        [(constants.RS_NORMAL, "node25"), (constants.RS_NORMAL, "node1")],
1321        [(constants.RS_NORMAL, "node20"), (constants.RS_NORMAL, "node1")]])
1322     self.assertEqual(q.OldStyleQuery(data), [
1323       ["node1", "node44"],
1324       ["node25", "node1"],
1325       ["node20", "node1"],
1326       ])
1327     self.assertEqual(q.Query(reversed(data)),
1328       [[(constants.RS_NORMAL, "node20"), (constants.RS_NORMAL, "node1")],
1329        [(constants.RS_NORMAL, "node25"), (constants.RS_NORMAL, "node1")],
1330        [(constants.RS_NORMAL, "node1"), (constants.RS_NORMAL, "node44")]])
1331     self.assertEqual(q.OldStyleQuery(reversed(data)), [
1332       ["node20", "node1"],
1333       ["node25", "node1"],
1334       ["node1", "node44"],
1335       ])
1336
1337     # Name field, but no sorting, result must be in incoming order
1338     q = query.Query(fielddefs, ["pnode", "snode"], namefield="pnode")
1339     self.assertFalse(q.RequestedData())
1340     self.assertEqual(q.Query(data, sort_by_name=False),
1341       [[(constants.RS_NORMAL, "node1"), (constants.RS_NORMAL, "node44")],
1342        [(constants.RS_NORMAL, "node30"), (constants.RS_NORMAL, "node90")],
1343        [(constants.RS_NORMAL, "node25"), (constants.RS_NORMAL, "node1")],
1344        [(constants.RS_NORMAL, "node20"), (constants.RS_NORMAL, "node1")]])
1345     self.assertEqual(q.OldStyleQuery(data, sort_by_name=False), [
1346       ["node1", "node44"],
1347       ["node30", "node90"],
1348       ["node25", "node1"],
1349       ["node20", "node1"],
1350       ])
1351     self.assertEqual(q.Query(reversed(data), sort_by_name=False),
1352       [[(constants.RS_NORMAL, "node20"), (constants.RS_NORMAL, "node1")],
1353        [(constants.RS_NORMAL, "node25"), (constants.RS_NORMAL, "node1")],
1354        [(constants.RS_NORMAL, "node30"), (constants.RS_NORMAL, "node90")],
1355        [(constants.RS_NORMAL, "node1"), (constants.RS_NORMAL, "node44")]])
1356     self.assertEqual(q.OldStyleQuery(reversed(data), sort_by_name=False), [
1357       ["node20", "node1"],
1358       ["node25", "node1"],
1359       ["node30", "node90"],
1360       ["node1", "node44"],
1361       ])
1362
1363   def testEqualNamesOrder(self):
1364     fielddefs = query._PrepareFieldList([
1365       (query._MakeField("pnode", "PNode", constants.QFT_TEXT, "Primary"),
1366        None, 0, lambda ctx, item: item["pnode"]),
1367       (query._MakeField("num", "Num", constants.QFT_NUMBER, "Num"),
1368        None, 0, lambda ctx, item: item["num"]),
1369       ], [])
1370
1371     data = [
1372       { "pnode": "node1", "num": 100, },
1373       { "pnode": "node1", "num": 25, },
1374       { "pnode": "node2", "num": 90, },
1375       { "pnode": "node2", "num": 30, },
1376       ]
1377
1378     q = query.Query(fielddefs, ["pnode", "num"], namefield="pnode",
1379                     qfilter=["|", ["=", "pnode", "node1"],
1380                                   ["=", "pnode", "node2"],
1381                                   ["=", "pnode", "node1"]])
1382     self.assertEqual(q.RequestedNames(), ["node1", "node2"],
1383                      msg="Did not return unique names")
1384     self.assertFalse(q.RequestedData())
1385     self.assertEqual(q.Query(data),
1386       [[(constants.RS_NORMAL, "node1"), (constants.RS_NORMAL, 100)],
1387        [(constants.RS_NORMAL, "node1"), (constants.RS_NORMAL, 25)],
1388        [(constants.RS_NORMAL, "node2"), (constants.RS_NORMAL, 90)],
1389        [(constants.RS_NORMAL, "node2"), (constants.RS_NORMAL, 30)]])
1390     self.assertEqual(q.Query(data, sort_by_name=False),
1391       [[(constants.RS_NORMAL, "node1"), (constants.RS_NORMAL, 100)],
1392        [(constants.RS_NORMAL, "node1"), (constants.RS_NORMAL, 25)],
1393        [(constants.RS_NORMAL, "node2"), (constants.RS_NORMAL, 90)],
1394        [(constants.RS_NORMAL, "node2"), (constants.RS_NORMAL, 30)]])
1395
1396     data = [
1397       { "pnode": "nodeX", "num": 50, },
1398       { "pnode": "nodeY", "num": 40, },
1399       { "pnode": "nodeX", "num": 30, },
1400       { "pnode": "nodeX", "num": 20, },
1401       { "pnode": "nodeM", "num": 10, },
1402       ]
1403
1404     q = query.Query(fielddefs, ["pnode", "num"], namefield="pnode",
1405                     qfilter=["|", ["=", "pnode", "nodeX"],
1406                                   ["=", "pnode", "nodeY"],
1407                                   ["=", "pnode", "nodeY"],
1408                                   ["=", "pnode", "nodeY"],
1409                                   ["=", "pnode", "nodeM"]])
1410     self.assertEqual(q.RequestedNames(), ["nodeX", "nodeY", "nodeM"],
1411                      msg="Did not return unique names")
1412     self.assertFalse(q.RequestedData())
1413
1414     # First sorted by name, then input order
1415     self.assertEqual(q.Query(data, sort_by_name=True),
1416       [[(constants.RS_NORMAL, "nodeM"), (constants.RS_NORMAL, 10)],
1417        [(constants.RS_NORMAL, "nodeX"), (constants.RS_NORMAL, 50)],
1418        [(constants.RS_NORMAL, "nodeX"), (constants.RS_NORMAL, 30)],
1419        [(constants.RS_NORMAL, "nodeX"), (constants.RS_NORMAL, 20)],
1420        [(constants.RS_NORMAL, "nodeY"), (constants.RS_NORMAL, 40)]])
1421
1422     # Input order
1423     self.assertEqual(q.Query(data, sort_by_name=False),
1424       [[(constants.RS_NORMAL, "nodeX"), (constants.RS_NORMAL, 50)],
1425        [(constants.RS_NORMAL, "nodeY"), (constants.RS_NORMAL, 40)],
1426        [(constants.RS_NORMAL, "nodeX"), (constants.RS_NORMAL, 30)],
1427        [(constants.RS_NORMAL, "nodeX"), (constants.RS_NORMAL, 20)],
1428        [(constants.RS_NORMAL, "nodeM"), (constants.RS_NORMAL, 10)]])
1429
1430   def testFilter(self):
1431     (DK_A, DK_B) = range(1000, 1002)
1432
1433     fielddefs = query._PrepareFieldList([
1434       (query._MakeField("name", "Name", constants.QFT_TEXT, "Name"),
1435        DK_A, 0, lambda ctx, item: item["name"]),
1436       (query._MakeField("other", "Other", constants.QFT_TEXT, "Other"),
1437        DK_B, 0, lambda ctx, item: item["other"]),
1438       ], [])
1439
1440     data = [
1441       { "name": "node1", "other": "foo", },
1442       { "name": "node2", "other": "bar", },
1443       { "name": "node3", "other": "Hello", },
1444       ]
1445
1446     # Empty filter
1447     q = query.Query(fielddefs, ["name", "other"], namefield="name",
1448                     qfilter=["|"])
1449     self.assertTrue(q.RequestedNames() is None)
1450     self.assertEqual(q.RequestedData(), set([DK_A, DK_B]))
1451     self.assertEqual(q.Query(data), [])
1452
1453     # Normal filter
1454     q = query.Query(fielddefs, ["name", "other"], namefield="name",
1455                     qfilter=["=", "name", "node1"])
1456     self.assertEqual(q.RequestedNames(), ["node1"])
1457     self.assertEqual(q.Query(data),
1458       [[(constants.RS_NORMAL, "node1"), (constants.RS_NORMAL, "foo")]])
1459
1460     q = query.Query(fielddefs, ["name", "other"], namefield="name",
1461                     qfilter=(["|", ["=", "name", "node1"],
1462                                    ["=", "name", "node3"]]))
1463     self.assertEqual(q.RequestedNames(), ["node1", "node3"])
1464     self.assertEqual(q.Query(data),
1465       [[(constants.RS_NORMAL, "node1"), (constants.RS_NORMAL, "foo")],
1466        [(constants.RS_NORMAL, "node3"), (constants.RS_NORMAL, "Hello")]])
1467
1468     # Complex filter
1469     q = query.Query(fielddefs, ["name", "other"], namefield="name",
1470                     qfilter=(["|", ["=", "name", "node1"],
1471                                    ["|", ["=", "name", "node3"],
1472                                          ["=", "name", "node2"]],
1473                                    ["=", "name", "node3"]]))
1474     self.assertEqual(q.RequestedNames(), ["node1", "node3", "node2"])
1475     self.assertEqual(q.RequestedData(), set([DK_A, DK_B]))
1476     self.assertEqual(q.Query(data),
1477       [[(constants.RS_NORMAL, "node1"), (constants.RS_NORMAL, "foo")],
1478        [(constants.RS_NORMAL, "node2"), (constants.RS_NORMAL, "bar")],
1479        [(constants.RS_NORMAL, "node3"), (constants.RS_NORMAL, "Hello")]])
1480
1481     # Filter data type mismatch
1482     for i in [-1, 0, 1, 123, [], None, True, False]:
1483       self.assertRaises(errors.ParameterError, query.Query,
1484                         fielddefs, ["name", "other"], namefield="name",
1485                         qfilter=["=", "name", i])
1486
1487     # Negative filter
1488     q = query.Query(fielddefs, ["name", "other"], namefield="name",
1489                     qfilter=["!", ["|", ["=", "name", "node1"],
1490                                         ["=", "name", "node3"]]])
1491     self.assertTrue(q.RequestedNames() is None)
1492     self.assertEqual(q.Query(data),
1493       [[(constants.RS_NORMAL, "node2"), (constants.RS_NORMAL, "bar")]])
1494
1495     # Not equal
1496     q = query.Query(fielddefs, ["name", "other"], namefield="name",
1497                     qfilter=["!=", "name", "node3"])
1498     self.assertTrue(q.RequestedNames() is None)
1499     self.assertEqual(q.Query(data),
1500       [[(constants.RS_NORMAL, "node1"), (constants.RS_NORMAL, "foo")],
1501        [(constants.RS_NORMAL, "node2"), (constants.RS_NORMAL, "bar")]])
1502
1503     # Data type
1504     q = query.Query(fielddefs, [], namefield="name",
1505                     qfilter=["|", ["=", "other", "bar"],
1506                                   ["=", "name", "foo"]])
1507     self.assertTrue(q.RequestedNames() is None)
1508     self.assertEqual(q.RequestedData(), set([DK_A, DK_B]))
1509     self.assertEqual(q.Query(data), [[]])
1510
1511     # Only one data type
1512     q = query.Query(fielddefs, ["other"], namefield="name",
1513                     qfilter=["=", "other", "bar"])
1514     self.assertTrue(q.RequestedNames() is None)
1515     self.assertEqual(q.RequestedData(), set([DK_B]))
1516     self.assertEqual(q.Query(data), [[(constants.RS_NORMAL, "bar")]])
1517
1518     q = query.Query(fielddefs, [], namefield="name",
1519                     qfilter=["=", "other", "bar"])
1520     self.assertTrue(q.RequestedNames() is None)
1521     self.assertEqual(q.RequestedData(), set([DK_B]))
1522     self.assertEqual(q.Query(data), [[]])
1523
1524   def testFilterContains(self):
1525     fielddefs = query._PrepareFieldList([
1526       (query._MakeField("name", "Name", constants.QFT_TEXT, "Name"),
1527        None, 0, lambda ctx, item: item["name"]),
1528       (query._MakeField("other", "Other", constants.QFT_OTHER, "Other"),
1529        None, 0, lambda ctx, item: item["other"]),
1530       ], [])
1531
1532     data = [
1533       { "name": "node2", "other": ["x", "y", "bar"], },
1534       { "name": "node3", "other": "Hello", },
1535       { "name": "node1", "other": ["a", "b", "foo"], },
1536       { "name": "empty", "other": []},
1537       ]
1538
1539     q = query.Query(fielddefs, ["name", "other"], namefield="name",
1540                     qfilter=["=[]", "other", "bar"])
1541     self.assertTrue(q.RequestedNames() is None)
1542     self.assertEqual(q.Query(data), [
1543       [(constants.RS_NORMAL, "node2"),
1544        (constants.RS_NORMAL, ["x", "y", "bar"])],
1545       ])
1546
1547     q = query.Query(fielddefs, ["name", "other"], namefield="name",
1548                     qfilter=["|", ["=[]", "other", "bar"],
1549                                   ["=[]", "other", "a"],
1550                                   ["=[]", "other", "b"]])
1551     self.assertTrue(q.RequestedNames() is None)
1552     self.assertEqual(q.Query(data), [
1553       [(constants.RS_NORMAL, "node1"),
1554        (constants.RS_NORMAL, ["a", "b", "foo"])],
1555       [(constants.RS_NORMAL, "node2"),
1556        (constants.RS_NORMAL, ["x", "y", "bar"])],
1557       ])
1558     self.assertEqual(q.OldStyleQuery(data), [
1559       ["node1", ["a", "b", "foo"]],
1560       ["node2", ["x", "y", "bar"]],
1561       ])
1562
1563     # Boolean test
1564     q = query.Query(fielddefs, ["name", "other"], namefield="name",
1565                     qfilter=["?", "other"])
1566     self.assertEqual(q.OldStyleQuery(data), [
1567       ["node1", ["a", "b", "foo"]],
1568       ["node2", ["x", "y", "bar"]],
1569       ["node3", "Hello"],
1570       ])
1571
1572     q = query.Query(fielddefs, ["name", "other"], namefield="name",
1573                     qfilter=["!", ["?", "other"]])
1574     self.assertEqual(q.OldStyleQuery(data), [
1575       ["empty", []],
1576       ])
1577
1578   def testFilterHostname(self):
1579     fielddefs = query._PrepareFieldList([
1580       (query._MakeField("name", "Name", constants.QFT_TEXT, "Name"),
1581        None, query.QFF_HOSTNAME, lambda ctx, item: item["name"]),
1582       ], [])
1583
1584     data = [
1585       { "name": "node1.example.com", },
1586       { "name": "node2.example.com", },
1587       { "name": "node2.example.net", },
1588       ]
1589
1590     q = query.Query(fielddefs, ["name"], namefield="name",
1591                     qfilter=["=", "name", "node2"])
1592     self.assertEqual(q.RequestedNames(), ["node2"])
1593     self.assertEqual(q.Query(data), [
1594       [(constants.RS_NORMAL, "node2.example.com")],
1595       [(constants.RS_NORMAL, "node2.example.net")],
1596       ])
1597
1598     q = query.Query(fielddefs, ["name"], namefield="name",
1599                     qfilter=["=", "name", "node1"])
1600     self.assertEqual(q.RequestedNames(), ["node1"])
1601     self.assertEqual(q.Query(data), [
1602       [(constants.RS_NORMAL, "node1.example.com")],
1603       ])
1604
1605     q = query.Query(fielddefs, ["name"], namefield="name",
1606                     qfilter=["=", "name", "othername"])
1607     self.assertEqual(q.RequestedNames(), ["othername"])
1608     self.assertEqual(q.Query(data), [])
1609
1610     q = query.Query(fielddefs, ["name"], namefield="name",
1611                     qfilter=["|", ["=", "name", "node1.example.com"],
1612                                   ["=", "name", "node2"]])
1613     self.assertEqual(q.RequestedNames(), ["node1.example.com", "node2"])
1614     self.assertEqual(q.Query(data), [
1615       [(constants.RS_NORMAL, "node1.example.com")],
1616       [(constants.RS_NORMAL, "node2.example.com")],
1617       [(constants.RS_NORMAL, "node2.example.net")],
1618       ])
1619     self.assertEqual(q.OldStyleQuery(data), [
1620       ["node1.example.com"],
1621       ["node2.example.com"],
1622       ["node2.example.net"],
1623       ])
1624
1625     q = query.Query(fielddefs, ["name"], namefield="name",
1626                     qfilter=["!=", "name", "node1"])
1627     self.assertTrue(q.RequestedNames() is None)
1628     self.assertEqual(q.Query(data), [
1629       [(constants.RS_NORMAL, "node2.example.com")],
1630       [(constants.RS_NORMAL, "node2.example.net")],
1631       ])
1632     self.assertEqual(q.OldStyleQuery(data), [
1633       ["node2.example.com"],
1634       ["node2.example.net"],
1635       ])
1636
1637   def testFilterBoolean(self):
1638     fielddefs = query._PrepareFieldList([
1639       (query._MakeField("name", "Name", constants.QFT_TEXT, "Name"),
1640        None, query.QFF_HOSTNAME, lambda ctx, item: item["name"]),
1641       (query._MakeField("value", "Value", constants.QFT_BOOL, "Value"),
1642        None, 0, lambda ctx, item: item["value"]),
1643       ], [])
1644
1645     data = [
1646       { "name": "node1", "value": False, },
1647       { "name": "node2", "value": True, },
1648       { "name": "node3", "value": True, },
1649       ]
1650
1651     q = query.Query(fielddefs, ["name", "value"],
1652                     qfilter=["|", ["=", "value", False],
1653                                   ["=", "value", True]])
1654     self.assertTrue(q.RequestedNames() is None)
1655     self.assertEqual(q.Query(data), [
1656       [(constants.RS_NORMAL, "node1"), (constants.RS_NORMAL, False)],
1657       [(constants.RS_NORMAL, "node2"), (constants.RS_NORMAL, True)],
1658       [(constants.RS_NORMAL, "node3"), (constants.RS_NORMAL, True)],
1659       ])
1660
1661     q = query.Query(fielddefs, ["name", "value"],
1662                     qfilter=["|", ["=", "value", False],
1663                                   ["!", ["=", "value", False]]])
1664     self.assertTrue(q.RequestedNames() is None)
1665     self.assertEqual(q.Query(data), [
1666       [(constants.RS_NORMAL, "node1"), (constants.RS_NORMAL, False)],
1667       [(constants.RS_NORMAL, "node2"), (constants.RS_NORMAL, True)],
1668       [(constants.RS_NORMAL, "node3"), (constants.RS_NORMAL, True)],
1669       ])
1670
1671     # Comparing bool with string
1672     for i in ["False", "True", "0", "1", "no", "yes", "N", "Y"]:
1673       self.assertRaises(errors.ParameterError, query.Query,
1674                         fielddefs, ["name", "value"],
1675                         qfilter=["=", "value", i])
1676
1677     # Truth filter
1678     q = query.Query(fielddefs, ["name", "value"], qfilter=["?", "value"])
1679     self.assertTrue(q.RequestedNames() is None)
1680     self.assertEqual(q.Query(data), [
1681       [(constants.RS_NORMAL, "node2"), (constants.RS_NORMAL, True)],
1682       [(constants.RS_NORMAL, "node3"), (constants.RS_NORMAL, True)],
1683       ])
1684
1685     # Negative bool filter
1686     q = query.Query(fielddefs, ["name", "value"], qfilter=["!", ["?", "value"]])
1687     self.assertTrue(q.RequestedNames() is None)
1688     self.assertEqual(q.Query(data), [
1689       [(constants.RS_NORMAL, "node1"), (constants.RS_NORMAL, False)],
1690       ])
1691
1692     # Complex truth filter
1693     q = query.Query(fielddefs, ["name", "value"],
1694                     qfilter=["|", ["&", ["=", "name", "node1"],
1695                                         ["!", ["?", "value"]]],
1696                                   ["?", "value"]])
1697     self.assertTrue(q.RequestedNames() is None)
1698     self.assertEqual(q.Query(data), [
1699       [(constants.RS_NORMAL, "node1"), (constants.RS_NORMAL, False)],
1700       [(constants.RS_NORMAL, "node2"), (constants.RS_NORMAL, True)],
1701       [(constants.RS_NORMAL, "node3"), (constants.RS_NORMAL, True)],
1702       ])
1703
1704   def testFilterRegex(self):
1705     fielddefs = query._PrepareFieldList([
1706       (query._MakeField("name", "Name", constants.QFT_TEXT, "Name"),
1707        None, 0, lambda ctx, item: item["name"]),
1708       ], [])
1709
1710     data = [
1711       { "name": "node1.example.com", },
1712       { "name": "node2.site.example.com", },
1713       { "name": "node2.example.net", },
1714
1715       # Empty name
1716       { "name": "", },
1717       ]
1718
1719     q = query.Query(fielddefs, ["name"], namefield="name",
1720                     qfilter=["=~", "name", "site"])
1721     self.assertTrue(q.RequestedNames() is None)
1722     self.assertEqual(q.Query(data), [
1723       [(constants.RS_NORMAL, "node2.site.example.com")],
1724       ])
1725
1726     q = query.Query(fielddefs, ["name"], namefield="name",
1727                     qfilter=["=~", "name", "^node2"])
1728     self.assertTrue(q.RequestedNames() is None)
1729     self.assertEqual(q.Query(data), [
1730       [(constants.RS_NORMAL, "node2.example.net")],
1731       [(constants.RS_NORMAL, "node2.site.example.com")],
1732       ])
1733
1734     q = query.Query(fielddefs, ["name"], namefield="name",
1735                     qfilter=["=~", "name", r"(?i)\.COM$"])
1736     self.assertTrue(q.RequestedNames() is None)
1737     self.assertEqual(q.Query(data), [
1738       [(constants.RS_NORMAL, "node1.example.com")],
1739       [(constants.RS_NORMAL, "node2.site.example.com")],
1740       ])
1741
1742     q = query.Query(fielddefs, ["name"], namefield="name",
1743                     qfilter=["=~", "name", r"."])
1744     self.assertTrue(q.RequestedNames() is None)
1745     self.assertEqual(q.Query(data), [
1746       [(constants.RS_NORMAL, "node1.example.com")],
1747       [(constants.RS_NORMAL, "node2.example.net")],
1748       [(constants.RS_NORMAL, "node2.site.example.com")],
1749       ])
1750
1751     q = query.Query(fielddefs, ["name"], namefield="name",
1752                     qfilter=["=~", "name", r"^$"])
1753     self.assertTrue(q.RequestedNames() is None)
1754     self.assertEqual(q.Query(data), [
1755       [(constants.RS_NORMAL, "")],
1756       ])
1757
1758     # Invalid regular expression
1759     self.assertRaises(errors.ParameterError, query.Query, fielddefs, ["name"],
1760                       qfilter=["=~", "name", r"["])
1761
1762   def testFilterLessGreater(self):
1763     fielddefs = query._PrepareFieldList([
1764       (query._MakeField("value", "Value", constants.QFT_NUMBER, "Value"),
1765        None, 0, lambda ctx, item: item),
1766       ], [])
1767
1768     data = range(100)
1769
1770     q = query.Query(fielddefs, ["value"],
1771                     qfilter=["<", "value", 20])
1772     self.assertTrue(q.RequestedNames() is None)
1773     self.assertEqual(q.Query(data),
1774                      [[(constants.RS_NORMAL, i)] for i in range(20)])
1775
1776     q = query.Query(fielddefs, ["value"],
1777                     qfilter=["<=", "value", 30])
1778     self.assertTrue(q.RequestedNames() is None)
1779     self.assertEqual(q.Query(data),
1780                      [[(constants.RS_NORMAL, i)] for i in range(31)])
1781
1782     q = query.Query(fielddefs, ["value"],
1783                     qfilter=[">", "value", 40])
1784     self.assertTrue(q.RequestedNames() is None)
1785     self.assertEqual(q.Query(data),
1786                      [[(constants.RS_NORMAL, i)] for i in range(41, 100)])
1787
1788     q = query.Query(fielddefs, ["value"],
1789                     qfilter=[">=", "value", 50])
1790     self.assertTrue(q.RequestedNames() is None)
1791     self.assertEqual(q.Query(data),
1792                      [[(constants.RS_NORMAL, i)] for i in range(50, 100)])
1793
1794   def testFilterLessGreaterJobId(self):
1795     fielddefs = query._PrepareFieldList([
1796       (query._MakeField("id", "ID", constants.QFT_TEXT, "Job ID"),
1797        None, query.QFF_JOB_ID, lambda ctx, item: item),
1798       ], [])
1799
1800     data = ["1", "2", "3", "10", "102", "120", "125", "15", "100", "7"]
1801
1802     assert data != utils.NiceSort(data), "Test data should not be sorted"
1803
1804     q = query.Query(fielddefs, ["id"], qfilter=["<", "id", "20"])
1805     self.assertTrue(q.RequestedNames() is None)
1806     self.assertEqual(q.Query(data), [
1807       [(constants.RS_NORMAL, "1")],
1808       [(constants.RS_NORMAL, "2")],
1809       [(constants.RS_NORMAL, "3")],
1810       [(constants.RS_NORMAL, "10")],
1811       [(constants.RS_NORMAL, "15")],
1812       [(constants.RS_NORMAL, "7")],
1813       ])
1814
1815     q = query.Query(fielddefs, ["id"], qfilter=[">=", "id", "100"])
1816     self.assertTrue(q.RequestedNames() is None)
1817     self.assertEqual(q.Query(data), [
1818       [(constants.RS_NORMAL, "102")],
1819       [(constants.RS_NORMAL, "120")],
1820       [(constants.RS_NORMAL, "125")],
1821       [(constants.RS_NORMAL, "100")],
1822       ])
1823
1824     # Integers are no valid job IDs
1825     self.assertRaises(errors.ParameterError, query.Query,
1826                       fielddefs, ["id"], qfilter=[">=", "id", 10])
1827
1828   def testFilterLessGreaterSplitTimestamp(self):
1829     fielddefs = query._PrepareFieldList([
1830       (query._MakeField("ts", "Timestamp", constants.QFT_OTHER, "Timestamp"),
1831        None, query.QFF_SPLIT_TIMESTAMP, lambda ctx, item: item),
1832       ], [])
1833
1834     data = [
1835       utils.SplitTime(0),
1836       utils.SplitTime(0.1),
1837       utils.SplitTime(18224.7872),
1838       utils.SplitTime(919896.12623),
1839       utils.SplitTime(999),
1840       utils.SplitTime(989.9999),
1841       ]
1842
1843     for i in [0, [0, 0]]:
1844       q = query.Query(fielddefs, ["ts"], qfilter=["<", "ts", i])
1845       self.assertTrue(q.RequestedNames() is None)
1846       self.assertEqual(q.Query(data), [])
1847
1848     q = query.Query(fielddefs, ["ts"], qfilter=["<", "ts", 1000])
1849     self.assertTrue(q.RequestedNames() is None)
1850     self.assertEqual(q.Query(data), [
1851       [(constants.RS_NORMAL, (0, 0))],
1852       [(constants.RS_NORMAL, (0, 100000))],
1853       [(constants.RS_NORMAL, (999, 0))],
1854       [(constants.RS_NORMAL, (989, 999900))],
1855       ])
1856
1857     q = query.Query(fielddefs, ["ts"], qfilter=[">=", "ts", 5000.3])
1858     self.assertTrue(q.RequestedNames() is None)
1859     self.assertEqual(q.Query(data), [
1860       [(constants.RS_NORMAL, (18224, 787200))],
1861       [(constants.RS_NORMAL, (919896, 126230))],
1862       ])
1863
1864     for i in [18224.7772, utils.SplitTime(18224.7772)]:
1865       q = query.Query(fielddefs, ["ts"], qfilter=[">=", "ts", i])
1866       self.assertTrue(q.RequestedNames() is None)
1867       self.assertEqual(q.Query(data), [
1868         [(constants.RS_NORMAL, (18224, 787200))],
1869         [(constants.RS_NORMAL, (919896, 126230))],
1870         ])
1871
1872     q = query.Query(fielddefs, ["ts"], qfilter=[">", "ts", 18224.7880])
1873     self.assertTrue(q.RequestedNames() is None)
1874     self.assertEqual(q.Query(data), [
1875       [(constants.RS_NORMAL, (919896, 126230))],
1876       ])
1877
1878
1879 if __name__ == "__main__":
1880   testutils.GanetiTestProgram()