Add unit tests for CheckAssignmentForSplitInstances
[ganeti-local] / test / ganeti.query_unittest.py
1 #!/usr/bin/python
2 #
3
4 # Copyright (C) 2010 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 (constants.QRFS_NORMAL, disks[nr])
60   except IndexError:
61     return (constants.QRFS_UNAVAIL, None)
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: (constants.QRFS_NORMAL, item["name"])),
71       (query._MakeField("master", "Master", constants.QFT_BOOL),
72        STATIC, lambda ctx, item: (constants.QRFS_NORMAL,
73                                   ctx.mastername == item["name"])),
74       ] +
75       [(query._MakeField("disk%s.size" % i, "DiskSize%s" % i,
76                          constants.QFT_UNIT),
77         DISK, compat.partial(_GetDiskSize, i))
78        for i in range(4)])
79
80     q = query.Query(fielddef, ["name"])
81     self.assertEqual(q.RequestedData(), set([STATIC]))
82     self.assertEqual(len(q._fields), 1)
83     self.assertEqual(len(q.GetFields()), 1)
84     self.assertEqual(q.GetFields()[0].ToDict(),
85       objects.QueryFieldDefinition(name="name",
86                                    title="Name",
87                                    kind=constants.QFT_TEXT).ToDict())
88
89     # Create data only once query has been prepared
90     data = [
91       { "name": "node1", "disks": [0, 1, 2], },
92       { "name": "node2", "disks": [3, 4], },
93       { "name": "node3", "disks": [5, 6, 7], },
94       ]
95
96     self.assertEqual(q.Query(_QueryData(data, mastername="node3")),
97                      [[(constants.QRFS_NORMAL, "node1")],
98                       [(constants.QRFS_NORMAL, "node2")],
99                       [(constants.QRFS_NORMAL, "node3")]])
100     self.assertEqual(q.OldStyleQuery(_QueryData(data, mastername="node3")),
101                      [["node1"], ["node2"], ["node3"]])
102
103     q = query.Query(fielddef, ["name", "master"])
104     self.assertEqual(q.RequestedData(), set([STATIC]))
105     self.assertEqual(len(q._fields), 2)
106     self.assertEqual(q.Query(_QueryData(data, mastername="node3")),
107                      [[(constants.QRFS_NORMAL, "node1"),
108                        (constants.QRFS_NORMAL, False)],
109                       [(constants.QRFS_NORMAL, "node2"),
110                        (constants.QRFS_NORMAL, False)],
111                       [(constants.QRFS_NORMAL, "node3"),
112                        (constants.QRFS_NORMAL, True)],
113                      ])
114
115     q = query.Query(fielddef, ["name", "master", "disk0.size"])
116     self.assertEqual(q.RequestedData(), set([STATIC, DISK]))
117     self.assertEqual(len(q._fields), 3)
118     self.assertEqual(q.Query(_QueryData(data, mastername="node2")),
119                      [[(constants.QRFS_NORMAL, "node1"),
120                        (constants.QRFS_NORMAL, False),
121                        (constants.QRFS_NORMAL, 0)],
122                       [(constants.QRFS_NORMAL, "node2"),
123                        (constants.QRFS_NORMAL, True),
124                        (constants.QRFS_NORMAL, 3)],
125                       [(constants.QRFS_NORMAL, "node3"),
126                        (constants.QRFS_NORMAL, False),
127                        (constants.QRFS_NORMAL, 5)],
128                      ])
129
130     # With unknown column
131     q = query.Query(fielddef, ["disk2.size", "disk1.size", "disk99.size",
132                                "disk0.size"])
133     self.assertEqual(q.RequestedData(), set([DISK]))
134     self.assertEqual(len(q._fields), 4)
135     self.assertEqual(q.Query(_QueryData(data, mastername="node2")),
136                      [[(constants.QRFS_NORMAL, 2),
137                        (constants.QRFS_NORMAL, 1),
138                        (constants.QRFS_UNKNOWN, None),
139                        (constants.QRFS_NORMAL, 0)],
140                       [(constants.QRFS_UNAVAIL, None),
141                        (constants.QRFS_NORMAL, 4),
142                        (constants.QRFS_UNKNOWN, None),
143                        (constants.QRFS_NORMAL, 3)],
144                       [(constants.QRFS_NORMAL, 7),
145                        (constants.QRFS_NORMAL, 6),
146                        (constants.QRFS_UNKNOWN, None),
147                        (constants.QRFS_NORMAL, 5)],
148                      ])
149     self.assertRaises(errors.OpPrereqError, q.OldStyleQuery,
150                       _QueryData(data, mastername="node2"))
151     self.assertEqual([fdef.ToDict() for fdef in q.GetFields()], [
152                      { "name": "disk2.size", "title": "DiskSize2",
153                        "kind": constants.QFT_UNIT, },
154                      { "name": "disk1.size", "title": "DiskSize1",
155                        "kind": constants.QFT_UNIT, },
156                      { "name": "disk99.size", "title": "disk99.size",
157                        "kind": constants.QFT_UNKNOWN, },
158                      { "name": "disk0.size", "title": "DiskSize0",
159                        "kind": constants.QFT_UNIT, },
160                      ])
161
162     # Empty query
163     q = query.Query(fielddef, [])
164     self.assertEqual(q.RequestedData(), set([]))
165     self.assertEqual(len(q._fields), 0)
166     self.assertEqual(q.Query(_QueryData(data, mastername="node2")),
167                      [[], [], []])
168     self.assertEqual(q.OldStyleQuery(_QueryData(data, mastername="node2")),
169                      [[], [], []])
170     self.assertEqual(q.GetFields(), [])
171
172   def testPrepareFieldList(self):
173     # Duplicate titles
174     for (a, b) in [("name", "name"), ("NAME", "name")]:
175       self.assertRaises(AssertionError, query._PrepareFieldList, [
176         (query._MakeField("name", b, constants.QFT_TEXT), None,
177          lambda *args: None),
178         (query._MakeField("other", a, constants.QFT_TEXT), None,
179          lambda *args: None),
180         ])
181
182     # Non-lowercase names
183     self.assertRaises(AssertionError, query._PrepareFieldList, [
184       (query._MakeField("NAME", "Name", constants.QFT_TEXT), None,
185        lambda *args: None),
186       ])
187     self.assertRaises(AssertionError, query._PrepareFieldList, [
188       (query._MakeField("Name", "Name", constants.QFT_TEXT), None,
189        lambda *args: None),
190       ])
191
192     # Empty name
193     self.assertRaises(AssertionError, query._PrepareFieldList, [
194       (query._MakeField("", "Name", constants.QFT_TEXT), None,
195        lambda *args: None),
196       ])
197
198     # Empty title
199     self.assertRaises(AssertionError, query._PrepareFieldList, [
200       (query._MakeField("name", "", constants.QFT_TEXT), None,
201        lambda *args: None),
202       ])
203
204     # Whitespace in title
205     self.assertRaises(AssertionError, query._PrepareFieldList, [
206       (query._MakeField("name", "Co lu mn", constants.QFT_TEXT), None,
207        lambda *args: None),
208       ])
209
210     # No callable function
211     self.assertRaises(AssertionError, query._PrepareFieldList, [
212       (query._MakeField("name", "Name", constants.QFT_TEXT), None, None),
213       ])
214
215   def testUnknown(self):
216     fielddef = query._PrepareFieldList([
217       (query._MakeField("name", "Name", constants.QFT_TEXT),
218        None, lambda _, item: (constants.QRFS_NORMAL, "name%s" % item)),
219       (query._MakeField("other0", "Other0", constants.QFT_TIMESTAMP),
220        None, lambda *args: (constants.QRFS_NORMAL, 1234)),
221       (query._MakeField("nodata", "NoData", constants.QFT_NUMBER),
222        None, lambda *args: (constants.QRFS_NODATA, None)),
223       (query._MakeField("unavail", "Unavail", constants.QFT_BOOL),
224        None, lambda *args: (constants.QRFS_UNAVAIL, None)),
225       ])
226
227     for selected in [["foo"], ["Hello", "World"],
228                      ["name1", "other", "foo"]]:
229       q = query.Query(fielddef, selected)
230       self.assertEqual(len(q._fields), len(selected))
231       self.assert_(compat.all(len(row) == len(selected)
232                               for row in q.Query(_QueryData(range(1, 10)))))
233       self.assertEqual(q.Query(_QueryData(range(1, 10))),
234                        [[(constants.QRFS_UNKNOWN, None)] * len(selected)
235                         for i in range(1, 10)])
236       self.assertEqual([fdef.ToDict() for fdef in q.GetFields()],
237                        [{ "name": name, "title": name,
238                           "kind": constants.QFT_UNKNOWN, }
239                         for name in selected])
240
241     q = query.Query(fielddef, ["name", "other0", "nodata", "unavail"])
242     self.assertEqual(len(q._fields), 4)
243     self.assertEqual(q.OldStyleQuery(_QueryData(range(1, 10))), [
244                      ["name%s" % i, 1234, None, None]
245                      for i in range(1, 10)
246                      ])
247
248     q = query.Query(fielddef, ["name", "other0", "nodata", "unavail", "unk"])
249     self.assertEqual(len(q._fields), 5)
250     self.assertEqual(q.Query(_QueryData(range(1, 10))),
251                      [[(constants.QRFS_NORMAL, "name%s" % i),
252                        (constants.QRFS_NORMAL, 1234),
253                        (constants.QRFS_NODATA, None),
254                        (constants.QRFS_UNAVAIL, None),
255                        (constants.QRFS_UNKNOWN, None)]
256                       for i in range(1, 10)])
257
258
259 class TestGetNodeRole(unittest.TestCase):
260   def testMaster(self):
261     node = objects.Node(name="node1")
262     self.assertEqual(query._GetNodeRole(node, "node1"), "M")
263
264   def testMasterCandidate(self):
265     node = objects.Node(name="node1", master_candidate=True)
266     self.assertEqual(query._GetNodeRole(node, "master"), "C")
267
268   def testRegular(self):
269     node = objects.Node(name="node1")
270     self.assertEqual(query._GetNodeRole(node, "master"), "R")
271
272   def testDrained(self):
273     node = objects.Node(name="node1", drained=True)
274     self.assertEqual(query._GetNodeRole(node, "master"), "D")
275
276   def testOffline(self):
277     node = objects.Node(name="node1", offline=True)
278     self.assertEqual(query._GetNodeRole(node, "master"), "O")
279
280
281 class TestNodeQuery(unittest.TestCase):
282   def _Create(self, selected):
283     return query.Query(query.NODE_FIELDS, selected)
284
285   def testSimple(self):
286     nodes = [
287       objects.Node(name="node1", drained=False),
288       objects.Node(name="node2", drained=True),
289       objects.Node(name="node3", drained=False),
290       ]
291     for live_data in [None, dict.fromkeys([node.name for node in nodes], {})]:
292       nqd = query.NodeQueryData(nodes, live_data, None, None, None, None, None)
293
294       q = self._Create(["name", "drained"])
295       self.assertEqual(q.RequestedData(), set([query.NQ_CONFIG]))
296       self.assertEqual(q.Query(nqd),
297                        [[(constants.QRFS_NORMAL, "node1"),
298                          (constants.QRFS_NORMAL, False)],
299                         [(constants.QRFS_NORMAL, "node2"),
300                          (constants.QRFS_NORMAL, True)],
301                         [(constants.QRFS_NORMAL, "node3"),
302                          (constants.QRFS_NORMAL, False)],
303                        ])
304       self.assertEqual(q.OldStyleQuery(nqd),
305                        [["node1", False],
306                         ["node2", True],
307                         ["node3", False]])
308
309   def test(self):
310     selected = query.NODE_FIELDS.keys()
311     field_index = dict((field, idx) for idx, field in enumerate(selected))
312
313     q = self._Create(selected)
314     self.assertEqual(q.RequestedData(),
315                      set([query.NQ_CONFIG, query.NQ_LIVE, query.NQ_INST,
316                           query.NQ_GROUP, query.NQ_OOB]))
317
318     node_names = ["node%s" % i for i in range(20)]
319     master_name = node_names[3]
320     nodes = [
321       objects.Node(name=name,
322                    primary_ip="192.0.2.%s" % idx,
323                    secondary_ip="192.0.100.%s" % idx,
324                    serial_no=7789 * idx,
325                    master_candidate=(name != master_name and idx % 3 == 0),
326                    offline=False,
327                    drained=False,
328                    vm_capable=False,
329                    master_capable=False,
330                    group="default",
331                    ctime=1290006900,
332                    mtime=1290006913,
333                    uuid="fd9ccebe-6339-43c9-a82e-94bbe575%04d" % idx)
334       for idx, name in enumerate(node_names)
335       ]
336
337     master_node = nodes[3]
338     master_node.AddTag("masternode")
339     master_node.AddTag("another")
340     master_node.AddTag("tag")
341     master_node.ctime = None
342     master_node.mtime = None
343     assert master_node.name == master_name
344
345     live_data_name = node_names[4]
346     assert live_data_name != master_name
347
348     fake_live_data = {
349       "bootid": "a2504766-498e-4b25-b21e-d23098dc3af4",
350       "cnodes": 4,
351       "csockets": 4,
352       "ctotal": 8,
353       "mnode": 128,
354       "mfree": 100,
355       "mtotal": 4096,
356       "dfree": 5 * 1024 * 1024,
357       "dtotal": 100 * 1024 * 1024,
358       }
359
360     assert (sorted(query._NODE_LIVE_FIELDS.keys()) ==
361             sorted(fake_live_data.keys()))
362
363     live_data = dict.fromkeys(node_names, {})
364     live_data[live_data_name] = \
365       dict((query._NODE_LIVE_FIELDS[name][2], value)
366            for name, value in fake_live_data.items())
367
368     node_to_primary = dict((name, set()) for name in node_names)
369     node_to_primary[master_name].update(["inst1", "inst2"])
370
371     node_to_secondary = dict((name, set()) for name in node_names)
372     node_to_secondary[live_data_name].update(["instX", "instY", "instZ"])
373
374     ng_uuid = "492b4b74-8670-478a-b98d-4c53a76238e6"
375     groups = {
376       ng_uuid: objects.NodeGroup(name="ng1", uuid=ng_uuid),
377       }
378
379     oob_support = dict((name, False) for name in node_names)
380
381     master_node.group = ng_uuid
382
383     nqd = query.NodeQueryData(nodes, live_data, master_name,
384                               node_to_primary, node_to_secondary, groups,
385                               oob_support)
386     result = q.Query(nqd)
387     self.assert_(compat.all(len(row) == len(selected) for row in result))
388     self.assertEqual([row[field_index["name"]] for row in result],
389                      [(constants.QRFS_NORMAL, name) for name in node_names])
390
391     node_to_row = dict((row[field_index["name"]][1], idx)
392                        for idx, row in enumerate(result))
393
394     master_row = result[node_to_row[master_name]]
395     self.assert_(master_row[field_index["master"]])
396     self.assert_(master_row[field_index["role"]], "M")
397     self.assertEqual(master_row[field_index["group"]],
398                      (constants.QRFS_NORMAL, "ng1"))
399     self.assertEqual(master_row[field_index["group.uuid"]],
400                      (constants.QRFS_NORMAL, ng_uuid))
401     self.assertEqual(master_row[field_index["ctime"]],
402                      (constants.QRFS_UNAVAIL, None))
403     self.assertEqual(master_row[field_index["mtime"]],
404                      (constants.QRFS_UNAVAIL, None))
405
406     self.assert_(row[field_index["pip"]] == node.primary_ip and
407                  row[field_index["sip"]] == node.secondary_ip and
408                  set(row[field_index["tags"]]) == node.GetTags() and
409                  row[field_index["serial_no"]] == node.serial_no and
410                  row[field_index["role"]] == query._GetNodeRole(node,
411                                                                 master_name) and
412                  (node.name == master_name or
413                   (row[field_index["group"]] == "<unknown>" and
414                    row[field_index["group.uuid"]] is None and
415                    row[field_index["ctime"]] == (constants.QRFS_NORMAL,
416                                                  node.ctime) and
417                    row[field_index["mtime"]] == (constants.QRFS_NORMAL,
418                                                  node.mtime)))
419                  for row, node in zip(result, nodes))
420
421     live_data_row = result[node_to_row[live_data_name]]
422
423     for (field, value) in fake_live_data.items():
424       self.assertEqual(live_data_row[field_index[field]],
425                        (constants.QRFS_NORMAL, value))
426
427     self.assertEqual(master_row[field_index["pinst_cnt"]],
428                      (constants.QRFS_NORMAL, 2))
429     self.assertEqual(live_data_row[field_index["sinst_cnt"]],
430                      (constants.QRFS_NORMAL, 3))
431     self.assertEqual(master_row[field_index["pinst_list"]],
432                      (constants.QRFS_NORMAL,
433                       list(node_to_primary[master_name])))
434     self.assertEqual(live_data_row[field_index["sinst_list"]],
435                      (constants.QRFS_NORMAL,
436                       list(node_to_secondary[live_data_name])))
437
438   def testGetLiveNodeField(self):
439     nodes = [
440       objects.Node(name="node1", drained=False, offline=False),
441       objects.Node(name="node2", drained=True, offline=False),
442       objects.Node(name="node3", drained=False, offline=False),
443       objects.Node(name="node4", drained=False, offline=True),
444       ]
445     live_data = dict.fromkeys([node.name for node in nodes], {})
446
447     # No data
448     nqd = query.NodeQueryData(None, None, None, None, None, None, None)
449     self.assertEqual(query._GetLiveNodeField("hello", constants.QFT_NUMBER,
450                                              nqd, nodes[0]),
451                      (constants.QRFS_NODATA, None))
452
453     # Missing field
454     ctx = _QueryData(None, curlive_data={
455       "some": 1,
456       "other": 2,
457       })
458     self.assertEqual(query._GetLiveNodeField("hello", constants.QFT_NUMBER,
459                                              ctx, nodes[0]),
460                      (constants.QRFS_UNAVAIL, None))
461
462     # Wrong format/datatype
463     ctx = _QueryData(None, curlive_data={
464       "hello": ["Hello World"],
465       "other": 2,
466       })
467     self.assertEqual(query._GetLiveNodeField("hello", constants.QFT_NUMBER,
468                                              ctx, nodes[0]),
469                      (constants.QRFS_UNAVAIL, None))
470
471     # Offline node
472     assert nodes[3].offline
473     ctx = _QueryData(None, curlive_data={})
474     self.assertEqual(query._GetLiveNodeField("hello", constants.QFT_NUMBER,
475                                              ctx, nodes[3]),
476                      (constants.QRFS_OFFLINE, None))
477
478     # Wrong field type
479     ctx = _QueryData(None, curlive_data={"hello": 123})
480     self.assertRaises(AssertionError, query._GetLiveNodeField,
481                       "hello", constants.QFT_BOOL, ctx, nodes[0])
482
483
484 class TestInstanceQuery(unittest.TestCase):
485   def _Create(self, selected):
486     return query.Query(query.INSTANCE_FIELDS, selected)
487
488   def testSimple(self):
489     q = self._Create(["name", "be/memory", "ip"])
490     self.assertEqual(q.RequestedData(), set([query.IQ_CONFIG]))
491
492     cluster = objects.Cluster(cluster_name="testcluster",
493       hvparams=constants.HVC_DEFAULTS,
494       beparams={
495         constants.PP_DEFAULT: constants.BEC_DEFAULTS,
496         },
497       nicparams={
498         constants.PP_DEFAULT: constants.NICC_DEFAULTS,
499         })
500
501     instances = [
502       objects.Instance(name="inst1", hvparams={}, beparams={}, nics=[]),
503       objects.Instance(name="inst2", hvparams={}, nics=[],
504         beparams={
505           constants.BE_MEMORY: 512,
506         }),
507       objects.Instance(name="inst3", hvparams={}, beparams={},
508         nics=[objects.NIC(ip="192.0.2.99", nicparams={})]),
509       ]
510
511     iqd = query.InstanceQueryData(instances, cluster, None, [], [], {})
512     self.assertEqual(q.Query(iqd),
513       [[(constants.QRFS_NORMAL, "inst1"),
514         (constants.QRFS_NORMAL, 128),
515         (constants.QRFS_UNAVAIL, None),
516        ],
517        [(constants.QRFS_NORMAL, "inst2"),
518         (constants.QRFS_NORMAL, 512),
519         (constants.QRFS_UNAVAIL, None),
520        ],
521        [(constants.QRFS_NORMAL, "inst3"),
522         (constants.QRFS_NORMAL, 128),
523         (constants.QRFS_NORMAL, "192.0.2.99"),
524        ]])
525     self.assertEqual(q.OldStyleQuery(iqd),
526       [["inst1", 128, None],
527        ["inst2", 512, None],
528        ["inst3", 128, "192.0.2.99"]])
529
530   def test(self):
531     selected = query.INSTANCE_FIELDS.keys()
532     fieldidx = dict((field, idx) for idx, field in enumerate(selected))
533
534     macs = ["00:11:22:%02x:%02x:%02x" % (i % 255, i % 3, (i * 123) % 255)
535             for i in range(20)]
536
537     q = self._Create(selected)
538     self.assertEqual(q.RequestedData(),
539                      set([query.IQ_CONFIG, query.IQ_LIVE, query.IQ_DISKUSAGE]))
540
541     cluster = objects.Cluster(cluster_name="testcluster",
542       hvparams=constants.HVC_DEFAULTS,
543       beparams={
544         constants.PP_DEFAULT: constants.BEC_DEFAULTS,
545         },
546       nicparams={
547         constants.PP_DEFAULT: constants.NICC_DEFAULTS,
548         },
549       os_hvp={},
550       tcpudp_port_pool=set())
551
552     offline_nodes = ["nodeoff1", "nodeoff2"]
553     bad_nodes = ["nodebad1", "nodebad2", "nodebad3"] + offline_nodes
554     nodes = ["node%s" % i for i in range(10)] + bad_nodes
555
556     instances = [
557       objects.Instance(name="inst1", hvparams={}, beparams={}, nics=[],
558         uuid="f90eccb3-e227-4e3c-bf2a-94a21ca8f9cd",
559         ctime=1291244000, mtime=1291244400, serial_no=30,
560         admin_up=True, hypervisor=constants.HT_XEN_PVM, os="linux1",
561         primary_node="node1",
562         disk_template=constants.DT_PLAIN,
563         disks=[]),
564       objects.Instance(name="inst2", hvparams={}, nics=[],
565         uuid="73a0f8a7-068c-4630-ada2-c3440015ab1a",
566         ctime=1291211000, mtime=1291211077, serial_no=1,
567         admin_up=True, hypervisor=constants.HT_XEN_HVM, os="deb99",
568         primary_node="node5",
569         disk_template=constants.DT_DISKLESS,
570         disks=[],
571         beparams={
572           constants.BE_MEMORY: 512,
573         }),
574       objects.Instance(name="inst3", hvparams={}, beparams={},
575         uuid="11ec8dff-fb61-4850-bfe0-baa1803ff280",
576         ctime=1291011000, mtime=1291013000, serial_no=1923,
577         admin_up=False, hypervisor=constants.HT_KVM, os="busybox",
578         primary_node="node6",
579         disk_template=constants.DT_DRBD8,
580         disks=[],
581         nics=[
582           objects.NIC(ip="192.0.2.99", mac=macs.pop(),
583                       nicparams={
584                         constants.NIC_LINK: constants.DEFAULT_BRIDGE,
585                         }),
586           objects.NIC(ip=None, mac=macs.pop(), nicparams={}),
587           ]),
588       objects.Instance(name="inst4", hvparams={}, beparams={},
589         uuid="68dab168-3ef5-4c9d-b4d3-801e0672068c",
590         ctime=1291244390, mtime=1291244395, serial_no=25,
591         admin_up=False, hypervisor=constants.HT_XEN_PVM, os="linux1",
592         primary_node="nodeoff2",
593         disk_template=constants.DT_DRBD8,
594         disks=[],
595         nics=[
596           objects.NIC(ip="192.0.2.1", mac=macs.pop(),
597                       nicparams={
598                         constants.NIC_LINK: constants.DEFAULT_BRIDGE,
599                         }),
600           objects.NIC(ip="192.0.2.2", mac=macs.pop(), nicparams={}),
601           objects.NIC(ip="192.0.2.3", mac=macs.pop(),
602                       nicparams={
603                         constants.NIC_MODE: constants.NIC_MODE_ROUTED,
604                         }),
605           objects.NIC(ip="192.0.2.4", mac=macs.pop(),
606                       nicparams={
607                         constants.NIC_MODE: constants.NIC_MODE_BRIDGED,
608                         constants.NIC_LINK: "eth123",
609                         }),
610           ]),
611       objects.Instance(name="inst5", hvparams={}, nics=[],
612         uuid="0e3dca12-5b42-4e24-98a2-415267545bd0",
613         ctime=1231211000, mtime=1261200000, serial_no=3,
614         admin_up=True, hypervisor=constants.HT_XEN_HVM, os="deb99",
615         primary_node="nodebad2",
616         disk_template=constants.DT_DISKLESS,
617         disks=[],
618         beparams={
619           constants.BE_MEMORY: 512,
620         }),
621       objects.Instance(name="inst6", hvparams={}, nics=[],
622         uuid="72de6580-c8d5-4661-b902-38b5785bb8b3",
623         ctime=7513, mtime=11501, serial_no=13390,
624         admin_up=False, hypervisor=constants.HT_XEN_HVM, os="deb99",
625         primary_node="node7",
626         disk_template=constants.DT_DISKLESS,
627         disks=[],
628         beparams={
629           constants.BE_MEMORY: 768,
630         }),
631       objects.Instance(name="inst7", hvparams={}, nics=[],
632         uuid="ceec5dc4-b729-4f42-ae28-69b3cd24920e",
633         ctime=None, mtime=None, serial_no=1947,
634         admin_up=False, hypervisor=constants.HT_XEN_HVM, os="deb99",
635         primary_node="node6",
636         disk_template=constants.DT_DISKLESS,
637         disks=[],
638         beparams={}),
639       ]
640
641     assert not utils.FindDuplicates(inst.name for inst in instances)
642
643     disk_usage = dict((inst.name,
644                        cmdlib._ComputeDiskSize(inst.disk_template,
645                                                [{"size": disk.size}
646                                                 for disk in inst.disks]))
647                       for inst in instances)
648
649     inst_bridges = {
650       "inst3": [constants.DEFAULT_BRIDGE, constants.DEFAULT_BRIDGE],
651       "inst4": [constants.DEFAULT_BRIDGE, constants.DEFAULT_BRIDGE,
652                 None, "eth123"],
653       }
654
655     live_data = {
656       "inst2": {
657         "vcpus": 3,
658         },
659       "inst4": {
660         "memory": 123,
661         },
662       "inst6": {
663         "memory": 768,
664         },
665       }
666
667     iqd = query.InstanceQueryData(instances, cluster, disk_usage,
668                                   offline_nodes, bad_nodes, live_data)
669     result = q.Query(iqd)
670     self.assertEqual(len(result), len(instances))
671     self.assert_(compat.all(len(row) == len(selected)
672                             for row in result))
673
674     assert len(set(bad_nodes) & set(offline_nodes)) == len(offline_nodes), \
675            "Offline nodes not included in bad nodes"
676
677     tested_status = set()
678
679     for (inst, row) in zip(instances, result):
680       assert inst.primary_node in nodes
681
682       self.assertEqual(row[fieldidx["name"]],
683                        (constants.QRFS_NORMAL, inst.name))
684
685       if inst.primary_node in offline_nodes:
686         exp_status = "ERROR_nodeoffline"
687       elif inst.primary_node in bad_nodes:
688         exp_status = "ERROR_nodedown"
689       elif inst.name in live_data:
690         if inst.admin_up:
691           exp_status = "running"
692         else:
693           exp_status = "ERROR_up"
694       elif inst.admin_up:
695         exp_status = "ERROR_down"
696       else:
697         exp_status = "ADMIN_down"
698
699       self.assertEqual(row[fieldidx["status"]],
700                        (constants.QRFS_NORMAL, exp_status))
701
702       (_, status) = row[fieldidx["status"]]
703       tested_status.add(status)
704
705       for (field, livefield) in [("oper_ram", "memory"),
706                                  ("oper_vcpus", "vcpus")]:
707         if inst.primary_node in bad_nodes:
708           exp = (constants.QRFS_NODATA, None)
709         elif inst.name in live_data:
710           value = live_data[inst.name].get(livefield, None)
711           if value is None:
712             exp = (constants.QRFS_UNAVAIL, None)
713           else:
714             exp = (constants.QRFS_NORMAL, value)
715         else:
716           exp = (constants.QRFS_UNAVAIL, None)
717
718         self.assertEqual(row[fieldidx[field]], exp)
719
720       bridges = inst_bridges.get(inst.name, [])
721       self.assertEqual(row[fieldidx["nic.bridges"]],
722                        (constants.QRFS_NORMAL, bridges))
723       if bridges:
724         self.assertEqual(row[fieldidx["bridge"]],
725                          (constants.QRFS_NORMAL, bridges[0]))
726       else:
727         self.assertEqual(row[fieldidx["bridge"]],
728                          (constants.QRFS_UNAVAIL, None))
729
730       for i in range(constants.MAX_NICS):
731         if i < len(bridges) and bridges[i] is not None:
732           exp = (constants.QRFS_NORMAL, bridges[i])
733         else:
734           exp = (constants.QRFS_UNAVAIL, None)
735         self.assertEqual(row[fieldidx["nic.bridge/%s" % i]], exp)
736
737       if inst.primary_node in bad_nodes:
738         exp = (constants.QRFS_NODATA, None)
739       else:
740         exp = (constants.QRFS_NORMAL, inst.name in live_data)
741       self.assertEqual(row[fieldidx["oper_state"]], exp)
742
743       usage = disk_usage[inst.name]
744       if usage is None:
745         usage = 0
746       self.assertEqual(row[fieldidx["disk_usage"]],
747                        (constants.QRFS_NORMAL, usage))
748
749       self.assertEqual(row[fieldidx["sda_size"]], row[fieldidx["disk.size/0"]])
750       self.assertEqual(row[fieldidx["sdb_size"]], row[fieldidx["disk.size/1"]])
751
752       for field in ["ctime", "mtime"]:
753         if getattr(inst, field) is None:
754           # No ctime/mtime
755           exp = (constants.QRFS_UNAVAIL, None)
756         else:
757           exp = (constants.QRFS_NORMAL, getattr(inst, field))
758         self.assertEqual(row[fieldidx[field]], exp)
759
760     # Ensure all possible status' have been tested
761     self.assertEqual(tested_status,
762                      set(["ERROR_nodeoffline", "ERROR_nodedown",
763                           "running", "ERROR_up", "ERROR_down",
764                           "ADMIN_down"]))
765
766
767 class TestGroupQuery(unittest.TestCase):
768
769   def setUp(self):
770     self.groups = [
771       objects.NodeGroup(name="default",
772                         uuid="c0e89160-18e7-11e0-a46e-001d0904baeb",
773                         alloc_policy=constants.ALLOC_POLICY_PREFERRED),
774       objects.NodeGroup(name="restricted",
775                         uuid="d2a40a74-18e7-11e0-9143-001d0904baeb",
776                         alloc_policy=constants.ALLOC_POLICY_LAST_RESORT),
777       ]
778
779   def _Create(self, selected):
780     return query.Query(query.GROUP_FIELDS, selected)
781
782   def testSimple(self):
783     q = self._Create(["name", "uuid", "alloc_policy"])
784     gqd = query.GroupQueryData(self.groups, None, None)
785
786     self.assertEqual(q.RequestedData(), set([query.GQ_CONFIG]))
787
788     self.assertEqual(q.Query(gqd),
789       [[(constants.QRFS_NORMAL, "default"),
790         (constants.QRFS_NORMAL, "c0e89160-18e7-11e0-a46e-001d0904baeb"),
791         (constants.QRFS_NORMAL, constants.ALLOC_POLICY_PREFERRED)
792         ],
793        [(constants.QRFS_NORMAL, "restricted"),
794         (constants.QRFS_NORMAL, "d2a40a74-18e7-11e0-9143-001d0904baeb"),
795         (constants.QRFS_NORMAL, constants.ALLOC_POLICY_LAST_RESORT)
796         ],
797        ])
798
799   def testNodes(self):
800     groups_to_nodes = {
801       "c0e89160-18e7-11e0-a46e-001d0904baeb": ["node1", "node2"],
802       "d2a40a74-18e7-11e0-9143-001d0904baeb": ["node1", "node10", "node9"],
803       }
804
805     q = self._Create(["name", "node_cnt", "node_list"])
806     gqd = query.GroupQueryData(self.groups, groups_to_nodes, None)
807
808     self.assertEqual(q.RequestedData(), set([query.GQ_CONFIG, query.GQ_NODE]))
809
810     self.assertEqual(q.Query(gqd),
811                      [[(constants.QRFS_NORMAL, "default"),
812                        (constants.QRFS_NORMAL, 2),
813                        (constants.QRFS_NORMAL, ["node1", "node2"]),
814                        ],
815                       [(constants.QRFS_NORMAL, "restricted"),
816                        (constants.QRFS_NORMAL, 3),
817                        (constants.QRFS_NORMAL, ["node1", "node9", "node10"]),
818                        ],
819                       ])
820
821   def testInstances(self):
822     groups_to_instances = {
823       "c0e89160-18e7-11e0-a46e-001d0904baeb": ["inst1", "inst2"],
824       "d2a40a74-18e7-11e0-9143-001d0904baeb": ["inst1", "inst10", "inst9"],
825       }
826
827     q = self._Create(["pinst_cnt", "pinst_list"])
828     gqd = query.GroupQueryData(self.groups, None, groups_to_instances)
829
830     self.assertEqual(q.RequestedData(), set([query.GQ_INST]))
831
832     self.assertEqual(q.Query(gqd),
833                      [[(constants.QRFS_NORMAL, 2),
834                        (constants.QRFS_NORMAL, ["inst1", "inst2"]),
835                        ],
836                       [(constants.QRFS_NORMAL, 3),
837                        (constants.QRFS_NORMAL, ["inst1", "inst9", "inst10"]),
838                        ],
839                       ])
840
841
842 class TestQueryFields(unittest.TestCase):
843   def testAllFields(self):
844     for fielddefs in query.ALL_FIELD_LISTS:
845       result = query.QueryFields(fielddefs, None)
846       self.assert_(isinstance(result, dict))
847       response = objects.QueryFieldsResponse.FromDict(result)
848       self.assertEqual([(fdef.name, fdef.title) for fdef in response.fields],
849         [(fdef2.name, fdef2.title)
850          for (fdef2, _, _) in utils.NiceSort(fielddefs.values(),
851                                              key=lambda x: x[0].name)])
852
853   def testSomeFields(self):
854     rnd = random.Random(5357)
855
856     for _ in range(10):
857       for fielddefs in query.ALL_FIELD_LISTS:
858         if len(fielddefs) > 20:
859           sample_size = rnd.randint(5, 20)
860         else:
861           sample_size = rnd.randint(1, max(1, len(fielddefs) - 1))
862         fields = [fdef for (fdef, _, _) in rnd.sample(fielddefs.values(),
863                                                       sample_size)]
864         result = query.QueryFields(fielddefs, [fdef.name for fdef in fields])
865         self.assert_(isinstance(result, dict))
866         response = objects.QueryFieldsResponse.FromDict(result)
867         self.assertEqual([(fdef.name, fdef.title) for fdef in response.fields],
868                          [(fdef2.name, fdef2.title) for fdef2 in fields])
869
870
871 if __name__ == "__main__":
872   testutils.GanetiTestProgram()