Statistics
| Branch: | Tag: | Revision:

root / test / py / ganeti.query_unittest.py @ 06fb92cf

History | View | Annotate | Download (73.6 kB)

1
#!/usr/bin/python
2
#
3

    
4
# Copyright (C) 2010, 2011, 2012, 2013 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
      "spfree": 0,
440
      "sptotal": 0,
441
      }
442

    
443
    assert (sorted(query._NODE_LIVE_FIELDS.keys()) ==
444
            sorted(fake_live_data.keys()))
445

    
446
    live_data = dict.fromkeys(node_names, {})
447
    live_data[live_data_name] = \
448
      dict((query._NODE_LIVE_FIELDS[name][2], value)
449
           for name, value in fake_live_data.items())
450

    
451
    node_to_primary = dict((name, set()) for name in node_names)
452
    node_to_primary[master_name].update(["inst1", "inst2"])
453

    
454
    node_to_secondary = dict((name, set()) for name in node_names)
455
    node_to_secondary[live_data_name].update(["instX", "instY", "instZ"])
456

    
457
    ng_uuid = "492b4b74-8670-478a-b98d-4c53a76238e6"
458
    groups = {
459
      ng_uuid: objects.NodeGroup(name="ng1", uuid=ng_uuid, ndparams={}),
460
      }
461

    
462
    oob_not_powered_node = node_names[0]
463
    nodes[0].powered = False
464
    oob_support = dict((name, False) for name in node_names)
465
    oob_support[master_name] = True
466
    oob_support[oob_not_powered_node] = True
467

    
468
    master_node.group = ng_uuid
469

    
470
    nqd = query.NodeQueryData(nodes, live_data, master_name,
471
                              node_to_primary, node_to_secondary, groups,
472
                              oob_support, cluster)
473
    result = q.Query(nqd)
474
    self.assert_(compat.all(len(row) == len(selected) for row in result))
475
    self.assertEqual([row[field_index["name"]] for row in result],
476
                     [(constants.RS_NORMAL, name) for name in node_names])
477

    
478
    node_to_row = dict((row[field_index["name"]][1], idx)
479
                       for idx, row in enumerate(result))
480

    
481
    master_row = result[node_to_row[master_name]]
482
    self.assert_(master_row[field_index["master"]])
483
    self.assert_(master_row[field_index["role"]], "M")
484
    self.assertEqual(master_row[field_index["group"]],
485
                     (constants.RS_NORMAL, "ng1"))
486
    self.assertEqual(master_row[field_index["group.uuid"]],
487
                     (constants.RS_NORMAL, ng_uuid))
488
    self.assertEqual(master_row[field_index["ctime"]],
489
                     (constants.RS_UNAVAIL, None))
490
    self.assertEqual(master_row[field_index["mtime"]],
491
                     (constants.RS_UNAVAIL, None))
492

    
493
    self.assert_(row[field_index["pip"]] == node.primary_ip and
494
                 row[field_index["sip"]] == node.secondary_ip and
495
                 set(row[field_index["tags"]]) == node.GetTags() and
496
                 row[field_index["serial_no"]] == node.serial_no and
497
                 row[field_index["role"]] == query._GetNodeRole(node,
498
                                                                master_name) and
499
                 (node.name == master_name or
500
                  (row[field_index["group"]] == "<unknown>" and
501
                   row[field_index["group.uuid"]] is None and
502
                   row[field_index["ctime"]] == (constants.RS_NORMAL,
503
                                                 node.ctime) and
504
                   row[field_index["mtime"]] == (constants.RS_NORMAL,
505
                                                 node.mtime) and
506
                   row[field_index["powered"]] == (constants.RS_NORMAL,
507
                                                   True))) or
508
                 (node.name == oob_not_powered_node and
509
                  row[field_index["powered"]] == (constants.RS_NORMAL,
510
                                                  False)) or
511
                 row[field_index["powered"]] == (constants.RS_UNAVAIL, None)
512
                 for row, node in zip(result, nodes))
513

    
514
    live_data_row = result[node_to_row[live_data_name]]
515

    
516
    for (field, value) in fake_live_data.items():
517
      self.assertEqual(live_data_row[field_index[field]],
518
                       (constants.RS_NORMAL, value))
519

    
520
    self.assertEqual(master_row[field_index["pinst_cnt"]],
521
                     (constants.RS_NORMAL, 2))
522
    self.assertEqual(live_data_row[field_index["sinst_cnt"]],
523
                     (constants.RS_NORMAL, 3))
524
    self.assertEqual(master_row[field_index["pinst_list"]],
525
                     (constants.RS_NORMAL,
526
                      list(node_to_primary[master_name])))
527
    self.assertEqual(live_data_row[field_index["sinst_list"]],
528
                     (constants.RS_NORMAL,
529
                      utils.NiceSort(list(node_to_secondary[live_data_name]))))
530

    
531
  def testGetLiveNodeField(self):
532
    nodes = [
533
      objects.Node(name="node1", drained=False, offline=False,
534
                   vm_capable=True),
535
      objects.Node(name="node2", drained=True, offline=False,
536
                   vm_capable=True),
537
      objects.Node(name="node3", drained=False, offline=False,
538
                   vm_capable=True),
539
      objects.Node(name="node4", drained=False, offline=True,
540
                   vm_capable=True),
541
      objects.Node(name="node5", drained=False, offline=False,
542
                   vm_capable=False),
543
      ]
544
    live_data = dict.fromkeys([node.name for node in nodes], {})
545

    
546
    # No data
547
    nqd = query.NodeQueryData(None, None, None, None, None, None, None, None)
548
    self.assertEqual(query._GetLiveNodeField("hello", constants.QFT_NUMBER,
549
                                             nqd, nodes[0]),
550
                     query._FS_NODATA)
551

    
552
    # Missing field
553
    ctx = _QueryData(None, curlive_data={
554
      "some": 1,
555
      "other": 2,
556
      })
557
    self.assertEqual(query._GetLiveNodeField("hello", constants.QFT_NUMBER,
558
                                             ctx, nodes[0]),
559
                     query._FS_UNAVAIL)
560

    
561
    # Wrong format/datatype
562
    ctx = _QueryData(None, curlive_data={
563
      "hello": ["Hello World"],
564
      "other": 2,
565
      })
566
    self.assertEqual(query._GetLiveNodeField("hello", constants.QFT_NUMBER,
567
                                             ctx, nodes[0]),
568
                     query._FS_UNAVAIL)
569

    
570
    # Offline node
571
    assert nodes[3].offline
572
    ctx = _QueryData(None, curlive_data={})
573
    self.assertEqual(query._GetLiveNodeField("hello", constants.QFT_NUMBER,
574
                                             ctx, nodes[3]),
575
                     query._FS_OFFLINE, None)
576

    
577
    # Wrong field type
578
    ctx = _QueryData(None, curlive_data={"hello": 123})
579
    self.assertRaises(AssertionError, query._GetLiveNodeField,
580
                      "hello", constants.QFT_BOOL, ctx, nodes[0])
581

    
582
    # Non-vm_capable node
583
    assert not nodes[4].vm_capable
584
    ctx = _QueryData(None, curlive_data={})
585
    self.assertEqual(query._GetLiveNodeField("hello", constants.QFT_NUMBER,
586
                                             ctx, nodes[4]),
587
                     query._FS_UNAVAIL, None)
588

    
589

    
590
class TestInstanceQuery(unittest.TestCase):
591
  def _Create(self, selected):
592
    return query.Query(query.INSTANCE_FIELDS, selected)
593

    
594
  def testSimple(self):
595
    q = self._Create(["name", "be/maxmem", "ip"])
596
    self.assertEqual(q.RequestedData(), set([query.IQ_CONFIG]))
597

    
598
    cluster = objects.Cluster(cluster_name="testcluster",
599
      hvparams=constants.HVC_DEFAULTS,
600
      beparams={
601
        constants.PP_DEFAULT: constants.BEC_DEFAULTS,
602
        },
603
      nicparams={
604
        constants.PP_DEFAULT: constants.NICC_DEFAULTS,
605
        },
606
      os_hvp={},
607
      osparams={})
608

    
609
    instances = [
610
      objects.Instance(name="inst1", hvparams={}, beparams={}, osparams={},
611
                       nics=[], os="deb1"),
612
      objects.Instance(name="inst2", hvparams={}, nics=[], osparams={},
613
        os="foomoo",
614
        beparams={
615
          constants.BE_MAXMEM: 512,
616
        }),
617
      objects.Instance(name="inst3", hvparams={}, beparams={}, osparams={},
618
        os="dos", nics=[objects.NIC(ip="192.0.2.99", nicparams={})]),
619
      ]
620

    
621
    iqd = query.InstanceQueryData(instances, cluster, None, [], [], {},
622
                                  set(), {}, None, None, None)
623
    self.assertEqual(q.Query(iqd),
624
      [[(constants.RS_NORMAL, "inst1"),
625
        (constants.RS_NORMAL, 128),
626
        (constants.RS_UNAVAIL, None),
627
       ],
628
       [(constants.RS_NORMAL, "inst2"),
629
        (constants.RS_NORMAL, 512),
630
        (constants.RS_UNAVAIL, None),
631
       ],
632
       [(constants.RS_NORMAL, "inst3"),
633
        (constants.RS_NORMAL, 128),
634
        (constants.RS_NORMAL, "192.0.2.99"),
635
       ]])
636
    self.assertEqual(q.OldStyleQuery(iqd),
637
      [["inst1", 128, None],
638
       ["inst2", 512, None],
639
       ["inst3", 128, "192.0.2.99"]])
640

    
641
  def test(self):
642
    selected = query.INSTANCE_FIELDS.keys()
643
    fieldidx = dict((field, idx) for idx, field in enumerate(selected))
644

    
645
    macs = ["00:11:22:%02x:%02x:%02x" % (i % 255, i % 3, (i * 123) % 255)
646
            for i in range(20)]
647

    
648
    q = self._Create(selected)
649
    self.assertEqual(q.RequestedData(),
650
                     set([query.IQ_CONFIG, query.IQ_LIVE, query.IQ_DISKUSAGE,
651
                          query.IQ_CONSOLE, query.IQ_NODES, query.IQ_NETWORKS]))
652

    
653
    cluster = objects.Cluster(cluster_name="testcluster",
654
      hvparams=constants.HVC_DEFAULTS,
655
      beparams={
656
        constants.PP_DEFAULT: constants.BEC_DEFAULTS,
657
        },
658
      nicparams={
659
        constants.PP_DEFAULT: constants.NICC_DEFAULTS,
660
        },
661
      os_hvp={},
662
      tcpudp_port_pool=set(),
663
      osparams={
664
        "deb99": {
665
          "clean_install": "yes",
666
          },
667
        })
668

    
669
    offline_nodes = ["nodeoff1", "nodeoff2"]
670
    bad_nodes = ["nodebad1", "nodebad2", "nodebad3"] + offline_nodes
671
    nodes = ["node%s" % i for i in range(10)] + bad_nodes
672

    
673
    instances = [
674
      objects.Instance(name="inst1", hvparams={}, beparams={}, nics=[],
675
        uuid="f90eccb3-e227-4e3c-bf2a-94a21ca8f9cd",
676
        ctime=1291244000, mtime=1291244400, serial_no=30,
677
        admin_state=constants.ADMINST_UP, hypervisor=constants.HT_XEN_PVM,
678
        os="linux1",
679
        primary_node="node1",
680
        disk_template=constants.DT_PLAIN,
681
        disks=[],
682
        disks_active=True,
683
        osparams={}),
684
      objects.Instance(name="inst2", hvparams={}, nics=[],
685
        uuid="73a0f8a7-068c-4630-ada2-c3440015ab1a",
686
        ctime=1291211000, mtime=1291211077, serial_no=1,
687
        admin_state=constants.ADMINST_UP, hypervisor=constants.HT_XEN_HVM,
688
        os="deb99",
689
        primary_node="node5",
690
        disk_template=constants.DT_DISKLESS,
691
        disks=[],
692
        disks_active=True,
693
        beparams={
694
          constants.BE_MAXMEM: 512,
695
          constants.BE_MINMEM: 256,
696
        },
697
        osparams={}),
698
      objects.Instance(name="inst3", hvparams={}, beparams={},
699
        uuid="11ec8dff-fb61-4850-bfe0-baa1803ff280",
700
        ctime=1291011000, mtime=1291013000, serial_no=1923,
701
        admin_state=constants.ADMINST_DOWN, hypervisor=constants.HT_KVM,
702
        os="busybox",
703
        primary_node="node6",
704
        disk_template=constants.DT_DRBD8,
705
        disks=[],
706
        disks_active=False,
707
        nics=[
708
          objects.NIC(ip="192.0.2.99", mac=macs.pop(),
709
                      nicparams={
710
                        constants.NIC_LINK: constants.DEFAULT_BRIDGE,
711
                        }),
712
          objects.NIC(ip=None, mac=macs.pop(), nicparams={}),
713
          ],
714
        osparams={}),
715
      objects.Instance(name="inst4", hvparams={}, beparams={},
716
        uuid="68dab168-3ef5-4c9d-b4d3-801e0672068c",
717
        ctime=1291244390, mtime=1291244395, serial_no=25,
718
        admin_state=constants.ADMINST_DOWN, hypervisor=constants.HT_XEN_PVM,
719
        os="linux1",
720
        primary_node="nodeoff2",
721
        disk_template=constants.DT_DRBD8,
722
        disks=[],
723
        disks_active=True,
724
        nics=[
725
          objects.NIC(ip="192.0.2.1", mac=macs.pop(),
726
                      nicparams={
727
                        constants.NIC_LINK: constants.DEFAULT_BRIDGE,
728
                        }),
729
          objects.NIC(ip="192.0.2.2", mac=macs.pop(), nicparams={}),
730
          objects.NIC(ip="192.0.2.3", mac=macs.pop(),
731
                      nicparams={
732
                        constants.NIC_MODE: constants.NIC_MODE_ROUTED,
733
                        }),
734
          objects.NIC(ip="192.0.2.4", mac=macs.pop(),
735
                      nicparams={
736
                        constants.NIC_MODE: constants.NIC_MODE_BRIDGED,
737
                        constants.NIC_LINK: "eth123",
738
                        }),
739
          ],
740
        osparams={}),
741
      objects.Instance(name="inst5", hvparams={}, nics=[],
742
        uuid="0e3dca12-5b42-4e24-98a2-415267545bd0",
743
        ctime=1231211000, mtime=1261200000, serial_no=3,
744
        admin_state=constants.ADMINST_UP, hypervisor=constants.HT_XEN_HVM,
745
        os="deb99",
746
        primary_node="nodebad2",
747
        disk_template=constants.DT_DISKLESS,
748
        disks=[],
749
        disks_active=True,
750
        beparams={
751
          constants.BE_MAXMEM: 512,
752
          constants.BE_MINMEM: 512,
753
        },
754
        osparams={}),
755
      objects.Instance(name="inst6", hvparams={}, nics=[],
756
        uuid="72de6580-c8d5-4661-b902-38b5785bb8b3",
757
        ctime=7513, mtime=11501, serial_no=13390,
758
        admin_state=constants.ADMINST_DOWN, hypervisor=constants.HT_XEN_HVM,
759
        os="deb99",
760
        primary_node="node7",
761
        disk_template=constants.DT_DISKLESS,
762
        disks=[],
763
        disks_active=False,
764
        beparams={
765
          constants.BE_MAXMEM: 768,
766
          constants.BE_MINMEM: 256,
767
        },
768
        osparams={
769
          "clean_install": "no",
770
          }),
771
      objects.Instance(name="inst7", hvparams={}, nics=[],
772
        uuid="ceec5dc4-b729-4f42-ae28-69b3cd24920e",
773
        ctime=None, mtime=None, serial_no=1947,
774
        admin_state=constants.ADMINST_DOWN, hypervisor=constants.HT_XEN_HVM,
775
        os="deb99",
776
        primary_node="node6",
777
        disk_template=constants.DT_DISKLESS,
778
        disks=[],
779
        disks_active=False,
780
        beparams={},
781
        osparams={}),
782
      objects.Instance(name="inst8", hvparams={}, nics=[],
783
        uuid="ceec5dc4-b729-4f42-ae28-69b3cd24920f",
784
        ctime=None, mtime=None, serial_no=19478,
785
        admin_state=constants.ADMINST_OFFLINE, hypervisor=constants.HT_XEN_HVM,
786
        os="deb99",
787
        primary_node="node6",
788
        disk_template=constants.DT_DISKLESS,
789
        disks=[],
790
        disks_active=False,
791
        beparams={},
792
        osparams={}),
793
      ]
794

    
795
    assert not utils.FindDuplicates(inst.name for inst in instances)
796

    
797
    instbyname = dict((inst.name, inst) for inst in instances)
798

    
799
    disk_usage = dict((inst.name,
800
                       gmi.ComputeDiskSize(inst.disk_template,
801
                                           [{"size": disk.size}
802
                                           for disk in inst.disks]))
803
                      for inst in instances)
804

    
805
    inst_bridges = {
806
      "inst3": [constants.DEFAULT_BRIDGE, constants.DEFAULT_BRIDGE],
807
      "inst4": [constants.DEFAULT_BRIDGE, constants.DEFAULT_BRIDGE,
808
                None, "eth123"],
809
      }
810

    
811
    live_data = {
812
      "inst2": {
813
        "vcpus": 3,
814
        },
815
      "inst4": {
816
        "memory": 123,
817
        },
818
      "inst6": {
819
        "memory": 768,
820
        },
821
      "inst7": {
822
        "vcpus": 3,
823
        },
824
      }
825
    wrongnode_inst = set(["inst7"])
826

    
827
    consinfo = dict((inst.name, None) for inst in instances)
828
    consinfo["inst7"] = \
829
      objects.InstanceConsole(instance="inst7", kind=constants.CONS_SSH,
830
                              host=instbyname["inst7"].primary_node,
831
                              user="root",
832
                              command=["hostname"]).ToDict()
833

    
834
    iqd = query.InstanceQueryData(instances, cluster, disk_usage,
835
                                  offline_nodes, bad_nodes, live_data,
836
                                  wrongnode_inst, consinfo, {}, {}, {})
837
    result = q.Query(iqd)
838
    self.assertEqual(len(result), len(instances))
839
    self.assert_(compat.all(len(row) == len(selected)
840
                            for row in result))
841

    
842
    assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
843
           "Offline nodes not included in bad nodes"
844

    
845
    tested_status = set()
846

    
847
    for (inst, row) in zip(instances, result):
848
      assert inst.primary_node in nodes
849

    
850
      self.assertEqual(row[fieldidx["name"]],
851
                       (constants.RS_NORMAL, inst.name))
852

    
853
      if inst.primary_node in offline_nodes:
854
        exp_status = constants.INSTST_NODEOFFLINE
855
      elif inst.primary_node in bad_nodes:
856
        exp_status = constants.INSTST_NODEDOWN
857
      elif inst.name in live_data:
858
        if inst.name in wrongnode_inst:
859
          exp_status = constants.INSTST_WRONGNODE
860
        elif inst.admin_state == constants.ADMINST_UP:
861
          exp_status = constants.INSTST_RUNNING
862
        else:
863
          exp_status = constants.INSTST_ERRORUP
864
      elif inst.admin_state == constants.ADMINST_UP:
865
        exp_status = constants.INSTST_ERRORDOWN
866
      elif inst.admin_state == constants.ADMINST_DOWN:
867
        exp_status = constants.INSTST_ADMINDOWN
868
      else:
869
        exp_status = constants.INSTST_ADMINOFFLINE
870

    
871
      self.assertEqual(row[fieldidx["status"]],
872
                       (constants.RS_NORMAL, exp_status))
873

    
874
      (_, status) = row[fieldidx["status"]]
875
      tested_status.add(status)
876

    
877
      #FIXME(dynmem): check oper_ram vs min/max mem
878
      for (field, livefield) in [("oper_vcpus", "vcpus")]:
879
        if inst.primary_node in bad_nodes:
880
          exp = (constants.RS_NODATA, None)
881
        elif inst.name in live_data:
882
          value = live_data[inst.name].get(livefield, None)
883
          if value is None:
884
            exp = (constants.RS_UNAVAIL, None)
885
          else:
886
            exp = (constants.RS_NORMAL, value)
887
        else:
888
          exp = (constants.RS_UNAVAIL, None)
889

    
890
        self.assertEqual(row[fieldidx[field]], exp)
891

    
892
      bridges = inst_bridges.get(inst.name, [])
893
      self.assertEqual(row[fieldidx["nic.bridges"]],
894
                       (constants.RS_NORMAL, bridges))
895
      if bridges:
896
        self.assertEqual(row[fieldidx["bridge"]],
897
                         (constants.RS_NORMAL, bridges[0]))
898
      else:
899
        self.assertEqual(row[fieldidx["bridge"]],
900
                         (constants.RS_UNAVAIL, None))
901

    
902
      for i in range(constants.MAX_NICS):
903
        if i < len(bridges) and bridges[i] is not None:
904
          exp = (constants.RS_NORMAL, bridges[i])
905
        else:
906
          exp = (constants.RS_UNAVAIL, None)
907
        self.assertEqual(row[fieldidx["nic.bridge/%s" % i]], exp)
908

    
909
      if inst.primary_node in bad_nodes:
910
        exp = (constants.RS_NODATA, None)
911
      else:
912
        exp = (constants.RS_NORMAL, inst.name in live_data)
913
      self.assertEqual(row[fieldidx["oper_state"]], exp)
914

    
915
      cust_exp = (constants.RS_NORMAL, {})
916
      if inst.os == "deb99":
917
        if inst.name == "inst6":
918
          exp = (constants.RS_NORMAL, {"clean_install": "no"})
919
          cust_exp = exp
920
        else:
921
          exp = (constants.RS_NORMAL, {"clean_install": "yes"})
922
      else:
923
        exp = (constants.RS_NORMAL, {})
924
      self.assertEqual(row[fieldidx["osparams"]], exp)
925
      self.assertEqual(row[fieldidx["custom_osparams"]], cust_exp)
926

    
927
      usage = disk_usage[inst.name]
928
      if usage is None:
929
        usage = 0
930
      self.assertEqual(row[fieldidx["disk_usage"]],
931
                       (constants.RS_NORMAL, usage))
932

    
933
      for alias, target in [("sda_size", "disk.size/0"),
934
                            ("sdb_size", "disk.size/1"),
935
                            ("vcpus", "be/vcpus"),
936
                            ("ip", "nic.ip/0"),
937
                            ("mac", "nic.mac/0"),
938
                            ("bridge", "nic.bridge/0"),
939
                            ("nic_mode", "nic.mode/0"),
940
                            ("nic_link", "nic.link/0"),
941
                            ]:
942
        self.assertEqual(row[fieldidx[alias]], row[fieldidx[target]])
943

    
944
      for field in ["ctime", "mtime"]:
945
        if getattr(inst, field) is None:
946
          # No ctime/mtime
947
          exp = (constants.RS_UNAVAIL, None)
948
        else:
949
          exp = (constants.RS_NORMAL, getattr(inst, field))
950
        self.assertEqual(row[fieldidx[field]], exp)
951

    
952
      self._CheckInstanceConsole(inst, row[fieldidx["console"]])
953

    
954
    # Ensure all possible status' have been tested
955
    self.assertEqual(tested_status, constants.INSTST_ALL)
956

    
957
  def _CheckInstanceConsole(self, instance, (status, consdata)):
958
    if instance.name == "inst7":
959
      self.assertEqual(status, constants.RS_NORMAL)
960
      console = objects.InstanceConsole.FromDict(consdata)
961
      self.assertTrue(console.Validate())
962
      self.assertEqual(console.host, instance.primary_node)
963
    else:
964
      self.assertEqual(status, constants.RS_UNAVAIL)
965

    
966

    
967
class TestGroupQuery(unittest.TestCase):
968

    
969
  def setUp(self):
970
    self.custom_diskparams = {
971
      constants.DT_DRBD8: {
972
        constants.DRBD_DEFAULT_METAVG: "foobar",
973
      },
974
    }
975

    
976
    self.groups = [
977
      objects.NodeGroup(name="default",
978
                        uuid="c0e89160-18e7-11e0-a46e-001d0904baeb",
979
                        alloc_policy=constants.ALLOC_POLICY_PREFERRED,
980
                        ipolicy=objects.MakeEmptyIPolicy(),
981
                        ndparams={},
982
                        diskparams={},
983
                        ),
984
      objects.NodeGroup(name="restricted",
985
                        uuid="d2a40a74-18e7-11e0-9143-001d0904baeb",
986
                        alloc_policy=constants.ALLOC_POLICY_LAST_RESORT,
987
                        ipolicy=objects.MakeEmptyIPolicy(),
988
                        ndparams={},
989
                        diskparams=self.custom_diskparams,
990
                        ),
991
      ]
992
    self.cluster = objects.Cluster(cluster_name="testcluster",
993
      hvparams=constants.HVC_DEFAULTS,
994
      beparams={
995
        constants.PP_DEFAULT: constants.BEC_DEFAULTS,
996
        },
997
      nicparams={
998
        constants.PP_DEFAULT: constants.NICC_DEFAULTS,
999
        },
1000
      ndparams=constants.NDC_DEFAULTS,
1001
      ipolicy=constants.IPOLICY_DEFAULTS,
1002
      diskparams=constants.DISK_DT_DEFAULTS,
1003
      )
1004

    
1005
  def _Create(self, selected):
1006
    return query.Query(query.GROUP_FIELDS, selected)
1007

    
1008
  def testSimple(self):
1009
    q = self._Create(["name", "uuid", "alloc_policy"])
1010
    gqd = query.GroupQueryData(self.cluster, self.groups, None, None, False)
1011

    
1012
    self.assertEqual(q.RequestedData(), set([query.GQ_CONFIG]))
1013

    
1014
    self.assertEqual(q.Query(gqd),
1015
      [[(constants.RS_NORMAL, "default"),
1016
        (constants.RS_NORMAL, "c0e89160-18e7-11e0-a46e-001d0904baeb"),
1017
        (constants.RS_NORMAL, constants.ALLOC_POLICY_PREFERRED)
1018
        ],
1019
       [(constants.RS_NORMAL, "restricted"),
1020
        (constants.RS_NORMAL, "d2a40a74-18e7-11e0-9143-001d0904baeb"),
1021
        (constants.RS_NORMAL, constants.ALLOC_POLICY_LAST_RESORT)
1022
        ],
1023
       ])
1024

    
1025
  def testNodes(self):
1026
    groups_to_nodes = {
1027
      "c0e89160-18e7-11e0-a46e-001d0904baeb": ["node1", "node2"],
1028
      "d2a40a74-18e7-11e0-9143-001d0904baeb": ["node1", "node10", "node9"],
1029
      }
1030

    
1031
    q = self._Create(["name", "node_cnt", "node_list"])
1032
    gqd = query.GroupQueryData(self.cluster, self.groups, groups_to_nodes, None,
1033
                               False)
1034

    
1035
    self.assertEqual(q.RequestedData(), set([query.GQ_CONFIG, query.GQ_NODE]))
1036

    
1037
    self.assertEqual(q.Query(gqd),
1038
                     [[(constants.RS_NORMAL, "default"),
1039
                       (constants.RS_NORMAL, 2),
1040
                       (constants.RS_NORMAL, ["node1", "node2"]),
1041
                       ],
1042
                      [(constants.RS_NORMAL, "restricted"),
1043
                       (constants.RS_NORMAL, 3),
1044
                       (constants.RS_NORMAL, ["node1", "node9", "node10"]),
1045
                       ],
1046
                      ])
1047

    
1048
  def testInstances(self):
1049
    groups_to_instances = {
1050
      "c0e89160-18e7-11e0-a46e-001d0904baeb": ["inst1", "inst2"],
1051
      "d2a40a74-18e7-11e0-9143-001d0904baeb": ["inst1", "inst10", "inst9"],
1052
      }
1053

    
1054
    q = self._Create(["pinst_cnt", "pinst_list"])
1055
    gqd = query.GroupQueryData(self.cluster, self.groups, None,
1056
      groups_to_instances, False)
1057

    
1058
    self.assertEqual(q.RequestedData(), set([query.GQ_INST]))
1059

    
1060
    self.assertEqual(q.Query(gqd),
1061
                     [[(constants.RS_NORMAL, 2),
1062
                       (constants.RS_NORMAL, ["inst1", "inst2"]),
1063
                       ],
1064
                      [(constants.RS_NORMAL, 3),
1065
                       (constants.RS_NORMAL, ["inst1", "inst9", "inst10"]),
1066
                       ],
1067
                      ])
1068

    
1069
  def testDiskparams(self):
1070
    q = self._Create(["name", "uuid", "diskparams", "custom_diskparams"])
1071
    gqd = query.GroupQueryData(self.cluster, self.groups, None, None, True)
1072

    
1073
    self.assertEqual(q.RequestedData(),
1074
                     set([query.GQ_CONFIG, query.GQ_DISKPARAMS]))
1075

    
1076
    self.assertEqual(q.Query(gqd),
1077
      [[(constants.RS_NORMAL, "default"),
1078
        (constants.RS_NORMAL, "c0e89160-18e7-11e0-a46e-001d0904baeb"),
1079
        (constants.RS_NORMAL, constants.DISK_DT_DEFAULTS),
1080
        (constants.RS_NORMAL, {}),
1081
        ],
1082
       [(constants.RS_NORMAL, "restricted"),
1083
        (constants.RS_NORMAL, "d2a40a74-18e7-11e0-9143-001d0904baeb"),
1084
        (constants.RS_NORMAL, objects.FillDiskParams(constants.DISK_DT_DEFAULTS,
1085
                                                     self.custom_diskparams)),
1086
        (constants.RS_NORMAL, self.custom_diskparams),
1087
        ],
1088
       ])
1089

    
1090

    
1091
class TestOsQuery(unittest.TestCase):
1092
  def _Create(self, selected):
1093
    return query.Query(query.OS_FIELDS, selected)
1094

    
1095
  def test(self):
1096
    variants = ["v00", "plain", "v3", "var0", "v33", "v20"]
1097
    api_versions = [10, 0, 15, 5]
1098
    parameters = ["zpar3", "apar9"]
1099

    
1100
    assert variants != sorted(variants) and variants != utils.NiceSort(variants)
1101
    assert (api_versions != sorted(api_versions) and
1102
            api_versions != utils.NiceSort(variants))
1103
    assert (parameters != sorted(parameters) and
1104
            parameters != utils.NiceSort(parameters))
1105

    
1106
    data = [
1107
      query.OsInfo(name="debian", valid=False, hidden=False, blacklisted=False,
1108
                   variants=set(), api_versions=set(), parameters=set(),
1109
                   node_status={ "some": "status", }),
1110
      query.OsInfo(name="dos", valid=True, hidden=False, blacklisted=True,
1111
                   variants=set(variants),
1112
                   api_versions=set(api_versions),
1113
                   parameters=set(parameters),
1114
                   node_status={ "some": "other", "status": None, }),
1115
      ]
1116

    
1117

    
1118
    q = self._Create(["name", "valid", "hidden", "blacklisted", "variants",
1119
                      "api_versions", "parameters", "node_status"])
1120
    self.assertEqual(q.RequestedData(), set([]))
1121
    self.assertEqual(q.Query(data),
1122
                     [[(constants.RS_NORMAL, "debian"),
1123
                       (constants.RS_NORMAL, False),
1124
                       (constants.RS_NORMAL, False),
1125
                       (constants.RS_NORMAL, False),
1126
                       (constants.RS_NORMAL, []),
1127
                       (constants.RS_NORMAL, []),
1128
                       (constants.RS_NORMAL, []),
1129
                       (constants.RS_NORMAL, {"some": "status"})],
1130
                      [(constants.RS_NORMAL, "dos"),
1131
                       (constants.RS_NORMAL, True),
1132
                       (constants.RS_NORMAL, False),
1133
                       (constants.RS_NORMAL, True),
1134
                       (constants.RS_NORMAL,
1135
                        ["plain", "v00", "v3", "v20", "v33", "var0"]),
1136
                       (constants.RS_NORMAL, [0, 5, 10, 15]),
1137
                       (constants.RS_NORMAL, ["apar9", "zpar3"]),
1138
                       (constants.RS_NORMAL,
1139
                        { "some": "other", "status": None, })
1140
                       ]])
1141

    
1142

    
1143
class TestQueryFields(unittest.TestCase):
1144
  def testAllFields(self):
1145
    for fielddefs in query.ALL_FIELD_LISTS:
1146
      result = query.QueryFields(fielddefs, None)
1147
      self.assert_(isinstance(result, dict))
1148
      response = objects.QueryFieldsResponse.FromDict(result)
1149
      self.assertEqual([(fdef.name, fdef.title) for fdef in response.fields],
1150
        [(fdef2.name, fdef2.title)
1151
         for (fdef2, _, _, _) in utils.NiceSort(fielddefs.values(),
1152
                                                key=lambda x: x[0].name)])
1153

    
1154
  def testSomeFields(self):
1155
    rnd = random.Random(5357)
1156

    
1157
    for _ in range(10):
1158
      for fielddefs in query.ALL_FIELD_LISTS:
1159
        if len(fielddefs) > 20:
1160
          sample_size = rnd.randint(5, 20)
1161
        else:
1162
          sample_size = rnd.randint(1, max(1, len(fielddefs) - 1))
1163
        fields = [fdef for (fdef, _, _, _) in rnd.sample(fielddefs.values(),
1164
                                                         sample_size)]
1165
        result = query.QueryFields(fielddefs, [fdef.name for fdef in fields])
1166
        self.assert_(isinstance(result, dict))
1167
        response = objects.QueryFieldsResponse.FromDict(result)
1168
        self.assertEqual([(fdef.name, fdef.title) for fdef in response.fields],
1169
                         [(fdef2.name, fdef2.title) for fdef2 in fields])
1170

    
1171

    
1172
class TestQueryFilter(unittest.TestCase):
1173
  def testRequestedNames(self):
1174
    for (what, fielddefs) in query.ALL_FIELDS.items():
1175
      if what == constants.QR_JOB:
1176
        namefield = "id"
1177
        nameval = 123
1178
        namevalempty = 0
1179
        genval = lambda i: i * 10
1180
        randvals = [17361, 22015, 13193, 15215]
1181
      else:
1182
        nameval = "abc"
1183
        namevalempty = ""
1184
        genval = lambda i: "x%s" % i
1185
        randvals = ["x17361", "x22015", "x13193", "x15215"]
1186
        if what == constants.QR_EXPORT:
1187
          namefield = "export"
1188
        else:
1189
          namefield = "name"
1190

    
1191
      assert namefield in fielddefs
1192

    
1193
      reqnames = [genval(i) for i in range(4)]
1194
      innerfilter = [["=", namefield, v] for v in reqnames]
1195

    
1196
      # No name field
1197
      q = query.Query(fielddefs, [namefield],
1198
                      qfilter=["=", namefield, nameval], namefield=None)
1199
      self.assertEqual(q.RequestedNames(), None)
1200

    
1201
      # No filter
1202
      q = query.Query(fielddefs, [namefield], qfilter=None, namefield=namefield)
1203
      self.assertEqual(q.RequestedNames(), None)
1204

    
1205
      # Check empty query
1206
      q = query.Query(fielddefs, [namefield], qfilter=["|"],
1207
                      namefield=namefield)
1208
      self.assertEqual(q.RequestedNames(), None)
1209

    
1210
      # Check order
1211
      q = query.Query(fielddefs, [namefield], qfilter=["|"] + innerfilter,
1212
                      namefield=namefield)
1213
      self.assertEqual(q.RequestedNames(), reqnames)
1214

    
1215
      # Check reverse order
1216
      q = query.Query(fielddefs, [namefield],
1217
                      qfilter=["|"] + list(reversed(innerfilter)),
1218
                      namefield=namefield)
1219
      self.assertEqual(q.RequestedNames(), list(reversed(reqnames)))
1220

    
1221
      # Duplicates
1222
      q = query.Query(fielddefs, [namefield],
1223
                      qfilter=["|"] + innerfilter + list(reversed(innerfilter)),
1224
                      namefield=namefield)
1225
      self.assertEqual(q.RequestedNames(), reqnames)
1226

    
1227
      # Unknown name field
1228
      self.assertRaises(AssertionError, query.Query, fielddefs, [namefield],
1229
                        namefield="_unknown_field_")
1230

    
1231
      # Filter with AND
1232
      q = query.Query(fielddefs, [namefield],
1233
                      qfilter=["|", ["=", namefield, nameval],
1234
                                    ["&", ["=", namefield, namevalempty]]],
1235
                      namefield=namefield)
1236
      self.assertTrue(q.RequestedNames() is None)
1237

    
1238
      # Filter with NOT
1239
      q = query.Query(fielddefs, [namefield],
1240
                      qfilter=["|", ["=", namefield, nameval],
1241
                                    ["!", ["=", namefield, namevalempty]]],
1242
                      namefield=namefield)
1243
      self.assertTrue(q.RequestedNames() is None)
1244

    
1245
      # Filter with only OR (names must be in correct order)
1246
      q = query.Query(fielddefs, [namefield],
1247
                      qfilter=["|", ["=", namefield, randvals[0]],
1248
                                    ["|", ["=", namefield, randvals[1]]],
1249
                                    ["|", ["|", ["=", namefield, randvals[2]]]],
1250
                                    ["=", namefield, randvals[3]]],
1251
                      namefield=namefield)
1252
      self.assertEqual(q.RequestedNames(), randvals)
1253

    
1254
  @staticmethod
1255
  def _GenNestedFilter(namefield, op, depth, nameval):
1256
    nested = ["=", namefield, nameval]
1257
    for i in range(depth):
1258
      nested = [op, nested]
1259
    return nested
1260

    
1261
  def testCompileFilter(self):
1262
    levels_max = query._FilterCompilerHelper._LEVELS_MAX
1263

    
1264
    for (what, fielddefs) in query.ALL_FIELDS.items():
1265
      if what == constants.QR_JOB:
1266
        namefield = "id"
1267
        nameval = 123
1268
      elif what == constants.QR_EXPORT:
1269
        namefield = "export"
1270
        nameval = "value"
1271
      else:
1272
        namefield = "name"
1273
        nameval = "value"
1274

    
1275
      checks = [
1276
        [], ["="], ["=", "foo"], ["unknownop"], ["!"],
1277
        ["=", "_unknown_field", "value"],
1278
        self._GenNestedFilter(namefield, "|", levels_max, nameval),
1279
        self._GenNestedFilter(namefield, "|", levels_max * 3, nameval),
1280
        self._GenNestedFilter(namefield, "!", levels_max, nameval),
1281
        ]
1282

    
1283
      for qfilter in checks:
1284
        self.assertRaises(errors.ParameterError, query._CompileFilter,
1285
                          fielddefs, None, qfilter)
1286

    
1287
      for op in ["|", "!"]:
1288
        qfilter = self._GenNestedFilter(namefield, op, levels_max - 1, nameval)
1289
        self.assertTrue(callable(query._CompileFilter(fielddefs, None,
1290
                                                      qfilter)))
1291

    
1292
  def testQueryInputOrder(self):
1293
    fielddefs = query._PrepareFieldList([
1294
      (query._MakeField("pnode", "PNode", constants.QFT_TEXT, "Primary"),
1295
       None, 0, lambda ctx, item: item["pnode"]),
1296
      (query._MakeField("snode", "SNode", constants.QFT_TEXT, "Secondary"),
1297
       None, 0, lambda ctx, item: item["snode"]),
1298
      ], [])
1299

    
1300
    data = [
1301
      { "pnode": "node1", "snode": "node44", },
1302
      { "pnode": "node30", "snode": "node90", },
1303
      { "pnode": "node25", "snode": "node1", },
1304
      { "pnode": "node20", "snode": "node1", },
1305
      ]
1306

    
1307
    qfilter = ["|", ["=", "pnode", "node1"], ["=", "snode", "node1"]]
1308

    
1309
    q = query.Query(fielddefs, ["pnode", "snode"], namefield="pnode",
1310
                    qfilter=qfilter)
1311
    self.assertTrue(q.RequestedNames() is None)
1312
    self.assertFalse(q.RequestedData())
1313
    self.assertEqual(q.Query(data),
1314
      [[(constants.RS_NORMAL, "node1"), (constants.RS_NORMAL, "node44")],
1315
       [(constants.RS_NORMAL, "node20"), (constants.RS_NORMAL, "node1")],
1316
       [(constants.RS_NORMAL, "node25"), (constants.RS_NORMAL, "node1")]])
1317

    
1318
    # Try again with reversed input data
1319
    self.assertEqual(q.Query(reversed(data)),
1320
      [[(constants.RS_NORMAL, "node1"), (constants.RS_NORMAL, "node44")],
1321
       [(constants.RS_NORMAL, "node20"), (constants.RS_NORMAL, "node1")],
1322
       [(constants.RS_NORMAL, "node25"), (constants.RS_NORMAL, "node1")]])
1323

    
1324
    # No name field, result must be in incoming order
1325
    q = query.Query(fielddefs, ["pnode", "snode"], namefield=None,
1326
                    qfilter=qfilter)
1327
    self.assertFalse(q.RequestedData())
1328
    self.assertEqual(q.Query(data),
1329
      [[(constants.RS_NORMAL, "node1"), (constants.RS_NORMAL, "node44")],
1330
       [(constants.RS_NORMAL, "node25"), (constants.RS_NORMAL, "node1")],
1331
       [(constants.RS_NORMAL, "node20"), (constants.RS_NORMAL, "node1")]])
1332
    self.assertEqual(q.OldStyleQuery(data), [
1333
      ["node1", "node44"],
1334
      ["node25", "node1"],
1335
      ["node20", "node1"],
1336
      ])
1337
    self.assertEqual(q.Query(reversed(data)),
1338
      [[(constants.RS_NORMAL, "node20"), (constants.RS_NORMAL, "node1")],
1339
       [(constants.RS_NORMAL, "node25"), (constants.RS_NORMAL, "node1")],
1340
       [(constants.RS_NORMAL, "node1"), (constants.RS_NORMAL, "node44")]])
1341
    self.assertEqual(q.OldStyleQuery(reversed(data)), [
1342
      ["node20", "node1"],
1343
      ["node25", "node1"],
1344
      ["node1", "node44"],
1345
      ])
1346

    
1347
    # Name field, but no sorting, result must be in incoming order
1348
    q = query.Query(fielddefs, ["pnode", "snode"], namefield="pnode")
1349
    self.assertFalse(q.RequestedData())
1350
    self.assertEqual(q.Query(data, sort_by_name=False),
1351
      [[(constants.RS_NORMAL, "node1"), (constants.RS_NORMAL, "node44")],
1352
       [(constants.RS_NORMAL, "node30"), (constants.RS_NORMAL, "node90")],
1353
       [(constants.RS_NORMAL, "node25"), (constants.RS_NORMAL, "node1")],
1354
       [(constants.RS_NORMAL, "node20"), (constants.RS_NORMAL, "node1")]])
1355
    self.assertEqual(q.OldStyleQuery(data, sort_by_name=False), [
1356
      ["node1", "node44"],
1357
      ["node30", "node90"],
1358
      ["node25", "node1"],
1359
      ["node20", "node1"],
1360
      ])
1361
    self.assertEqual(q.Query(reversed(data), sort_by_name=False),
1362
      [[(constants.RS_NORMAL, "node20"), (constants.RS_NORMAL, "node1")],
1363
       [(constants.RS_NORMAL, "node25"), (constants.RS_NORMAL, "node1")],
1364
       [(constants.RS_NORMAL, "node30"), (constants.RS_NORMAL, "node90")],
1365
       [(constants.RS_NORMAL, "node1"), (constants.RS_NORMAL, "node44")]])
1366
    self.assertEqual(q.OldStyleQuery(reversed(data), sort_by_name=False), [
1367
      ["node20", "node1"],
1368
      ["node25", "node1"],
1369
      ["node30", "node90"],
1370
      ["node1", "node44"],
1371
      ])
1372

    
1373
  def testEqualNamesOrder(self):
1374
    fielddefs = query._PrepareFieldList([
1375
      (query._MakeField("pnode", "PNode", constants.QFT_TEXT, "Primary"),
1376
       None, 0, lambda ctx, item: item["pnode"]),
1377
      (query._MakeField("num", "Num", constants.QFT_NUMBER, "Num"),
1378
       None, 0, lambda ctx, item: item["num"]),
1379
      ], [])
1380

    
1381
    data = [
1382
      { "pnode": "node1", "num": 100, },
1383
      { "pnode": "node1", "num": 25, },
1384
      { "pnode": "node2", "num": 90, },
1385
      { "pnode": "node2", "num": 30, },
1386
      ]
1387

    
1388
    q = query.Query(fielddefs, ["pnode", "num"], namefield="pnode",
1389
                    qfilter=["|", ["=", "pnode", "node1"],
1390
                                  ["=", "pnode", "node2"],
1391
                                  ["=", "pnode", "node1"]])
1392
    self.assertEqual(q.RequestedNames(), ["node1", "node2"],
1393
                     msg="Did not return unique names")
1394
    self.assertFalse(q.RequestedData())
1395
    self.assertEqual(q.Query(data),
1396
      [[(constants.RS_NORMAL, "node1"), (constants.RS_NORMAL, 100)],
1397
       [(constants.RS_NORMAL, "node1"), (constants.RS_NORMAL, 25)],
1398
       [(constants.RS_NORMAL, "node2"), (constants.RS_NORMAL, 90)],
1399
       [(constants.RS_NORMAL, "node2"), (constants.RS_NORMAL, 30)]])
1400
    self.assertEqual(q.Query(data, sort_by_name=False),
1401
      [[(constants.RS_NORMAL, "node1"), (constants.RS_NORMAL, 100)],
1402
       [(constants.RS_NORMAL, "node1"), (constants.RS_NORMAL, 25)],
1403
       [(constants.RS_NORMAL, "node2"), (constants.RS_NORMAL, 90)],
1404
       [(constants.RS_NORMAL, "node2"), (constants.RS_NORMAL, 30)]])
1405

    
1406
    data = [
1407
      { "pnode": "nodeX", "num": 50, },
1408
      { "pnode": "nodeY", "num": 40, },
1409
      { "pnode": "nodeX", "num": 30, },
1410
      { "pnode": "nodeX", "num": 20, },
1411
      { "pnode": "nodeM", "num": 10, },
1412
      ]
1413

    
1414
    q = query.Query(fielddefs, ["pnode", "num"], namefield="pnode",
1415
                    qfilter=["|", ["=", "pnode", "nodeX"],
1416
                                  ["=", "pnode", "nodeY"],
1417
                                  ["=", "pnode", "nodeY"],
1418
                                  ["=", "pnode", "nodeY"],
1419
                                  ["=", "pnode", "nodeM"]])
1420
    self.assertEqual(q.RequestedNames(), ["nodeX", "nodeY", "nodeM"],
1421
                     msg="Did not return unique names")
1422
    self.assertFalse(q.RequestedData())
1423

    
1424
    # First sorted by name, then input order
1425
    self.assertEqual(q.Query(data, sort_by_name=True),
1426
      [[(constants.RS_NORMAL, "nodeM"), (constants.RS_NORMAL, 10)],
1427
       [(constants.RS_NORMAL, "nodeX"), (constants.RS_NORMAL, 50)],
1428
       [(constants.RS_NORMAL, "nodeX"), (constants.RS_NORMAL, 30)],
1429
       [(constants.RS_NORMAL, "nodeX"), (constants.RS_NORMAL, 20)],
1430
       [(constants.RS_NORMAL, "nodeY"), (constants.RS_NORMAL, 40)]])
1431

    
1432
    # Input order
1433
    self.assertEqual(q.Query(data, sort_by_name=False),
1434
      [[(constants.RS_NORMAL, "nodeX"), (constants.RS_NORMAL, 50)],
1435
       [(constants.RS_NORMAL, "nodeY"), (constants.RS_NORMAL, 40)],
1436
       [(constants.RS_NORMAL, "nodeX"), (constants.RS_NORMAL, 30)],
1437
       [(constants.RS_NORMAL, "nodeX"), (constants.RS_NORMAL, 20)],
1438
       [(constants.RS_NORMAL, "nodeM"), (constants.RS_NORMAL, 10)]])
1439

    
1440
  def testFilter(self):
1441
    (DK_A, DK_B) = range(1000, 1002)
1442

    
1443
    fielddefs = query._PrepareFieldList([
1444
      (query._MakeField("name", "Name", constants.QFT_TEXT, "Name"),
1445
       DK_A, 0, lambda ctx, item: item["name"]),
1446
      (query._MakeField("other", "Other", constants.QFT_TEXT, "Other"),
1447
       DK_B, 0, lambda ctx, item: item["other"]),
1448
      ], [])
1449

    
1450
    data = [
1451
      { "name": "node1", "other": "foo", },
1452
      { "name": "node2", "other": "bar", },
1453
      { "name": "node3", "other": "Hello", },
1454
      ]
1455

    
1456
    # Empty filter
1457
    q = query.Query(fielddefs, ["name", "other"], namefield="name",
1458
                    qfilter=["|"])
1459
    self.assertTrue(q.RequestedNames() is None)
1460
    self.assertEqual(q.RequestedData(), set([DK_A, DK_B]))
1461
    self.assertEqual(q.Query(data), [])
1462

    
1463
    # Normal filter
1464
    q = query.Query(fielddefs, ["name", "other"], namefield="name",
1465
                    qfilter=["=", "name", "node1"])
1466
    self.assertEqual(q.RequestedNames(), ["node1"])
1467
    self.assertEqual(q.Query(data),
1468
      [[(constants.RS_NORMAL, "node1"), (constants.RS_NORMAL, "foo")]])
1469

    
1470
    q = query.Query(fielddefs, ["name", "other"], namefield="name",
1471
                    qfilter=(["|", ["=", "name", "node1"],
1472
                                   ["=", "name", "node3"]]))
1473
    self.assertEqual(q.RequestedNames(), ["node1", "node3"])
1474
    self.assertEqual(q.Query(data),
1475
      [[(constants.RS_NORMAL, "node1"), (constants.RS_NORMAL, "foo")],
1476
       [(constants.RS_NORMAL, "node3"), (constants.RS_NORMAL, "Hello")]])
1477

    
1478
    # Complex filter
1479
    q = query.Query(fielddefs, ["name", "other"], namefield="name",
1480
                    qfilter=(["|", ["=", "name", "node1"],
1481
                                   ["|", ["=", "name", "node3"],
1482
                                         ["=", "name", "node2"]],
1483
                                   ["=", "name", "node3"]]))
1484
    self.assertEqual(q.RequestedNames(), ["node1", "node3", "node2"])
1485
    self.assertEqual(q.RequestedData(), set([DK_A, DK_B]))
1486
    self.assertEqual(q.Query(data),
1487
      [[(constants.RS_NORMAL, "node1"), (constants.RS_NORMAL, "foo")],
1488
       [(constants.RS_NORMAL, "node2"), (constants.RS_NORMAL, "bar")],
1489
       [(constants.RS_NORMAL, "node3"), (constants.RS_NORMAL, "Hello")]])
1490

    
1491
    # Filter data type mismatch
1492
    for i in [-1, 0, 1, 123, [], None, True, False]:
1493
      self.assertRaises(errors.ParameterError, query.Query,
1494
                        fielddefs, ["name", "other"], namefield="name",
1495
                        qfilter=["=", "name", i])
1496

    
1497
    # Negative filter
1498
    q = query.Query(fielddefs, ["name", "other"], namefield="name",
1499
                    qfilter=["!", ["|", ["=", "name", "node1"],
1500
                                        ["=", "name", "node3"]]])
1501
    self.assertTrue(q.RequestedNames() is None)
1502
    self.assertEqual(q.Query(data),
1503
      [[(constants.RS_NORMAL, "node2"), (constants.RS_NORMAL, "bar")]])
1504

    
1505
    # Not equal
1506
    q = query.Query(fielddefs, ["name", "other"], namefield="name",
1507
                    qfilter=["!=", "name", "node3"])
1508
    self.assertTrue(q.RequestedNames() is None)
1509
    self.assertEqual(q.Query(data),
1510
      [[(constants.RS_NORMAL, "node1"), (constants.RS_NORMAL, "foo")],
1511
       [(constants.RS_NORMAL, "node2"), (constants.RS_NORMAL, "bar")]])
1512

    
1513
    # Data type
1514
    q = query.Query(fielddefs, [], namefield="name",
1515
                    qfilter=["|", ["=", "other", "bar"],
1516
                                  ["=", "name", "foo"]])
1517
    self.assertTrue(q.RequestedNames() is None)
1518
    self.assertEqual(q.RequestedData(), set([DK_A, DK_B]))
1519
    self.assertEqual(q.Query(data), [[]])
1520

    
1521
    # Only one data type
1522
    q = query.Query(fielddefs, ["other"], namefield="name",
1523
                    qfilter=["=", "other", "bar"])
1524
    self.assertTrue(q.RequestedNames() is None)
1525
    self.assertEqual(q.RequestedData(), set([DK_B]))
1526
    self.assertEqual(q.Query(data), [[(constants.RS_NORMAL, "bar")]])
1527

    
1528
    q = query.Query(fielddefs, [], namefield="name",
1529
                    qfilter=["=", "other", "bar"])
1530
    self.assertTrue(q.RequestedNames() is None)
1531
    self.assertEqual(q.RequestedData(), set([DK_B]))
1532
    self.assertEqual(q.Query(data), [[]])
1533

    
1534
    # Data type in boolean operator
1535
    q = query.Query(fielddefs, [], namefield="name",
1536
                    qfilter=["?", "name"])
1537
    self.assertTrue(q.RequestedNames() is None)
1538
    self.assertEqual(q.RequestedData(), set([DK_A]))
1539
    self.assertEqual(q.Query(data), [[], [], []])
1540

    
1541
    q = query.Query(fielddefs, [], namefield="name",
1542
                    qfilter=["!", ["?", "name"]])
1543
    self.assertTrue(q.RequestedNames() is None)
1544
    self.assertEqual(q.RequestedData(), set([DK_A]))
1545
    self.assertEqual(q.Query(data), [])
1546

    
1547
  def testFilterContains(self):
1548
    fielddefs = query._PrepareFieldList([
1549
      (query._MakeField("name", "Name", constants.QFT_TEXT, "Name"),
1550
       None, 0, lambda ctx, item: item["name"]),
1551
      (query._MakeField("other", "Other", constants.QFT_OTHER, "Other"),
1552
       None, 0, lambda ctx, item: item["other"]),
1553
      ], [])
1554

    
1555
    data = [
1556
      { "name": "node2", "other": ["x", "y", "bar"], },
1557
      { "name": "node3", "other": "Hello", },
1558
      { "name": "node1", "other": ["a", "b", "foo"], },
1559
      { "name": "empty", "other": []},
1560
      ]
1561

    
1562
    q = query.Query(fielddefs, ["name", "other"], namefield="name",
1563
                    qfilter=["=[]", "other", "bar"])
1564
    self.assertTrue(q.RequestedNames() is None)
1565
    self.assertEqual(q.Query(data), [
1566
      [(constants.RS_NORMAL, "node2"),
1567
       (constants.RS_NORMAL, ["x", "y", "bar"])],
1568
      ])
1569

    
1570
    q = query.Query(fielddefs, ["name", "other"], namefield="name",
1571
                    qfilter=["|", ["=[]", "other", "bar"],
1572
                                  ["=[]", "other", "a"],
1573
                                  ["=[]", "other", "b"]])
1574
    self.assertTrue(q.RequestedNames() is None)
1575
    self.assertEqual(q.Query(data), [
1576
      [(constants.RS_NORMAL, "node1"),
1577
       (constants.RS_NORMAL, ["a", "b", "foo"])],
1578
      [(constants.RS_NORMAL, "node2"),
1579
       (constants.RS_NORMAL, ["x", "y", "bar"])],
1580
      ])
1581
    self.assertEqual(q.OldStyleQuery(data), [
1582
      ["node1", ["a", "b", "foo"]],
1583
      ["node2", ["x", "y", "bar"]],
1584
      ])
1585

    
1586
    # Boolean test
1587
    q = query.Query(fielddefs, ["name", "other"], namefield="name",
1588
                    qfilter=["?", "other"])
1589
    self.assertEqual(q.OldStyleQuery(data), [
1590
      ["node1", ["a", "b", "foo"]],
1591
      ["node2", ["x", "y", "bar"]],
1592
      ["node3", "Hello"],
1593
      ])
1594

    
1595
    q = query.Query(fielddefs, ["name", "other"], namefield="name",
1596
                    qfilter=["!", ["?", "other"]])
1597
    self.assertEqual(q.OldStyleQuery(data), [
1598
      ["empty", []],
1599
      ])
1600

    
1601
  def testFilterHostname(self):
1602
    fielddefs = query._PrepareFieldList([
1603
      (query._MakeField("name", "Name", constants.QFT_TEXT, "Name"),
1604
       None, query.QFF_HOSTNAME, lambda ctx, item: item["name"]),
1605
      ], [])
1606

    
1607
    data = [
1608
      { "name": "node1.example.com", },
1609
      { "name": "node2.example.com", },
1610
      { "name": "node2.example.net", },
1611
      ]
1612

    
1613
    q = query.Query(fielddefs, ["name"], namefield="name",
1614
                    qfilter=["=", "name", "node2"])
1615
    self.assertEqual(q.RequestedNames(), ["node2"])
1616
    self.assertEqual(q.Query(data), [
1617
      [(constants.RS_NORMAL, "node2.example.com")],
1618
      [(constants.RS_NORMAL, "node2.example.net")],
1619
      ])
1620

    
1621
    q = query.Query(fielddefs, ["name"], namefield="name",
1622
                    qfilter=["=", "name", "node1"])
1623
    self.assertEqual(q.RequestedNames(), ["node1"])
1624
    self.assertEqual(q.Query(data), [
1625
      [(constants.RS_NORMAL, "node1.example.com")],
1626
      ])
1627

    
1628
    q = query.Query(fielddefs, ["name"], namefield="name",
1629
                    qfilter=["=", "name", "othername"])
1630
    self.assertEqual(q.RequestedNames(), ["othername"])
1631
    self.assertEqual(q.Query(data), [])
1632

    
1633
    q = query.Query(fielddefs, ["name"], namefield="name",
1634
                    qfilter=["|", ["=", "name", "node1.example.com"],
1635
                                  ["=", "name", "node2"]])
1636
    self.assertEqual(q.RequestedNames(), ["node1.example.com", "node2"])
1637
    self.assertEqual(q.Query(data), [
1638
      [(constants.RS_NORMAL, "node1.example.com")],
1639
      [(constants.RS_NORMAL, "node2.example.com")],
1640
      [(constants.RS_NORMAL, "node2.example.net")],
1641
      ])
1642
    self.assertEqual(q.OldStyleQuery(data), [
1643
      ["node1.example.com"],
1644
      ["node2.example.com"],
1645
      ["node2.example.net"],
1646
      ])
1647

    
1648
    q = query.Query(fielddefs, ["name"], namefield="name",
1649
                    qfilter=["!=", "name", "node1"])
1650
    self.assertTrue(q.RequestedNames() is None)
1651
    self.assertEqual(q.Query(data), [
1652
      [(constants.RS_NORMAL, "node2.example.com")],
1653
      [(constants.RS_NORMAL, "node2.example.net")],
1654
      ])
1655
    self.assertEqual(q.OldStyleQuery(data), [
1656
      ["node2.example.com"],
1657
      ["node2.example.net"],
1658
      ])
1659

    
1660
  def testFilterBoolean(self):
1661
    fielddefs = query._PrepareFieldList([
1662
      (query._MakeField("name", "Name", constants.QFT_TEXT, "Name"),
1663
       None, query.QFF_HOSTNAME, lambda ctx, item: item["name"]),
1664
      (query._MakeField("value", "Value", constants.QFT_BOOL, "Value"),
1665
       None, 0, lambda ctx, item: item["value"]),
1666
      ], [])
1667

    
1668
    data = [
1669
      { "name": "node1", "value": False, },
1670
      { "name": "node2", "value": True, },
1671
      { "name": "node3", "value": True, },
1672
      ]
1673

    
1674
    q = query.Query(fielddefs, ["name", "value"],
1675
                    qfilter=["|", ["=", "value", False],
1676
                                  ["=", "value", True]])
1677
    self.assertTrue(q.RequestedNames() is None)
1678
    self.assertEqual(q.Query(data), [
1679
      [(constants.RS_NORMAL, "node1"), (constants.RS_NORMAL, False)],
1680
      [(constants.RS_NORMAL, "node2"), (constants.RS_NORMAL, True)],
1681
      [(constants.RS_NORMAL, "node3"), (constants.RS_NORMAL, True)],
1682
      ])
1683

    
1684
    q = query.Query(fielddefs, ["name", "value"],
1685
                    qfilter=["|", ["=", "value", False],
1686
                                  ["!", ["=", "value", False]]])
1687
    self.assertTrue(q.RequestedNames() is None)
1688
    self.assertEqual(q.Query(data), [
1689
      [(constants.RS_NORMAL, "node1"), (constants.RS_NORMAL, False)],
1690
      [(constants.RS_NORMAL, "node2"), (constants.RS_NORMAL, True)],
1691
      [(constants.RS_NORMAL, "node3"), (constants.RS_NORMAL, True)],
1692
      ])
1693

    
1694
    # Comparing bool with string
1695
    for i in ["False", "True", "0", "1", "no", "yes", "N", "Y"]:
1696
      self.assertRaises(errors.ParameterError, query.Query,
1697
                        fielddefs, ["name", "value"],
1698
                        qfilter=["=", "value", i])
1699

    
1700
    # Truth filter
1701
    q = query.Query(fielddefs, ["name", "value"], qfilter=["?", "value"])
1702
    self.assertTrue(q.RequestedNames() is None)
1703
    self.assertEqual(q.Query(data), [
1704
      [(constants.RS_NORMAL, "node2"), (constants.RS_NORMAL, True)],
1705
      [(constants.RS_NORMAL, "node3"), (constants.RS_NORMAL, True)],
1706
      ])
1707

    
1708
    # Negative bool filter
1709
    q = query.Query(fielddefs, ["name", "value"], qfilter=["!", ["?", "value"]])
1710
    self.assertTrue(q.RequestedNames() is None)
1711
    self.assertEqual(q.Query(data), [
1712
      [(constants.RS_NORMAL, "node1"), (constants.RS_NORMAL, False)],
1713
      ])
1714

    
1715
    # Complex truth filter
1716
    q = query.Query(fielddefs, ["name", "value"],
1717
                    qfilter=["|", ["&", ["=", "name", "node1"],
1718
                                        ["!", ["?", "value"]]],
1719
                                  ["?", "value"]])
1720
    self.assertTrue(q.RequestedNames() is None)
1721
    self.assertEqual(q.Query(data), [
1722
      [(constants.RS_NORMAL, "node1"), (constants.RS_NORMAL, False)],
1723
      [(constants.RS_NORMAL, "node2"), (constants.RS_NORMAL, True)],
1724
      [(constants.RS_NORMAL, "node3"), (constants.RS_NORMAL, True)],
1725
      ])
1726

    
1727
  def testFilterRegex(self):
1728
    fielddefs = query._PrepareFieldList([
1729
      (query._MakeField("name", "Name", constants.QFT_TEXT, "Name"),
1730
       None, 0, lambda ctx, item: item["name"]),
1731
      ], [])
1732

    
1733
    data = [
1734
      { "name": "node1.example.com", },
1735
      { "name": "node2.site.example.com", },
1736
      { "name": "node2.example.net", },
1737

    
1738
      # Empty name
1739
      { "name": "", },
1740
      ]
1741

    
1742
    q = query.Query(fielddefs, ["name"], namefield="name",
1743
                    qfilter=["=~", "name", "site"])
1744
    self.assertTrue(q.RequestedNames() is None)
1745
    self.assertEqual(q.Query(data), [
1746
      [(constants.RS_NORMAL, "node2.site.example.com")],
1747
      ])
1748

    
1749
    q = query.Query(fielddefs, ["name"], namefield="name",
1750
                    qfilter=["=~", "name", "^node2"])
1751
    self.assertTrue(q.RequestedNames() is None)
1752
    self.assertEqual(q.Query(data), [
1753
      [(constants.RS_NORMAL, "node2.example.net")],
1754
      [(constants.RS_NORMAL, "node2.site.example.com")],
1755
      ])
1756

    
1757
    q = query.Query(fielddefs, ["name"], namefield="name",
1758
                    qfilter=["=~", "name", r"(?i)\.COM$"])
1759
    self.assertTrue(q.RequestedNames() is None)
1760
    self.assertEqual(q.Query(data), [
1761
      [(constants.RS_NORMAL, "node1.example.com")],
1762
      [(constants.RS_NORMAL, "node2.site.example.com")],
1763
      ])
1764

    
1765
    q = query.Query(fielddefs, ["name"], namefield="name",
1766
                    qfilter=["=~", "name", r"."])
1767
    self.assertTrue(q.RequestedNames() is None)
1768
    self.assertEqual(q.Query(data), [
1769
      [(constants.RS_NORMAL, "node1.example.com")],
1770
      [(constants.RS_NORMAL, "node2.example.net")],
1771
      [(constants.RS_NORMAL, "node2.site.example.com")],
1772
      ])
1773

    
1774
    q = query.Query(fielddefs, ["name"], namefield="name",
1775
                    qfilter=["=~", "name", r"^$"])
1776
    self.assertTrue(q.RequestedNames() is None)
1777
    self.assertEqual(q.Query(data), [
1778
      [(constants.RS_NORMAL, "")],
1779
      ])
1780

    
1781
    # Invalid regular expression
1782
    self.assertRaises(errors.ParameterError, query.Query, fielddefs, ["name"],
1783
                      qfilter=["=~", "name", r"["])
1784

    
1785
  def testFilterLessGreater(self):
1786
    fielddefs = query._PrepareFieldList([
1787
      (query._MakeField("value", "Value", constants.QFT_NUMBER, "Value"),
1788
       None, 0, lambda ctx, item: item),
1789
      ], [])
1790

    
1791
    data = range(100)
1792

    
1793
    q = query.Query(fielddefs, ["value"],
1794
                    qfilter=["<", "value", 20])
1795
    self.assertTrue(q.RequestedNames() is None)
1796
    self.assertEqual(q.Query(data),
1797
                     [[(constants.RS_NORMAL, i)] for i in range(20)])
1798

    
1799
    q = query.Query(fielddefs, ["value"],
1800
                    qfilter=["<=", "value", 30])
1801
    self.assertTrue(q.RequestedNames() is None)
1802
    self.assertEqual(q.Query(data),
1803
                     [[(constants.RS_NORMAL, i)] for i in range(31)])
1804

    
1805
    q = query.Query(fielddefs, ["value"],
1806
                    qfilter=[">", "value", 40])
1807
    self.assertTrue(q.RequestedNames() is None)
1808
    self.assertEqual(q.Query(data),
1809
                     [[(constants.RS_NORMAL, i)] for i in range(41, 100)])
1810

    
1811
    q = query.Query(fielddefs, ["value"],
1812
                    qfilter=[">=", "value", 50])
1813
    self.assertTrue(q.RequestedNames() is None)
1814
    self.assertEqual(q.Query(data),
1815
                     [[(constants.RS_NORMAL, i)] for i in range(50, 100)])
1816

    
1817
  def testFilterLessGreaterJobId(self):
1818
    fielddefs = query._PrepareFieldList([
1819
      (query._MakeField("id", "ID", constants.QFT_TEXT, "Job ID"),
1820
       None, query.QFF_JOB_ID, lambda ctx, item: item),
1821
      ], [])
1822

    
1823
    data = ["1", "2", "3", "10", "102", "120", "125", "15", "100", "7"]
1824

    
1825
    assert data != utils.NiceSort(data), "Test data should not be sorted"
1826

    
1827
    q = query.Query(fielddefs, ["id"], qfilter=["<", "id", "20"])
1828
    self.assertTrue(q.RequestedNames() is None)
1829
    self.assertEqual(q.Query(data), [
1830
      [(constants.RS_NORMAL, "1")],
1831
      [(constants.RS_NORMAL, "2")],
1832
      [(constants.RS_NORMAL, "3")],
1833
      [(constants.RS_NORMAL, "10")],
1834
      [(constants.RS_NORMAL, "15")],
1835
      [(constants.RS_NORMAL, "7")],
1836
      ])
1837

    
1838
    q = query.Query(fielddefs, ["id"], qfilter=[">=", "id", "100"])
1839
    self.assertTrue(q.RequestedNames() is None)
1840
    self.assertEqual(q.Query(data), [
1841
      [(constants.RS_NORMAL, "102")],
1842
      [(constants.RS_NORMAL, "120")],
1843
      [(constants.RS_NORMAL, "125")],
1844
      [(constants.RS_NORMAL, "100")],
1845
      ])
1846

    
1847
    # Integers are no valid job IDs
1848
    self.assertRaises(errors.ParameterError, query.Query,
1849
                      fielddefs, ["id"], qfilter=[">=", "id", 10])
1850

    
1851
  def testFilterLessGreaterSplitTimestamp(self):
1852
    fielddefs = query._PrepareFieldList([
1853
      (query._MakeField("ts", "Timestamp", constants.QFT_OTHER, "Timestamp"),
1854
       None, query.QFF_SPLIT_TIMESTAMP, lambda ctx, item: item),
1855
      ], [])
1856

    
1857
    data = [
1858
      utils.SplitTime(0),
1859
      utils.SplitTime(0.1),
1860
      utils.SplitTime(18224.7872),
1861
      utils.SplitTime(919896.12623),
1862
      utils.SplitTime(999),
1863
      utils.SplitTime(989.9999),
1864
      ]
1865

    
1866
    for i in [0, [0, 0]]:
1867
      q = query.Query(fielddefs, ["ts"], qfilter=["<", "ts", i])
1868
      self.assertTrue(q.RequestedNames() is None)
1869
      self.assertEqual(q.Query(data), [])
1870

    
1871
    q = query.Query(fielddefs, ["ts"], qfilter=["<", "ts", 1000])
1872
    self.assertTrue(q.RequestedNames() is None)
1873
    self.assertEqual(q.Query(data), [
1874
      [(constants.RS_NORMAL, (0, 0))],
1875
      [(constants.RS_NORMAL, (0, 100000))],
1876
      [(constants.RS_NORMAL, (999, 0))],
1877
      [(constants.RS_NORMAL, (989, 999900))],
1878
      ])
1879

    
1880
    q = query.Query(fielddefs, ["ts"], qfilter=[">=", "ts", 5000.3])
1881
    self.assertTrue(q.RequestedNames() is None)
1882
    self.assertEqual(q.Query(data), [
1883
      [(constants.RS_NORMAL, (18224, 787200))],
1884
      [(constants.RS_NORMAL, (919896, 126230))],
1885
      ])
1886

    
1887
    for i in [18224.7772, utils.SplitTime(18224.7772)]:
1888
      q = query.Query(fielddefs, ["ts"], qfilter=[">=", "ts", i])
1889
      self.assertTrue(q.RequestedNames() is None)
1890
      self.assertEqual(q.Query(data), [
1891
        [(constants.RS_NORMAL, (18224, 787200))],
1892
        [(constants.RS_NORMAL, (919896, 126230))],
1893
        ])
1894

    
1895
    q = query.Query(fielddefs, ["ts"], qfilter=[">", "ts", 18224.7880])
1896
    self.assertTrue(q.RequestedNames() is None)
1897
    self.assertEqual(q.Query(data), [
1898
      [(constants.RS_NORMAL, (919896, 126230))],
1899
      ])
1900

    
1901

    
1902
if __name__ == "__main__":
1903
  testutils.GanetiTestProgram()