hv_xen.py: _RunXen and GetInstanceList use hvparams
[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 TestParseXmList(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._ParseXmList(data.splitlines(), False), [])
124
125     # Include node
126     result = hv_xen._ParseXmList(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._ParseXmList(["Header would be here"] + lines, False)
157       except errors.HypervisorError, err:
158         self.assertTrue("Can't parse output of xm 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({}, lambda _: []), {})
232
233   def _FakeXmList(self, include_node):
234     self.assertTrue(include_node)
235     return [
236       (hv_xen._DOM0_NAME, NotImplemented, 4096, 7, NotImplemented,
237        NotImplemented),
238       ("inst1.example.com", NotImplemented, 2048, 4, NotImplemented,
239        NotImplemented),
240       ]
241
242   def testMissingNodeInfo(self):
243     result = hv_xen._MergeInstanceInfo({}, self._FakeXmList)
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     result = hv_xen._GetNodeInfo(info, self._FakeXmList)
252     self.assertEqual(result, {
253       "cpu_nodes": 1,
254       "cpu_sockets": 2,
255       "cpu_total": 4,
256       "dom0_cpus": 7,
257       "hv_version": (4, 0),
258       "memory_dom0": 4096,
259       "memory_free": 8004,
260       "memory_hv": 2230,
261       "memory_total": 16378,
262       })
263
264
265 class TestGetConfigFileDiskData(unittest.TestCase):
266   def testLetterCount(self):
267     self.assertEqual(len(hv_xen._DISK_LETTERS), 26)
268
269   def testNoDisks(self):
270     self.assertEqual(hv_xen._GetConfigFileDiskData([], "hd"), [])
271
272   def testManyDisks(self):
273     for offset in [0, 1, 10]:
274       disks = [(objects.Disk(dev_type=constants.LD_LV), "/tmp/disk/%s" % idx)
275                for idx in range(len(hv_xen._DISK_LETTERS) + offset)]
276
277       if offset == 0:
278         result = hv_xen._GetConfigFileDiskData(disks, "hd")
279         self.assertEqual(result, [
280           "'phy:/tmp/disk/%s,hd%s,r'" % (idx, string.ascii_lowercase[idx])
281           for idx in range(len(hv_xen._DISK_LETTERS) + offset)
282           ])
283       else:
284         try:
285           hv_xen._GetConfigFileDiskData(disks, "hd")
286         except errors.HypervisorError, err:
287           self.assertEqual(str(err), "Too many disks")
288         else:
289           self.fail("Exception was not raised")
290
291   def testTwoLvDisksWithMode(self):
292     disks = [
293       (objects.Disk(dev_type=constants.LD_LV, mode=constants.DISK_RDWR),
294        "/tmp/diskFirst"),
295       (objects.Disk(dev_type=constants.LD_LV, mode=constants.DISK_RDONLY),
296        "/tmp/diskLast"),
297       ]
298
299     result = hv_xen._GetConfigFileDiskData(disks, "hd")
300     self.assertEqual(result, [
301       "'phy:/tmp/diskFirst,hda,w'",
302       "'phy:/tmp/diskLast,hdb,r'",
303       ])
304
305   def testFileDisks(self):
306     disks = [
307       (objects.Disk(dev_type=constants.LD_FILE, mode=constants.DISK_RDWR,
308                     physical_id=[constants.FD_LOOP]),
309        "/tmp/diskFirst"),
310       (objects.Disk(dev_type=constants.LD_FILE, mode=constants.DISK_RDONLY,
311                     physical_id=[constants.FD_BLKTAP]),
312        "/tmp/diskTwo"),
313       (objects.Disk(dev_type=constants.LD_FILE, mode=constants.DISK_RDWR,
314                     physical_id=[constants.FD_LOOP]),
315        "/tmp/diskThree"),
316       (objects.Disk(dev_type=constants.LD_FILE, mode=constants.DISK_RDWR,
317                     physical_id=[constants.FD_BLKTAP]),
318        "/tmp/diskLast"),
319       ]
320
321     result = hv_xen._GetConfigFileDiskData(disks, "sd")
322     self.assertEqual(result, [
323       "'file:/tmp/diskFirst,sda,w'",
324       "'tap:aio:/tmp/diskTwo,sdb,r'",
325       "'file:/tmp/diskThree,sdc,w'",
326       "'tap:aio:/tmp/diskLast,sdd,w'",
327       ])
328
329   def testInvalidFileDisk(self):
330     disks = [
331       (objects.Disk(dev_type=constants.LD_FILE, mode=constants.DISK_RDWR,
332                     physical_id=["#unknown#"]),
333        "/tmp/diskinvalid"),
334       ]
335
336     self.assertRaises(KeyError, hv_xen._GetConfigFileDiskData, disks, "sd")
337
338
339 class TestXenHypervisorRunXen(unittest.TestCase):
340
341   XEN_SUB_CMD = "help"
342
343   def testCommandUnknown(self):
344     cmd = "#unknown command#"
345     self.assertFalse(cmd in constants.KNOWN_XEN_COMMANDS)
346     hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
347                               _run_cmd_fn=NotImplemented,
348                               _cmd=cmd)
349     self.assertRaises(errors.ProgrammerError, hv._RunXen, [])
350
351   def testCommandValid(self):
352     xen_cmd = "xm"
353     mock_run_cmd = mock.Mock()
354     hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
355                               _run_cmd_fn=mock_run_cmd)
356     hv._RunXen([self.XEN_SUB_CMD])
357     mock_run_cmd.assert_called_with([xen_cmd, self.XEN_SUB_CMD])
358
359   def testCommandFromHvparams(self):
360     expected_xen_cmd = "xl"
361     hvparams = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
362     mock_run_cmd = mock.Mock()
363     hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
364                               _run_cmd_fn=mock_run_cmd)
365     hv._RunXen([self.XEN_SUB_CMD], hvparams=hvparams)
366     mock_run_cmd.assert_called_with([expected_xen_cmd, self.XEN_SUB_CMD])
367
368
369 class TestXenHypervisorGetInstanceList(unittest.TestCase):
370
371   RESULT_OK = utils.RunResult(0, None, "", "", "", None, None)
372   XEN_LIST = "list"
373
374   def testOk(self):
375     expected_xen_cmd = "xm"
376     mock_run_cmd = mock.Mock( return_value=self.RESULT_OK )
377     hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
378                               _run_cmd_fn=mock_run_cmd)
379     hv._GetInstanceList(True)
380     mock_run_cmd.assert_called_with([expected_xen_cmd, self.XEN_LIST])
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=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 testDefaultXm(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     hv.ListInstances()
403     mock_run_cmd.assert_called_with([expected_xen_cmd, self.XEN_LIST])
404
405   def testHvparamsXl(self):
406     expected_xen_cmd = "xl"
407     hvparams = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
408     mock_run_cmd = mock.Mock( return_value=self.RESULT_OK )
409     hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
410                               _run_cmd_fn=mock_run_cmd)
411     hv.ListInstances(hvparams=hvparams)
412     mock_run_cmd.assert_called_with([expected_xen_cmd, self.XEN_LIST])
413
414
415 class TestXenHypervisorWriteConfigFile(unittest.TestCase):
416   def setUp(self):
417     self.tmpdir = tempfile.mkdtemp()
418
419   def tearDown(self):
420     shutil.rmtree(self.tmpdir)
421
422   def testWriteError(self):
423     cfgdir = utils.PathJoin(self.tmpdir, "foobar")
424
425     hv = hv_xen.XenHypervisor(_cfgdir=cfgdir,
426                               _run_cmd_fn=NotImplemented,
427                               _cmd=NotImplemented)
428
429     self.assertFalse(os.path.exists(cfgdir))
430
431     try:
432       hv._WriteConfigFile("name", "data")
433     except errors.HypervisorError, err:
434       self.assertTrue(str(err).startswith("Cannot write Xen instance"))
435     else:
436       self.fail("Exception was not raised")
437
438
439 class _TestXenHypervisor(object):
440   TARGET = NotImplemented
441   CMD = NotImplemented
442   HVNAME = NotImplemented
443
444   def setUp(self):
445     super(_TestXenHypervisor, self).setUp()
446
447     self.tmpdir = tempfile.mkdtemp()
448
449     self.vncpw = "".join(random.sample(string.ascii_letters, 10))
450
451     self.vncpw_path = utils.PathJoin(self.tmpdir, "vncpw")
452     utils.WriteFile(self.vncpw_path, data=self.vncpw)
453
454   def tearDown(self):
455     super(_TestXenHypervisor, self).tearDown()
456
457     shutil.rmtree(self.tmpdir)
458
459   def _GetHv(self, run_cmd=NotImplemented):
460     return self.TARGET(_cfgdir=self.tmpdir, _run_cmd_fn=run_cmd, _cmd=self.CMD)
461
462   def _SuccessCommand(self, stdout, cmd):
463     self.assertEqual(cmd[0], self.CMD)
464
465     return utils.RunResult(constants.EXIT_SUCCESS, None, stdout, "", None,
466                            NotImplemented, NotImplemented)
467
468   def _FailingCommand(self, cmd):
469     self.assertEqual(cmd[0], self.CMD)
470
471     return utils.RunResult(constants.EXIT_FAILURE, None,
472                            "", "This command failed", None,
473                            NotImplemented, NotImplemented)
474
475   def _FakeTcpPing(self, expected, result, target, port, **kwargs):
476     self.assertEqual((target, port), expected)
477     return result
478
479   def testReadingNonExistentConfigFile(self):
480     hv = self._GetHv()
481
482     try:
483       hv._ReadConfigFile("inst15780.example.com")
484     except errors.HypervisorError, err:
485       self.assertTrue(str(err).startswith("Failed to load Xen config file:"))
486     else:
487       self.fail("Exception was not raised")
488
489   def testRemovingAutoConfigFile(self):
490     name = "inst8206.example.com"
491     cfgfile = utils.PathJoin(self.tmpdir, name)
492     autodir = utils.PathJoin(self.tmpdir, "auto")
493     autocfgfile = utils.PathJoin(autodir, name)
494
495     os.mkdir(autodir)
496
497     utils.WriteFile(autocfgfile, data="")
498
499     hv = self._GetHv()
500
501     self.assertTrue(os.path.isfile(autocfgfile))
502     hv._WriteConfigFile(name, "content")
503     self.assertFalse(os.path.exists(autocfgfile))
504     self.assertEqual(utils.ReadFile(cfgfile), "content")
505
506   def _XenList(self, cmd):
507     self.assertEqual(cmd, [self.CMD, "list"])
508
509     # TODO: Use actual data from "xl" command
510     output = testutils.ReadTestData("xen-xm-list-4.0.1-four-instances.txt")
511
512     return self._SuccessCommand(output, cmd)
513
514   def testGetInstanceInfo(self):
515     hv = self._GetHv(run_cmd=self._XenList)
516
517     (name, instid, memory, vcpus, state, runtime) = \
518       hv.GetInstanceInfo("server01.example.com")
519
520     self.assertEqual(name, "server01.example.com")
521     self.assertEqual(instid, 1)
522     self.assertEqual(memory, 1024)
523     self.assertEqual(vcpus, 1)
524     self.assertEqual(state, "-b----")
525     self.assertAlmostEqual(runtime, 167643.2)
526
527   def testGetInstanceInfoDom0(self):
528     hv = self._GetHv(run_cmd=self._XenList)
529
530     # TODO: Not sure if this is actually used anywhere (can't find it), but the
531     # code supports querying for Dom0
532     (name, instid, memory, vcpus, state, runtime) = \
533       hv.GetInstanceInfo(hv_xen._DOM0_NAME)
534
535     self.assertEqual(name, "Domain-0")
536     self.assertEqual(instid, 0)
537     self.assertEqual(memory, 1023)
538     self.assertEqual(vcpus, 1)
539     self.assertEqual(state, "r-----")
540     self.assertAlmostEqual(runtime, 154706.1)
541
542   def testGetInstanceInfoUnknown(self):
543     hv = self._GetHv(run_cmd=self._XenList)
544
545     result = hv.GetInstanceInfo("unknown.example.com")
546     self.assertTrue(result is None)
547
548   def testGetAllInstancesInfo(self):
549     hv = self._GetHv(run_cmd=self._XenList)
550
551     result = hv.GetAllInstancesInfo()
552
553     self.assertEqual(map(compat.fst, result), [
554       "server01.example.com",
555       "web3106215069.example.com",
556       "testinstance.example.com",
557       ])
558
559   def testListInstances(self):
560     hv = self._GetHv(run_cmd=self._XenList)
561
562     self.assertEqual(hv.ListInstances(), [
563       "server01.example.com",
564       "web3106215069.example.com",
565       "testinstance.example.com",
566       ])
567
568   def testVerify(self):
569     output = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
570     hv = self._GetHv(run_cmd=compat.partial(self._SuccessCommand,
571                                             output))
572     self.assertTrue(hv.Verify() is None)
573
574   def testVerifyFailing(self):
575     hv = self._GetHv(run_cmd=self._FailingCommand)
576     self.assertTrue("failed:" in hv.Verify())
577
578   def _StartInstanceCommand(self, inst, paused, failcreate, cmd):
579     if cmd == [self.CMD, "info"]:
580       output = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
581     elif cmd == [self.CMD, "list"]:
582       output = testutils.ReadTestData("xen-xm-list-4.0.1-dom0-only.txt")
583     elif cmd[:2] == [self.CMD, "create"]:
584       args = cmd[2:]
585       cfgfile = utils.PathJoin(self.tmpdir, inst.name)
586
587       if paused:
588         self.assertEqual(args, ["-p", cfgfile])
589       else:
590         self.assertEqual(args, [cfgfile])
591
592       if failcreate:
593         return self._FailingCommand(cmd)
594
595       output = ""
596     else:
597       self.fail("Unhandled command: %s" % (cmd, ))
598
599     return self._SuccessCommand(output, cmd)
600
601   def _MakeInstance(self):
602     # Copy default parameters
603     bep = objects.FillDict(constants.BEC_DEFAULTS, {})
604     hvp = objects.FillDict(constants.HVC_DEFAULTS[self.HVNAME], {})
605
606     # Override default VNC password file path
607     if constants.HV_VNC_PASSWORD_FILE in hvp:
608       hvp[constants.HV_VNC_PASSWORD_FILE] = self.vncpw_path
609
610     disks = [
611       (objects.Disk(dev_type=constants.LD_LV, mode=constants.DISK_RDWR),
612        utils.PathJoin(self.tmpdir, "disk0")),
613       (objects.Disk(dev_type=constants.LD_LV, mode=constants.DISK_RDONLY),
614        utils.PathJoin(self.tmpdir, "disk1")),
615       ]
616
617     inst = objects.Instance(name="server01.example.com",
618                             hvparams=hvp, beparams=bep,
619                             osparams={}, nics=[], os="deb1",
620                             disks=map(compat.fst, disks))
621     inst.UpgradeConfig()
622
623     return (inst, disks)
624
625   def testStartInstance(self):
626     (inst, disks) = self._MakeInstance()
627     pathutils.LOG_XEN_DIR = self.tmpdir
628
629     for failcreate in [False, True]:
630       for paused in [False, True]:
631         run_cmd = compat.partial(self._StartInstanceCommand,
632                                  inst, paused, failcreate)
633
634         hv = self._GetHv(run_cmd=run_cmd)
635
636         # Ensure instance is not listed
637         self.assertTrue(inst.name not in hv.ListInstances())
638
639         # Remove configuration
640         cfgfile = utils.PathJoin(self.tmpdir, inst.name)
641         utils.RemoveFile(cfgfile)
642
643         if failcreate:
644           self.assertRaises(errors.HypervisorError, hv.StartInstance,
645                             inst, disks, paused)
646           # Check whether a stale config file is left behind
647           self.assertFalse(os.path.exists(cfgfile))
648         else:
649           hv.StartInstance(inst, disks, paused)
650           # Check if configuration was updated
651           lines = utils.ReadFile(cfgfile).splitlines()
652
653         if constants.HV_VNC_PASSWORD_FILE in inst.hvparams:
654           self.assertTrue(("vncpasswd = '%s'" % self.vncpw) in lines)
655         else:
656           extra = inst.hvparams[constants.HV_KERNEL_ARGS]
657           self.assertTrue(("extra = '%s'" % extra) in lines)
658
659   def _StopInstanceCommand(self, instance_name, force, fail, cmd):
660     if ((force and cmd[:2] == [self.CMD, "destroy"]) or
661         (not force and cmd[:2] == [self.CMD, "shutdown"])):
662       self.assertEqual(cmd[2:], [instance_name])
663       output = ""
664     else:
665       self.fail("Unhandled command: %s" % (cmd, ))
666
667     if fail:
668       # Simulate a failing command
669       return self._FailingCommand(cmd)
670     else:
671       return self._SuccessCommand(output, cmd)
672
673   def testStopInstance(self):
674     name = "inst4284.example.com"
675     cfgfile = utils.PathJoin(self.tmpdir, name)
676     cfgdata = "config file content\n"
677
678     for force in [False, True]:
679       for fail in [False, True]:
680         utils.WriteFile(cfgfile, data=cfgdata)
681
682         run_cmd = compat.partial(self._StopInstanceCommand, name, force, fail)
683
684         hv = self._GetHv(run_cmd=run_cmd)
685
686         self.assertTrue(os.path.isfile(cfgfile))
687
688         if fail:
689           try:
690             hv._StopInstance(name, force, None)
691           except errors.HypervisorError, err:
692             self.assertTrue(str(err).startswith("Failed to stop instance"))
693           else:
694             self.fail("Exception was not raised")
695           self.assertEqual(utils.ReadFile(cfgfile), cfgdata,
696                            msg=("Configuration was removed when stopping"
697                                 " instance failed"))
698         else:
699           hv._StopInstance(name, force, None)
700           self.assertFalse(os.path.exists(cfgfile))
701
702   def _MigrateNonRunningInstCmd(self, cmd):
703     if cmd == [self.CMD, "list"]:
704       output = testutils.ReadTestData("xen-xm-list-4.0.1-dom0-only.txt")
705     else:
706       self.fail("Unhandled command: %s" % (cmd, ))
707
708     return self._SuccessCommand(output, cmd)
709
710   def testMigrateInstanceNotRunning(self):
711     name = "nonexistinginstance.example.com"
712     target = constants.IP4_ADDRESS_LOCALHOST
713     port = 14618
714
715     hv = self._GetHv(run_cmd=self._MigrateNonRunningInstCmd)
716
717     for live in [False, True]:
718       try:
719         hv._MigrateInstance(NotImplemented, name, target, port, live,
720                             _ping_fn=NotImplemented)
721       except errors.HypervisorError, err:
722         self.assertEqual(str(err), "Instance not running, cannot migrate")
723       else:
724         self.fail("Exception was not raised")
725
726   def _MigrateInstTargetUnreachCmd(self, cmd):
727     if cmd == [self.CMD, "list"]:
728       output = testutils.ReadTestData("xen-xm-list-4.0.1-four-instances.txt")
729     else:
730       self.fail("Unhandled command: %s" % (cmd, ))
731
732     return self._SuccessCommand(output, cmd)
733
734   def testMigrateTargetUnreachable(self):
735     name = "server01.example.com"
736     target = constants.IP4_ADDRESS_LOCALHOST
737     port = 28349
738
739     hv = self._GetHv(run_cmd=self._MigrateInstTargetUnreachCmd)
740
741     for live in [False, True]:
742       if self.CMD == constants.XEN_CMD_XL:
743         # TODO: Detect unreachable targets
744         pass
745       else:
746         try:
747           hv._MigrateInstance(NotImplemented, name, target, port, live,
748                               _ping_fn=compat.partial(self._FakeTcpPing,
749                                                       (target, port), False))
750         except errors.HypervisorError, err:
751           wanted = "Remote host %s not" % target
752           self.assertTrue(str(err).startswith(wanted))
753         else:
754           self.fail("Exception was not raised")
755
756   def _MigrateInstanceCmd(self, cluster_name, instance_name, target, port,
757                           live, fail, cmd):
758     if cmd == [self.CMD, "list"]:
759       output = testutils.ReadTestData("xen-xm-list-4.0.1-four-instances.txt")
760     elif cmd[:2] == [self.CMD, "migrate"]:
761       if self.CMD == constants.XEN_CMD_XM:
762         args = ["-p", str(port)]
763
764         if live:
765           args.append("-l")
766
767       elif self.CMD == constants.XEN_CMD_XL:
768         args = [
769           "-s", constants.XL_SSH_CMD % cluster_name,
770           "-C", utils.PathJoin(self.tmpdir, instance_name),
771           ]
772
773       else:
774         self.fail("Unknown Xen command '%s'" % self.CMD)
775
776       args.extend([instance_name, target])
777       self.assertEqual(cmd[2:], args)
778
779       if fail:
780         return self._FailingCommand(cmd)
781
782       output = ""
783     else:
784       self.fail("Unhandled command: %s" % (cmd, ))
785
786     return self._SuccessCommand(output, cmd)
787
788   def testMigrateInstance(self):
789     clustername = "cluster.example.com"
790     instname = "server01.example.com"
791     target = constants.IP4_ADDRESS_LOCALHOST
792     port = 22364
793
794     for live in [False, True]:
795       for fail in [False, True]:
796         ping_fn = \
797           testutils.CallCounter(compat.partial(self._FakeTcpPing,
798                                                (target, port), True))
799
800         run_cmd = \
801           compat.partial(self._MigrateInstanceCmd,
802                          clustername, instname, target, port, live,
803                          fail)
804
805         hv = self._GetHv(run_cmd=run_cmd)
806
807         if fail:
808           try:
809             hv._MigrateInstance(clustername, instname, target, port, live,
810                                 _ping_fn=ping_fn)
811           except errors.HypervisorError, err:
812             self.assertTrue(str(err).startswith("Failed to migrate instance"))
813           else:
814             self.fail("Exception was not raised")
815         else:
816           hv._MigrateInstance(clustername, instname, target, port, live,
817                               _ping_fn=ping_fn)
818
819         if self.CMD == constants.XEN_CMD_XM:
820           expected_pings = 1
821         else:
822           expected_pings = 0
823
824         self.assertEqual(ping_fn.Count(), expected_pings)
825
826   def _GetNodeInfoCmd(self, fail, cmd):
827     if cmd == [self.CMD, "info"]:
828       if fail:
829         return self._FailingCommand(cmd)
830       else:
831         output = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
832     elif cmd == [self.CMD, "list"]:
833       if fail:
834         self.fail("'xm list' shouldn't be called when 'xm info' failed")
835       else:
836         output = testutils.ReadTestData("xen-xm-list-4.0.1-four-instances.txt")
837     else:
838       self.fail("Unhandled command: %s" % (cmd, ))
839
840     return self._SuccessCommand(output, cmd)
841
842   def testGetNodeInfo(self):
843     run_cmd = compat.partial(self._GetNodeInfoCmd, False)
844     hv = self._GetHv(run_cmd=run_cmd)
845     result = hv.GetNodeInfo()
846
847     self.assertEqual(result["hv_version"], (4, 0))
848     self.assertEqual(result["memory_free"], 8004)
849
850   def testGetNodeInfoFailing(self):
851     run_cmd = compat.partial(self._GetNodeInfoCmd, True)
852     hv = self._GetHv(run_cmd=run_cmd)
853     self.assertTrue(hv.GetNodeInfo() is None)
854
855
856 def _MakeTestClass(cls, cmd):
857   """Makes a class for testing.
858
859   The returned class has structure as shown in the following pseudo code:
860
861     class Test{cls.__name__}{cmd}(_TestXenHypervisor, unittest.TestCase):
862       TARGET = {cls}
863       CMD = {cmd}
864       HVNAME = {Hypervisor name retrieved using class}
865
866   @type cls: class
867   @param cls: Hypervisor class to be tested
868   @type cmd: string
869   @param cmd: Hypervisor command
870   @rtype: tuple
871   @return: Class name and class object (not instance)
872
873   """
874   name = "Test%sCmd%s" % (cls.__name__, cmd.title())
875   bases = (_TestXenHypervisor, unittest.TestCase)
876   hvname = HVCLASS_TO_HVNAME[cls]
877
878   return (name, type(name, bases, dict(TARGET=cls, CMD=cmd, HVNAME=hvname)))
879
880
881 # Create test classes programmatically instead of manually to reduce the risk
882 # of forgetting some combinations
883 for cls in [hv_xen.XenPvmHypervisor, hv_xen.XenHvmHypervisor]:
884   for cmd in constants.KNOWN_XEN_COMMANDS:
885     (name, testcls) = _MakeTestClass(cls, cmd)
886
887     assert name not in locals()
888
889     locals()[name] = testcls
890
891
892 if __name__ == "__main__":
893   testutils.GanetiTestProgram()