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