rlib: Expose node group tags
[ganeti-local] / test / ganeti.rapi.rlib2_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 unittesting the RAPI rlib2 module
23
24 """
25
26
27 import unittest
28 import tempfile
29
30 from ganeti import constants
31 from ganeti import opcodes
32 from ganeti import compat
33 from ganeti import http
34 from ganeti import query
35
36 from ganeti.rapi import rlib2
37
38 import testutils
39
40
41 class TestConstants(unittest.TestCase):
42   def testConsole(self):
43     # Exporting the console field without authentication might expose
44     # information
45     assert "console" in query.INSTANCE_FIELDS
46     self.assertTrue("console" not in rlib2.I_FIELDS)
47
48   def testFields(self):
49     checks = {
50       constants.QR_INSTANCE: rlib2.I_FIELDS,
51       constants.QR_NODE: rlib2.N_FIELDS,
52       constants.QR_GROUP: rlib2.G_FIELDS,
53       }
54
55     for (qr, fields) in checks.items():
56       self.assertFalse(set(fields) - set(query.ALL_FIELDS[qr].keys()))
57
58
59 class TestParseInstanceCreateRequestVersion1(testutils.GanetiTestCase):
60   def setUp(self):
61     testutils.GanetiTestCase.setUp(self)
62
63     self.Parse = rlib2._ParseInstanceCreateRequestVersion1
64
65   def test(self):
66     disk_variants = [
67       # No disks
68       [],
69
70       # Two disks
71       [{"size": 5, }, {"size": 100, }],
72
73       # Disk with mode
74       [{"size": 123, "mode": constants.DISK_RDWR, }],
75       ]
76
77     nic_variants = [
78       # No NIC
79       [],
80
81       # Three NICs
82       [{}, {}, {}],
83
84       # Two NICs
85       [
86         { "ip": "192.0.2.6", "mode": constants.NIC_MODE_ROUTED,
87           "mac": "01:23:45:67:68:9A",
88         },
89         { "mode": constants.NIC_MODE_BRIDGED, "link": "br1" },
90       ],
91       ]
92
93     beparam_variants = [
94       None,
95       {},
96       { constants.BE_VCPUS: 2, },
97       { constants.BE_MEMORY: 123, },
98       { constants.BE_VCPUS: 2,
99         constants.BE_MEMORY: 1024,
100         constants.BE_AUTO_BALANCE: True, }
101       ]
102
103     hvparam_variants = [
104       None,
105       { constants.HV_BOOT_ORDER: "anc", },
106       { constants.HV_KERNEL_PATH: "/boot/fookernel",
107         constants.HV_ROOT_PATH: "/dev/hda1", },
108       ]
109
110     for mode in [constants.INSTANCE_CREATE, constants.INSTANCE_IMPORT]:
111       for nics in nic_variants:
112         for disk_template in constants.DISK_TEMPLATES:
113           for disks in disk_variants:
114             for beparams in beparam_variants:
115               for hvparams in hvparam_variants:
116                 data = {
117                   "name": "inst1.example.com",
118                   "hypervisor": constants.HT_FAKE,
119                   "disks": disks,
120                   "nics": nics,
121                   "mode": mode,
122                   "disk_template": disk_template,
123                   "os": "debootstrap",
124                   }
125
126                 if beparams is not None:
127                   data["beparams"] = beparams
128
129                 if hvparams is not None:
130                   data["hvparams"] = hvparams
131
132                 for dry_run in [False, True]:
133                   op = self.Parse(data, dry_run)
134                   self.assert_(isinstance(op, opcodes.OpInstanceCreate))
135                   self.assertEqual(op.mode, mode)
136                   self.assertEqual(op.disk_template, disk_template)
137                   self.assertEqual(op.dry_run, dry_run)
138                   self.assertEqual(len(op.disks), len(disks))
139                   self.assertEqual(len(op.nics), len(nics))
140
141                   for opdisk, disk in zip(op.disks, disks):
142                     for key in constants.IDISK_PARAMS:
143                       self.assertEqual(opdisk.get(key), disk.get(key))
144                     self.assertFalse("unknown" in opdisk)
145
146                   for opnic, nic in zip(op.nics, nics):
147                     for key in constants.INIC_PARAMS:
148                       self.assertEqual(opnic.get(key), nic.get(key))
149                     self.assertFalse("unknown" in opnic)
150                     self.assertFalse("foobar" in opnic)
151
152                   if beparams is None:
153                     self.assertFalse(hasattr(op, "beparams"))
154                   else:
155                     self.assertEqualValues(op.beparams, beparams)
156
157                   if hvparams is None:
158                     self.assertFalse(hasattr(op, "hvparams"))
159                   else:
160                     self.assertEqualValues(op.hvparams, hvparams)
161
162   def testLegacyName(self):
163     name = "inst29128.example.com"
164     data = {
165       "name": name,
166       "disks": [],
167       "nics": [],
168       "mode": constants.INSTANCE_CREATE,
169       "disk_template": constants.DT_PLAIN,
170       }
171     op = self.Parse(data, False)
172     self.assert_(isinstance(op, opcodes.OpInstanceCreate))
173     self.assertEqual(op.instance_name, name)
174     self.assertFalse(hasattr(op, "name"))
175
176     # Define both
177     data = {
178       "name": name,
179       "instance_name": "other.example.com",
180       "disks": [],
181       "nics": [],
182       "mode": constants.INSTANCE_CREATE,
183       "disk_template": constants.DT_PLAIN,
184       }
185     self.assertRaises(http.HttpBadRequest, self.Parse, data, False)
186
187   def testLegacyOs(self):
188     name = "inst4673.example.com"
189     os = "linux29206"
190     data = {
191       "name": name,
192       "os_type": os,
193       "disks": [],
194       "nics": [],
195       "mode": constants.INSTANCE_CREATE,
196       "disk_template": constants.DT_PLAIN,
197       }
198     op = self.Parse(data, False)
199     self.assert_(isinstance(op, opcodes.OpInstanceCreate))
200     self.assertEqual(op.instance_name, name)
201     self.assertEqual(op.os_type, os)
202     self.assertFalse(hasattr(op, "os"))
203
204     # Define both
205     data = {
206       "instance_name": name,
207       "os": os,
208       "os_type": "linux9584",
209       "disks": [],
210       "nics": [],
211       "mode": constants.INSTANCE_CREATE,
212       "disk_template": constants.DT_PLAIN,
213       }
214     self.assertRaises(http.HttpBadRequest, self.Parse, data, False)
215
216   def testErrors(self):
217     # Test all required fields
218     reqfields = {
219       "name": "inst1.example.com",
220       "disks": [],
221       "nics": [],
222       "mode": constants.INSTANCE_CREATE,
223       "disk_template": constants.DT_PLAIN,
224       }
225
226     for name in reqfields.keys():
227       self.assertRaises(http.HttpBadRequest, self.Parse,
228                         dict(i for i in reqfields.iteritems() if i[0] != name),
229                         False)
230
231     # Invalid disks and nics
232     for field in ["disks", "nics"]:
233       invalid_values = [None, 1, "", {}, [1, 2, 3], ["hda1", "hda2"],
234                         [{"_unknown_": 999, }]]
235
236       for invvalue in invalid_values:
237         data = reqfields.copy()
238         data[field] = invvalue
239         self.assertRaises(http.HttpBadRequest, self.Parse, data, False)
240
241
242 class TestParseExportInstanceRequest(testutils.GanetiTestCase):
243   def setUp(self):
244     testutils.GanetiTestCase.setUp(self)
245
246     self.Parse = rlib2._ParseExportInstanceRequest
247
248   def test(self):
249     name = "instmoo"
250     data = {
251       "mode": constants.EXPORT_MODE_REMOTE,
252       "destination": [(1, 2, 3), (99, 99, 99)],
253       "shutdown": True,
254       "remove_instance": True,
255       "x509_key_name": ["name", "hash"],
256       "destination_x509_ca": "---cert---"
257       }
258     op = self.Parse(name, data)
259     self.assert_(isinstance(op, opcodes.OpBackupExport))
260     self.assertEqual(op.instance_name, name)
261     self.assertEqual(op.mode, constants.EXPORT_MODE_REMOTE)
262     self.assertEqual(op.shutdown, True)
263     self.assertEqual(op.remove_instance, True)
264     self.assertEqualValues(op.x509_key_name, ("name", "hash"))
265     self.assertEqual(op.destination_x509_ca, "---cert---")
266
267   def testDefaults(self):
268     name = "inst1"
269     data = {
270       "destination": "node2",
271       "shutdown": False,
272       }
273     op = self.Parse(name, data)
274     self.assert_(isinstance(op, opcodes.OpBackupExport))
275     self.assertEqual(op.instance_name, name)
276     self.assertEqual(op.target_node, "node2")
277     self.assertFalse(hasattr(op, "mode"))
278     self.assertFalse(hasattr(op, "remove_instance"))
279     self.assertFalse(hasattr(op, "destination"))
280
281   def testErrors(self):
282     self.assertRaises(http.HttpBadRequest, self.Parse, "err1",
283                       { "remove_instance": "True", })
284     self.assertRaises(http.HttpBadRequest, self.Parse, "err1",
285                       { "remove_instance": "False", })
286
287
288 class TestParseMigrateInstanceRequest(testutils.GanetiTestCase):
289   def setUp(self):
290     testutils.GanetiTestCase.setUp(self)
291
292     self.Parse = rlib2._ParseMigrateInstanceRequest
293
294   def test(self):
295     name = "instYooho6ek"
296
297     for cleanup in [False, True]:
298       for mode in constants.HT_MIGRATION_MODES:
299         data = {
300           "cleanup": cleanup,
301           "mode": mode,
302           }
303         op = self.Parse(name, data)
304         self.assert_(isinstance(op, opcodes.OpInstanceMigrate))
305         self.assertEqual(op.instance_name, name)
306         self.assertEqual(op.mode, mode)
307         self.assertEqual(op.cleanup, cleanup)
308
309   def testDefaults(self):
310     name = "instnohZeex0"
311
312     op = self.Parse(name, {})
313     self.assert_(isinstance(op, opcodes.OpInstanceMigrate))
314     self.assertEqual(op.instance_name, name)
315     self.assertFalse(hasattr(op, "mode"))
316     self.assertFalse(hasattr(op, "cleanup"))
317
318
319 class TestParseRenameInstanceRequest(testutils.GanetiTestCase):
320   def setUp(self):
321     testutils.GanetiTestCase.setUp(self)
322
323     self.Parse = rlib2._ParseRenameInstanceRequest
324
325   def test(self):
326     name = "instij0eeph7"
327
328     for new_name in ["ua0aiyoo", "fai3ongi"]:
329       for ip_check in [False, True]:
330         for name_check in [False, True]:
331           data = {
332             "new_name": new_name,
333             "ip_check": ip_check,
334             "name_check": name_check,
335             }
336
337           op = self.Parse(name, data)
338           self.assert_(isinstance(op, opcodes.OpInstanceRename))
339           self.assertEqual(op.instance_name, name)
340           self.assertEqual(op.new_name, new_name)
341           self.assertEqual(op.ip_check, ip_check)
342           self.assertEqual(op.name_check, name_check)
343
344   def testDefaults(self):
345     name = "instahchie3t"
346
347     for new_name in ["thag9mek", "quees7oh"]:
348       data = {
349         "new_name": new_name,
350         }
351
352       op = self.Parse(name, data)
353       self.assert_(isinstance(op, opcodes.OpInstanceRename))
354       self.assertEqual(op.instance_name, name)
355       self.assertEqual(op.new_name, new_name)
356       self.assertFalse(hasattr(op, "ip_check"))
357       self.assertFalse(hasattr(op, "name_check"))
358
359
360 class TestParseModifyInstanceRequest(testutils.GanetiTestCase):
361   def setUp(self):
362     testutils.GanetiTestCase.setUp(self)
363
364     self.Parse = rlib2._ParseModifyInstanceRequest
365
366   def test(self):
367     name = "instush8gah"
368
369     test_disks = [
370       [],
371       [(1, { constants.IDISK_MODE: constants.DISK_RDWR, })],
372       ]
373
374     for osparams in [{}, { "some": "value", "other": "Hello World", }]:
375       for hvparams in [{}, { constants.HV_KERNEL_PATH: "/some/kernel", }]:
376         for beparams in [{}, { constants.BE_MEMORY: 128, }]:
377           for force in [False, True]:
378             for nics in [[], [(0, { constants.INIC_IP: "192.0.2.1", })]]:
379               for disks in test_disks:
380                 for disk_template in constants.DISK_TEMPLATES:
381                   data = {
382                     "osparams": osparams,
383                     "hvparams": hvparams,
384                     "beparams": beparams,
385                     "nics": nics,
386                     "disks": disks,
387                     "force": force,
388                     "disk_template": disk_template,
389                     }
390
391                   op = self.Parse(name, data)
392                   self.assert_(isinstance(op, opcodes.OpInstanceSetParams))
393                   self.assertEqual(op.instance_name, name)
394                   self.assertEqual(op.hvparams, hvparams)
395                   self.assertEqual(op.beparams, beparams)
396                   self.assertEqual(op.osparams, osparams)
397                   self.assertEqual(op.force, force)
398                   self.assertEqual(op.nics, nics)
399                   self.assertEqual(op.disks, disks)
400                   self.assertEqual(op.disk_template, disk_template)
401                   self.assertFalse(hasattr(op, "remote_node"))
402                   self.assertFalse(hasattr(op, "os_name"))
403                   self.assertFalse(hasattr(op, "force_variant"))
404
405   def testDefaults(self):
406     name = "instir8aish31"
407
408     op = self.Parse(name, {})
409     self.assert_(isinstance(op, opcodes.OpInstanceSetParams))
410     self.assertEqual(op.instance_name, name)
411     for i in ["hvparams", "beparams", "osparams", "force", "nics", "disks",
412               "disk_template", "remote_node", "os_name", "force_variant"]:
413       self.assertFalse(hasattr(op, i))
414
415
416 class TestParseInstanceReinstallRequest(testutils.GanetiTestCase):
417   def setUp(self):
418     testutils.GanetiTestCase.setUp(self)
419
420     self.Parse = rlib2._ParseInstanceReinstallRequest
421
422   def _Check(self, ops, name):
423     expcls = [
424       opcodes.OpInstanceShutdown,
425       opcodes.OpInstanceReinstall,
426       opcodes.OpInstanceStartup,
427       ]
428
429     self.assert_(compat.all(isinstance(op, exp)
430                             for op, exp in zip(ops, expcls)))
431     self.assert_(compat.all(op.instance_name == name for op in ops))
432
433   def test(self):
434     name = "shoo0tihohma"
435
436     ops = self.Parse(name, {"os": "sys1", "start": True,})
437     self.assertEqual(len(ops), 3)
438     self._Check(ops, name)
439     self.assertEqual(ops[1].os_type, "sys1")
440     self.assertFalse(ops[1].osparams)
441
442     ops = self.Parse(name, {"os": "sys2", "start": False,})
443     self.assertEqual(len(ops), 2)
444     self._Check(ops, name)
445     self.assertEqual(ops[1].os_type, "sys2")
446
447     osparams = {
448       "reformat": "1",
449       }
450     ops = self.Parse(name, {"os": "sys4035", "start": True,
451                             "osparams": osparams,})
452     self.assertEqual(len(ops), 3)
453     self._Check(ops, name)
454     self.assertEqual(ops[1].os_type, "sys4035")
455     self.assertEqual(ops[1].osparams, osparams)
456
457   def testDefaults(self):
458     name = "noolee0g"
459
460     ops = self.Parse(name, {"os": "linux1"})
461     self.assertEqual(len(ops), 3)
462     self._Check(ops, name)
463     self.assertEqual(ops[1].os_type, "linux1")
464     self.assertFalse(ops[1].osparams)
465
466
467 class TestParseRenameGroupRequest(testutils.GanetiTestCase):
468   def setUp(self):
469     testutils.GanetiTestCase.setUp(self)
470
471     self.Parse = rlib2._ParseRenameGroupRequest
472
473   def test(self):
474     name = "instij0eeph7"
475     data = {
476       "new_name": "ua0aiyoo",
477       }
478
479     op = self.Parse(name, data, False)
480
481     self.assert_(isinstance(op, opcodes.OpGroupRename))
482     self.assertEqual(op.group_name, name)
483     self.assertEqual(op.new_name, "ua0aiyoo")
484     self.assertFalse(op.dry_run)
485
486   def testDryRun(self):
487     name = "instij0eeph7"
488     data = {
489       "new_name": "ua0aiyoo",
490       }
491
492     op = self.Parse(name, data, True)
493
494     self.assert_(isinstance(op, opcodes.OpGroupRename))
495     self.assertEqual(op.group_name, name)
496     self.assertEqual(op.new_name, "ua0aiyoo")
497     self.assert_(op.dry_run)
498
499
500 class TestParseInstanceReplaceDisksRequest(unittest.TestCase):
501   def setUp(self):
502     self.Parse = rlib2._ParseInstanceReplaceDisksRequest
503
504   def test(self):
505     name = "inst22568"
506
507     for disks in [range(1, 4), "1,2,3", "1, 2, 3"]:
508       data = {
509         "mode": constants.REPLACE_DISK_SEC,
510         "disks": disks,
511         "iallocator": "myalloc",
512         }
513
514       op = self.Parse(name, data)
515       self.assert_(isinstance(op, opcodes.OpInstanceReplaceDisks))
516       self.assertEqual(op.mode, constants.REPLACE_DISK_SEC)
517       self.assertEqual(op.disks, [1, 2, 3])
518       self.assertEqual(op.iallocator, "myalloc")
519
520   def testDefaults(self):
521     name = "inst11413"
522     data = {
523       "mode": constants.REPLACE_DISK_AUTO,
524       }
525
526     op = self.Parse(name, data)
527     self.assert_(isinstance(op, opcodes.OpInstanceReplaceDisks))
528     self.assertEqual(op.mode, constants.REPLACE_DISK_AUTO)
529     self.assertFalse(hasattr(op, "iallocator"))
530     self.assertFalse(hasattr(op, "disks"))
531
532   def testWrong(self):
533     self.assertRaises(http.HttpBadRequest, self.Parse, "inst",
534                       { "mode": constants.REPLACE_DISK_AUTO,
535                         "disks": "hello world",
536                       })
537
538
539 class TestParseModifyGroupRequest(unittest.TestCase):
540   def setUp(self):
541     self.Parse = rlib2._ParseModifyGroupRequest
542
543   def test(self):
544     name = "group6002"
545
546     for policy in constants.VALID_ALLOC_POLICIES:
547       data = {
548         "alloc_policy": policy,
549         }
550
551       op = self.Parse(name, data)
552       self.assert_(isinstance(op, opcodes.OpGroupSetParams))
553       self.assertEqual(op.group_name, name)
554       self.assertEqual(op.alloc_policy, policy)
555
556   def testUnknownPolicy(self):
557     data = {
558       "alloc_policy": "_unknown_policy_",
559       }
560
561     self.assertRaises(http.HttpBadRequest, self.Parse, "name", data)
562
563   def testDefaults(self):
564     name = "group6679"
565     data = {}
566
567     op = self.Parse(name, data)
568     self.assert_(isinstance(op, opcodes.OpGroupSetParams))
569     self.assertEqual(op.group_name, name)
570     self.assertFalse(hasattr(op, "alloc_policy"))
571
572
573 class TestParseCreateGroupRequest(unittest.TestCase):
574   def setUp(self):
575     self.Parse = rlib2._ParseCreateGroupRequest
576
577   def test(self):
578     name = "group3618"
579
580     for policy in constants.VALID_ALLOC_POLICIES:
581       data = {
582         "group_name": name,
583         "alloc_policy": policy,
584         }
585
586       op = self.Parse(data, False)
587       self.assert_(isinstance(op, opcodes.OpGroupAdd))
588       self.assertEqual(op.group_name, name)
589       self.assertEqual(op.alloc_policy, policy)
590       self.assertFalse(op.dry_run)
591
592   def testUnknownPolicy(self):
593     data = {
594       "alloc_policy": "_unknown_policy_",
595       }
596
597     self.assertRaises(http.HttpBadRequest, self.Parse, "name", data)
598
599   def testDefaults(self):
600     name = "group15395"
601     data = {
602       "group_name": name,
603       }
604
605     op = self.Parse(data, True)
606     self.assert_(isinstance(op, opcodes.OpGroupAdd))
607     self.assertEqual(op.group_name, name)
608     self.assertFalse(hasattr(op, "alloc_policy"))
609     self.assertTrue(op.dry_run)
610
611   def testLegacyName(self):
612     name = "group29852"
613     data = {
614       "name": name,
615       }
616
617     op = self.Parse(data, True)
618     self.assert_(isinstance(op, opcodes.OpGroupAdd))
619     self.assertEqual(op.group_name, name)
620
621
622 if __name__ == '__main__':
623   testutils.GanetiTestProgram()