Ensure all resources are used by RAPI client
[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=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     self.assertEqual(q.Query(iqd),
544       [[(constants.RS_NORMAL, "inst1"),
545         (constants.RS_NORMAL, 128),
546         (constants.RS_UNAVAIL, None),
547        ],
548        [(constants.RS_NORMAL, "inst2"),
549         (constants.RS_NORMAL, 512),
550         (constants.RS_UNAVAIL, None),
551        ],
552        [(constants.RS_NORMAL, "inst3"),
553         (constants.RS_NORMAL, 128),
554         (constants.RS_NORMAL, "192.0.2.99"),
555        ]])
556     self.assertEqual(q.OldStyleQuery(iqd),
557       [["inst1", 128, None],
558        ["inst2", 512, None],
559        ["inst3", 128, "192.0.2.99"]])
560
561   def test(self):
562     selected = query.INSTANCE_FIELDS.keys()
563     fieldidx = dict((field, idx) for idx, field in enumerate(selected))
564
565     macs = ["00:11:22:%02x:%02x:%02x" % (i % 255, i % 3, (i * 123) % 255)
566             for i in range(20)]
567
568     q = self._Create(selected)
569     self.assertEqual(q.RequestedData(),
570                      set([query.IQ_CONFIG, query.IQ_LIVE, query.IQ_DISKUSAGE]))
571
572     cluster = objects.Cluster(cluster_name="testcluster",
573       hvparams=constants.HVC_DEFAULTS,
574       beparams={
575         constants.PP_DEFAULT: constants.BEC_DEFAULTS,
576         },
577       nicparams={
578         constants.PP_DEFAULT: constants.NICC_DEFAULTS,
579         },
580       os_hvp={},
581       tcpudp_port_pool=set())
582
583     offline_nodes = ["nodeoff1", "nodeoff2"]
584     bad_nodes = ["nodebad1", "nodebad2", "nodebad3"] + offline_nodes
585     nodes = ["node%s" % i for i in range(10)] + bad_nodes
586
587     instances = [
588       objects.Instance(name="inst1", hvparams={}, beparams={}, nics=[],
589         uuid="f90eccb3-e227-4e3c-bf2a-94a21ca8f9cd",
590         ctime=1291244000, mtime=1291244400, serial_no=30,
591         admin_up=True, hypervisor=constants.HT_XEN_PVM, os="linux1",
592         primary_node="node1",
593         disk_template=constants.DT_PLAIN,
594         disks=[]),
595       objects.Instance(name="inst2", hvparams={}, nics=[],
596         uuid="73a0f8a7-068c-4630-ada2-c3440015ab1a",
597         ctime=1291211000, mtime=1291211077, serial_no=1,
598         admin_up=True, hypervisor=constants.HT_XEN_HVM, os="deb99",
599         primary_node="node5",
600         disk_template=constants.DT_DISKLESS,
601         disks=[],
602         beparams={
603           constants.BE_MEMORY: 512,
604         }),
605       objects.Instance(name="inst3", hvparams={}, beparams={},
606         uuid="11ec8dff-fb61-4850-bfe0-baa1803ff280",
607         ctime=1291011000, mtime=1291013000, serial_no=1923,
608         admin_up=False, hypervisor=constants.HT_KVM, os="busybox",
609         primary_node="node6",
610         disk_template=constants.DT_DRBD8,
611         disks=[],
612         nics=[
613           objects.NIC(ip="192.0.2.99", mac=macs.pop(),
614                       nicparams={
615                         constants.NIC_LINK: constants.DEFAULT_BRIDGE,
616                         }),
617           objects.NIC(ip=None, mac=macs.pop(), nicparams={}),
618           ]),
619       objects.Instance(name="inst4", hvparams={}, beparams={},
620         uuid="68dab168-3ef5-4c9d-b4d3-801e0672068c",
621         ctime=1291244390, mtime=1291244395, serial_no=25,
622         admin_up=False, hypervisor=constants.HT_XEN_PVM, os="linux1",
623         primary_node="nodeoff2",
624         disk_template=constants.DT_DRBD8,
625         disks=[],
626         nics=[
627           objects.NIC(ip="192.0.2.1", mac=macs.pop(),
628                       nicparams={
629                         constants.NIC_LINK: constants.DEFAULT_BRIDGE,
630                         }),
631           objects.NIC(ip="192.0.2.2", mac=macs.pop(), nicparams={}),
632           objects.NIC(ip="192.0.2.3", mac=macs.pop(),
633                       nicparams={
634                         constants.NIC_MODE: constants.NIC_MODE_ROUTED,
635                         }),
636           objects.NIC(ip="192.0.2.4", mac=macs.pop(),
637                       nicparams={
638                         constants.NIC_MODE: constants.NIC_MODE_BRIDGED,
639                         constants.NIC_LINK: "eth123",
640                         }),
641           ]),
642       objects.Instance(name="inst5", hvparams={}, nics=[],
643         uuid="0e3dca12-5b42-4e24-98a2-415267545bd0",
644         ctime=1231211000, mtime=1261200000, serial_no=3,
645         admin_up=True, hypervisor=constants.HT_XEN_HVM, os="deb99",
646         primary_node="nodebad2",
647         disk_template=constants.DT_DISKLESS,
648         disks=[],
649         beparams={
650           constants.BE_MEMORY: 512,
651         }),
652       objects.Instance(name="inst6", hvparams={}, nics=[],
653         uuid="72de6580-c8d5-4661-b902-38b5785bb8b3",
654         ctime=7513, mtime=11501, serial_no=13390,
655         admin_up=False, hypervisor=constants.HT_XEN_HVM, os="deb99",
656         primary_node="node7",
657         disk_template=constants.DT_DISKLESS,
658         disks=[],
659         beparams={
660           constants.BE_MEMORY: 768,
661         }),
662       objects.Instance(name="inst7", hvparams={}, nics=[],
663         uuid="ceec5dc4-b729-4f42-ae28-69b3cd24920e",
664         ctime=None, mtime=None, serial_no=1947,
665         admin_up=False, hypervisor=constants.HT_XEN_HVM, os="deb99",
666         primary_node="node6",
667         disk_template=constants.DT_DISKLESS,
668         disks=[],
669         beparams={}),
670       ]
671
672     assert not utils.FindDuplicates(inst.name for inst in instances)
673
674     disk_usage = dict((inst.name,
675                        cmdlib._ComputeDiskSize(inst.disk_template,
676                                                [{"size": disk.size}
677                                                 for disk in inst.disks]))
678                       for inst in instances)
679
680     inst_bridges = {
681       "inst3": [constants.DEFAULT_BRIDGE, constants.DEFAULT_BRIDGE],
682       "inst4": [constants.DEFAULT_BRIDGE, constants.DEFAULT_BRIDGE,
683                 None, "eth123"],
684       }
685
686     live_data = {
687       "inst2": {
688         "vcpus": 3,
689         },
690       "inst4": {
691         "memory": 123,
692         },
693       "inst6": {
694         "memory": 768,
695         },
696       }
697
698     iqd = query.InstanceQueryData(instances, cluster, disk_usage,
699                                   offline_nodes, bad_nodes, live_data)
700     result = q.Query(iqd)
701     self.assertEqual(len(result), len(instances))
702     self.assert_(compat.all(len(row) == len(selected)
703                             for row in result))
704
705     assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
706            "Offline nodes not included in bad nodes"
707
708     tested_status = set()
709
710     for (inst, row) in zip(instances, result):
711       assert inst.primary_node in nodes
712
713       self.assertEqual(row[fieldidx["name"]],
714                        (constants.RS_NORMAL, inst.name))
715
716       if inst.primary_node in offline_nodes:
717         exp_status = "ERROR_nodeoffline"
718       elif inst.primary_node in bad_nodes:
719         exp_status = "ERROR_nodedown"
720       elif inst.name in live_data:
721         if inst.admin_up:
722           exp_status = "running"
723         else:
724           exp_status = "ERROR_up"
725       elif inst.admin_up:
726         exp_status = "ERROR_down"
727       else:
728         exp_status = "ADMIN_down"
729
730       self.assertEqual(row[fieldidx["status"]],
731                        (constants.RS_NORMAL, exp_status))
732
733       (_, status) = row[fieldidx["status"]]
734       tested_status.add(status)
735
736       for (field, livefield) in [("oper_ram", "memory"),
737                                  ("oper_vcpus", "vcpus")]:
738         if inst.primary_node in bad_nodes:
739           exp = (constants.RS_NODATA, None)
740         elif inst.name in live_data:
741           value = live_data[inst.name].get(livefield, None)
742           if value is None:
743             exp = (constants.RS_UNAVAIL, None)
744           else:
745             exp = (constants.RS_NORMAL, value)
746         else:
747           exp = (constants.RS_UNAVAIL, None)
748
749         self.assertEqual(row[fieldidx[field]], exp)
750
751       bridges = inst_bridges.get(inst.name, [])
752       self.assertEqual(row[fieldidx["nic.bridges"]],
753                        (constants.RS_NORMAL, bridges))
754       if bridges:
755         self.assertEqual(row[fieldidx["bridge"]],
756                          (constants.RS_NORMAL, bridges[0]))
757       else:
758         self.assertEqual(row[fieldidx["bridge"]],
759                          (constants.RS_UNAVAIL, None))
760
761       for i in range(constants.MAX_NICS):
762         if i < len(bridges) and bridges[i] is not None:
763           exp = (constants.RS_NORMAL, bridges[i])
764         else:
765           exp = (constants.RS_UNAVAIL, None)
766         self.assertEqual(row[fieldidx["nic.bridge/%s" % i]], exp)
767
768       if inst.primary_node in bad_nodes:
769         exp = (constants.RS_NODATA, None)
770       else:
771         exp = (constants.RS_NORMAL, inst.name in live_data)
772       self.assertEqual(row[fieldidx["oper_state"]], exp)
773
774       usage = disk_usage[inst.name]
775       if usage is None:
776         usage = 0
777       self.assertEqual(row[fieldidx["disk_usage"]],
778                        (constants.RS_NORMAL, usage))
779
780       self.assertEqual(row[fieldidx["sda_size"]], row[fieldidx["disk.size/0"]])
781       self.assertEqual(row[fieldidx["sdb_size"]], row[fieldidx["disk.size/1"]])
782
783       for field in ["ctime", "mtime"]:
784         if getattr(inst, field) is None:
785           # No ctime/mtime
786           exp = (constants.RS_UNAVAIL, None)
787         else:
788           exp = (constants.RS_NORMAL, getattr(inst, field))
789         self.assertEqual(row[fieldidx[field]], exp)
790
791     # Ensure all possible status' have been tested
792     self.assertEqual(tested_status,
793                      set(["ERROR_nodeoffline", "ERROR_nodedown",
794                           "running", "ERROR_up", "ERROR_down",
795                           "ADMIN_down"]))
796
797
798 class TestGroupQuery(unittest.TestCase):
799
800   def setUp(self):
801     self.groups = [
802       objects.NodeGroup(name="default",
803                         uuid="c0e89160-18e7-11e0-a46e-001d0904baeb",
804                         alloc_policy=constants.ALLOC_POLICY_PREFERRED),
805       objects.NodeGroup(name="restricted",
806                         uuid="d2a40a74-18e7-11e0-9143-001d0904baeb",
807                         alloc_policy=constants.ALLOC_POLICY_LAST_RESORT),
808       ]
809
810   def _Create(self, selected):
811     return query.Query(query.GROUP_FIELDS, selected)
812
813   def testSimple(self):
814     q = self._Create(["name", "uuid", "alloc_policy"])
815     gqd = query.GroupQueryData(self.groups, None, None)
816
817     self.assertEqual(q.RequestedData(), set([query.GQ_CONFIG]))
818
819     self.assertEqual(q.Query(gqd),
820       [[(constants.RS_NORMAL, "default"),
821         (constants.RS_NORMAL, "c0e89160-18e7-11e0-a46e-001d0904baeb"),
822         (constants.RS_NORMAL, constants.ALLOC_POLICY_PREFERRED)
823         ],
824        [(constants.RS_NORMAL, "restricted"),
825         (constants.RS_NORMAL, "d2a40a74-18e7-11e0-9143-001d0904baeb"),
826         (constants.RS_NORMAL, constants.ALLOC_POLICY_LAST_RESORT)
827         ],
828        ])
829
830   def testNodes(self):
831     groups_to_nodes = {
832       "c0e89160-18e7-11e0-a46e-001d0904baeb": ["node1", "node2"],
833       "d2a40a74-18e7-11e0-9143-001d0904baeb": ["node1", "node10", "node9"],
834       }
835
836     q = self._Create(["name", "node_cnt", "node_list"])
837     gqd = query.GroupQueryData(self.groups, groups_to_nodes, None)
838
839     self.assertEqual(q.RequestedData(), set([query.GQ_CONFIG, query.GQ_NODE]))
840
841     self.assertEqual(q.Query(gqd),
842                      [[(constants.RS_NORMAL, "default"),
843                        (constants.RS_NORMAL, 2),
844                        (constants.RS_NORMAL, ["node1", "node2"]),
845                        ],
846                       [(constants.RS_NORMAL, "restricted"),
847                        (constants.RS_NORMAL, 3),
848                        (constants.RS_NORMAL, ["node1", "node9", "node10"]),
849                        ],
850                       ])
851
852   def testInstances(self):
853     groups_to_instances = {
854       "c0e89160-18e7-11e0-a46e-001d0904baeb": ["inst1", "inst2"],
855       "d2a40a74-18e7-11e0-9143-001d0904baeb": ["inst1", "inst10", "inst9"],
856       }
857
858     q = self._Create(["pinst_cnt", "pinst_list"])
859     gqd = query.GroupQueryData(self.groups, None, groups_to_instances)
860
861     self.assertEqual(q.RequestedData(), set([query.GQ_INST]))
862
863     self.assertEqual(q.Query(gqd),
864                      [[(constants.RS_NORMAL, 2),
865                        (constants.RS_NORMAL, ["inst1", "inst2"]),
866                        ],
867                       [(constants.RS_NORMAL, 3),
868                        (constants.RS_NORMAL, ["inst1", "inst9", "inst10"]),
869                        ],
870                       ])
871
872
873 class TestQueryFields(unittest.TestCase):
874   def testAllFields(self):
875     for fielddefs in query.ALL_FIELD_LISTS:
876       result = query.QueryFields(fielddefs, None)
877       self.assert_(isinstance(result, dict))
878       response = objects.QueryFieldsResponse.FromDict(result)
879       self.assertEqual([(fdef.name, fdef.title) for fdef in response.fields],
880         [(fdef2.name, fdef2.title)
881          for (fdef2, _, _) in utils.NiceSort(fielddefs.values(),
882                                              key=lambda x: x[0].name)])
883
884   def testSomeFields(self):
885     rnd = random.Random(5357)
886
887     for _ in range(10):
888       for fielddefs in query.ALL_FIELD_LISTS:
889         if len(fielddefs) > 20:
890           sample_size = rnd.randint(5, 20)
891         else:
892           sample_size = rnd.randint(1, max(1, len(fielddefs) - 1))
893         fields = [fdef for (fdef, _, _) in rnd.sample(fielddefs.values(),
894                                                       sample_size)]
895         result = query.QueryFields(fielddefs, [fdef.name for fdef in fields])
896         self.assert_(isinstance(result, dict))
897         response = objects.QueryFieldsResponse.FromDict(result)
898         self.assertEqual([(fdef.name, fdef.title) for fdef in response.fields],
899                          [(fdef2.name, fdef2.title) for fdef2 in fields])
900
901
902 if __name__ == "__main__":
903   testutils.GanetiTestProgram()