hv_xen: Test various forms of getting instance/node info
[ganeti-github.git] / 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 testReadingNonExistentConfigFile(self):
370 hv = self._GetHv()
371
372 try:
373 hv._ReadConfigFile("inst15780.example.com")
374 except errors.HypervisorError, err:
375 self.assertTrue(str(err).startswith("Failed to load Xen config file:"))
376 else:
377 self.fail("Exception was not raised")
378
379 def testRemovingAutoConfigFile(self):
380 name = "inst8206.example.com"
381 cfgfile = utils.PathJoin(self.tmpdir, name)
382 autodir = utils.PathJoin(self.tmpdir, "auto")
383 autocfgfile = utils.PathJoin(autodir, name)
384
385 os.mkdir(autodir)
386
387 utils.WriteFile(autocfgfile, data="")
388
389 hv = self._GetHv()
390
391 self.assertTrue(os.path.isfile(autocfgfile))
392 hv._WriteConfigFile(name, "content")
393 self.assertFalse(os.path.exists(autocfgfile))
394 self.assertEqual(utils.ReadFile(cfgfile), "content")
395
396 def _XenList(self, cmd):
397 self.assertEqual(cmd, [self.CMD, "list"])
398
399 # TODO: Use actual data from "xl" command
400 output = testutils.ReadTestData("xen-xm-list-4.0.1-four-instances.txt")
401
402 return self._SuccessCommand(output, cmd)
403
404 def testGetInstanceInfo(self):
405 hv = self._GetHv(run_cmd=self._XenList)
406
407 (name, instid, memory, vcpus, state, runtime) = \
408 hv.GetInstanceInfo("server01.example.com")
409
410 self.assertEqual(name, "server01.example.com")
411 self.assertEqual(instid, 1)
412 self.assertEqual(memory, 1024)
413 self.assertEqual(vcpus, 1)
414 self.assertEqual(state, "-b----")
415 self.assertAlmostEqual(runtime, 167643.2)
416
417 def testGetInstanceInfoDom0(self):
418 hv = self._GetHv(run_cmd=self._XenList)
419
420 # TODO: Not sure if this is actually used anywhere (can't find it), but the
421 # code supports querying for Dom0
422 (name, instid, memory, vcpus, state, runtime) = \
423 hv.GetInstanceInfo(hv_xen._DOM0_NAME)
424
425 self.assertEqual(name, "Domain-0")
426 self.assertEqual(instid, 0)
427 self.assertEqual(memory, 1023)
428 self.assertEqual(vcpus, 1)
429 self.assertEqual(state, "r-----")
430 self.assertAlmostEqual(runtime, 154706.1)
431
432 def testGetInstanceInfoUnknown(self):
433 hv = self._GetHv(run_cmd=self._XenList)
434
435 result = hv.GetInstanceInfo("unknown.example.com")
436 self.assertTrue(result is None)
437
438 def testGetAllInstancesInfo(self):
439 hv = self._GetHv(run_cmd=self._XenList)
440
441 result = hv.GetAllInstancesInfo()
442
443 self.assertEqual(map(compat.fst, result), [
444 "server01.example.com",
445 "web3106215069.example.com",
446 "testinstance.example.com",
447 ])
448
449 def testListInstances(self):
450 hv = self._GetHv(run_cmd=self._XenList)
451
452 self.assertEqual(hv.ListInstances(), [
453 "server01.example.com",
454 "web3106215069.example.com",
455 "testinstance.example.com",
456 ])
457
458 def testVerify(self):
459 output = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
460 hv = self._GetHv(run_cmd=compat.partial(self._SuccessCommand,
461 output))
462 self.assertTrue(hv.Verify() is None)
463
464 def testVerifyFailing(self):
465 hv = self._GetHv(run_cmd=self._FailingCommand)
466 self.assertTrue("failed:" in hv.Verify())
467
468 def _StartInstanceCommand(self, inst, paused, failcreate, cmd):
469 if cmd == [self.CMD, "info"]:
470 output = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
471 elif cmd == [self.CMD, "list"]:
472 output = testutils.ReadTestData("xen-xm-list-4.0.1-dom0-only.txt")
473 elif cmd[:2] == [self.CMD, "create"]:
474 args = cmd[2:]
475 cfgfile = utils.PathJoin(self.tmpdir, inst.name)
476
477 if paused:
478 self.assertEqual(args, ["-p", cfgfile])
479 else:
480 self.assertEqual(args, [cfgfile])
481
482 if failcreate:
483 return self._FailingCommand(cmd)
484
485 output = ""
486 else:
487 self.fail("Unhandled command: %s" % (cmd, ))
488
489 return self._SuccessCommand(output, cmd)
490 #return self._FailingCommand(cmd)
491
492 def _MakeInstance(self):
493 # Copy default parameters
494 bep = objects.FillDict(constants.BEC_DEFAULTS, {})
495 hvp = objects.FillDict(constants.HVC_DEFAULTS[self.HVNAME], {})
496
497 # Override default VNC password file path
498 if constants.HV_VNC_PASSWORD_FILE in hvp:
499 hvp[constants.HV_VNC_PASSWORD_FILE] = self.vncpw_path
500
501 disks = [
502 (objects.Disk(dev_type=constants.LD_LV, mode=constants.DISK_RDWR),
503 utils.PathJoin(self.tmpdir, "disk0")),
504 (objects.Disk(dev_type=constants.LD_LV, mode=constants.DISK_RDONLY),
505 utils.PathJoin(self.tmpdir, "disk1")),
506 ]
507
508 inst = objects.Instance(name="server01.example.com",
509 hvparams=hvp, beparams=bep,
510 osparams={}, nics=[], os="deb1",
511 disks=map(compat.fst, disks))
512 inst.UpgradeConfig()
513
514 return (inst, disks)
515
516 def testStartInstance(self):
517 (inst, disks) = self._MakeInstance()
518
519 for failcreate in [False, True]:
520 for paused in [False, True]:
521 run_cmd = compat.partial(self._StartInstanceCommand,
522 inst, paused, failcreate)
523
524 hv = self._GetHv(run_cmd=run_cmd)
525
526 # Ensure instance is not listed
527 self.assertTrue(inst.name not in hv.ListInstances())
528
529 # Remove configuration
530 cfgfile = utils.PathJoin(self.tmpdir, inst.name)
531 utils.RemoveFile(cfgfile)
532
533 if failcreate:
534 self.assertRaises(errors.HypervisorError, hv.StartInstance,
535 inst, disks, paused)
536 else:
537 hv.StartInstance(inst, disks, paused)
538
539 # Check if configuration was updated
540 lines = utils.ReadFile(cfgfile).splitlines()
541
542 if constants.HV_VNC_PASSWORD_FILE in inst.hvparams:
543 self.assertTrue(("vncpasswd = '%s'" % self.vncpw) in lines)
544 else:
545 extra = inst.hvparams[constants.HV_KERNEL_ARGS]
546 self.assertTrue(("extra = '%s'" % extra) in lines)
547
548 def _StopInstanceCommand(self, instance_name, force, fail, cmd):
549 if ((force and cmd[:2] == [self.CMD, "destroy"]) or
550 (not force and cmd[:2] == [self.CMD, "shutdown"])):
551 self.assertEqual(cmd[2:], [instance_name])
552 output = ""
553 else:
554 self.fail("Unhandled command: %s" % (cmd, ))
555
556 if fail:
557 # Simulate a failing command
558 return self._FailingCommand(cmd)
559 else:
560 return self._SuccessCommand(output, cmd)
561
562 def testStopInstance(self):
563 name = "inst4284.example.com"
564 cfgfile = utils.PathJoin(self.tmpdir, name)
565 cfgdata = "config file content\n"
566
567 for force in [False, True]:
568 for fail in [False, True]:
569 utils.WriteFile(cfgfile, data=cfgdata)
570
571 run_cmd = compat.partial(self._StopInstanceCommand, name, force, fail)
572
573 hv = self._GetHv(run_cmd=run_cmd)
574
575 self.assertTrue(os.path.isfile(cfgfile))
576
577 if fail:
578 try:
579 hv._StopInstance(name, force)
580 except errors.HypervisorError, err:
581 self.assertTrue(str(err).startswith("Failed to stop instance"))
582 else:
583 self.fail("Exception was not raised")
584 self.assertEqual(utils.ReadFile(cfgfile), cfgdata,
585 msg=("Configuration was removed when stopping"
586 " instance failed"))
587 else:
588 hv._StopInstance(name, force)
589 self.assertFalse(os.path.exists(cfgfile))
590
591
592 def _MakeTestClass(cls, cmd):
593 """Makes a class for testing.
594
595 The returned class has structure as shown in the following pseudo code:
596
597 class Test{cls.__name__}{cmd}(_TestXenHypervisor, unittest.TestCase):
598 TARGET = {cls}
599 CMD = {cmd}
600 HVNAME = {Hypervisor name retrieved using class}
601
602 @type cls: class
603 @param cls: Hypervisor class to be tested
604 @type cmd: string
605 @param cmd: Hypervisor command
606 @rtype: tuple
607 @return: Class name and class object (not instance)
608
609 """
610 name = "Test%sCmd%s" % (cls.__name__, cmd.title())
611 bases = (_TestXenHypervisor, unittest.TestCase)
612 hvname = HVCLASS_TO_HVNAME[cls]
613
614 return (name, type(name, bases, dict(TARGET=cls, CMD=cmd, HVNAME=hvname)))
615
616
617 # Create test classes programmatically instead of manually to reduce the risk
618 # of forgetting some combinations
619 for cls in [hv_xen.XenPvmHypervisor, hv_xen.XenHvmHypervisor]:
620 for cmd in constants.KNOWN_XEN_COMMANDS:
621 (name, testcls) = _MakeTestClass(cls, cmd)
622
623 assert name not in locals()
624
625 locals()[name] = testcls
626
627
628 if __name__ == "__main__":
629 testutils.GanetiTestProgram()