NodeQuery: mark live fields as UNAVAIL for non-vm_capable nodes
[ganeti-local] / test / ganeti.query_unittest.py
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       }
714     wrongnode_inst = set("inst2")
715
716     consinfo = dict((inst.name, None) for inst in instances)
717     consinfo["inst7"] = \
718       objects.InstanceConsole(instance="inst7", kind=constants.CONS_SSH,
719                               host=instbyname["inst7"].primary_node,
720                               user=constants.GANETI_RUNAS,
721                               command=["hostname"]).ToDict()
722
723     iqd = query.InstanceQueryData(instances, cluster, disk_usage,
724                                   offline_nodes, bad_nodes, live_data,
725                                   wrongnode_inst, consinfo)
726     result = q.Query(iqd)
727     self.assertEqual(len(result), len(instances))
728     self.assert_(compat.all(len(row) == len(selected)
729                             for row in result))
730
731     assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
732            "Offline nodes not included in bad nodes"
733
734     tested_status = set()
735
736     for (inst, row) in zip(instances, result):
737       assert inst.primary_node in nodes
738
739       self.assertEqual(row[fieldidx["name"]],
740                        (constants.RS_NORMAL, inst.name))
741
742       if inst.primary_node in offline_nodes:
743         exp_status = "ERROR_nodeoffline"
744       elif inst.primary_node in bad_nodes:
745         exp_status = "ERROR_nodedown"
746       elif inst.name in live_data:
747         if inst.name in wrongnode_inst:
748           exp_status = "ERROR_wrongnode"
749         elif inst.admin_up:
750           exp_status = "running"
751         else:
752           exp_status = "ERROR_up"
753       elif inst.admin_up:
754         exp_status = "ERROR_down"
755       else:
756         exp_status = "ADMIN_down"
757
758       self.assertEqual(row[fieldidx["status"]],
759                        (constants.RS_NORMAL, exp_status))
760
761       (_, status) = row[fieldidx["status"]]
762       tested_status.add(status)
763
764       for (field, livefield) in [("oper_ram", "memory"),
765                                  ("oper_vcpus", "vcpus")]:
766         if inst.primary_node in bad_nodes:
767           exp = (constants.RS_NODATA, None)
768         elif inst.name in live_data:
769           value = live_data[inst.name].get(livefield, None)
770           if value is None:
771             exp = (constants.RS_UNAVAIL, None)
772           else:
773             exp = (constants.RS_NORMAL, value)
774         else:
775           exp = (constants.RS_UNAVAIL, None)
776
777         self.assertEqual(row[fieldidx[field]], exp)
778
779       bridges = inst_bridges.get(inst.name, [])
780       self.assertEqual(row[fieldidx["nic.bridges"]],
781                        (constants.RS_NORMAL, bridges))
782       if bridges:
783         self.assertEqual(row[fieldidx["bridge"]],
784                          (constants.RS_NORMAL, bridges[0]))
785       else:
786         self.assertEqual(row[fieldidx["bridge"]],
787                          (constants.RS_UNAVAIL, None))
788
789       for i in range(constants.MAX_NICS):
790         if i < len(bridges) and bridges[i] is not None:
791           exp = (constants.RS_NORMAL, bridges[i])
792         else:
793           exp = (constants.RS_UNAVAIL, None)
794         self.assertEqual(row[fieldidx["nic.bridge/%s" % i]], exp)
795
796       if inst.primary_node in bad_nodes:
797         exp = (constants.RS_NODATA, None)
798       else:
799         exp = (constants.RS_NORMAL, inst.name in live_data)
800       self.assertEqual(row[fieldidx["oper_state"]], exp)
801
802       usage = disk_usage[inst.name]
803       if usage is None:
804         usage = 0
805       self.assertEqual(row[fieldidx["disk_usage"]],
806                        (constants.RS_NORMAL, usage))
807
808       self.assertEqual(row[fieldidx["sda_size"]], row[fieldidx["disk.size/0"]])
809       self.assertEqual(row[fieldidx["sdb_size"]], row[fieldidx["disk.size/1"]])
810
811       for field in ["ctime", "mtime"]:
812         if getattr(inst, field) is None:
813           # No ctime/mtime
814           exp = (constants.RS_UNAVAIL, None)
815         else:
816           exp = (constants.RS_NORMAL, getattr(inst, field))
817         self.assertEqual(row[fieldidx[field]], exp)
818
819       self._CheckInstanceConsole(inst, row[fieldidx["console"]])
820
821     # Ensure all possible status' have been tested
822     self.assertEqual(tested_status,
823                      set(["ERROR_nodeoffline", "ERROR_nodedown",
824                           "running", "ERROR_up", "ERROR_down",
825                           "ADMIN_down"]))
826
827   def _CheckInstanceConsole(self, instance, (status, consdata)):
828     if instance.name == "inst7":
829       self.assertEqual(status, constants.RS_NORMAL)
830       console = objects.InstanceConsole.FromDict(consdata)
831       self.assertTrue(console.Validate())
832       self.assertEqual(console.host, instance.primary_node)
833     else:
834       self.assertEqual(status, constants.RS_UNAVAIL)
835
836
837 class TestGroupQuery(unittest.TestCase):
838
839   def setUp(self):
840     self.groups = [
841       objects.NodeGroup(name="default",
842                         uuid="c0e89160-18e7-11e0-a46e-001d0904baeb",
843                         alloc_policy=constants.ALLOC_POLICY_PREFERRED),
844       objects.NodeGroup(name="restricted",
845                         uuid="d2a40a74-18e7-11e0-9143-001d0904baeb",
846                         alloc_policy=constants.ALLOC_POLICY_LAST_RESORT),
847       ]
848
849   def _Create(self, selected):
850     return query.Query(query.GROUP_FIELDS, selected)
851
852   def testSimple(self):
853     q = self._Create(["name", "uuid", "alloc_policy"])
854     gqd = query.GroupQueryData(self.groups, None, None)
855
856     self.assertEqual(q.RequestedData(), set([query.GQ_CONFIG]))
857
858     self.assertEqual(q.Query(gqd),
859       [[(constants.RS_NORMAL, "default"),
860         (constants.RS_NORMAL, "c0e89160-18e7-11e0-a46e-001d0904baeb"),
861         (constants.RS_NORMAL, constants.ALLOC_POLICY_PREFERRED)
862         ],
863        [(constants.RS_NORMAL, "restricted"),
864         (constants.RS_NORMAL, "d2a40a74-18e7-11e0-9143-001d0904baeb"),
865         (constants.RS_NORMAL, constants.ALLOC_POLICY_LAST_RESORT)
866         ],
867        ])
868
869   def testNodes(self):
870     groups_to_nodes = {
871       "c0e89160-18e7-11e0-a46e-001d0904baeb": ["node1", "node2"],
872       "d2a40a74-18e7-11e0-9143-001d0904baeb": ["node1", "node10", "node9"],
873       }
874
875     q = self._Create(["name", "node_cnt", "node_list"])
876     gqd = query.GroupQueryData(self.groups, groups_to_nodes, None)
877
878     self.assertEqual(q.RequestedData(), set([query.GQ_CONFIG, query.GQ_NODE]))
879
880     self.assertEqual(q.Query(gqd),
881                      [[(constants.RS_NORMAL, "default"),
882                        (constants.RS_NORMAL, 2),
883                        (constants.RS_NORMAL, ["node1", "node2"]),
884                        ],
885                       [(constants.RS_NORMAL, "restricted"),
886                        (constants.RS_NORMAL, 3),
887                        (constants.RS_NORMAL, ["node1", "node9", "node10"]),
888                        ],
889                       ])
890
891   def testInstances(self):
892     groups_to_instances = {
893       "c0e89160-18e7-11e0-a46e-001d0904baeb": ["inst1", "inst2"],
894       "d2a40a74-18e7-11e0-9143-001d0904baeb": ["inst1", "inst10", "inst9"],
895       }
896
897     q = self._Create(["pinst_cnt", "pinst_list"])
898     gqd = query.GroupQueryData(self.groups, None, groups_to_instances)
899
900     self.assertEqual(q.RequestedData(), set([query.GQ_INST]))
901
902     self.assertEqual(q.Query(gqd),
903                      [[(constants.RS_NORMAL, 2),
904                        (constants.RS_NORMAL, ["inst1", "inst2"]),
905                        ],
906                       [(constants.RS_NORMAL, 3),
907                        (constants.RS_NORMAL, ["inst1", "inst9", "inst10"]),
908                        ],
909                       ])
910
911
912 class TestQueryFields(unittest.TestCase):
913   def testAllFields(self):
914     for fielddefs in query.ALL_FIELD_LISTS:
915       result = query.QueryFields(fielddefs, None)
916       self.assert_(isinstance(result, dict))
917       response = objects.QueryFieldsResponse.FromDict(result)
918       self.assertEqual([(fdef.name, fdef.title) for fdef in response.fields],
919         [(fdef2.name, fdef2.title)
920          for (fdef2, _, _) in utils.NiceSort(fielddefs.values(),
921                                              key=lambda x: x[0].name)])
922
923   def testSomeFields(self):
924     rnd = random.Random(5357)
925
926     for _ in range(10):
927       for fielddefs in query.ALL_FIELD_LISTS:
928         if len(fielddefs) > 20:
929           sample_size = rnd.randint(5, 20)
930         else:
931           sample_size = rnd.randint(1, max(1, len(fielddefs) - 1))
932         fields = [fdef for (fdef, _, _) in rnd.sample(fielddefs.values(),
933                                                       sample_size)]
934         result = query.QueryFields(fielddefs, [fdef.name for fdef in fields])
935         self.assert_(isinstance(result, dict))
936         response = objects.QueryFieldsResponse.FromDict(result)
937         self.assertEqual([(fdef.name, fdef.title) for fdef in response.fields],
938                          [(fdef2.name, fdef2.title) for fdef2 in fields])
939
940
941 if __name__ == "__main__":
942   testutils.GanetiTestProgram()