Statistics
| Branch: | Tag: | Revision:

root / test / ganeti.query_unittest.py @ 6e7f0cd9

History | View | Annotate | Download (35.3 kB)

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

    
4
# Copyright (C) 2010, 2011 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21

    
22
"""Script for testing ganeti.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),
70
       STATIC, lambda ctx, item: item["name"]),
71
      (query._MakeField("master", "Master", constants.QFT_BOOL),
72
       STATIC, lambda ctx, item: ctx.mastername == item["name"]),
73
      ] +
74
      [(query._MakeField("disk%s.size" % i, "DiskSize%s" % i,
75
                         constants.QFT_UNIT),
76
        DISK, 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).ToDict())
87

    
88
    # Create data only once query has been prepared
89
    data = [
90
      { "name": "node1", "disks": [0, 1, 2], },
91
      { "name": "node2", "disks": [3, 4], },
92
      { "name": "node3", "disks": [5, 6, 7], },
93
      ]
94

    
95
    self.assertEqual(q.Query(_QueryData(data, mastername="node3")),
96
                     [[(constants.RS_NORMAL, "node1")],
97
                      [(constants.RS_NORMAL, "node2")],
98
                      [(constants.RS_NORMAL, "node3")]])
99
    self.assertEqual(q.OldStyleQuery(_QueryData(data, mastername="node3")),
100
                     [["node1"], ["node2"], ["node3"]])
101

    
102
    q = query.Query(fielddef, ["name", "master"])
103
    self.assertEqual(q.RequestedData(), set([STATIC]))
104
    self.assertEqual(len(q._fields), 2)
105
    self.assertEqual(q.Query(_QueryData(data, mastername="node3")),
106
                     [[(constants.RS_NORMAL, "node1"),
107
                       (constants.RS_NORMAL, False)],
108
                      [(constants.RS_NORMAL, "node2"),
109
                       (constants.RS_NORMAL, False)],
110
                      [(constants.RS_NORMAL, "node3"),
111
                       (constants.RS_NORMAL, True)],
112
                     ])
113

    
114
    q = query.Query(fielddef, ["name", "master", "disk0.size"])
115
    self.assertEqual(q.RequestedData(), set([STATIC, DISK]))
116
    self.assertEqual(len(q._fields), 3)
117
    self.assertEqual(q.Query(_QueryData(data, mastername="node2")),
118
                     [[(constants.RS_NORMAL, "node1"),
119
                       (constants.RS_NORMAL, False),
120
                       (constants.RS_NORMAL, 0)],
121
                      [(constants.RS_NORMAL, "node2"),
122
                       (constants.RS_NORMAL, True),
123
                       (constants.RS_NORMAL, 3)],
124
                      [(constants.RS_NORMAL, "node3"),
125
                       (constants.RS_NORMAL, False),
126
                       (constants.RS_NORMAL, 5)],
127
                     ])
128

    
129
    # With unknown column
130
    q = query.Query(fielddef, ["disk2.size", "disk1.size", "disk99.size",
131
                               "disk0.size"])
132
    self.assertEqual(q.RequestedData(), set([DISK]))
133
    self.assertEqual(len(q._fields), 4)
134
    self.assertEqual(q.Query(_QueryData(data, mastername="node2")),
135
                     [[(constants.RS_NORMAL, 2),
136
                       (constants.RS_NORMAL, 1),
137
                       (constants.RS_UNKNOWN, None),
138
                       (constants.RS_NORMAL, 0)],
139
                      [(constants.RS_UNAVAIL, None),
140
                       (constants.RS_NORMAL, 4),
141
                       (constants.RS_UNKNOWN, None),
142
                       (constants.RS_NORMAL, 3)],
143
                      [(constants.RS_NORMAL, 7),
144
                       (constants.RS_NORMAL, 6),
145
                       (constants.RS_UNKNOWN, None),
146
                       (constants.RS_NORMAL, 5)],
147
                     ])
148
    self.assertRaises(errors.OpPrereqError, q.OldStyleQuery,
149
                      _QueryData(data, mastername="node2"))
150
    self.assertEqual([fdef.ToDict() for fdef in q.GetFields()], [
151
                     { "name": "disk2.size", "title": "DiskSize2",
152
                       "kind": constants.QFT_UNIT, },
153
                     { "name": "disk1.size", "title": "DiskSize1",
154
                       "kind": constants.QFT_UNIT, },
155
                     { "name": "disk99.size", "title": "disk99.size",
156
                       "kind": constants.QFT_UNKNOWN, },
157
                     { "name": "disk0.size", "title": "DiskSize0",
158
                       "kind": constants.QFT_UNIT, },
159
                     ])
160

    
161
    # Empty query
162
    q = query.Query(fielddef, [])
163
    self.assertEqual(q.RequestedData(), set([]))
164
    self.assertEqual(len(q._fields), 0)
165
    self.assertEqual(q.Query(_QueryData(data, mastername="node2")),
166
                     [[], [], []])
167
    self.assertEqual(q.OldStyleQuery(_QueryData(data, mastername="node2")),
168
                     [[], [], []])
169
    self.assertEqual(q.GetFields(), [])
170

    
171
  def testPrepareFieldList(self):
172
    # Duplicate titles
173
    for (a, b) in [("name", "name"), ("NAME", "name")]:
174
      self.assertRaises(AssertionError, query._PrepareFieldList, [
175
        (query._MakeField("name", b, constants.QFT_TEXT), None,
176
         lambda *args: None),
177
        (query._MakeField("other", a, constants.QFT_TEXT), None,
178
         lambda *args: None),
179
        ], [])
180

    
181
    # Non-lowercase names
182
    self.assertRaises(AssertionError, query._PrepareFieldList, [
183
      (query._MakeField("NAME", "Name", constants.QFT_TEXT), None,
184
       lambda *args: None),
185
      ], [])
186
    self.assertRaises(AssertionError, query._PrepareFieldList, [
187
      (query._MakeField("Name", "Name", constants.QFT_TEXT), None,
188
       lambda *args: None),
189
      ], [])
190

    
191
    # Empty name
192
    self.assertRaises(AssertionError, query._PrepareFieldList, [
193
      (query._MakeField("", "Name", constants.QFT_TEXT), None,
194
       lambda *args: None),
195
      ], [])
196

    
197
    # Empty title
198
    self.assertRaises(AssertionError, query._PrepareFieldList, [
199
      (query._MakeField("name", "", constants.QFT_TEXT), None,
200
       lambda *args: None),
201
      ], [])
202

    
203
    # Whitespace in title
204
    self.assertRaises(AssertionError, query._PrepareFieldList, [
205
      (query._MakeField("name", "Co lu mn", constants.QFT_TEXT), None,
206
       lambda *args: None),
207
      ], [])
208

    
209
    # No callable function
210
    self.assertRaises(AssertionError, query._PrepareFieldList, [
211
      (query._MakeField("name", "Name", constants.QFT_TEXT), None, None),
212
      ], [])
213

    
214
  def testUnknown(self):
215
    fielddef = query._PrepareFieldList([
216
      (query._MakeField("name", "Name", constants.QFT_TEXT),
217
       None, lambda _, item: "name%s" % item),
218
      (query._MakeField("other0", "Other0", constants.QFT_TIMESTAMP),
219
       None, lambda *args: 1234),
220
      (query._MakeField("nodata", "NoData", constants.QFT_NUMBER),
221
       None, lambda *args: query._FS_NODATA ),
222
      (query._MakeField("unavail", "Unavail", constants.QFT_BOOL),
223
       None, lambda *args: query._FS_UNAVAIL),
224
      ], [])
225

    
226
    for selected in [["foo"], ["Hello", "World"],
227
                     ["name1", "other", "foo"]]:
228
      q = query.Query(fielddef, selected)
229
      self.assertEqual(len(q._fields), len(selected))
230
      self.assert_(compat.all(len(row) == len(selected)
231
                              for row in q.Query(_QueryData(range(1, 10)))))
232
      self.assertEqual(q.Query(_QueryData(range(1, 10))),
233
                       [[(constants.RS_UNKNOWN, None)] * len(selected)
234
                        for i in range(1, 10)])
235
      self.assertEqual([fdef.ToDict() for fdef in q.GetFields()],
236
                       [{ "name": name, "title": name,
237
                          "kind": constants.QFT_UNKNOWN, }
238
                        for name in selected])
239

    
240
    q = query.Query(fielddef, ["name", "other0", "nodata", "unavail"])
241
    self.assertEqual(len(q._fields), 4)
242
    self.assertEqual(q.OldStyleQuery(_QueryData(range(1, 10))), [
243
                     ["name%s" % i, 1234, None, None]
244
                     for i in range(1, 10)
245
                     ])
246

    
247
    q = query.Query(fielddef, ["name", "other0", "nodata", "unavail", "unk"])
248
    self.assertEqual(len(q._fields), 5)
249
    self.assertEqual(q.Query(_QueryData(range(1, 10))),
250
                     [[(constants.RS_NORMAL, "name%s" % i),
251
                       (constants.RS_NORMAL, 1234),
252
                       (constants.RS_NODATA, None),
253
                       (constants.RS_UNAVAIL, None),
254
                       (constants.RS_UNKNOWN, None)]
255
                      for i in range(1, 10)])
256

    
257
  def testAliases(self):
258
    fields = [
259
      (query._MakeField("a", "a-title", constants.QFT_TEXT), None,
260
       lambda *args: None),
261
      (query._MakeField("b", "b-title", constants.QFT_TEXT), None,
262
       lambda *args: None),
263
      ]
264
    # duplicate field
265
    self.assertRaises(AssertionError, query._PrepareFieldList, fields,
266
                      [("b", "a")])
267
    self.assertRaises(AssertionError, query._PrepareFieldList, fields,
268
                      [("c", "b"), ("c", "a")])
269
    # missing target
270
    self.assertRaises(AssertionError, query._PrepareFieldList, fields,
271
                      [("c", "d")])
272
    fdefs = query._PrepareFieldList(fields, [("c", "b")])
273
    self.assertEqual(len(fdefs), 3)
274
    self.assertEqual(fdefs["b"][1:], fdefs["c"][1:])
275

    
276

    
277
class TestGetNodeRole(unittest.TestCase):
278
  def testMaster(self):
279
    node = objects.Node(name="node1")
280
    self.assertEqual(query._GetNodeRole(node, "node1"), "M")
281

    
282
  def testMasterCandidate(self):
283
    node = objects.Node(name="node1", master_candidate=True)
284
    self.assertEqual(query._GetNodeRole(node, "master"), "C")
285

    
286
  def testRegular(self):
287
    node = objects.Node(name="node1")
288
    self.assertEqual(query._GetNodeRole(node, "master"), "R")
289

    
290
  def testDrained(self):
291
    node = objects.Node(name="node1", drained=True)
292
    self.assertEqual(query._GetNodeRole(node, "master"), "D")
293

    
294
  def testOffline(self):
295
    node = objects.Node(name="node1", offline=True)
296
    self.assertEqual(query._GetNodeRole(node, "master"), "O")
297

    
298

    
299
class TestNodeQuery(unittest.TestCase):
300
  def _Create(self, selected):
301
    return query.Query(query.NODE_FIELDS, selected)
302

    
303
  def testSimple(self):
304
    nodes = [
305
      objects.Node(name="node1", drained=False),
306
      objects.Node(name="node2", drained=True),
307
      objects.Node(name="node3", drained=False),
308
      ]
309
    for live_data in [None, dict.fromkeys([node.name for node in nodes], {})]:
310
      nqd = query.NodeQueryData(nodes, live_data, None, None, None, None, None,
311
                                None)
312

    
313
      q = self._Create(["name", "drained"])
314
      self.assertEqual(q.RequestedData(), set([query.NQ_CONFIG]))
315
      self.assertEqual(q.Query(nqd),
316
                       [[(constants.RS_NORMAL, "node1"),
317
                         (constants.RS_NORMAL, False)],
318
                        [(constants.RS_NORMAL, "node2"),
319
                         (constants.RS_NORMAL, True)],
320
                        [(constants.RS_NORMAL, "node3"),
321
                         (constants.RS_NORMAL, False)],
322
                       ])
323
      self.assertEqual(q.OldStyleQuery(nqd),
324
                       [["node1", False],
325
                        ["node2", True],
326
                        ["node3", False]])
327

    
328
  def test(self):
329
    selected = query.NODE_FIELDS.keys()
330
    field_index = dict((field, idx) for idx, field in enumerate(selected))
331

    
332
    q = self._Create(selected)
333
    self.assertEqual(q.RequestedData(),
334
                     set([query.NQ_CONFIG, query.NQ_LIVE, query.NQ_INST,
335
                          query.NQ_GROUP, query.NQ_OOB]))
336

    
337
    cluster = objects.Cluster(cluster_name="testcluster",
338
      hvparams=constants.HVC_DEFAULTS,
339
      beparams={
340
        constants.PP_DEFAULT: constants.BEC_DEFAULTS,
341
        },
342
      nicparams={
343
        constants.PP_DEFAULT: constants.NICC_DEFAULTS,
344
        },
345
      ndparams=constants.NDC_DEFAULTS,
346
        )
347

    
348
    node_names = ["node%s" % i for i in range(20)]
349
    master_name = node_names[3]
350
    nodes = [
351
      objects.Node(name=name,
352
                   primary_ip="192.0.2.%s" % idx,
353
                   secondary_ip="192.0.100.%s" % idx,
354
                   serial_no=7789 * idx,
355
                   master_candidate=(name != master_name and idx % 3 == 0),
356
                   offline=False,
357
                   drained=False,
358
                   vm_capable=True,
359
                   master_capable=False,
360
                   ndparams={},
361
                   group="default",
362
                   ctime=1290006900,
363
                   mtime=1290006913,
364
                   uuid="fd9ccebe-6339-43c9-a82e-94bbe575%04d" % idx)
365
      for idx, name in enumerate(node_names)
366
      ]
367

    
368
    master_node = nodes[3]
369
    master_node.AddTag("masternode")
370
    master_node.AddTag("another")
371
    master_node.AddTag("tag")
372
    master_node.ctime = None
373
    master_node.mtime = None
374
    assert master_node.name == master_name
375

    
376
    live_data_name = node_names[4]
377
    assert live_data_name != master_name
378

    
379
    fake_live_data = {
380
      "bootid": "a2504766-498e-4b25-b21e-d23098dc3af4",
381
      "cnodes": 4,
382
      "csockets": 4,
383
      "ctotal": 8,
384
      "mnode": 128,
385
      "mfree": 100,
386
      "mtotal": 4096,
387
      "dfree": 5 * 1024 * 1024,
388
      "dtotal": 100 * 1024 * 1024,
389
      }
390

    
391
    assert (sorted(query._NODE_LIVE_FIELDS.keys()) ==
392
            sorted(fake_live_data.keys()))
393

    
394
    live_data = dict.fromkeys(node_names, {})
395
    live_data[live_data_name] = \
396
      dict((query._NODE_LIVE_FIELDS[name][2], value)
397
           for name, value in fake_live_data.items())
398

    
399
    node_to_primary = dict((name, set()) for name in node_names)
400
    node_to_primary[master_name].update(["inst1", "inst2"])
401

    
402
    node_to_secondary = dict((name, set()) for name in node_names)
403
    node_to_secondary[live_data_name].update(["instX", "instY", "instZ"])
404

    
405
    ng_uuid = "492b4b74-8670-478a-b98d-4c53a76238e6"
406
    groups = {
407
      ng_uuid: objects.NodeGroup(name="ng1", uuid=ng_uuid, ndparams={}),
408
      }
409

    
410
    oob_support = dict((name, False) for name in node_names)
411

    
412
    master_node.group = ng_uuid
413

    
414
    nqd = query.NodeQueryData(nodes, live_data, master_name,
415
                              node_to_primary, node_to_secondary, groups,
416
                              oob_support, cluster)
417
    result = q.Query(nqd)
418
    self.assert_(compat.all(len(row) == len(selected) for row in result))
419
    self.assertEqual([row[field_index["name"]] for row in result],
420
                     [(constants.RS_NORMAL, name) for name in node_names])
421

    
422
    node_to_row = dict((row[field_index["name"]][1], idx)
423
                       for idx, row in enumerate(result))
424

    
425
    master_row = result[node_to_row[master_name]]
426
    self.assert_(master_row[field_index["master"]])
427
    self.assert_(master_row[field_index["role"]], "M")
428
    self.assertEqual(master_row[field_index["group"]],
429
                     (constants.RS_NORMAL, "ng1"))
430
    self.assertEqual(master_row[field_index["group.uuid"]],
431
                     (constants.RS_NORMAL, ng_uuid))
432
    self.assertEqual(master_row[field_index["ctime"]],
433
                     (constants.RS_UNAVAIL, None))
434
    self.assertEqual(master_row[field_index["mtime"]],
435
                     (constants.RS_UNAVAIL, None))
436

    
437
    self.assert_(row[field_index["pip"]] == node.primary_ip and
438
                 row[field_index["sip"]] == node.secondary_ip and
439
                 set(row[field_index["tags"]]) == node.GetTags() and
440
                 row[field_index["serial_no"]] == node.serial_no and
441
                 row[field_index["role"]] == query._GetNodeRole(node,
442
                                                                master_name) and
443
                 (node.name == master_name or
444
                  (row[field_index["group"]] == "<unknown>" and
445
                   row[field_index["group.uuid"]] is None and
446
                   row[field_index["ctime"]] == (constants.RS_NORMAL,
447
                                                 node.ctime) and
448
                   row[field_index["mtime"]] == (constants.RS_NORMAL,
449
                                                 node.mtime)))
450
                 for row, node in zip(result, nodes))
451

    
452
    live_data_row = result[node_to_row[live_data_name]]
453

    
454
    for (field, value) in fake_live_data.items():
455
      self.assertEqual(live_data_row[field_index[field]],
456
                       (constants.RS_NORMAL, value))
457

    
458
    self.assertEqual(master_row[field_index["pinst_cnt"]],
459
                     (constants.RS_NORMAL, 2))
460
    self.assertEqual(live_data_row[field_index["sinst_cnt"]],
461
                     (constants.RS_NORMAL, 3))
462
    self.assertEqual(master_row[field_index["pinst_list"]],
463
                     (constants.RS_NORMAL,
464
                      list(node_to_primary[master_name])))
465
    self.assertEqual(live_data_row[field_index["sinst_list"]],
466
                     (constants.RS_NORMAL,
467
                      list(node_to_secondary[live_data_name])))
468

    
469
  def testGetLiveNodeField(self):
470
    nodes = [
471
      objects.Node(name="node1", drained=False, offline=False,
472
                   vm_capable=True),
473
      objects.Node(name="node2", drained=True, offline=False,
474
                   vm_capable=True),
475
      objects.Node(name="node3", drained=False, offline=False,
476
                   vm_capable=True),
477
      objects.Node(name="node4", drained=False, offline=True,
478
                   vm_capable=True),
479
      objects.Node(name="node5", drained=False, offline=False,
480
                   vm_capable=False),
481
      ]
482
    live_data = dict.fromkeys([node.name for node in nodes], {})
483

    
484
    # No data
485
    nqd = query.NodeQueryData(None, None, None, None, None, None, None, None)
486
    self.assertEqual(query._GetLiveNodeField("hello", constants.QFT_NUMBER,
487
                                             nqd, nodes[0]),
488
                     query._FS_NODATA)
489

    
490
    # Missing field
491
    ctx = _QueryData(None, curlive_data={
492
      "some": 1,
493
      "other": 2,
494
      })
495
    self.assertEqual(query._GetLiveNodeField("hello", constants.QFT_NUMBER,
496
                                             ctx, nodes[0]),
497
                     query._FS_UNAVAIL)
498

    
499
    # Wrong format/datatype
500
    ctx = _QueryData(None, curlive_data={
501
      "hello": ["Hello World"],
502
      "other": 2,
503
      })
504
    self.assertEqual(query._GetLiveNodeField("hello", constants.QFT_NUMBER,
505
                                             ctx, nodes[0]),
506
                     query._FS_UNAVAIL)
507

    
508
    # Offline node
509
    assert nodes[3].offline
510
    ctx = _QueryData(None, curlive_data={})
511
    self.assertEqual(query._GetLiveNodeField("hello", constants.QFT_NUMBER,
512
                                             ctx, nodes[3]),
513
                     query._FS_OFFLINE, None)
514

    
515
    # Wrong field type
516
    ctx = _QueryData(None, curlive_data={"hello": 123})
517
    self.assertRaises(AssertionError, query._GetLiveNodeField,
518
                      "hello", constants.QFT_BOOL, ctx, nodes[0])
519

    
520
    # Non-vm_capable node
521
    assert not nodes[4].vm_capable
522
    ctx = _QueryData(None, curlive_data={})
523
    self.assertEqual(query._GetLiveNodeField("hello", constants.QFT_NUMBER,
524
                                             ctx, nodes[4]),
525
                     query._FS_UNAVAIL, None)
526

    
527

    
528
class TestInstanceQuery(unittest.TestCase):
529
  def _Create(self, selected):
530
    return query.Query(query.INSTANCE_FIELDS, selected)
531

    
532
  def testSimple(self):
533
    q = self._Create(["name", "be/memory", "ip"])
534
    self.assertEqual(q.RequestedData(), set([query.IQ_CONFIG]))
535

    
536
    cluster = objects.Cluster(cluster_name="testcluster",
537
      hvparams=constants.HVC_DEFAULTS,
538
      beparams={
539
        constants.PP_DEFAULT: constants.BEC_DEFAULTS,
540
        },
541
      nicparams={
542
        constants.PP_DEFAULT: constants.NICC_DEFAULTS,
543
        })
544

    
545
    instances = [
546
      objects.Instance(name="inst1", hvparams={}, beparams={}, nics=[]),
547
      objects.Instance(name="inst2", hvparams={}, nics=[],
548
        beparams={
549
          constants.BE_MEMORY: 512,
550
        }),
551
      objects.Instance(name="inst3", hvparams={}, beparams={},
552
        nics=[objects.NIC(ip="192.0.2.99", nicparams={})]),
553
      ]
554

    
555
    iqd = query.InstanceQueryData(instances, cluster, None, [], [], {},
556
                                  set(), {})
557
    self.assertEqual(q.Query(iqd),
558
      [[(constants.RS_NORMAL, "inst1"),
559
        (constants.RS_NORMAL, 128),
560
        (constants.RS_UNAVAIL, None),
561
       ],
562
       [(constants.RS_NORMAL, "inst2"),
563
        (constants.RS_NORMAL, 512),
564
        (constants.RS_UNAVAIL, None),
565
       ],
566
       [(constants.RS_NORMAL, "inst3"),
567
        (constants.RS_NORMAL, 128),
568
        (constants.RS_NORMAL, "192.0.2.99"),
569
       ]])
570
    self.assertEqual(q.OldStyleQuery(iqd),
571
      [["inst1", 128, None],
572
       ["inst2", 512, None],
573
       ["inst3", 128, "192.0.2.99"]])
574

    
575
  def test(self):
576
    selected = query.INSTANCE_FIELDS.keys()
577
    fieldidx = dict((field, idx) for idx, field in enumerate(selected))
578

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

    
582
    q = self._Create(selected)
583
    self.assertEqual(q.RequestedData(),
584
                     set([query.IQ_CONFIG, query.IQ_LIVE, query.IQ_DISKUSAGE,
585
                          query.IQ_CONSOLE]))
586

    
587
    cluster = objects.Cluster(cluster_name="testcluster",
588
      hvparams=constants.HVC_DEFAULTS,
589
      beparams={
590
        constants.PP_DEFAULT: constants.BEC_DEFAULTS,
591
        },
592
      nicparams={
593
        constants.PP_DEFAULT: constants.NICC_DEFAULTS,
594
        },
595
      os_hvp={},
596
      tcpudp_port_pool=set())
597

    
598
    offline_nodes = ["nodeoff1", "nodeoff2"]
599
    bad_nodes = ["nodebad1", "nodebad2", "nodebad3"] + offline_nodes
600
    nodes = ["node%s" % i for i in range(10)] + bad_nodes
601

    
602
    instances = [
603
      objects.Instance(name="inst1", hvparams={}, beparams={}, nics=[],
604
        uuid="f90eccb3-e227-4e3c-bf2a-94a21ca8f9cd",
605
        ctime=1291244000, mtime=1291244400, serial_no=30,
606
        admin_up=True, hypervisor=constants.HT_XEN_PVM, os="linux1",
607
        primary_node="node1",
608
        disk_template=constants.DT_PLAIN,
609
        disks=[]),
610
      objects.Instance(name="inst2", hvparams={}, nics=[],
611
        uuid="73a0f8a7-068c-4630-ada2-c3440015ab1a",
612
        ctime=1291211000, mtime=1291211077, serial_no=1,
613
        admin_up=True, hypervisor=constants.HT_XEN_HVM, os="deb99",
614
        primary_node="node5",
615
        disk_template=constants.DT_DISKLESS,
616
        disks=[],
617
        beparams={
618
          constants.BE_MEMORY: 512,
619
        }),
620
      objects.Instance(name="inst3", hvparams={}, beparams={},
621
        uuid="11ec8dff-fb61-4850-bfe0-baa1803ff280",
622
        ctime=1291011000, mtime=1291013000, serial_no=1923,
623
        admin_up=False, hypervisor=constants.HT_KVM, os="busybox",
624
        primary_node="node6",
625
        disk_template=constants.DT_DRBD8,
626
        disks=[],
627
        nics=[
628
          objects.NIC(ip="192.0.2.99", mac=macs.pop(),
629
                      nicparams={
630
                        constants.NIC_LINK: constants.DEFAULT_BRIDGE,
631
                        }),
632
          objects.NIC(ip=None, mac=macs.pop(), nicparams={}),
633
          ]),
634
      objects.Instance(name="inst4", hvparams={}, beparams={},
635
        uuid="68dab168-3ef5-4c9d-b4d3-801e0672068c",
636
        ctime=1291244390, mtime=1291244395, serial_no=25,
637
        admin_up=False, hypervisor=constants.HT_XEN_PVM, os="linux1",
638
        primary_node="nodeoff2",
639
        disk_template=constants.DT_DRBD8,
640
        disks=[],
641
        nics=[
642
          objects.NIC(ip="192.0.2.1", mac=macs.pop(),
643
                      nicparams={
644
                        constants.NIC_LINK: constants.DEFAULT_BRIDGE,
645
                        }),
646
          objects.NIC(ip="192.0.2.2", mac=macs.pop(), nicparams={}),
647
          objects.NIC(ip="192.0.2.3", mac=macs.pop(),
648
                      nicparams={
649
                        constants.NIC_MODE: constants.NIC_MODE_ROUTED,
650
                        }),
651
          objects.NIC(ip="192.0.2.4", mac=macs.pop(),
652
                      nicparams={
653
                        constants.NIC_MODE: constants.NIC_MODE_BRIDGED,
654
                        constants.NIC_LINK: "eth123",
655
                        }),
656
          ]),
657
      objects.Instance(name="inst5", hvparams={}, nics=[],
658
        uuid="0e3dca12-5b42-4e24-98a2-415267545bd0",
659
        ctime=1231211000, mtime=1261200000, serial_no=3,
660
        admin_up=True, hypervisor=constants.HT_XEN_HVM, os="deb99",
661
        primary_node="nodebad2",
662
        disk_template=constants.DT_DISKLESS,
663
        disks=[],
664
        beparams={
665
          constants.BE_MEMORY: 512,
666
        }),
667
      objects.Instance(name="inst6", hvparams={}, nics=[],
668
        uuid="72de6580-c8d5-4661-b902-38b5785bb8b3",
669
        ctime=7513, mtime=11501, serial_no=13390,
670
        admin_up=False, hypervisor=constants.HT_XEN_HVM, os="deb99",
671
        primary_node="node7",
672
        disk_template=constants.DT_DISKLESS,
673
        disks=[],
674
        beparams={
675
          constants.BE_MEMORY: 768,
676
        }),
677
      objects.Instance(name="inst7", hvparams={}, nics=[],
678
        uuid="ceec5dc4-b729-4f42-ae28-69b3cd24920e",
679
        ctime=None, mtime=None, serial_no=1947,
680
        admin_up=False, hypervisor=constants.HT_XEN_HVM, os="deb99",
681
        primary_node="node6",
682
        disk_template=constants.DT_DISKLESS,
683
        disks=[],
684
        beparams={}),
685
      ]
686

    
687
    assert not utils.FindDuplicates(inst.name for inst in instances)
688

    
689
    instbyname = dict((inst.name, inst) for inst in instances)
690

    
691
    disk_usage = dict((inst.name,
692
                       cmdlib._ComputeDiskSize(inst.disk_template,
693
                                               [{"size": disk.size}
694
                                                for disk in inst.disks]))
695
                      for inst in instances)
696

    
697
    inst_bridges = {
698
      "inst3": [constants.DEFAULT_BRIDGE, constants.DEFAULT_BRIDGE],
699
      "inst4": [constants.DEFAULT_BRIDGE, constants.DEFAULT_BRIDGE,
700
                None, "eth123"],
701
      }
702

    
703
    live_data = {
704
      "inst2": {
705
        "vcpus": 3,
706
        },
707
      "inst4": {
708
        "memory": 123,
709
        },
710
      "inst6": {
711
        "memory": 768,
712
        },
713
      "inst7": {
714
        "vcpus": 3,
715
        },
716
      }
717
    wrongnode_inst = set(["inst7"])
718

    
719
    consinfo = dict((inst.name, None) for inst in instances)
720
    consinfo["inst7"] = \
721
      objects.InstanceConsole(instance="inst7", kind=constants.CONS_SSH,
722
                              host=instbyname["inst7"].primary_node,
723
                              user=constants.GANETI_RUNAS,
724
                              command=["hostname"]).ToDict()
725

    
726
    iqd = query.InstanceQueryData(instances, cluster, disk_usage,
727
                                  offline_nodes, bad_nodes, live_data,
728
                                  wrongnode_inst, consinfo)
729
    result = q.Query(iqd)
730
    self.assertEqual(len(result), len(instances))
731
    self.assert_(compat.all(len(row) == len(selected)
732
                            for row in result))
733

    
734
    assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
735
           "Offline nodes not included in bad nodes"
736

    
737
    tested_status = set()
738

    
739
    for (inst, row) in zip(instances, result):
740
      assert inst.primary_node in nodes
741

    
742
      self.assertEqual(row[fieldidx["name"]],
743
                       (constants.RS_NORMAL, inst.name))
744

    
745
      if inst.primary_node in offline_nodes:
746
        exp_status = "ERROR_nodeoffline"
747
      elif inst.primary_node in bad_nodes:
748
        exp_status = "ERROR_nodedown"
749
      elif inst.name in live_data:
750
        if inst.name in wrongnode_inst:
751
          exp_status = "ERROR_wrongnode"
752
        elif inst.admin_up:
753
          exp_status = "running"
754
        else:
755
          exp_status = "ERROR_up"
756
      elif inst.admin_up:
757
        exp_status = "ERROR_down"
758
      else:
759
        exp_status = "ADMIN_down"
760

    
761
      self.assertEqual(row[fieldidx["status"]],
762
                       (constants.RS_NORMAL, exp_status))
763

    
764
      (_, status) = row[fieldidx["status"]]
765
      tested_status.add(status)
766

    
767
      for (field, livefield) in [("oper_ram", "memory"),
768
                                 ("oper_vcpus", "vcpus")]:
769
        if inst.primary_node in bad_nodes:
770
          exp = (constants.RS_NODATA, None)
771
        elif inst.name in live_data:
772
          value = live_data[inst.name].get(livefield, None)
773
          if value is None:
774
            exp = (constants.RS_UNAVAIL, None)
775
          else:
776
            exp = (constants.RS_NORMAL, value)
777
        else:
778
          exp = (constants.RS_UNAVAIL, None)
779

    
780
        self.assertEqual(row[fieldidx[field]], exp)
781

    
782
      bridges = inst_bridges.get(inst.name, [])
783
      self.assertEqual(row[fieldidx["nic.bridges"]],
784
                       (constants.RS_NORMAL, bridges))
785
      if bridges:
786
        self.assertEqual(row[fieldidx["bridge"]],
787
                         (constants.RS_NORMAL, bridges[0]))
788
      else:
789
        self.assertEqual(row[fieldidx["bridge"]],
790
                         (constants.RS_UNAVAIL, None))
791

    
792
      for i in range(constants.MAX_NICS):
793
        if i < len(bridges) and bridges[i] is not None:
794
          exp = (constants.RS_NORMAL, bridges[i])
795
        else:
796
          exp = (constants.RS_UNAVAIL, None)
797
        self.assertEqual(row[fieldidx["nic.bridge/%s" % i]], exp)
798

    
799
      if inst.primary_node in bad_nodes:
800
        exp = (constants.RS_NODATA, None)
801
      else:
802
        exp = (constants.RS_NORMAL, inst.name in live_data)
803
      self.assertEqual(row[fieldidx["oper_state"]], exp)
804

    
805
      usage = disk_usage[inst.name]
806
      if usage is None:
807
        usage = 0
808
      self.assertEqual(row[fieldidx["disk_usage"]],
809
                       (constants.RS_NORMAL, usage))
810

    
811
      self.assertEqual(row[fieldidx["sda_size"]], row[fieldidx["disk.size/0"]])
812
      self.assertEqual(row[fieldidx["sdb_size"]], row[fieldidx["disk.size/1"]])
813

    
814
      for field in ["ctime", "mtime"]:
815
        if getattr(inst, field) is None:
816
          # No ctime/mtime
817
          exp = (constants.RS_UNAVAIL, None)
818
        else:
819
          exp = (constants.RS_NORMAL, getattr(inst, field))
820
        self.assertEqual(row[fieldidx[field]], exp)
821

    
822
      self._CheckInstanceConsole(inst, row[fieldidx["console"]])
823

    
824
    # Ensure all possible status' have been tested
825
    self.assertEqual(tested_status,
826
                     set(["ERROR_nodeoffline", "ERROR_nodedown",
827
                          "running", "ERROR_up", "ERROR_down",
828
                          "ADMIN_down", "ERROR_wrongnode"]))
829

    
830
  def _CheckInstanceConsole(self, instance, (status, consdata)):
831
    if instance.name == "inst7":
832
      self.assertEqual(status, constants.RS_NORMAL)
833
      console = objects.InstanceConsole.FromDict(consdata)
834
      self.assertTrue(console.Validate())
835
      self.assertEqual(console.host, instance.primary_node)
836
    else:
837
      self.assertEqual(status, constants.RS_UNAVAIL)
838

    
839

    
840
class TestGroupQuery(unittest.TestCase):
841

    
842
  def setUp(self):
843
    self.groups = [
844
      objects.NodeGroup(name="default",
845
                        uuid="c0e89160-18e7-11e0-a46e-001d0904baeb",
846
                        alloc_policy=constants.ALLOC_POLICY_PREFERRED),
847
      objects.NodeGroup(name="restricted",
848
                        uuid="d2a40a74-18e7-11e0-9143-001d0904baeb",
849
                        alloc_policy=constants.ALLOC_POLICY_LAST_RESORT),
850
      ]
851

    
852
  def _Create(self, selected):
853
    return query.Query(query.GROUP_FIELDS, selected)
854

    
855
  def testSimple(self):
856
    q = self._Create(["name", "uuid", "alloc_policy"])
857
    gqd = query.GroupQueryData(self.groups, None, None)
858

    
859
    self.assertEqual(q.RequestedData(), set([query.GQ_CONFIG]))
860

    
861
    self.assertEqual(q.Query(gqd),
862
      [[(constants.RS_NORMAL, "default"),
863
        (constants.RS_NORMAL, "c0e89160-18e7-11e0-a46e-001d0904baeb"),
864
        (constants.RS_NORMAL, constants.ALLOC_POLICY_PREFERRED)
865
        ],
866
       [(constants.RS_NORMAL, "restricted"),
867
        (constants.RS_NORMAL, "d2a40a74-18e7-11e0-9143-001d0904baeb"),
868
        (constants.RS_NORMAL, constants.ALLOC_POLICY_LAST_RESORT)
869
        ],
870
       ])
871

    
872
  def testNodes(self):
873
    groups_to_nodes = {
874
      "c0e89160-18e7-11e0-a46e-001d0904baeb": ["node1", "node2"],
875
      "d2a40a74-18e7-11e0-9143-001d0904baeb": ["node1", "node10", "node9"],
876
      }
877

    
878
    q = self._Create(["name", "node_cnt", "node_list"])
879
    gqd = query.GroupQueryData(self.groups, groups_to_nodes, None)
880

    
881
    self.assertEqual(q.RequestedData(), set([query.GQ_CONFIG, query.GQ_NODE]))
882

    
883
    self.assertEqual(q.Query(gqd),
884
                     [[(constants.RS_NORMAL, "default"),
885
                       (constants.RS_NORMAL, 2),
886
                       (constants.RS_NORMAL, ["node1", "node2"]),
887
                       ],
888
                      [(constants.RS_NORMAL, "restricted"),
889
                       (constants.RS_NORMAL, 3),
890
                       (constants.RS_NORMAL, ["node1", "node9", "node10"]),
891
                       ],
892
                      ])
893

    
894
  def testInstances(self):
895
    groups_to_instances = {
896
      "c0e89160-18e7-11e0-a46e-001d0904baeb": ["inst1", "inst2"],
897
      "d2a40a74-18e7-11e0-9143-001d0904baeb": ["inst1", "inst10", "inst9"],
898
      }
899

    
900
    q = self._Create(["pinst_cnt", "pinst_list"])
901
    gqd = query.GroupQueryData(self.groups, None, groups_to_instances)
902

    
903
    self.assertEqual(q.RequestedData(), set([query.GQ_INST]))
904

    
905
    self.assertEqual(q.Query(gqd),
906
                     [[(constants.RS_NORMAL, 2),
907
                       (constants.RS_NORMAL, ["inst1", "inst2"]),
908
                       ],
909
                      [(constants.RS_NORMAL, 3),
910
                       (constants.RS_NORMAL, ["inst1", "inst9", "inst10"]),
911
                       ],
912
                      ])
913

    
914

    
915
class TestQueryFields(unittest.TestCase):
916
  def testAllFields(self):
917
    for fielddefs in query.ALL_FIELD_LISTS:
918
      result = query.QueryFields(fielddefs, None)
919
      self.assert_(isinstance(result, dict))
920
      response = objects.QueryFieldsResponse.FromDict(result)
921
      self.assertEqual([(fdef.name, fdef.title) for fdef in response.fields],
922
        [(fdef2.name, fdef2.title)
923
         for (fdef2, _, _) in utils.NiceSort(fielddefs.values(),
924
                                             key=lambda x: x[0].name)])
925

    
926
  def testSomeFields(self):
927
    rnd = random.Random(5357)
928

    
929
    for _ in range(10):
930
      for fielddefs in query.ALL_FIELD_LISTS:
931
        if len(fielddefs) > 20:
932
          sample_size = rnd.randint(5, 20)
933
        else:
934
          sample_size = rnd.randint(1, max(1, len(fielddefs) - 1))
935
        fields = [fdef for (fdef, _, _) in rnd.sample(fielddefs.values(),
936
                                                      sample_size)]
937
        result = query.QueryFields(fielddefs, [fdef.name for fdef in fields])
938
        self.assert_(isinstance(result, dict))
939
        response = objects.QueryFieldsResponse.FromDict(result)
940
        self.assertEqual([(fdef.name, fdef.title) for fdef in response.fields],
941
                         [(fdef2.name, fdef2.title) for fdef2 in fields])
942

    
943

    
944
if __name__ == "__main__":
945
  testutils.GanetiTestProgram()