Merge branch 'stable-2.8' into stable-2.9
[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_RDONLY,
315                     physical_id=[constants.FD_BLKTAP2]),
316        "/tmp/diskFour"),
317       (objects.Disk(dev_type=constants.DT_FILE, mode=constants.DISK_RDWR,
318                     physical_id=[constants.FD_BLKTAP]),
319        "/tmp/diskLast"),
320       ]
321
322     result = hv_xen._GetConfigFileDiskData(disks, "sd")
323     self.assertEqual(result, [
324       "'file:/tmp/diskFirst,sda,w'",
325       "'tap:aio:/tmp/diskTwo,sdb,r'",
326       "'file:/tmp/diskThree,sdc,w'",
327       "'tap2:tapdisk:aio:/tmp/diskFour,sdd,r'",
328       "'tap:aio:/tmp/diskLast,sde,w'",
329       ])
330
331   def testInvalidFileDisk(self):
332     disks = [
333       (objects.Disk(dev_type=constants.DT_FILE, mode=constants.DISK_RDWR,
334                     physical_id=["#unknown#"]),
335        "/tmp/diskinvalid"),
336       ]
337
338     self.assertRaises(KeyError, hv_xen._GetConfigFileDiskData, disks, "sd")
339
340
341 class TestXenHypervisorRunXen(unittest.TestCase):
342
343   XEN_SUB_CMD = "help"
344
345   def testCommandUnknown(self):
346     cmd = "#unknown command#"
347     self.assertFalse(cmd in constants.KNOWN_XEN_COMMANDS)
348     hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
349                               _run_cmd_fn=NotImplemented,
350                               _cmd=cmd)
351     self.assertRaises(errors.ProgrammerError, hv._RunXen, [], None)
352
353   def testCommandNoHvparams(self):
354     hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
355                               _run_cmd_fn=NotImplemented)
356     hvparams = None
357     self.assertRaises(errors.HypervisorError, hv._RunXen, [self.XEN_SUB_CMD],
358                       hvparams)
359
360   def testCommandFromHvparams(self):
361     expected_xen_cmd = "xl"
362     hvparams = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
363     mock_run_cmd = mock.Mock()
364     hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
365                               _run_cmd_fn=mock_run_cmd)
366     hv._RunXen([self.XEN_SUB_CMD], hvparams=hvparams)
367     mock_run_cmd.assert_called_with([expected_xen_cmd, self.XEN_SUB_CMD])
368
369
370 class TestXenHypervisorGetInstanceList(unittest.TestCase):
371
372   RESULT_OK = utils.RunResult(0, None, "", "", "", None, None)
373   XEN_LIST = "list"
374
375   def testNoHvparams(self):
376     expected_xen_cmd = "xm"
377     mock_run_cmd = mock.Mock( return_value=self.RESULT_OK )
378     hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
379                               _run_cmd_fn=mock_run_cmd)
380     self.assertRaises(errors.HypervisorError, hv._GetInstanceList, True, None)
381
382   def testFromHvparams(self):
383     expected_xen_cmd = "xl"
384     hvparams = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
385     mock_run_cmd = mock.Mock( return_value=self.RESULT_OK )
386     hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
387                               _run_cmd_fn=mock_run_cmd)
388     hv._GetInstanceList(True, hvparams)
389     mock_run_cmd.assert_called_with([expected_xen_cmd, self.XEN_LIST])
390
391
392 class TestXenHypervisorListInstances(unittest.TestCase):
393
394   RESULT_OK = utils.RunResult(0, None, "", "", "", None, None)
395   XEN_LIST = "list"
396
397   def testNoHvparams(self):
398     expected_xen_cmd = "xm"
399     mock_run_cmd = mock.Mock( return_value=self.RESULT_OK )
400     hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
401                               _run_cmd_fn=mock_run_cmd)
402     self.assertRaises(errors.HypervisorError, hv.ListInstances)
403
404   def testHvparamsXl(self):
405     expected_xen_cmd = "xl"
406     hvparams = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
407     mock_run_cmd = mock.Mock( return_value=self.RESULT_OK )
408     hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
409                               _run_cmd_fn=mock_run_cmd)
410     hv.ListInstances(hvparams=hvparams)
411     mock_run_cmd.assert_called_with([expected_xen_cmd, self.XEN_LIST])
412
413
414 class TestXenHypervisorCheckToolstack(unittest.TestCase):
415
416   def setUp(self):
417     self.tmpdir = tempfile.mkdtemp()
418     self.cfg_name = "xen_config"
419     self.cfg_path = utils.PathJoin(self.tmpdir, self.cfg_name)
420     self.hv = hv_xen.XenHypervisor()
421
422   def tearDown(self):
423     shutil.rmtree(self.tmpdir)
424
425   def testBinaryNotFound(self):
426     RESULT_FAILED = utils.RunResult(1, None, "", "", "", None, None)
427     mock_run_cmd = mock.Mock(return_value=RESULT_FAILED)
428     hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
429                               _run_cmd_fn=mock_run_cmd)
430     result = hv._CheckToolstackBinary("xl")
431     self.assertFalse(result)
432
433   def testCheckToolstackXlConfigured(self):
434     RESULT_OK = utils.RunResult(0, None, "", "", "", None, None)
435     mock_run_cmd = mock.Mock(return_value=RESULT_OK)
436     hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
437                               _run_cmd_fn=mock_run_cmd)
438     result = hv._CheckToolstackXlConfigured()
439     self.assertTrue(result)
440
441   def testCheckToolstackXlNotConfigured(self):
442     RESULT_FAILED = utils.RunResult(
443         1, None, "",
444         "ERROR:  A different toolstack (xm) have been selected!",
445         "", None, None)
446     mock_run_cmd = mock.Mock(return_value=RESULT_FAILED)
447     hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
448                               _run_cmd_fn=mock_run_cmd)
449     result = hv._CheckToolstackXlConfigured()
450     self.assertFalse(result)
451
452   def testCheckToolstackXlFails(self):
453     RESULT_FAILED = utils.RunResult(
454         1, None, "",
455         "ERROR: The pink bunny hid the binary.",
456         "", None, None)
457     mock_run_cmd = mock.Mock(return_value=RESULT_FAILED)
458     hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
459                               _run_cmd_fn=mock_run_cmd)
460     self.assertRaises(errors.HypervisorError, hv._CheckToolstackXlConfigured)
461
462
463 class TestXenHypervisorWriteConfigFile(unittest.TestCase):
464   def setUp(self):
465     self.tmpdir = tempfile.mkdtemp()
466
467   def tearDown(self):
468     shutil.rmtree(self.tmpdir)
469
470   def testWriteError(self):
471     cfgdir = utils.PathJoin(self.tmpdir, "foobar")
472
473     hv = hv_xen.XenHypervisor(_cfgdir=cfgdir,
474                               _run_cmd_fn=NotImplemented,
475                               _cmd=NotImplemented)
476
477     self.assertFalse(os.path.exists(cfgdir))
478
479     try:
480       hv._WriteConfigFile("name", "data")
481     except errors.HypervisorError, err:
482       self.assertTrue(str(err).startswith("Cannot write Xen instance"))
483     else:
484       self.fail("Exception was not raised")
485
486
487 class TestXenHypervisorVerify(unittest.TestCase):
488
489   def setUp(self):
490     output = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
491     self._result_ok = utils.RunResult(0, None, output, "", "", None, None)
492
493   def testVerify(self):
494     hvparams = {constants.HV_XEN_CMD : constants.XEN_CMD_XL}
495     mock_run_cmd = mock.Mock(return_value=self._result_ok)
496     hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
497                               _run_cmd_fn=mock_run_cmd)
498     hv._CheckToolstack = mock.Mock(return_value=True)
499     result = hv.Verify(hvparams)
500     self.assertTrue(result is None)
501
502   def testVerifyToolstackNotOk(self):
503     hvparams = {constants.HV_XEN_CMD : constants.XEN_CMD_XL}
504     mock_run_cmd = mock.Mock(return_value=self._result_ok)
505     hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
506                               _run_cmd_fn=mock_run_cmd)
507     hv._CheckToolstack = mock.Mock()
508     hv._CheckToolstack.side_effect = errors.HypervisorError("foo")
509     result = hv.Verify(hvparams)
510     self.assertTrue(result is not None)
511
512   def testVerifyFailing(self):
513     result_failed = utils.RunResult(1, None, "", "", "", None, None)
514     mock_run_cmd = mock.Mock(return_value=result_failed)
515     hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
516                               _run_cmd_fn=mock_run_cmd)
517     hv._CheckToolstack = mock.Mock(return_value=True)
518     result = hv.Verify()
519     self.assertTrue(result is not None)
520
521
522 class _TestXenHypervisor(object):
523   TARGET = NotImplemented
524   CMD = NotImplemented
525   HVNAME = NotImplemented
526   VALID_HVPARAMS = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
527
528   def setUp(self):
529     super(_TestXenHypervisor, self).setUp()
530
531     self.tmpdir = tempfile.mkdtemp()
532
533     self.vncpw = "".join(random.sample(string.ascii_letters, 10))
534
535     self.vncpw_path = utils.PathJoin(self.tmpdir, "vncpw")
536     utils.WriteFile(self.vncpw_path, data=self.vncpw)
537
538   def tearDown(self):
539     super(_TestXenHypervisor, self).tearDown()
540
541     shutil.rmtree(self.tmpdir)
542
543   def _GetHv(self, run_cmd=NotImplemented):
544     return self.TARGET(_cfgdir=self.tmpdir, _run_cmd_fn=run_cmd, _cmd=self.CMD)
545
546   def _SuccessCommand(self, stdout, cmd):
547     self.assertEqual(cmd[0], self.CMD)
548
549     return utils.RunResult(constants.EXIT_SUCCESS, None, stdout, "", None,
550                            NotImplemented, NotImplemented)
551
552   def _FailingCommand(self, cmd):
553     self.assertEqual(cmd[0], self.CMD)
554
555     return utils.RunResult(constants.EXIT_FAILURE, None,
556                            "", "This command failed", None,
557                            NotImplemented, NotImplemented)
558
559   def _FakeTcpPing(self, expected, result, target, port, **kwargs):
560     self.assertEqual((target, port), expected)
561     return result
562
563   def testReadingNonExistentConfigFile(self):
564     hv = self._GetHv()
565
566     try:
567       hv._ReadConfigFile("inst15780.example.com")
568     except errors.HypervisorError, err:
569       self.assertTrue(str(err).startswith("Failed to load Xen config file:"))
570     else:
571       self.fail("Exception was not raised")
572
573   def testRemovingAutoConfigFile(self):
574     name = "inst8206.example.com"
575     cfgfile = utils.PathJoin(self.tmpdir, name)
576     autodir = utils.PathJoin(self.tmpdir, "auto")
577     autocfgfile = utils.PathJoin(autodir, name)
578
579     os.mkdir(autodir)
580
581     utils.WriteFile(autocfgfile, data="")
582
583     hv = self._GetHv()
584
585     self.assertTrue(os.path.isfile(autocfgfile))
586     hv._WriteConfigFile(name, "content")
587     self.assertFalse(os.path.exists(autocfgfile))
588     self.assertEqual(utils.ReadFile(cfgfile), "content")
589
590   def _XenList(self, cmd):
591     self.assertEqual(cmd, [self.CMD, "list"])
592
593     # TODO: Use actual data from "xl" command
594     output = testutils.ReadTestData("xen-xm-list-4.0.1-four-instances.txt")
595
596     return self._SuccessCommand(output, cmd)
597
598   def testGetInstanceInfo(self):
599     hv = self._GetHv(run_cmd=self._XenList)
600
601     (name, instid, memory, vcpus, state, runtime) = \
602       hv.GetInstanceInfo("server01.example.com")
603
604     self.assertEqual(name, "server01.example.com")
605     self.assertEqual(instid, 1)
606     self.assertEqual(memory, 1024)
607     self.assertEqual(vcpus, 1)
608     self.assertEqual(state, "-b----")
609     self.assertAlmostEqual(runtime, 167643.2)
610
611   def testGetInstanceInfoDom0(self):
612     hv = self._GetHv(run_cmd=self._XenList)
613
614     # TODO: Not sure if this is actually used anywhere (can't find it), but the
615     # code supports querying for Dom0
616     (name, instid, memory, vcpus, state, runtime) = \
617       hv.GetInstanceInfo(hv_xen._DOM0_NAME)
618
619     self.assertEqual(name, "Domain-0")
620     self.assertEqual(instid, 0)
621     self.assertEqual(memory, 1023)
622     self.assertEqual(vcpus, 1)
623     self.assertEqual(state, "r-----")
624     self.assertAlmostEqual(runtime, 154706.1)
625
626   def testGetInstanceInfoUnknown(self):
627     hv = self._GetHv(run_cmd=self._XenList)
628
629     result = hv.GetInstanceInfo("unknown.example.com")
630     self.assertTrue(result is None)
631
632   def testGetAllInstancesInfo(self):
633     hv = self._GetHv(run_cmd=self._XenList)
634
635     result = hv.GetAllInstancesInfo()
636
637     self.assertEqual(map(compat.fst, result), [
638       "server01.example.com",
639       "web3106215069.example.com",
640       "testinstance.example.com",
641       ])
642
643   def testListInstances(self):
644     hv = self._GetHv(run_cmd=self._XenList)
645
646     self.assertEqual(hv.ListInstances(), [
647       "server01.example.com",
648       "web3106215069.example.com",
649       "testinstance.example.com",
650       ])
651
652   def _StartInstanceCommand(self, inst, paused, failcreate, cmd):
653     if cmd == [self.CMD, "info"]:
654       output = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
655     elif cmd == [self.CMD, "list"]:
656       output = testutils.ReadTestData("xen-xm-list-4.0.1-dom0-only.txt")
657     elif cmd[:2] == [self.CMD, "create"]:
658       args = cmd[2:]
659       cfgfile = utils.PathJoin(self.tmpdir, inst.name)
660
661       if paused:
662         self.assertEqual(args, ["-p", cfgfile])
663       else:
664         self.assertEqual(args, [cfgfile])
665
666       if failcreate:
667         return self._FailingCommand(cmd)
668
669       output = ""
670     else:
671       self.fail("Unhandled command: %s" % (cmd, ))
672
673     return self._SuccessCommand(output, cmd)
674
675   def _MakeInstance(self):
676     # Copy default parameters
677     bep = objects.FillDict(constants.BEC_DEFAULTS, {})
678     hvp = objects.FillDict(constants.HVC_DEFAULTS[self.HVNAME], {})
679
680     # Override default VNC password file path
681     if constants.HV_VNC_PASSWORD_FILE in hvp:
682       hvp[constants.HV_VNC_PASSWORD_FILE] = self.vncpw_path
683
684     disks = [
685       (objects.Disk(dev_type=constants.DT_PLAIN, mode=constants.DISK_RDWR),
686        utils.PathJoin(self.tmpdir, "disk0")),
687       (objects.Disk(dev_type=constants.DT_PLAIN, mode=constants.DISK_RDONLY),
688        utils.PathJoin(self.tmpdir, "disk1")),
689       ]
690
691     inst = objects.Instance(name="server01.example.com",
692                             hvparams=hvp, beparams=bep,
693                             osparams={}, nics=[], os="deb1",
694                             disks=map(compat.fst, disks))
695     inst.UpgradeConfig()
696
697     return (inst, disks)
698
699   def testStartInstance(self):
700     (inst, disks) = self._MakeInstance()
701     pathutils.LOG_XEN_DIR = self.tmpdir
702
703     for failcreate in [False, True]:
704       for paused in [False, True]:
705         run_cmd = compat.partial(self._StartInstanceCommand,
706                                  inst, paused, failcreate)
707
708         hv = self._GetHv(run_cmd=run_cmd)
709
710         # Ensure instance is not listed
711         self.assertTrue(inst.name not in hv.ListInstances())
712
713         # Remove configuration
714         cfgfile = utils.PathJoin(self.tmpdir, inst.name)
715         utils.RemoveFile(cfgfile)
716
717         if failcreate:
718           self.assertRaises(errors.HypervisorError, hv.StartInstance,
719                             inst, disks, paused)
720           # Check whether a stale config file is left behind
721           self.assertFalse(os.path.exists(cfgfile))
722         else:
723           hv.StartInstance(inst, disks, paused)
724           # Check if configuration was updated
725           lines = utils.ReadFile(cfgfile).splitlines()
726
727         if constants.HV_VNC_PASSWORD_FILE in inst.hvparams:
728           self.assertTrue(("vncpasswd = '%s'" % self.vncpw) in lines)
729         else:
730           extra = inst.hvparams[constants.HV_KERNEL_ARGS]
731           self.assertTrue(("extra = '%s'" % extra) in lines)
732
733   def _StopInstanceCommand(self, instance_name, force, fail, cmd):
734     if (cmd == [self.CMD, "list"]):
735       output = "Name  ID  Mem  VCPUs  State  Time(s)\n" \
736         "Domain-0  0  1023  1  r-----  142691.0\n" \
737         "%s  417  128  1  r-----  3.2\n" % instance_name
738     elif cmd[:2] == [self.CMD, "destroy"]:
739       self.assertEqual(cmd[2:], [instance_name])
740       output = ""
741     elif not force and cmd[:3] == [self.CMD, "shutdown", "-w"]:
742       self.assertEqual(cmd[3:], [instance_name])
743       output = ""
744     else:
745       self.fail("Unhandled command: %s" % (cmd, ))
746
747     if fail:
748       # Simulate a failing command
749       return self._FailingCommand(cmd)
750     else:
751       return self._SuccessCommand(output, cmd)
752
753   def testStopInstance(self):
754     name = "inst4284.example.com"
755     cfgfile = utils.PathJoin(self.tmpdir, name)
756     cfgdata = "config file content\n"
757
758     for force in [False, True]:
759       for fail in [False, True]:
760         utils.WriteFile(cfgfile, data=cfgdata)
761
762         run_cmd = compat.partial(self._StopInstanceCommand, name, force, fail)
763
764         hv = self._GetHv(run_cmd=run_cmd)
765
766         self.assertTrue(os.path.isfile(cfgfile))
767
768         if fail:
769           try:
770             hv._StopInstance(name, force, None)
771           except errors.HypervisorError, err:
772             self.assertTrue(str(err).startswith("listing instances failed"),
773                             msg=str(err))
774           else:
775             self.fail("Exception was not raised")
776           self.assertEqual(utils.ReadFile(cfgfile), cfgdata,
777                            msg=("Configuration was removed when stopping"
778                                 " instance failed"))
779         else:
780           hv._StopInstance(name, force, None)
781           self.assertFalse(os.path.exists(cfgfile))
782
783   def _MigrateNonRunningInstCmd(self, cmd):
784     if cmd == [self.CMD, "list"]:
785       output = testutils.ReadTestData("xen-xm-list-4.0.1-dom0-only.txt")
786     else:
787       self.fail("Unhandled command: %s" % (cmd, ))
788
789     return self._SuccessCommand(output, cmd)
790
791   def testMigrateInstanceNotRunning(self):
792     name = "nonexistinginstance.example.com"
793     target = constants.IP4_ADDRESS_LOCALHOST
794     port = 14618
795
796     hv = self._GetHv(run_cmd=self._MigrateNonRunningInstCmd)
797
798     for live in [False, True]:
799       try:
800         hv._MigrateInstance(NotImplemented, name, target, port, live,
801                             self.VALID_HVPARAMS, _ping_fn=NotImplemented)
802       except errors.HypervisorError, err:
803         self.assertEqual(str(err), "Instance not running, cannot migrate")
804       else:
805         self.fail("Exception was not raised")
806
807   def _MigrateInstTargetUnreachCmd(self, cmd):
808     if cmd == [self.CMD, "list"]:
809       output = testutils.ReadTestData("xen-xm-list-4.0.1-four-instances.txt")
810     else:
811       self.fail("Unhandled command: %s" % (cmd, ))
812
813     return self._SuccessCommand(output, cmd)
814
815   def testMigrateTargetUnreachable(self):
816     name = "server01.example.com"
817     target = constants.IP4_ADDRESS_LOCALHOST
818     port = 28349
819
820     hv = self._GetHv(run_cmd=self._MigrateInstTargetUnreachCmd)
821     hvparams = {constants.HV_XEN_CMD: self.CMD}
822
823     for live in [False, True]:
824       if self.CMD == constants.XEN_CMD_XL:
825         # TODO: Detect unreachable targets
826         pass
827       else:
828         try:
829           hv._MigrateInstance(NotImplemented, name, target, port, live,
830                               hvparams,
831                               _ping_fn=compat.partial(self._FakeTcpPing,
832                                                       (target, port), False))
833         except errors.HypervisorError, err:
834           wanted = "Remote host %s not" % target
835           self.assertTrue(str(err).startswith(wanted))
836         else:
837           self.fail("Exception was not raised")
838
839   def _MigrateInstanceCmd(self, cluster_name, instance_name, target, port,
840                           live, fail, cmd):
841     if cmd == [self.CMD, "list"]:
842       output = testutils.ReadTestData("xen-xm-list-4.0.1-four-instances.txt")
843     elif cmd[:2] == [self.CMD, "migrate"]:
844       if self.CMD == constants.XEN_CMD_XM:
845         args = ["-p", str(port)]
846
847         if live:
848           args.append("-l")
849
850       elif self.CMD == constants.XEN_CMD_XL:
851         args = [
852           "-s", constants.XL_SSH_CMD % cluster_name,
853           "-C", utils.PathJoin(self.tmpdir, instance_name),
854           ]
855
856       else:
857         self.fail("Unknown Xen command '%s'" % self.CMD)
858
859       args.extend([instance_name, target])
860       self.assertEqual(cmd[2:], args)
861
862       if fail:
863         return self._FailingCommand(cmd)
864
865       output = ""
866     else:
867       self.fail("Unhandled command: %s" % (cmd, ))
868
869     return self._SuccessCommand(output, cmd)
870
871   def testMigrateInstance(self):
872     clustername = "cluster.example.com"
873     instname = "server01.example.com"
874     target = constants.IP4_ADDRESS_LOCALHOST
875     port = 22364
876
877     hvparams = {constants.HV_XEN_CMD: self.CMD}
878
879     for live in [False, True]:
880       for fail in [False, True]:
881         ping_fn = \
882           testutils.CallCounter(compat.partial(self._FakeTcpPing,
883                                                (target, port), True))
884
885         run_cmd = \
886           compat.partial(self._MigrateInstanceCmd,
887                          clustername, instname, target, port, live,
888                          fail)
889
890         hv = self._GetHv(run_cmd=run_cmd)
891
892         if fail:
893           try:
894             hv._MigrateInstance(clustername, instname, target, port, live,
895                                 hvparams, _ping_fn=ping_fn)
896           except errors.HypervisorError, err:
897             self.assertTrue(str(err).startswith("Failed to migrate instance"))
898           else:
899             self.fail("Exception was not raised")
900         else:
901           hv._MigrateInstance(clustername, instname, target, port, live,
902                               hvparams, _ping_fn=ping_fn)
903
904         if self.CMD == constants.XEN_CMD_XM:
905           expected_pings = 1
906         else:
907           expected_pings = 0
908
909         self.assertEqual(ping_fn.Count(), expected_pings)
910
911   def _GetNodeInfoCmd(self, fail, cmd):
912     if cmd == [self.CMD, "info"]:
913       if fail:
914         return self._FailingCommand(cmd)
915       else:
916         output = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
917     elif cmd == [self.CMD, "list"]:
918       if fail:
919         self.fail("'xm list' shouldn't be called when 'xm info' failed")
920       else:
921         output = testutils.ReadTestData("xen-xm-list-4.0.1-four-instances.txt")
922     else:
923       self.fail("Unhandled command: %s" % (cmd, ))
924
925     return self._SuccessCommand(output, cmd)
926
927   def testGetNodeInfo(self):
928     run_cmd = compat.partial(self._GetNodeInfoCmd, False)
929     hv = self._GetHv(run_cmd=run_cmd)
930     result = hv.GetNodeInfo()
931
932     self.assertEqual(result["hv_version"], (4, 0))
933     self.assertEqual(result["memory_free"], 8004)
934
935   def testGetNodeInfoFailing(self):
936     run_cmd = compat.partial(self._GetNodeInfoCmd, True)
937     hv = self._GetHv(run_cmd=run_cmd)
938     self.assertTrue(hv.GetNodeInfo() is None)
939
940
941 def _MakeTestClass(cls, cmd):
942   """Makes a class for testing.
943
944   The returned class has structure as shown in the following pseudo code:
945
946     class Test{cls.__name__}{cmd}(_TestXenHypervisor, unittest.TestCase):
947       TARGET = {cls}
948       CMD = {cmd}
949       HVNAME = {Hypervisor name retrieved using class}
950
951   @type cls: class
952   @param cls: Hypervisor class to be tested
953   @type cmd: string
954   @param cmd: Hypervisor command
955   @rtype: tuple
956   @return: Class name and class object (not instance)
957
958   """
959   name = "Test%sCmd%s" % (cls.__name__, cmd.title())
960   bases = (_TestXenHypervisor, unittest.TestCase)
961   hvname = HVCLASS_TO_HVNAME[cls]
962
963   return (name, type(name, bases, dict(TARGET=cls, CMD=cmd, HVNAME=hvname)))
964
965
966 # Create test classes programmatically instead of manually to reduce the risk
967 # of forgetting some combinations
968 for cls in [hv_xen.XenPvmHypervisor, hv_xen.XenHvmHypervisor]:
969   for cmd in constants.KNOWN_XEN_COMMANDS:
970     (name, testcls) = _MakeTestClass(cls, cmd)
971
972     assert name not in locals()
973
974     locals()[name] = testcls
975
976
977 if __name__ == "__main__":
978   testutils.GanetiTestProgram()