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