Statistics
| Branch: | Tag: | Revision:

root / test / ganeti.query_unittest.py @ 5d28cb6f

History | View | Annotate | Download (34.7 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=False,
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
      objects.Node(name="node2", drained=True, offline=False),
473
      objects.Node(name="node3", drained=False, offline=False),
474
      objects.Node(name="node4", drained=False, offline=True),
475
      ]
476
    live_data = dict.fromkeys([node.name for node in nodes], {})
477

    
478
    # No data
479
    nqd = query.NodeQueryData(None, None, None, None, None, None, None, None)
480
    self.assertEqual(query._GetLiveNodeField("hello", constants.QFT_NUMBER,
481
                                             nqd, nodes[0]),
482
                     query._FS_NODATA)
483

    
484
    # Missing field
485
    ctx = _QueryData(None, curlive_data={
486
      "some": 1,
487
      "other": 2,
488
      })
489
    self.assertEqual(query._GetLiveNodeField("hello", constants.QFT_NUMBER,
490
                                             ctx, nodes[0]),
491
                     query._FS_UNAVAIL)
492

    
493
    # Wrong format/datatype
494
    ctx = _QueryData(None, curlive_data={
495
      "hello": ["Hello World"],
496
      "other": 2,
497
      })
498
    self.assertEqual(query._GetLiveNodeField("hello", constants.QFT_NUMBER,
499
                                             ctx, nodes[0]),
500
                     query._FS_UNAVAIL)
501

    
502
    # Offline node
503
    assert nodes[3].offline
504
    ctx = _QueryData(None, curlive_data={})
505
    self.assertEqual(query._GetLiveNodeField("hello", constants.QFT_NUMBER,
506
                                             ctx, nodes[3]),
507
                     query._FS_OFFLINE, None)
508

    
509
    # Wrong field type
510
    ctx = _QueryData(None, curlive_data={"hello": 123})
511
    self.assertRaises(AssertionError, query._GetLiveNodeField,
512
                      "hello", constants.QFT_BOOL, ctx, nodes[0])
513

    
514

    
515
class TestInstanceQuery(unittest.TestCase):
516
  def _Create(self, selected):
517
    return query.Query(query.INSTANCE_FIELDS, selected)
518

    
519
  def testSimple(self):
520
    q = self._Create(["name", "be/memory", "ip"])
521
    self.assertEqual(q.RequestedData(), set([query.IQ_CONFIG]))
522

    
523
    cluster = objects.Cluster(cluster_name="testcluster",
524
      hvparams=constants.HVC_DEFAULTS,
525
      beparams={
526
        constants.PP_DEFAULT: constants.BEC_DEFAULTS,
527
        },
528
      nicparams={
529
        constants.PP_DEFAULT: constants.NICC_DEFAULTS,
530
        })
531

    
532
    instances = [
533
      objects.Instance(name="inst1", hvparams={}, beparams={}, nics=[]),
534
      objects.Instance(name="inst2", hvparams={}, nics=[],
535
        beparams={
536
          constants.BE_MEMORY: 512,
537
        }),
538
      objects.Instance(name="inst3", hvparams={}, beparams={},
539
        nics=[objects.NIC(ip="192.0.2.99", nicparams={})]),
540
      ]
541

    
542
    iqd = query.InstanceQueryData(instances, cluster, None, [], [], {},
543
                                  set(), {})
544
    self.assertEqual(q.Query(iqd),
545
      [[(constants.RS_NORMAL, "inst1"),
546
        (constants.RS_NORMAL, 128),
547
        (constants.RS_UNAVAIL, None),
548
       ],
549
       [(constants.RS_NORMAL, "inst2"),
550
        (constants.RS_NORMAL, 512),
551
        (constants.RS_UNAVAIL, None),
552
       ],
553
       [(constants.RS_NORMAL, "inst3"),
554
        (constants.RS_NORMAL, 128),
555
        (constants.RS_NORMAL, "192.0.2.99"),
556
       ]])
557
    self.assertEqual(q.OldStyleQuery(iqd),
558
      [["inst1", 128, None],
559
       ["inst2", 512, None],
560
       ["inst3", 128, "192.0.2.99"]])
561

    
562
  def test(self):
563
    selected = query.INSTANCE_FIELDS.keys()
564
    fieldidx = dict((field, idx) for idx, field in enumerate(selected))
565

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

    
569
    q = self._Create(selected)
570
    self.assertEqual(q.RequestedData(),
571
                     set([query.IQ_CONFIG, query.IQ_LIVE, query.IQ_DISKUSAGE,
572
                          query.IQ_CONSOLE]))
573

    
574
    cluster = objects.Cluster(cluster_name="testcluster",
575
      hvparams=constants.HVC_DEFAULTS,
576
      beparams={
577
        constants.PP_DEFAULT: constants.BEC_DEFAULTS,
578
        },
579
      nicparams={
580
        constants.PP_DEFAULT: constants.NICC_DEFAULTS,
581
        },
582
      os_hvp={},
583
      tcpudp_port_pool=set())
584

    
585
    offline_nodes = ["nodeoff1", "nodeoff2"]
586
    bad_nodes = ["nodebad1", "nodebad2", "nodebad3"] + offline_nodes
587
    nodes = ["node%s" % i for i in range(10)] + bad_nodes
588

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

    
674
    assert not utils.FindDuplicates(inst.name for inst in instances)
675

    
676
    instbyname = dict((inst.name, inst) for inst in instances)
677

    
678
    disk_usage = dict((inst.name,
679
                       cmdlib._ComputeDiskSize(inst.disk_template,
680
                                               [{"size": disk.size}
681
                                                for disk in inst.disks]))
682
                      for inst in instances)
683

    
684
    inst_bridges = {
685
      "inst3": [constants.DEFAULT_BRIDGE, constants.DEFAULT_BRIDGE],
686
      "inst4": [constants.DEFAULT_BRIDGE, constants.DEFAULT_BRIDGE,
687
                None, "eth123"],
688
      }
689

    
690
    live_data = {
691
      "inst2": {
692
        "vcpus": 3,
693
        },
694
      "inst4": {
695
        "memory": 123,
696
        },
697
      "inst6": {
698
        "memory": 768,
699
        },
700
      }
701
    wrongnode_inst = set("inst2")
702

    
703
    consinfo = dict((inst.name, None) for inst in instances)
704
    consinfo["inst7"] = \
705
      objects.InstanceConsole(instance="inst7", kind=constants.CONS_SSH,
706
                              host=instbyname["inst7"].primary_node,
707
                              user=constants.GANETI_RUNAS,
708
                              command=["hostname"]).ToDict()
709

    
710
    iqd = query.InstanceQueryData(instances, cluster, disk_usage,
711
                                  offline_nodes, bad_nodes, live_data,
712
                                  wrongnode_inst, consinfo)
713
    result = q.Query(iqd)
714
    self.assertEqual(len(result), len(instances))
715
    self.assert_(compat.all(len(row) == len(selected)
716
                            for row in result))
717

    
718
    assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
719
           "Offline nodes not included in bad nodes"
720

    
721
    tested_status = set()
722

    
723
    for (inst, row) in zip(instances, result):
724
      assert inst.primary_node in nodes
725

    
726
      self.assertEqual(row[fieldidx["name"]],
727
                       (constants.RS_NORMAL, inst.name))
728

    
729
      if inst.primary_node in offline_nodes:
730
        exp_status = "ERROR_nodeoffline"
731
      elif inst.primary_node in bad_nodes:
732
        exp_status = "ERROR_nodedown"
733
      elif inst.name in live_data:
734
        if inst.name in wrongnode_inst:
735
          exp_status = "ERROR_wrongnode"
736
        elif inst.admin_up:
737
          exp_status = "running"
738
        else:
739
          exp_status = "ERROR_up"
740
      elif inst.admin_up:
741
        exp_status = "ERROR_down"
742
      else:
743
        exp_status = "ADMIN_down"
744

    
745
      self.assertEqual(row[fieldidx["status"]],
746
                       (constants.RS_NORMAL, exp_status))
747

    
748
      (_, status) = row[fieldidx["status"]]
749
      tested_status.add(status)
750

    
751
      for (field, livefield) in [("oper_ram", "memory"),
752
                                 ("oper_vcpus", "vcpus")]:
753
        if inst.primary_node in bad_nodes:
754
          exp = (constants.RS_NODATA, None)
755
        elif inst.name in live_data:
756
          value = live_data[inst.name].get(livefield, None)
757
          if value is None:
758
            exp = (constants.RS_UNAVAIL, None)
759
          else:
760
            exp = (constants.RS_NORMAL, value)
761
        else:
762
          exp = (constants.RS_UNAVAIL, None)
763

    
764
        self.assertEqual(row[fieldidx[field]], exp)
765

    
766
      bridges = inst_bridges.get(inst.name, [])
767
      self.assertEqual(row[fieldidx["nic.bridges"]],
768
                       (constants.RS_NORMAL, bridges))
769
      if bridges:
770
        self.assertEqual(row[fieldidx["bridge"]],
771
                         (constants.RS_NORMAL, bridges[0]))
772
      else:
773
        self.assertEqual(row[fieldidx["bridge"]],
774
                         (constants.RS_UNAVAIL, None))
775

    
776
      for i in range(constants.MAX_NICS):
777
        if i < len(bridges) and bridges[i] is not None:
778
          exp = (constants.RS_NORMAL, bridges[i])
779
        else:
780
          exp = (constants.RS_UNAVAIL, None)
781
        self.assertEqual(row[fieldidx["nic.bridge/%s" % i]], exp)
782

    
783
      if inst.primary_node in bad_nodes:
784
        exp = (constants.RS_NODATA, None)
785
      else:
786
        exp = (constants.RS_NORMAL, inst.name in live_data)
787
      self.assertEqual(row[fieldidx["oper_state"]], exp)
788

    
789
      usage = disk_usage[inst.name]
790
      if usage is None:
791
        usage = 0
792
      self.assertEqual(row[fieldidx["disk_usage"]],
793
                       (constants.RS_NORMAL, usage))
794

    
795
      self.assertEqual(row[fieldidx["sda_size"]], row[fieldidx["disk.size/0"]])
796
      self.assertEqual(row[fieldidx["sdb_size"]], row[fieldidx["disk.size/1"]])
797

    
798
      for field in ["ctime", "mtime"]:
799
        if getattr(inst, field) is None:
800
          # No ctime/mtime
801
          exp = (constants.RS_UNAVAIL, None)
802
        else:
803
          exp = (constants.RS_NORMAL, getattr(inst, field))
804
        self.assertEqual(row[fieldidx[field]], exp)
805

    
806
      self._CheckInstanceConsole(inst, row[fieldidx["console"]])
807

    
808
    # Ensure all possible status' have been tested
809
    self.assertEqual(tested_status,
810
                     set(["ERROR_nodeoffline", "ERROR_nodedown",
811
                          "running", "ERROR_up", "ERROR_down",
812
                          "ADMIN_down"]))
813

    
814
  def _CheckInstanceConsole(self, instance, (status, consdata)):
815
    if instance.name == "inst7":
816
      self.assertEqual(status, constants.RS_NORMAL)
817
      console = objects.InstanceConsole.FromDict(consdata)
818
      self.assertTrue(console.Validate())
819
      self.assertEqual(console.host, instance.primary_node)
820
    else:
821
      self.assertEqual(status, constants.RS_UNAVAIL)
822

    
823

    
824
class TestGroupQuery(unittest.TestCase):
825

    
826
  def setUp(self):
827
    self.groups = [
828
      objects.NodeGroup(name="default",
829
                        uuid="c0e89160-18e7-11e0-a46e-001d0904baeb",
830
                        alloc_policy=constants.ALLOC_POLICY_PREFERRED),
831
      objects.NodeGroup(name="restricted",
832
                        uuid="d2a40a74-18e7-11e0-9143-001d0904baeb",
833
                        alloc_policy=constants.ALLOC_POLICY_LAST_RESORT),
834
      ]
835

    
836
  def _Create(self, selected):
837
    return query.Query(query.GROUP_FIELDS, selected)
838

    
839
  def testSimple(self):
840
    q = self._Create(["name", "uuid", "alloc_policy"])
841
    gqd = query.GroupQueryData(self.groups, None, None)
842

    
843
    self.assertEqual(q.RequestedData(), set([query.GQ_CONFIG]))
844

    
845
    self.assertEqual(q.Query(gqd),
846
      [[(constants.RS_NORMAL, "default"),
847
        (constants.RS_NORMAL, "c0e89160-18e7-11e0-a46e-001d0904baeb"),
848
        (constants.RS_NORMAL, constants.ALLOC_POLICY_PREFERRED)
849
        ],
850
       [(constants.RS_NORMAL, "restricted"),
851
        (constants.RS_NORMAL, "d2a40a74-18e7-11e0-9143-001d0904baeb"),
852
        (constants.RS_NORMAL, constants.ALLOC_POLICY_LAST_RESORT)
853
        ],
854
       ])
855

    
856
  def testNodes(self):
857
    groups_to_nodes = {
858
      "c0e89160-18e7-11e0-a46e-001d0904baeb": ["node1", "node2"],
859
      "d2a40a74-18e7-11e0-9143-001d0904baeb": ["node1", "node10", "node9"],
860
      }
861

    
862
    q = self._Create(["name", "node_cnt", "node_list"])
863
    gqd = query.GroupQueryData(self.groups, groups_to_nodes, None)
864

    
865
    self.assertEqual(q.RequestedData(), set([query.GQ_CONFIG, query.GQ_NODE]))
866

    
867
    self.assertEqual(q.Query(gqd),
868
                     [[(constants.RS_NORMAL, "default"),
869
                       (constants.RS_NORMAL, 2),
870
                       (constants.RS_NORMAL, ["node1", "node2"]),
871
                       ],
872
                      [(constants.RS_NORMAL, "restricted"),
873
                       (constants.RS_NORMAL, 3),
874
                       (constants.RS_NORMAL, ["node1", "node9", "node10"]),
875
                       ],
876
                      ])
877

    
878
  def testInstances(self):
879
    groups_to_instances = {
880
      "c0e89160-18e7-11e0-a46e-001d0904baeb": ["inst1", "inst2"],
881
      "d2a40a74-18e7-11e0-9143-001d0904baeb": ["inst1", "inst10", "inst9"],
882
      }
883

    
884
    q = self._Create(["pinst_cnt", "pinst_list"])
885
    gqd = query.GroupQueryData(self.groups, None, groups_to_instances)
886

    
887
    self.assertEqual(q.RequestedData(), set([query.GQ_INST]))
888

    
889
    self.assertEqual(q.Query(gqd),
890
                     [[(constants.RS_NORMAL, 2),
891
                       (constants.RS_NORMAL, ["inst1", "inst2"]),
892
                       ],
893
                      [(constants.RS_NORMAL, 3),
894
                       (constants.RS_NORMAL, ["inst1", "inst9", "inst10"]),
895
                       ],
896
                      ])
897

    
898

    
899
class TestQueryFields(unittest.TestCase):
900
  def testAllFields(self):
901
    for fielddefs in query.ALL_FIELD_LISTS:
902
      result = query.QueryFields(fielddefs, None)
903
      self.assert_(isinstance(result, dict))
904
      response = objects.QueryFieldsResponse.FromDict(result)
905
      self.assertEqual([(fdef.name, fdef.title) for fdef in response.fields],
906
        [(fdef2.name, fdef2.title)
907
         for (fdef2, _, _) in utils.NiceSort(fielddefs.values(),
908
                                             key=lambda x: x[0].name)])
909

    
910
  def testSomeFields(self):
911
    rnd = random.Random(5357)
912

    
913
    for _ in range(10):
914
      for fielddefs in query.ALL_FIELD_LISTS:
915
        if len(fielddefs) > 20:
916
          sample_size = rnd.randint(5, 20)
917
        else:
918
          sample_size = rnd.randint(1, max(1, len(fielddefs) - 1))
919
        fields = [fdef for (fdef, _, _) in rnd.sample(fielddefs.values(),
920
                                                      sample_size)]
921
        result = query.QueryFields(fielddefs, [fdef.name for fdef in fields])
922
        self.assert_(isinstance(result, dict))
923
        response = objects.QueryFieldsResponse.FromDict(result)
924
        self.assertEqual([(fdef.name, fdef.title) for fdef in response.fields],
925
                         [(fdef2.name, fdef2.title) for fdef2 in fields])
926

    
927

    
928
if __name__ == "__main__":
929
  testutils.GanetiTestProgram()