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