Replace LD_* constants with DT_* constants
[ganeti-local] / test / py / ganeti.hypervisor.hv_xen_unittest.py
1 #!/usr/bin/python
2 #
3
4 # Copyright (C) 2011, 2013 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.hypervisor.hv_xen"""
23
24 import string # pylint: disable=W0402
25 import unittest
26 import tempfile
27 import shutil
28 import random
29 import os
30 import mock
31
32 from ganeti import constants
33 from ganeti import objects
34 from ganeti import pathutils
35 from ganeti import hypervisor
36 from ganeti import utils
37 from ganeti import errors
38 from ganeti import compat
39
40 from ganeti.hypervisor import hv_xen
41
42 import testutils
43
44
45 # Map from hypervisor class to hypervisor name
46 HVCLASS_TO_HVNAME = utils.InvertDict(hypervisor._HYPERVISOR_MAP)
47
48
49 class TestConsole(unittest.TestCase):
50   def test(self):
51     hvparams = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
52     for cls in [hv_xen.XenPvmHypervisor(), hv_xen.XenHvmHypervisor()]:
53       instance = objects.Instance(name="xen.example.com",
54                                   primary_node="node24828-uuid")
55       node = objects.Node(name="node24828", uuid="node24828-uuid")
56       cons = cls.GetInstanceConsole(instance, node, hvparams, {})
57       self.assertTrue(cons.Validate())
58       self.assertEqual(cons.kind, constants.CONS_SSH)
59       self.assertEqual(cons.host, node.name)
60       self.assertEqual(cons.command[-1], instance.name)
61
62
63 class TestCreateConfigCpus(unittest.TestCase):
64   def testEmpty(self):
65     for cpu_mask in [None, ""]:
66       self.assertEqual(hv_xen._CreateConfigCpus(cpu_mask),
67                        "cpus = [  ]")
68
69   def testAll(self):
70     self.assertEqual(hv_xen._CreateConfigCpus(constants.CPU_PINNING_ALL),
71                      None)
72
73   def testOne(self):
74     self.assertEqual(hv_xen._CreateConfigCpus("9"), "cpu = \"9\"")
75
76   def testMultiple(self):
77     self.assertEqual(hv_xen._CreateConfigCpus("0-2,4,5-5:3:all"),
78                      ("cpus = [ \"0,1,2,4,5\", \"3\", \"%s\" ]" %
79                       constants.CPU_PINNING_ALL_XEN))
80
81
82 class TestGetCommand(testutils.GanetiTestCase):
83   def testCommandExplicit(self):
84     """Test the case when the command is given as class parameter explicitly.
85
86     """
87     expected_cmd = "xl"
88     hv = hv_xen.XenHypervisor(_cmd=constants.XEN_CMD_XL)
89     self.assertEqual(hv._GetCommand(None), expected_cmd)
90
91   def testCommandInvalid(self):
92     """Test the case an invalid command is given as class parameter explicitly.
93
94     """
95     hv = hv_xen.XenHypervisor(_cmd="invalidcommand")
96     self.assertRaises(errors.ProgrammerError, hv._GetCommand, None)
97
98   def testCommandHvparams(self):
99     expected_cmd = "xl"
100     test_hvparams = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
101     hv = hv_xen.XenHypervisor()
102     self.assertEqual(hv._GetCommand(test_hvparams), expected_cmd)
103
104   def testCommandHvparamsInvalid(self):
105     test_hvparams = {}
106     hv = hv_xen.XenHypervisor()
107     self.assertRaises(errors.HypervisorError, hv._GetCommand, test_hvparams)
108
109   def testCommandHvparamsCmdInvalid(self):
110     test_hvparams = {constants.HV_XEN_CMD: "invalidcommand"}
111     hv = hv_xen.XenHypervisor()
112     self.assertRaises(errors.ProgrammerError, hv._GetCommand, test_hvparams)
113
114
115 class TestParseInstanceList(testutils.GanetiTestCase):
116   def test(self):
117     data = testutils.ReadTestData("xen-xm-list-4.0.1-dom0-only.txt")
118
119     # Exclude node
120     self.assertEqual(hv_xen._ParseInstanceList(data.splitlines(), False), [])
121
122     # Include node
123     result = hv_xen._ParseInstanceList(data.splitlines(), True)
124     self.assertEqual(len(result), 1)
125     self.assertEqual(len(result[0]), 6)
126
127     # Name
128     self.assertEqual(result[0][0], hv_xen._DOM0_NAME)
129
130     # ID
131     self.assertEqual(result[0][1], 0)
132
133     # Memory
134     self.assertEqual(result[0][2], 1023)
135
136     # VCPUs
137     self.assertEqual(result[0][3], 1)
138
139     # State
140     self.assertEqual(result[0][4], "r-----")
141
142     # Time
143     self.assertAlmostEqual(result[0][5], 121152.6)
144
145   def testWrongLineFormat(self):
146     tests = [
147       ["three fields only"],
148       ["name InvalidID 128 1 r----- 12345"],
149       ]
150
151     for lines in tests:
152       try:
153         hv_xen._ParseInstanceList(["Header would be here"] + lines, False)
154       except errors.HypervisorError, err:
155         self.assertTrue("Can't parse instance list" in str(err))
156       else:
157         self.fail("Exception was not raised")
158
159
160 class TestGetInstanceList(testutils.GanetiTestCase):
161   def _Fail(self):
162     return utils.RunResult(constants.EXIT_FAILURE, None,
163                            "stdout", "stderr", None,
164                            NotImplemented, NotImplemented)
165
166   def testTimeout(self):
167     fn = testutils.CallCounter(self._Fail)
168     try:
169       hv_xen._GetInstanceList(fn, False, _timeout=0.1)
170     except errors.HypervisorError, err:
171       self.assertTrue("timeout exceeded" in str(err))
172     else:
173       self.fail("Exception was not raised")
174
175     self.assertTrue(fn.Count() < 10,
176                     msg="'xm list' was called too many times")
177
178   def _Success(self, stdout):
179     return utils.RunResult(constants.EXIT_SUCCESS, None, stdout, "", None,
180                            NotImplemented, NotImplemented)
181
182   def testSuccess(self):
183     data = testutils.ReadTestData("xen-xm-list-4.0.1-four-instances.txt")
184
185     fn = testutils.CallCounter(compat.partial(self._Success, data))
186
187     result = hv_xen._GetInstanceList(fn, True, _timeout=0.1)
188
189     self.assertEqual(len(result), 4)
190
191     self.assertEqual(map(compat.fst, result), [
192       "Domain-0",
193       "server01.example.com",
194       "web3106215069.example.com",
195       "testinstance.example.com",
196       ])
197
198     self.assertEqual(fn.Count(), 1)
199
200
201 class TestParseNodeInfo(testutils.GanetiTestCase):
202   def testEmpty(self):
203     self.assertEqual(hv_xen._ParseNodeInfo(""), {})
204
205   def testUnknownInput(self):
206     data = "\n".join([
207       "foo bar",
208       "something else goes",
209       "here",
210       ])
211     self.assertEqual(hv_xen._ParseNodeInfo(data), {})
212
213   def testBasicInfo(self):
214     data = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
215     result = hv_xen._ParseNodeInfo(data)
216     self.assertEqual(result, {
217       "cpu_nodes": 1,
218       "cpu_sockets": 2,
219       "cpu_total": 4,
220       "hv_version": (4, 0),
221       "memory_free": 8004,
222       "memory_total": 16378,
223       })
224
225
226 class TestMergeInstanceInfo(testutils.GanetiTestCase):
227   def testEmpty(self):
228     self.assertEqual(hv_xen._MergeInstanceInfo({}, []), {})
229
230   def _FakeXmList(self, include_node):
231     return [
232       (hv_xen._DOM0_NAME, NotImplemented, 4096, 7, NotImplemented,
233        NotImplemented),
234       ("inst1.example.com", NotImplemented, 2048, 4, NotImplemented,
235        NotImplemented),
236       ]
237
238   def testMissingNodeInfo(self):
239     instance_list = self._FakeXmList(True)
240     result = hv_xen._MergeInstanceInfo({}, instance_list)
241     self.assertEqual(result, {
242       "memory_dom0": 4096,
243       "cpu_dom0": 7,
244       })
245
246   def testWithNodeInfo(self):
247     info = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
248     instance_list = self._FakeXmList(True)
249     result = hv_xen._GetNodeInfo(info, instance_list)
250     self.assertEqual(result, {
251       "cpu_nodes": 1,
252       "cpu_sockets": 2,
253       "cpu_total": 4,
254       "cpu_dom0": 7,
255       "hv_version": (4, 0),
256       "memory_dom0": 4096,
257       "memory_free": 8004,
258       "memory_hv": 2230,
259       "memory_total": 16378,
260       })
261
262
263 class TestGetConfigFileDiskData(unittest.TestCase):
264   def testLetterCount(self):
265     self.assertEqual(len(hv_xen._DISK_LETTERS), 26)
266
267   def testNoDisks(self):
268     self.assertEqual(hv_xen._GetConfigFileDiskData([], "hd"), [])
269
270   def testManyDisks(self):
271     for offset in [0, 1, 10]:
272       disks = [(objects.Disk(dev_type=constants.DT_PLAIN), "/tmp/disk/%s" % idx)
273                for idx in range(len(hv_xen._DISK_LETTERS) + offset)]
274
275       if offset == 0:
276         result = hv_xen._GetConfigFileDiskData(disks, "hd")
277         self.assertEqual(result, [
278           "'phy:/tmp/disk/%s,hd%s,r'" % (idx, string.ascii_lowercase[idx])
279           for idx in range(len(hv_xen._DISK_LETTERS) + offset)
280           ])
281       else:
282         try:
283           hv_xen._GetConfigFileDiskData(disks, "hd")
284         except errors.HypervisorError, err:
285           self.assertEqual(str(err), "Too many disks")
286         else:
287           self.fail("Exception was not raised")
288
289   def testTwoLvDisksWithMode(self):
290     disks = [
291       (objects.Disk(dev_type=constants.DT_PLAIN, mode=constants.DISK_RDWR),
292        "/tmp/diskFirst"),
293       (objects.Disk(dev_type=constants.DT_PLAIN, mode=constants.DISK_RDONLY),
294        "/tmp/diskLast"),
295       ]
296
297     result = hv_xen._GetConfigFileDiskData(disks, "hd")
298     self.assertEqual(result, [
299       "'phy:/tmp/diskFirst,hda,w'",
300       "'phy:/tmp/diskLast,hdb,r'",
301       ])
302
303   def testFileDisks(self):
304     disks = [
305       (objects.Disk(dev_type=constants.DT_FILE, mode=constants.DISK_RDWR,
306                     physical_id=[constants.FD_LOOP]),
307        "/tmp/diskFirst"),
308       (objects.Disk(dev_type=constants.DT_FILE, mode=constants.DISK_RDONLY,
309                     physical_id=[constants.FD_BLKTAP]),
310        "/tmp/diskTwo"),
311       (objects.Disk(dev_type=constants.DT_FILE, mode=constants.DISK_RDWR,
312                     physical_id=[constants.FD_LOOP]),
313        "/tmp/diskThree"),
314       (objects.Disk(dev_type=constants.DT_FILE, mode=constants.DISK_RDWR,
315                     physical_id=[constants.FD_BLKTAP]),
316        "/tmp/diskLast"),
317       ]
318
319     result = hv_xen._GetConfigFileDiskData(disks, "sd")
320     self.assertEqual(result, [
321       "'file:/tmp/diskFirst,sda,w'",
322       "'tap:aio:/tmp/diskTwo,sdb,r'",
323       "'file:/tmp/diskThree,sdc,w'",
324       "'tap:aio:/tmp/diskLast,sdd,w'",
325       ])
326
327   def testInvalidFileDisk(self):
328     disks = [
329       (objects.Disk(dev_type=constants.DT_FILE, mode=constants.DISK_RDWR,
330                     physical_id=["#unknown#"]),
331        "/tmp/diskinvalid"),
332       ]
333
334     self.assertRaises(KeyError, hv_xen._GetConfigFileDiskData, disks, "sd")
335
336
337 class TestXenHypervisorRunXen(unittest.TestCase):
338
339   XEN_SUB_CMD = "help"
340
341   def testCommandUnknown(self):
342     cmd = "#unknown command#"
343     self.assertFalse(cmd in constants.KNOWN_XEN_COMMANDS)
344     hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
345                               _run_cmd_fn=NotImplemented,
346                               _cmd=cmd)
347     self.assertRaises(errors.ProgrammerError, hv._RunXen, [], None)
348
349   def testCommandNoHvparams(self):
350     hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
351                               _run_cmd_fn=NotImplemented)
352     hvparams = None
353     self.assertRaises(errors.HypervisorError, hv._RunXen, [self.XEN_SUB_CMD],
354                       hvparams)
355
356   def testCommandFromHvparams(self):
357     expected_xen_cmd = "xl"
358     hvparams = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
359     mock_run_cmd = mock.Mock()
360     hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
361                               _run_cmd_fn=mock_run_cmd)
362     hv._RunXen([self.XEN_SUB_CMD], hvparams=hvparams)
363     mock_run_cmd.assert_called_with([expected_xen_cmd, self.XEN_SUB_CMD])
364
365
366 class TestXenHypervisorGetInstanceList(unittest.TestCase):
367
368   RESULT_OK = utils.RunResult(0, None, "", "", "", None, None)
369   XEN_LIST = "list"
370
371   def testNoHvparams(self):
372     expected_xen_cmd = "xm"
373     mock_run_cmd = mock.Mock( return_value=self.RESULT_OK )
374     hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
375                               _run_cmd_fn=mock_run_cmd)
376     self.assertRaises(errors.HypervisorError, hv._GetInstanceList, True, None)
377
378   def testFromHvparams(self):
379     expected_xen_cmd = "xl"
380     hvparams = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
381     mock_run_cmd = mock.Mock( return_value=self.RESULT_OK )
382     hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
383                               _run_cmd_fn=mock_run_cmd)
384     hv._GetInstanceList(True, hvparams)
385     mock_run_cmd.assert_called_with([expected_xen_cmd, self.XEN_LIST])
386
387
388 class TestXenHypervisorListInstances(unittest.TestCase):
389
390   RESULT_OK = utils.RunResult(0, None, "", "", "", None, None)
391   XEN_LIST = "list"
392
393   def testNoHvparams(self):
394     expected_xen_cmd = "xm"
395     mock_run_cmd = mock.Mock( return_value=self.RESULT_OK )
396     hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
397                               _run_cmd_fn=mock_run_cmd)
398     self.assertRaises(errors.HypervisorError, hv.ListInstances)
399
400   def testHvparamsXl(self):
401     expected_xen_cmd = "xl"
402     hvparams = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
403     mock_run_cmd = mock.Mock( return_value=self.RESULT_OK )
404     hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
405                               _run_cmd_fn=mock_run_cmd)
406     hv.ListInstances(hvparams=hvparams)
407     mock_run_cmd.assert_called_with([expected_xen_cmd, self.XEN_LIST])
408
409
410 class TestXenHypervisorCheckToolstack(unittest.TestCase):
411
412   def setUp(self):
413     self.tmpdir = tempfile.mkdtemp()
414     self.cfg_name = "xen_config"
415     self.cfg_path = utils.PathJoin(self.tmpdir, self.cfg_name)
416     self.hv = hv_xen.XenHypervisor()
417
418   def tearDown(self):
419     shutil.rmtree(self.tmpdir)
420
421   def testBinaryNotFound(self):
422     RESULT_FAILED = utils.RunResult(1, None, "", "", "", None, None)
423     mock_run_cmd = mock.Mock(return_value=RESULT_FAILED)
424     hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
425                               _run_cmd_fn=mock_run_cmd)
426     result = hv._CheckToolstackBinary("xl")
427     self.assertFalse(result)
428
429   def testCheckToolstackXlConfigured(self):
430     RESULT_OK = utils.RunResult(0, None, "", "", "", None, None)
431     mock_run_cmd = mock.Mock(return_value=RESULT_OK)
432     hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
433                               _run_cmd_fn=mock_run_cmd)
434     result = hv._CheckToolstackXlConfigured()
435     self.assertTrue(result)
436
437   def testCheckToolstackXlNotConfigured(self):
438     RESULT_FAILED = utils.RunResult(
439         1, None, "",
440         "ERROR:  A different toolstack (xm) have been selected!",
441         "", None, None)
442     mock_run_cmd = mock.Mock(return_value=RESULT_FAILED)
443     hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
444                               _run_cmd_fn=mock_run_cmd)
445     result = hv._CheckToolstackXlConfigured()
446     self.assertFalse(result)
447
448   def testCheckToolstackXlFails(self):
449     RESULT_FAILED = utils.RunResult(
450         1, None, "",
451         "ERROR: The pink bunny hid the binary.",
452         "", None, None)
453     mock_run_cmd = mock.Mock(return_value=RESULT_FAILED)
454     hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
455                               _run_cmd_fn=mock_run_cmd)
456     self.assertRaises(errors.HypervisorError, hv._CheckToolstackXlConfigured)
457
458
459 class TestXenHypervisorWriteConfigFile(unittest.TestCase):
460   def setUp(self):
461     self.tmpdir = tempfile.mkdtemp()
462
463   def tearDown(self):
464     shutil.rmtree(self.tmpdir)
465
466   def testWriteError(self):
467     cfgdir = utils.PathJoin(self.tmpdir, "foobar")
468
469     hv = hv_xen.XenHypervisor(_cfgdir=cfgdir,
470                               _run_cmd_fn=NotImplemented,
471                               _cmd=NotImplemented)
472
473     self.assertFalse(os.path.exists(cfgdir))
474
475     try:
476       hv._WriteConfigFile("name", "data")
477     except errors.HypervisorError, err:
478       self.assertTrue(str(err).startswith("Cannot write Xen instance"))
479     else:
480       self.fail("Exception was not raised")
481
482
483 class TestXenHypervisorVerify(unittest.TestCase):
484
485   def setUp(self):
486     output = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
487     self._result_ok = utils.RunResult(0, None, output, "", "", None, None)
488
489   def testVerify(self):
490     hvparams = {constants.HV_XEN_CMD : constants.XEN_CMD_XL}
491     mock_run_cmd = mock.Mock(return_value=self._result_ok)
492     hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
493                               _run_cmd_fn=mock_run_cmd)
494     hv._CheckToolstack = mock.Mock(return_value=True)
495     result = hv.Verify(hvparams)
496     self.assertTrue(result is None)
497
498   def testVerifyToolstackNotOk(self):
499     hvparams = {constants.HV_XEN_CMD : constants.XEN_CMD_XL}
500     mock_run_cmd = mock.Mock(return_value=self._result_ok)
501     hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
502                               _run_cmd_fn=mock_run_cmd)
503     hv._CheckToolstack = mock.Mock()
504     hv._CheckToolstack.side_effect = errors.HypervisorError("foo")
505     result = hv.Verify(hvparams)
506     self.assertTrue(result is not None)
507
508   def testVerifyFailing(self):
509     result_failed = utils.RunResult(1, None, "", "", "", None, None)
510     mock_run_cmd = mock.Mock(return_value=result_failed)
511     hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
512                               _run_cmd_fn=mock_run_cmd)
513     hv._CheckToolstack = mock.Mock(return_value=True)
514     result = hv.Verify()
515     self.assertTrue(result is not None)
516
517
518 class _TestXenHypervisor(object):
519   TARGET = NotImplemented
520   CMD = NotImplemented
521   HVNAME = NotImplemented
522   VALID_HVPARAMS = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
523
524   def setUp(self):
525     super(_TestXenHypervisor, self).setUp()
526
527     self.tmpdir = tempfile.mkdtemp()
528
529     self.vncpw = "".join(random.sample(string.ascii_letters, 10))
530
531     self.vncpw_path = utils.PathJoin(self.tmpdir, "vncpw")
532     utils.WriteFile(self.vncpw_path, data=self.vncpw)
533
534   def tearDown(self):
535     super(_TestXenHypervisor, self).tearDown()
536
537     shutil.rmtree(self.tmpdir)
538
539   def _GetHv(self, run_cmd=NotImplemented):
540     return self.TARGET(_cfgdir=self.tmpdir, _run_cmd_fn=run_cmd, _cmd=self.CMD)
541
542   def _SuccessCommand(self, stdout, cmd):
543     self.assertEqual(cmd[0], self.CMD)
544
545     return utils.RunResult(constants.EXIT_SUCCESS, None, stdout, "", None,
546                            NotImplemented, NotImplemented)
547
548   def _FailingCommand(self, cmd):
549     self.assertEqual(cmd[0], self.CMD)
550
551     return utils.RunResult(constants.EXIT_FAILURE, None,
552                            "", "This command failed", None,
553                            NotImplemented, NotImplemented)
554
555   def _FakeTcpPing(self, expected, result, target, port, **kwargs):
556     self.assertEqual((target, port), expected)
557     return result
558
559   def testReadingNonExistentConfigFile(self):
560     hv = self._GetHv()
561
562     try:
563       hv._ReadConfigFile("inst15780.example.com")
564     except errors.HypervisorError, err:
565       self.assertTrue(str(err).startswith("Failed to load Xen config file:"))
566     else:
567       self.fail("Exception was not raised")
568
569   def testRemovingAutoConfigFile(self):
570     name = "inst8206.example.com"
571     cfgfile = utils.PathJoin(self.tmpdir, name)
572     autodir = utils.PathJoin(self.tmpdir, "auto")
573     autocfgfile = utils.PathJoin(autodir, name)
574
575     os.mkdir(autodir)
576
577     utils.WriteFile(autocfgfile, data="")
578
579     hv = self._GetHv()
580
581     self.assertTrue(os.path.isfile(autocfgfile))
582     hv._WriteConfigFile(name, "content")
583     self.assertFalse(os.path.exists(autocfgfile))
584     self.assertEqual(utils.ReadFile(cfgfile), "content")
585
586   def _XenList(self, cmd):
587     self.assertEqual(cmd, [self.CMD, "list"])
588
589     # TODO: Use actual data from "xl" command
590     output = testutils.ReadTestData("xen-xm-list-4.0.1-four-instances.txt")
591
592     return self._SuccessCommand(output, cmd)
593
594   def testGetInstanceInfo(self):
595     hv = self._GetHv(run_cmd=self._XenList)
596
597     (name, instid, memory, vcpus, state, runtime) = \
598       hv.GetInstanceInfo("server01.example.com")
599
600     self.assertEqual(name, "server01.example.com")
601     self.assertEqual(instid, 1)
602     self.assertEqual(memory, 1024)
603     self.assertEqual(vcpus, 1)
604     self.assertEqual(state, "-b----")
605     self.assertAlmostEqual(runtime, 167643.2)
606
607   def testGetInstanceInfoDom0(self):
608     hv = self._GetHv(run_cmd=self._XenList)
609
610     # TODO: Not sure if this is actually used anywhere (can't find it), but the
611     # code supports querying for Dom0
612     (name, instid, memory, vcpus, state, runtime) = \
613       hv.GetInstanceInfo(hv_xen._DOM0_NAME)
614
615     self.assertEqual(name, "Domain-0")
616     self.assertEqual(instid, 0)
617     self.assertEqual(memory, 1023)
618     self.assertEqual(vcpus, 1)
619     self.assertEqual(state, "r-----")
620     self.assertAlmostEqual(runtime, 154706.1)
621
622   def testGetInstanceInfoUnknown(self):
623     hv = self._GetHv(run_cmd=self._XenList)
624
625     result = hv.GetInstanceInfo("unknown.example.com")
626     self.assertTrue(result is None)
627
628   def testGetAllInstancesInfo(self):
629     hv = self._GetHv(run_cmd=self._XenList)
630
631     result = hv.GetAllInstancesInfo()
632
633     self.assertEqual(map(compat.fst, result), [
634       "server01.example.com",
635       "web3106215069.example.com",
636       "testinstance.example.com",
637       ])
638
639   def testListInstances(self):
640     hv = self._GetHv(run_cmd=self._XenList)
641
642     self.assertEqual(hv.ListInstances(), [
643       "server01.example.com",
644       "web3106215069.example.com",
645       "testinstance.example.com",
646       ])
647
648   def _StartInstanceCommand(self, inst, paused, failcreate, cmd):
649     if cmd == [self.CMD, "info"]:
650       output = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
651     elif cmd == [self.CMD, "list"]:
652       output = testutils.ReadTestData("xen-xm-list-4.0.1-dom0-only.txt")
653     elif cmd[:2] == [self.CMD, "create"]:
654       args = cmd[2:]
655       cfgfile = utils.PathJoin(self.tmpdir, inst.name)
656
657       if paused:
658         self.assertEqual(args, ["-p", cfgfile])
659       else:
660         self.assertEqual(args, [cfgfile])
661
662       if failcreate:
663         return self._FailingCommand(cmd)
664
665       output = ""
666     else:
667       self.fail("Unhandled command: %s" % (cmd, ))
668
669     return self._SuccessCommand(output, cmd)
670
671   def _MakeInstance(self):
672     # Copy default parameters
673     bep = objects.FillDict(constants.BEC_DEFAULTS, {})
674     hvp = objects.FillDict(constants.HVC_DEFAULTS[self.HVNAME], {})
675
676     # Override default VNC password file path
677     if constants.HV_VNC_PASSWORD_FILE in hvp:
678       hvp[constants.HV_VNC_PASSWORD_FILE] = self.vncpw_path
679
680     disks = [
681       (objects.Disk(dev_type=constants.DT_PLAIN, mode=constants.DISK_RDWR),
682        utils.PathJoin(self.tmpdir, "disk0")),
683       (objects.Disk(dev_type=constants.DT_PLAIN, mode=constants.DISK_RDONLY),
684        utils.PathJoin(self.tmpdir, "disk1")),
685       ]
686
687     inst = objects.Instance(name="server01.example.com",
688                             hvparams=hvp, beparams=bep,
689                             osparams={}, nics=[], os="deb1",
690                             disks=map(compat.fst, disks))
691     inst.UpgradeConfig()
692
693     return (inst, disks)
694
695   def testStartInstance(self):
696     (inst, disks) = self._MakeInstance()
697     pathutils.LOG_XEN_DIR = self.tmpdir
698
699     for failcreate in [False, True]:
700       for paused in [False, True]:
701         run_cmd = compat.partial(self._StartInstanceCommand,
702                                  inst, paused, failcreate)
703
704         hv = self._GetHv(run_cmd=run_cmd)
705
706         # Ensure instance is not listed
707         self.assertTrue(inst.name not in hv.ListInstances())
708
709         # Remove configuration
710         cfgfile = utils.PathJoin(self.tmpdir, inst.name)
711         utils.RemoveFile(cfgfile)
712
713         if failcreate:
714           self.assertRaises(errors.HypervisorError, hv.StartInstance,
715                             inst, disks, paused)
716           # Check whether a stale config file is left behind
717           self.assertFalse(os.path.exists(cfgfile))
718         else:
719           hv.StartInstance(inst, disks, paused)
720           # Check if configuration was updated
721           lines = utils.ReadFile(cfgfile).splitlines()
722
723         if constants.HV_VNC_PASSWORD_FILE in inst.hvparams:
724           self.assertTrue(("vncpasswd = '%s'" % self.vncpw) in lines)
725         else:
726           extra = inst.hvparams[constants.HV_KERNEL_ARGS]
727           self.assertTrue(("extra = '%s'" % extra) in lines)
728
729   def _StopInstanceCommand(self, instance_name, force, fail, cmd):
730     if ((force and cmd[:2] == [self.CMD, "destroy"]) or
731         (not force and cmd[:2] == [self.CMD, "shutdown"])):
732       self.assertEqual(cmd[2:], [instance_name])
733       output = ""
734     else:
735       self.fail("Unhandled command: %s" % (cmd, ))
736
737     if fail:
738       # Simulate a failing command
739       return self._FailingCommand(cmd)
740     else:
741       return self._SuccessCommand(output, cmd)
742
743   def testStopInstance(self):
744     name = "inst4284.example.com"
745     cfgfile = utils.PathJoin(self.tmpdir, name)
746     cfgdata = "config file content\n"
747
748     for force in [False, True]:
749       for fail in [False, True]:
750         utils.WriteFile(cfgfile, data=cfgdata)
751
752         run_cmd = compat.partial(self._StopInstanceCommand, name, force, fail)
753
754         hv = self._GetHv(run_cmd=run_cmd)
755
756         self.assertTrue(os.path.isfile(cfgfile))
757
758         if fail:
759           try:
760             hv._StopInstance(name, force, None)
761           except errors.HypervisorError, err:
762             self.assertTrue(str(err).startswith("Failed to stop instance"))
763           else:
764             self.fail("Exception was not raised")
765           self.assertEqual(utils.ReadFile(cfgfile), cfgdata,
766                            msg=("Configuration was removed when stopping"
767                                 " instance failed"))
768         else:
769           hv._StopInstance(name, force, None)
770           self.assertFalse(os.path.exists(cfgfile))
771
772   def _MigrateNonRunningInstCmd(self, cmd):
773     if cmd == [self.CMD, "list"]:
774       output = testutils.ReadTestData("xen-xm-list-4.0.1-dom0-only.txt")
775     else:
776       self.fail("Unhandled command: %s" % (cmd, ))
777
778     return self._SuccessCommand(output, cmd)
779
780   def testMigrateInstanceNotRunning(self):
781     name = "nonexistinginstance.example.com"
782     target = constants.IP4_ADDRESS_LOCALHOST
783     port = 14618
784
785     hv = self._GetHv(run_cmd=self._MigrateNonRunningInstCmd)
786
787     for live in [False, True]:
788       try:
789         hv._MigrateInstance(NotImplemented, name, target, port, live,
790                             self.VALID_HVPARAMS, _ping_fn=NotImplemented)
791       except errors.HypervisorError, err:
792         self.assertEqual(str(err), "Instance not running, cannot migrate")
793       else:
794         self.fail("Exception was not raised")
795
796   def _MigrateInstTargetUnreachCmd(self, cmd):
797     if cmd == [self.CMD, "list"]:
798       output = testutils.ReadTestData("xen-xm-list-4.0.1-four-instances.txt")
799     else:
800       self.fail("Unhandled command: %s" % (cmd, ))
801
802     return self._SuccessCommand(output, cmd)
803
804   def testMigrateTargetUnreachable(self):
805     name = "server01.example.com"
806     target = constants.IP4_ADDRESS_LOCALHOST
807     port = 28349
808
809     hv = self._GetHv(run_cmd=self._MigrateInstTargetUnreachCmd)
810     hvparams = {constants.HV_XEN_CMD: self.CMD}
811
812     for live in [False, True]:
813       if self.CMD == constants.XEN_CMD_XL:
814         # TODO: Detect unreachable targets
815         pass
816       else:
817         try:
818           hv._MigrateInstance(NotImplemented, name, target, port, live,
819                               hvparams,
820                               _ping_fn=compat.partial(self._FakeTcpPing,
821                                                       (target, port), False))
822         except errors.HypervisorError, err:
823           wanted = "Remote host %s not" % target
824           self.assertTrue(str(err).startswith(wanted))
825         else:
826           self.fail("Exception was not raised")
827
828   def _MigrateInstanceCmd(self, cluster_name, instance_name, target, port,
829                           live, fail, cmd):
830     if cmd == [self.CMD, "list"]:
831       output = testutils.ReadTestData("xen-xm-list-4.0.1-four-instances.txt")
832     elif cmd[:2] == [self.CMD, "migrate"]:
833       if self.CMD == constants.XEN_CMD_XM:
834         args = ["-p", str(port)]
835
836         if live:
837           args.append("-l")
838
839       elif self.CMD == constants.XEN_CMD_XL:
840         args = [
841           "-s", constants.XL_SSH_CMD % cluster_name,
842           "-C", utils.PathJoin(self.tmpdir, instance_name),
843           ]
844
845       else:
846         self.fail("Unknown Xen command '%s'" % self.CMD)
847
848       args.extend([instance_name, target])
849       self.assertEqual(cmd[2:], args)
850
851       if fail:
852         return self._FailingCommand(cmd)
853
854       output = ""
855     else:
856       self.fail("Unhandled command: %s" % (cmd, ))
857
858     return self._SuccessCommand(output, cmd)
859
860   def testMigrateInstance(self):
861     clustername = "cluster.example.com"
862     instname = "server01.example.com"
863     target = constants.IP4_ADDRESS_LOCALHOST
864     port = 22364
865
866     hvparams = {constants.HV_XEN_CMD: self.CMD}
867
868     for live in [False, True]:
869       for fail in [False, True]:
870         ping_fn = \
871           testutils.CallCounter(compat.partial(self._FakeTcpPing,
872                                                (target, port), True))
873
874         run_cmd = \
875           compat.partial(self._MigrateInstanceCmd,
876                          clustername, instname, target, port, live,
877                          fail)
878
879         hv = self._GetHv(run_cmd=run_cmd)
880
881         if fail:
882           try:
883             hv._MigrateInstance(clustername, instname, target, port, live,
884                                 hvparams, _ping_fn=ping_fn)
885           except errors.HypervisorError, err:
886             self.assertTrue(str(err).startswith("Failed to migrate instance"))
887           else:
888             self.fail("Exception was not raised")
889         else:
890           hv._MigrateInstance(clustername, instname, target, port, live,
891                               hvparams, _ping_fn=ping_fn)
892
893         if self.CMD == constants.XEN_CMD_XM:
894           expected_pings = 1
895         else:
896           expected_pings = 0
897
898         self.assertEqual(ping_fn.Count(), expected_pings)
899
900   def _GetNodeInfoCmd(self, fail, cmd):
901     if cmd == [self.CMD, "info"]:
902       if fail:
903         return self._FailingCommand(cmd)
904       else:
905         output = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
906     elif cmd == [self.CMD, "list"]:
907       if fail:
908         self.fail("'xm list' shouldn't be called when 'xm info' failed")
909       else:
910         output = testutils.ReadTestData("xen-xm-list-4.0.1-four-instances.txt")
911     else:
912       self.fail("Unhandled command: %s" % (cmd, ))
913
914     return self._SuccessCommand(output, cmd)
915
916   def testGetNodeInfo(self):
917     run_cmd = compat.partial(self._GetNodeInfoCmd, False)
918     hv = self._GetHv(run_cmd=run_cmd)
919     result = hv.GetNodeInfo()
920
921     self.assertEqual(result["hv_version"], (4, 0))
922     self.assertEqual(result["memory_free"], 8004)
923
924   def testGetNodeInfoFailing(self):
925     run_cmd = compat.partial(self._GetNodeInfoCmd, True)
926     hv = self._GetHv(run_cmd=run_cmd)
927     self.assertTrue(hv.GetNodeInfo() is None)
928
929
930 def _MakeTestClass(cls, cmd):
931   """Makes a class for testing.
932
933   The returned class has structure as shown in the following pseudo code:
934
935     class Test{cls.__name__}{cmd}(_TestXenHypervisor, unittest.TestCase):
936       TARGET = {cls}
937       CMD = {cmd}
938       HVNAME = {Hypervisor name retrieved using class}
939
940   @type cls: class
941   @param cls: Hypervisor class to be tested
942   @type cmd: string
943   @param cmd: Hypervisor command
944   @rtype: tuple
945   @return: Class name and class object (not instance)
946
947   """
948   name = "Test%sCmd%s" % (cls.__name__, cmd.title())
949   bases = (_TestXenHypervisor, unittest.TestCase)
950   hvname = HVCLASS_TO_HVNAME[cls]
951
952   return (name, type(name, bases, dict(TARGET=cls, CMD=cmd, HVNAME=hvname)))
953
954
955 # Create test classes programmatically instead of manually to reduce the risk
956 # of forgetting some combinations
957 for cls in [hv_xen.XenPvmHypervisor, hv_xen.XenHvmHypervisor]:
958   for cmd in constants.KNOWN_XEN_COMMANDS:
959     (name, testcls) = _MakeTestClass(cls, cmd)
960
961     assert name not in locals()
962
963     locals()[name] = testcls
964
965
966 if __name__ == "__main__":
967   testutils.GanetiTestProgram()