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