Statistics
| Branch: | Tag: | Revision:

root / test / py / ganeti.hypervisor.hv_xen_unittest.py @ fac489a5

History | View | Annotate | Download (31 kB)

1
#!/usr/bin/python
2
#
3

    
4
# Copyright (C) 2011, 2013 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21

    
22
"""Script for testing ganeti.hypervisor.hv_xen"""
23

    
24
import string # pylint: disable=W0402
25
import unittest
26
import tempfile
27
import shutil
28
import random
29
import os
30
import mock
31

    
32
from ganeti import constants
33
from ganeti import objects
34
from ganeti import pathutils
35
from ganeti import hypervisor
36
from ganeti import utils
37
from ganeti import errors
38
from ganeti import compat
39

    
40
from ganeti.hypervisor import hv_xen
41

    
42
import testutils
43

    
44

    
45
# Map from hypervisor class to hypervisor name
46
HVCLASS_TO_HVNAME = utils.InvertDict(hypervisor._HYPERVISOR_MAP)
47

    
48

    
49
class TestConsole(unittest.TestCase):
50
  def test(self):
51
    for cls in [hv_xen.XenPvmHypervisor, hv_xen.XenHvmHypervisor]:
52
      instance = objects.Instance(name="xen.example.com",
53
                                  primary_node="node24828")
54
      cons = cls.GetInstanceConsole(instance, {}, {})
55
      self.assertTrue(cons.Validate())
56
      self.assertEqual(cons.kind, constants.CONS_SSH)
57
      self.assertEqual(cons.host, instance.primary_node)
58
      self.assertEqual(cons.command[-1], instance.name)
59

    
60

    
61
class TestCreateConfigCpus(unittest.TestCase):
62
  def testEmpty(self):
63
    for cpu_mask in [None, ""]:
64
      self.assertEqual(hv_xen._CreateConfigCpus(cpu_mask),
65
                       "cpus = [  ]")
66

    
67
  def testAll(self):
68
    self.assertEqual(hv_xen._CreateConfigCpus(constants.CPU_PINNING_ALL),
69
                     None)
70

    
71
  def testOne(self):
72
    self.assertEqual(hv_xen._CreateConfigCpus("9"), "cpu = \"9\"")
73

    
74
  def testMultiple(self):
75
    self.assertEqual(hv_xen._CreateConfigCpus("0-2,4,5-5:3:all"),
76
                     ("cpus = [ \"0,1,2,4,5\", \"3\", \"%s\" ]" %
77
                      constants.CPU_PINNING_ALL_XEN))
78

    
79

    
80
class TestGetCommand(testutils.GanetiTestCase):
81
  def testDefault(self):
82
    expected_cmd = "xm"
83
    hv = hv_xen.XenHypervisor()
84
    self.assertEqual(hv._GetCommand(), expected_cmd)
85

    
86
  def testCommandExplicit(self):
87
    """Test the case when the command is given as class parameter explicitly.
88

89
    """
90
    expected_cmd = "xl"
91
    hv = hv_xen.XenHypervisor(_cmd=constants.XEN_CMD_XL)
92
    self.assertEqual(hv._GetCommand(), expected_cmd)
93

    
94
  def testCommandInvalid(self):
95
    """Test the case an invalid command is given as class parameter explicitly.
96

97
    """
98
    hv = hv_xen.XenHypervisor(_cmd="invalidcommand")
99
    self.assertRaises(errors.ProgrammerError, hv._GetCommand, None)
100

    
101
  def testCommandHvparams(self):
102
    expected_cmd = "xl"
103
    test_hvparams = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
104
    hv = hv_xen.XenHypervisor()
105
    self.assertEqual(hv._GetCommand(hvparams=test_hvparams), expected_cmd)
106

    
107
  def testCommandHvparamsInvalid(self):
108
    test_hvparams = {}
109
    hv = hv_xen.XenHypervisor()
110
    self.assertRaises(KeyError, hv._GetCommand, test_hvparams)
111

    
112
  def testCommandHvparamsCmdInvalid(self):
113
    test_hvparams = {constants.HV_XEN_CMD: "invalidcommand"}
114
    hv = hv_xen.XenHypervisor()
115
    self.assertRaises(errors.ProgrammerError, hv._GetCommand, test_hvparams)
116

    
117

    
118
class TestParseInstanceList(testutils.GanetiTestCase):
119
  def test(self):
120
    data = testutils.ReadTestData("xen-xm-list-4.0.1-dom0-only.txt")
121

    
122
    # Exclude node
123
    self.assertEqual(hv_xen._ParseInstanceList(data.splitlines(), False), [])
124

    
125
    # Include node
126
    result = hv_xen._ParseInstanceList(data.splitlines(), True)
127
    self.assertEqual(len(result), 1)
128
    self.assertEqual(len(result[0]), 6)
129

    
130
    # Name
131
    self.assertEqual(result[0][0], hv_xen._DOM0_NAME)
132

    
133
    # ID
134
    self.assertEqual(result[0][1], 0)
135

    
136
    # Memory
137
    self.assertEqual(result[0][2], 1023)
138

    
139
    # VCPUs
140
    self.assertEqual(result[0][3], 1)
141

    
142
    # State
143
    self.assertEqual(result[0][4], "r-----")
144

    
145
    # Time
146
    self.assertAlmostEqual(result[0][5], 121152.6)
147

    
148
  def testWrongLineFormat(self):
149
    tests = [
150
      ["three fields only"],
151
      ["name InvalidID 128 1 r----- 12345"],
152
      ]
153

    
154
    for lines in tests:
155
      try:
156
        hv_xen._ParseInstanceList(["Header would be here"] + lines, False)
157
      except errors.HypervisorError, err:
158
        self.assertTrue("Can't parse instance list" in str(err))
159
      else:
160
        self.fail("Exception was not raised")
161

    
162

    
163
class TestGetInstanceList(testutils.GanetiTestCase):
164
  def _Fail(self):
165
    return utils.RunResult(constants.EXIT_FAILURE, None,
166
                           "stdout", "stderr", None,
167
                           NotImplemented, NotImplemented)
168

    
169
  def testTimeout(self):
170
    fn = testutils.CallCounter(self._Fail)
171
    try:
172
      hv_xen._GetInstanceList(fn, False, _timeout=0.1)
173
    except errors.HypervisorError, err:
174
      self.assertTrue("timeout exceeded" in str(err))
175
    else:
176
      self.fail("Exception was not raised")
177

    
178
    self.assertTrue(fn.Count() < 10,
179
                    msg="'xm list' was called too many times")
180

    
181
  def _Success(self, stdout):
182
    return utils.RunResult(constants.EXIT_SUCCESS, None, stdout, "", None,
183
                           NotImplemented, NotImplemented)
184

    
185
  def testSuccess(self):
186
    data = testutils.ReadTestData("xen-xm-list-4.0.1-four-instances.txt")
187

    
188
    fn = testutils.CallCounter(compat.partial(self._Success, data))
189

    
190
    result = hv_xen._GetInstanceList(fn, True, _timeout=0.1)
191

    
192
    self.assertEqual(len(result), 4)
193

    
194
    self.assertEqual(map(compat.fst, result), [
195
      "Domain-0",
196
      "server01.example.com",
197
      "web3106215069.example.com",
198
      "testinstance.example.com",
199
      ])
200

    
201
    self.assertEqual(fn.Count(), 1)
202

    
203

    
204
class TestParseNodeInfo(testutils.GanetiTestCase):
205
  def testEmpty(self):
206
    self.assertEqual(hv_xen._ParseNodeInfo(""), {})
207

    
208
  def testUnknownInput(self):
209
    data = "\n".join([
210
      "foo bar",
211
      "something else goes",
212
      "here",
213
      ])
214
    self.assertEqual(hv_xen._ParseNodeInfo(data), {})
215

    
216
  def testBasicInfo(self):
217
    data = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
218
    result = hv_xen._ParseNodeInfo(data)
219
    self.assertEqual(result, {
220
      "cpu_nodes": 1,
221
      "cpu_sockets": 2,
222
      "cpu_total": 4,
223
      "hv_version": (4, 0),
224
      "memory_free": 8004,
225
      "memory_total": 16378,
226
      })
227

    
228

    
229
class TestMergeInstanceInfo(testutils.GanetiTestCase):
230
  def testEmpty(self):
231
    self.assertEqual(hv_xen._MergeInstanceInfo({}, []), {})
232

    
233
  def _FakeXmList(self, include_node):
234
    return [
235
      (hv_xen._DOM0_NAME, NotImplemented, 4096, 7, NotImplemented,
236
       NotImplemented),
237
      ("inst1.example.com", NotImplemented, 2048, 4, NotImplemented,
238
       NotImplemented),
239
      ]
240

    
241
  def testMissingNodeInfo(self):
242
    instance_list = self._FakeXmList(True)
243
    result = hv_xen._MergeInstanceInfo({}, instance_list)
244
    self.assertEqual(result, {
245
      "memory_dom0": 4096,
246
      "dom0_cpus": 7,
247
      })
248

    
249
  def testWithNodeInfo(self):
250
    info = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
251
    instance_list = self._FakeXmList(True)
252
    result = hv_xen._GetNodeInfo(info, instance_list)
253
    self.assertEqual(result, {
254
      "cpu_nodes": 1,
255
      "cpu_sockets": 2,
256
      "cpu_total": 4,
257
      "dom0_cpus": 7,
258
      "hv_version": (4, 0),
259
      "memory_dom0": 4096,
260
      "memory_free": 8004,
261
      "memory_hv": 2230,
262
      "memory_total": 16378,
263
      })
264

    
265

    
266
class TestGetConfigFileDiskData(unittest.TestCase):
267
  def testLetterCount(self):
268
    self.assertEqual(len(hv_xen._DISK_LETTERS), 26)
269

    
270
  def testNoDisks(self):
271
    self.assertEqual(hv_xen._GetConfigFileDiskData([], "hd"), [])
272

    
273
  def testManyDisks(self):
274
    for offset in [0, 1, 10]:
275
      disks = [(objects.Disk(dev_type=constants.LD_LV), "/tmp/disk/%s" % idx)
276
               for idx in range(len(hv_xen._DISK_LETTERS) + offset)]
277

    
278
      if offset == 0:
279
        result = hv_xen._GetConfigFileDiskData(disks, "hd")
280
        self.assertEqual(result, [
281
          "'phy:/tmp/disk/%s,hd%s,r'" % (idx, string.ascii_lowercase[idx])
282
          for idx in range(len(hv_xen._DISK_LETTERS) + offset)
283
          ])
284
      else:
285
        try:
286
          hv_xen._GetConfigFileDiskData(disks, "hd")
287
        except errors.HypervisorError, err:
288
          self.assertEqual(str(err), "Too many disks")
289
        else:
290
          self.fail("Exception was not raised")
291

    
292
  def testTwoLvDisksWithMode(self):
293
    disks = [
294
      (objects.Disk(dev_type=constants.LD_LV, mode=constants.DISK_RDWR),
295
       "/tmp/diskFirst"),
296
      (objects.Disk(dev_type=constants.LD_LV, mode=constants.DISK_RDONLY),
297
       "/tmp/diskLast"),
298
      ]
299

    
300
    result = hv_xen._GetConfigFileDiskData(disks, "hd")
301
    self.assertEqual(result, [
302
      "'phy:/tmp/diskFirst,hda,w'",
303
      "'phy:/tmp/diskLast,hdb,r'",
304
      ])
305

    
306
  def testFileDisks(self):
307
    disks = [
308
      (objects.Disk(dev_type=constants.LD_FILE, mode=constants.DISK_RDWR,
309
                    physical_id=[constants.FD_LOOP]),
310
       "/tmp/diskFirst"),
311
      (objects.Disk(dev_type=constants.LD_FILE, mode=constants.DISK_RDONLY,
312
                    physical_id=[constants.FD_BLKTAP]),
313
       "/tmp/diskTwo"),
314
      (objects.Disk(dev_type=constants.LD_FILE, mode=constants.DISK_RDWR,
315
                    physical_id=[constants.FD_LOOP]),
316
       "/tmp/diskThree"),
317
      (objects.Disk(dev_type=constants.LD_FILE, mode=constants.DISK_RDWR,
318
                    physical_id=[constants.FD_BLKTAP]),
319
       "/tmp/diskLast"),
320
      ]
321

    
322
    result = hv_xen._GetConfigFileDiskData(disks, "sd")
323
    self.assertEqual(result, [
324
      "'file:/tmp/diskFirst,sda,w'",
325
      "'tap:aio:/tmp/diskTwo,sdb,r'",
326
      "'file:/tmp/diskThree,sdc,w'",
327
      "'tap:aio:/tmp/diskLast,sdd,w'",
328
      ])
329

    
330
  def testInvalidFileDisk(self):
331
    disks = [
332
      (objects.Disk(dev_type=constants.LD_FILE, mode=constants.DISK_RDWR,
333
                    physical_id=["#unknown#"]),
334
       "/tmp/diskinvalid"),
335
      ]
336

    
337
    self.assertRaises(KeyError, hv_xen._GetConfigFileDiskData, disks, "sd")
338

    
339

    
340
class TestXenHypervisorRunXen(unittest.TestCase):
341

    
342
  XEN_SUB_CMD = "help"
343

    
344
  def testCommandUnknown(self):
345
    cmd = "#unknown command#"
346
    self.assertFalse(cmd in constants.KNOWN_XEN_COMMANDS)
347
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
348
                              _run_cmd_fn=NotImplemented,
349
                              _cmd=cmd)
350
    self.assertRaises(errors.ProgrammerError, hv._RunXen, [])
351

    
352
  def testCommandValid(self):
353
    xen_cmd = "xm"
354
    mock_run_cmd = mock.Mock()
355
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
356
                              _run_cmd_fn=mock_run_cmd)
357
    hv._RunXen([self.XEN_SUB_CMD])
358
    mock_run_cmd.assert_called_with([xen_cmd, self.XEN_SUB_CMD])
359

    
360
  def testCommandFromHvparams(self):
361
    expected_xen_cmd = "xl"
362
    hvparams = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
363
    mock_run_cmd = mock.Mock()
364
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
365
                              _run_cmd_fn=mock_run_cmd)
366
    hv._RunXen([self.XEN_SUB_CMD], hvparams=hvparams)
367
    mock_run_cmd.assert_called_with([expected_xen_cmd, self.XEN_SUB_CMD])
368

    
369

    
370
class TestXenHypervisorGetInstanceList(unittest.TestCase):
371

    
372
  RESULT_OK = utils.RunResult(0, None, "", "", "", None, None)
373
  XEN_LIST = "list"
374

    
375
  def testOk(self):
376
    expected_xen_cmd = "xm"
377
    mock_run_cmd = mock.Mock( return_value=self.RESULT_OK )
378
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
379
                              _run_cmd_fn=mock_run_cmd)
380
    hv._GetInstanceList(True)
381
    mock_run_cmd.assert_called_with([expected_xen_cmd, self.XEN_LIST])
382

    
383
  def testFromHvparams(self):
384
    expected_xen_cmd = "xl"
385
    hvparams = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
386
    mock_run_cmd = mock.Mock( return_value=self.RESULT_OK )
387
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
388
                              _run_cmd_fn=mock_run_cmd)
389
    hv._GetInstanceList(True, hvparams=hvparams)
390
    mock_run_cmd.assert_called_with([expected_xen_cmd, self.XEN_LIST])
391

    
392

    
393
class TestXenHypervisorListInstances(unittest.TestCase):
394

    
395
  RESULT_OK = utils.RunResult(0, None, "", "", "", None, None)
396
  XEN_LIST = "list"
397

    
398
  def testDefaultXm(self):
399
    expected_xen_cmd = "xm"
400
    mock_run_cmd = mock.Mock( return_value=self.RESULT_OK )
401
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
402
                              _run_cmd_fn=mock_run_cmd)
403
    hv.ListInstances()
404
    mock_run_cmd.assert_called_with([expected_xen_cmd, self.XEN_LIST])
405

    
406
  def testHvparamsXl(self):
407
    expected_xen_cmd = "xl"
408
    hvparams = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
409
    mock_run_cmd = mock.Mock( return_value=self.RESULT_OK )
410
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
411
                              _run_cmd_fn=mock_run_cmd)
412
    hv.ListInstances(hvparams=hvparams)
413
    mock_run_cmd.assert_called_with([expected_xen_cmd, self.XEN_LIST])
414

    
415

    
416
class TestXenHypervisorCheckToolstack(unittest.TestCase):
417

    
418
  def setUp(self):
419
    self.tmpdir = tempfile.mkdtemp()
420
    self.cfg_name = "xen_config"
421
    self.cfg_path = utils.PathJoin(self.tmpdir, self.cfg_name)
422
    self.hv = hv_xen.XenHypervisor()
423

    
424
  def tearDown(self):
425
    shutil.rmtree(self.tmpdir)
426

    
427
  def testBinaryNotFound(self):
428
    RESULT_FAILED = utils.RunResult(1, None, "", "", "", None, None)
429
    mock_run_cmd = mock.Mock(return_value=RESULT_FAILED)
430
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
431
                              _run_cmd_fn=mock_run_cmd)
432
    result = hv._CheckToolstackBinary("xl")
433
    self.assertFalse(result)
434

    
435
  def testCheckToolstackXlConfigured(self):
436
    RESULT_OK = utils.RunResult(0, None, "", "", "", None, None)
437
    mock_run_cmd = mock.Mock(return_value=RESULT_OK)
438
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
439
                              _run_cmd_fn=mock_run_cmd)
440
    result = hv._CheckToolstackXlConfigured()
441
    self.assertTrue(result)
442

    
443
  def testCheckToolstackXlNotConfigured(self):
444
    RESULT_FAILED = utils.RunResult(
445
        1, None, "",
446
        "ERROR:  A different toolstack (xm) have been selected!",
447
        "", None, None)
448
    mock_run_cmd = mock.Mock(return_value=RESULT_FAILED)
449
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
450
                              _run_cmd_fn=mock_run_cmd)
451
    result = hv._CheckToolstackXlConfigured()
452
    self.assertFalse(result)
453

    
454
  def testCheckToolstackXlFails(self):
455
    RESULT_FAILED = utils.RunResult(
456
        1, None, "",
457
        "ERROR: The pink bunny hid the binary.",
458
        "", None, None)
459
    mock_run_cmd = mock.Mock(return_value=RESULT_FAILED)
460
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
461
                              _run_cmd_fn=mock_run_cmd)
462
    self.assertRaises(errors.HypervisorError, hv._CheckToolstackXlConfigured)
463

    
464

    
465
class TestXenHypervisorWriteConfigFile(unittest.TestCase):
466
  def setUp(self):
467
    self.tmpdir = tempfile.mkdtemp()
468

    
469
  def tearDown(self):
470
    shutil.rmtree(self.tmpdir)
471

    
472
  def testWriteError(self):
473
    cfgdir = utils.PathJoin(self.tmpdir, "foobar")
474

    
475
    hv = hv_xen.XenHypervisor(_cfgdir=cfgdir,
476
                              _run_cmd_fn=NotImplemented,
477
                              _cmd=NotImplemented)
478

    
479
    self.assertFalse(os.path.exists(cfgdir))
480

    
481
    try:
482
      hv._WriteConfigFile("name", "data")
483
    except errors.HypervisorError, err:
484
      self.assertTrue(str(err).startswith("Cannot write Xen instance"))
485
    else:
486
      self.fail("Exception was not raised")
487

    
488

    
489
class TestXenHypervisorVerify(unittest.TestCase):
490

    
491
  def setUp(self):
492
    output = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
493
    self._result_ok = utils.RunResult(0, None, output, "", "", None, None)
494

    
495
  def testVerify(self):
496
    hvparams = {constants.HV_XEN_CMD : constants.XEN_CMD_XL}
497
    mock_run_cmd = mock.Mock(return_value=self._result_ok)
498
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
499
                              _run_cmd_fn=mock_run_cmd)
500
    hv._CheckToolstack = mock.Mock(return_value=True)
501
    result = hv.Verify(hvparams)
502
    self.assertTrue(result is None)
503

    
504
  def testVerifyToolstackNotOk(self):
505
    hvparams = {constants.HV_XEN_CMD : constants.XEN_CMD_XL}
506
    mock_run_cmd = mock.Mock(return_value=self._result_ok)
507
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
508
                              _run_cmd_fn=mock_run_cmd)
509
    hv._CheckToolstack = mock.Mock()
510
    hv._CheckToolstack.side_effect = errors.HypervisorError("foo")
511
    result = hv.Verify(hvparams)
512
    self.assertTrue(result is not None)
513

    
514
  def testVerifyFailing(self):
515
    result_failed = utils.RunResult(1, None, "", "", "", None, None)
516
    mock_run_cmd = mock.Mock(return_value=result_failed)
517
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
518
                              _run_cmd_fn=mock_run_cmd)
519
    hv._CheckToolstack = mock.Mock(return_value=True)
520
    result = hv.Verify()
521
    self.assertTrue(result is not None)
522

    
523

    
524
class _TestXenHypervisor(object):
525
  TARGET = NotImplemented
526
  CMD = NotImplemented
527
  HVNAME = NotImplemented
528
  VALID_HVPARAMS = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
529

    
530
  def setUp(self):
531
    super(_TestXenHypervisor, self).setUp()
532

    
533
    self.tmpdir = tempfile.mkdtemp()
534

    
535
    self.vncpw = "".join(random.sample(string.ascii_letters, 10))
536

    
537
    self.vncpw_path = utils.PathJoin(self.tmpdir, "vncpw")
538
    utils.WriteFile(self.vncpw_path, data=self.vncpw)
539

    
540
  def tearDown(self):
541
    super(_TestXenHypervisor, self).tearDown()
542

    
543
    shutil.rmtree(self.tmpdir)
544

    
545
  def _GetHv(self, run_cmd=NotImplemented):
546
    return self.TARGET(_cfgdir=self.tmpdir, _run_cmd_fn=run_cmd, _cmd=self.CMD)
547

    
548
  def _SuccessCommand(self, stdout, cmd):
549
    self.assertEqual(cmd[0], self.CMD)
550

    
551
    return utils.RunResult(constants.EXIT_SUCCESS, None, stdout, "", None,
552
                           NotImplemented, NotImplemented)
553

    
554
  def _FailingCommand(self, cmd):
555
    self.assertEqual(cmd[0], self.CMD)
556

    
557
    return utils.RunResult(constants.EXIT_FAILURE, None,
558
                           "", "This command failed", None,
559
                           NotImplemented, NotImplemented)
560

    
561
  def _FakeTcpPing(self, expected, result, target, port, **kwargs):
562
    self.assertEqual((target, port), expected)
563
    return result
564

    
565
  def testReadingNonExistentConfigFile(self):
566
    hv = self._GetHv()
567

    
568
    try:
569
      hv._ReadConfigFile("inst15780.example.com")
570
    except errors.HypervisorError, err:
571
      self.assertTrue(str(err).startswith("Failed to load Xen config file:"))
572
    else:
573
      self.fail("Exception was not raised")
574

    
575
  def testRemovingAutoConfigFile(self):
576
    name = "inst8206.example.com"
577
    cfgfile = utils.PathJoin(self.tmpdir, name)
578
    autodir = utils.PathJoin(self.tmpdir, "auto")
579
    autocfgfile = utils.PathJoin(autodir, name)
580

    
581
    os.mkdir(autodir)
582

    
583
    utils.WriteFile(autocfgfile, data="")
584

    
585
    hv = self._GetHv()
586

    
587
    self.assertTrue(os.path.isfile(autocfgfile))
588
    hv._WriteConfigFile(name, "content")
589
    self.assertFalse(os.path.exists(autocfgfile))
590
    self.assertEqual(utils.ReadFile(cfgfile), "content")
591

    
592
  def _XenList(self, cmd):
593
    self.assertEqual(cmd, [self.CMD, "list"])
594

    
595
    # TODO: Use actual data from "xl" command
596
    output = testutils.ReadTestData("xen-xm-list-4.0.1-four-instances.txt")
597

    
598
    return self._SuccessCommand(output, cmd)
599

    
600
  def testGetInstanceInfo(self):
601
    hv = self._GetHv(run_cmd=self._XenList)
602

    
603
    (name, instid, memory, vcpus, state, runtime) = \
604
      hv.GetInstanceInfo("server01.example.com")
605

    
606
    self.assertEqual(name, "server01.example.com")
607
    self.assertEqual(instid, 1)
608
    self.assertEqual(memory, 1024)
609
    self.assertEqual(vcpus, 1)
610
    self.assertEqual(state, "-b----")
611
    self.assertAlmostEqual(runtime, 167643.2)
612

    
613
  def testGetInstanceInfoDom0(self):
614
    hv = self._GetHv(run_cmd=self._XenList)
615

    
616
    # TODO: Not sure if this is actually used anywhere (can't find it), but the
617
    # code supports querying for Dom0
618
    (name, instid, memory, vcpus, state, runtime) = \
619
      hv.GetInstanceInfo(hv_xen._DOM0_NAME)
620

    
621
    self.assertEqual(name, "Domain-0")
622
    self.assertEqual(instid, 0)
623
    self.assertEqual(memory, 1023)
624
    self.assertEqual(vcpus, 1)
625
    self.assertEqual(state, "r-----")
626
    self.assertAlmostEqual(runtime, 154706.1)
627

    
628
  def testGetInstanceInfoUnknown(self):
629
    hv = self._GetHv(run_cmd=self._XenList)
630

    
631
    result = hv.GetInstanceInfo("unknown.example.com")
632
    self.assertTrue(result is None)
633

    
634
  def testGetAllInstancesInfo(self):
635
    hv = self._GetHv(run_cmd=self._XenList)
636

    
637
    result = hv.GetAllInstancesInfo()
638

    
639
    self.assertEqual(map(compat.fst, result), [
640
      "server01.example.com",
641
      "web3106215069.example.com",
642
      "testinstance.example.com",
643
      ])
644

    
645
  def testListInstances(self):
646
    hv = self._GetHv(run_cmd=self._XenList)
647

    
648
    self.assertEqual(hv.ListInstances(), [
649
      "server01.example.com",
650
      "web3106215069.example.com",
651
      "testinstance.example.com",
652
      ])
653

    
654
  def _StartInstanceCommand(self, inst, paused, failcreate, cmd):
655
    if cmd == [self.CMD, "info"]:
656
      output = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
657
    elif cmd == [self.CMD, "list"]:
658
      output = testutils.ReadTestData("xen-xm-list-4.0.1-dom0-only.txt")
659
    elif cmd[:2] == [self.CMD, "create"]:
660
      args = cmd[2:]
661
      cfgfile = utils.PathJoin(self.tmpdir, inst.name)
662

    
663
      if paused:
664
        self.assertEqual(args, ["-p", cfgfile])
665
      else:
666
        self.assertEqual(args, [cfgfile])
667

    
668
      if failcreate:
669
        return self._FailingCommand(cmd)
670

    
671
      output = ""
672
    else:
673
      self.fail("Unhandled command: %s" % (cmd, ))
674

    
675
    return self._SuccessCommand(output, cmd)
676

    
677
  def _MakeInstance(self):
678
    # Copy default parameters
679
    bep = objects.FillDict(constants.BEC_DEFAULTS, {})
680
    hvp = objects.FillDict(constants.HVC_DEFAULTS[self.HVNAME], {})
681

    
682
    # Override default VNC password file path
683
    if constants.HV_VNC_PASSWORD_FILE in hvp:
684
      hvp[constants.HV_VNC_PASSWORD_FILE] = self.vncpw_path
685

    
686
    disks = [
687
      (objects.Disk(dev_type=constants.LD_LV, mode=constants.DISK_RDWR),
688
       utils.PathJoin(self.tmpdir, "disk0")),
689
      (objects.Disk(dev_type=constants.LD_LV, mode=constants.DISK_RDONLY),
690
       utils.PathJoin(self.tmpdir, "disk1")),
691
      ]
692

    
693
    inst = objects.Instance(name="server01.example.com",
694
                            hvparams=hvp, beparams=bep,
695
                            osparams={}, nics=[], os="deb1",
696
                            disks=map(compat.fst, disks))
697
    inst.UpgradeConfig()
698

    
699
    return (inst, disks)
700

    
701
  def testStartInstance(self):
702
    (inst, disks) = self._MakeInstance()
703
    pathutils.LOG_XEN_DIR = self.tmpdir
704

    
705
    for failcreate in [False, True]:
706
      for paused in [False, True]:
707
        run_cmd = compat.partial(self._StartInstanceCommand,
708
                                 inst, paused, failcreate)
709

    
710
        hv = self._GetHv(run_cmd=run_cmd)
711

    
712
        # Ensure instance is not listed
713
        self.assertTrue(inst.name not in hv.ListInstances())
714

    
715
        # Remove configuration
716
        cfgfile = utils.PathJoin(self.tmpdir, inst.name)
717
        utils.RemoveFile(cfgfile)
718

    
719
        if failcreate:
720
          self.assertRaises(errors.HypervisorError, hv.StartInstance,
721
                            inst, disks, paused)
722
          # Check whether a stale config file is left behind
723
          self.assertFalse(os.path.exists(cfgfile))
724
        else:
725
          hv.StartInstance(inst, disks, paused)
726
          # Check if configuration was updated
727
          lines = utils.ReadFile(cfgfile).splitlines()
728

    
729
        if constants.HV_VNC_PASSWORD_FILE in inst.hvparams:
730
          self.assertTrue(("vncpasswd = '%s'" % self.vncpw) in lines)
731
        else:
732
          extra = inst.hvparams[constants.HV_KERNEL_ARGS]
733
          self.assertTrue(("extra = '%s'" % extra) in lines)
734

    
735
  def _StopInstanceCommand(self, instance_name, force, fail, cmd):
736
    if ((force and cmd[:2] == [self.CMD, "destroy"]) or
737
        (not force and cmd[:2] == [self.CMD, "shutdown"])):
738
      self.assertEqual(cmd[2:], [instance_name])
739
      output = ""
740
    else:
741
      self.fail("Unhandled command: %s" % (cmd, ))
742

    
743
    if fail:
744
      # Simulate a failing command
745
      return self._FailingCommand(cmd)
746
    else:
747
      return self._SuccessCommand(output, cmd)
748

    
749
  def testStopInstance(self):
750
    name = "inst4284.example.com"
751
    cfgfile = utils.PathJoin(self.tmpdir, name)
752
    cfgdata = "config file content\n"
753

    
754
    for force in [False, True]:
755
      for fail in [False, True]:
756
        utils.WriteFile(cfgfile, data=cfgdata)
757

    
758
        run_cmd = compat.partial(self._StopInstanceCommand, name, force, fail)
759

    
760
        hv = self._GetHv(run_cmd=run_cmd)
761

    
762
        self.assertTrue(os.path.isfile(cfgfile))
763

    
764
        if fail:
765
          try:
766
            hv._StopInstance(name, force, None)
767
          except errors.HypervisorError, err:
768
            self.assertTrue(str(err).startswith("Failed to stop instance"))
769
          else:
770
            self.fail("Exception was not raised")
771
          self.assertEqual(utils.ReadFile(cfgfile), cfgdata,
772
                           msg=("Configuration was removed when stopping"
773
                                " instance failed"))
774
        else:
775
          hv._StopInstance(name, force, None)
776
          self.assertFalse(os.path.exists(cfgfile))
777

    
778
  def _MigrateNonRunningInstCmd(self, cmd):
779
    if cmd == [self.CMD, "list"]:
780
      output = testutils.ReadTestData("xen-xm-list-4.0.1-dom0-only.txt")
781
    else:
782
      self.fail("Unhandled command: %s" % (cmd, ))
783

    
784
    return self._SuccessCommand(output, cmd)
785

    
786
  def testMigrateInstanceNotRunning(self):
787
    name = "nonexistinginstance.example.com"
788
    target = constants.IP4_ADDRESS_LOCALHOST
789
    port = 14618
790

    
791
    hv = self._GetHv(run_cmd=self._MigrateNonRunningInstCmd)
792

    
793
    for live in [False, True]:
794
      try:
795
        hv._MigrateInstance(NotImplemented, name, target, port, live,
796
                            self.VALID_HVPARAMS, _ping_fn=NotImplemented)
797
      except errors.HypervisorError, err:
798
        self.assertEqual(str(err), "Instance not running, cannot migrate")
799
      else:
800
        self.fail("Exception was not raised")
801

    
802
  def _MigrateInstTargetUnreachCmd(self, cmd):
803
    if cmd == [self.CMD, "list"]:
804
      output = testutils.ReadTestData("xen-xm-list-4.0.1-four-instances.txt")
805
    else:
806
      self.fail("Unhandled command: %s" % (cmd, ))
807

    
808
    return self._SuccessCommand(output, cmd)
809

    
810
  def testMigrateTargetUnreachable(self):
811
    name = "server01.example.com"
812
    target = constants.IP4_ADDRESS_LOCALHOST
813
    port = 28349
814

    
815
    hv = self._GetHv(run_cmd=self._MigrateInstTargetUnreachCmd)
816
    hvparams = {constants.HV_XEN_CMD: self.CMD}
817

    
818
    for live in [False, True]:
819
      if self.CMD == constants.XEN_CMD_XL:
820
        # TODO: Detect unreachable targets
821
        pass
822
      else:
823
        try:
824
          hv._MigrateInstance(NotImplemented, name, target, port, live,
825
                              hvparams,
826
                              _ping_fn=compat.partial(self._FakeTcpPing,
827
                                                      (target, port), False))
828
        except errors.HypervisorError, err:
829
          wanted = "Remote host %s not" % target
830
          self.assertTrue(str(err).startswith(wanted))
831
        else:
832
          self.fail("Exception was not raised")
833

    
834
  def _MigrateInstanceCmd(self, cluster_name, instance_name, target, port,
835
                          live, fail, cmd):
836
    if cmd == [self.CMD, "list"]:
837
      output = testutils.ReadTestData("xen-xm-list-4.0.1-four-instances.txt")
838
    elif cmd[:2] == [self.CMD, "migrate"]:
839
      if self.CMD == constants.XEN_CMD_XM:
840
        args = ["-p", str(port)]
841

    
842
        if live:
843
          args.append("-l")
844

    
845
      elif self.CMD == constants.XEN_CMD_XL:
846
        args = [
847
          "-s", constants.XL_SSH_CMD % cluster_name,
848
          "-C", utils.PathJoin(self.tmpdir, instance_name),
849
          ]
850

    
851
      else:
852
        self.fail("Unknown Xen command '%s'" % self.CMD)
853

    
854
      args.extend([instance_name, target])
855
      self.assertEqual(cmd[2:], args)
856

    
857
      if fail:
858
        return self._FailingCommand(cmd)
859

    
860
      output = ""
861
    else:
862
      self.fail("Unhandled command: %s" % (cmd, ))
863

    
864
    return self._SuccessCommand(output, cmd)
865

    
866
  def testMigrateInstance(self):
867
    clustername = "cluster.example.com"
868
    instname = "server01.example.com"
869
    target = constants.IP4_ADDRESS_LOCALHOST
870
    port = 22364
871

    
872
    hvparams = {constants.HV_XEN_CMD: self.CMD}
873

    
874
    for live in [False, True]:
875
      for fail in [False, True]:
876
        ping_fn = \
877
          testutils.CallCounter(compat.partial(self._FakeTcpPing,
878
                                               (target, port), True))
879

    
880
        run_cmd = \
881
          compat.partial(self._MigrateInstanceCmd,
882
                         clustername, instname, target, port, live,
883
                         fail)
884

    
885
        hv = self._GetHv(run_cmd=run_cmd)
886

    
887
        if fail:
888
          try:
889
            hv._MigrateInstance(clustername, instname, target, port, live,
890
                                hvparams, _ping_fn=ping_fn)
891
          except errors.HypervisorError, err:
892
            self.assertTrue(str(err).startswith("Failed to migrate instance"))
893
          else:
894
            self.fail("Exception was not raised")
895
        else:
896
          hv._MigrateInstance(clustername, instname, target, port, live,
897
                              hvparams, _ping_fn=ping_fn)
898

    
899
        if self.CMD == constants.XEN_CMD_XM:
900
          expected_pings = 1
901
        else:
902
          expected_pings = 0
903

    
904
        self.assertEqual(ping_fn.Count(), expected_pings)
905

    
906
  def _GetNodeInfoCmd(self, fail, cmd):
907
    if cmd == [self.CMD, "info"]:
908
      if fail:
909
        return self._FailingCommand(cmd)
910
      else:
911
        output = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
912
    elif cmd == [self.CMD, "list"]:
913
      if fail:
914
        self.fail("'xm list' shouldn't be called when 'xm info' failed")
915
      else:
916
        output = testutils.ReadTestData("xen-xm-list-4.0.1-four-instances.txt")
917
    else:
918
      self.fail("Unhandled command: %s" % (cmd, ))
919

    
920
    return self._SuccessCommand(output, cmd)
921

    
922
  def testGetNodeInfo(self):
923
    run_cmd = compat.partial(self._GetNodeInfoCmd, False)
924
    hv = self._GetHv(run_cmd=run_cmd)
925
    result = hv.GetNodeInfo()
926

    
927
    self.assertEqual(result["hv_version"], (4, 0))
928
    self.assertEqual(result["memory_free"], 8004)
929

    
930
  def testGetNodeInfoFailing(self):
931
    run_cmd = compat.partial(self._GetNodeInfoCmd, True)
932
    hv = self._GetHv(run_cmd=run_cmd)
933
    self.assertTrue(hv.GetNodeInfo() is None)
934

    
935

    
936
def _MakeTestClass(cls, cmd):
937
  """Makes a class for testing.
938

939
  The returned class has structure as shown in the following pseudo code:
940

941
    class Test{cls.__name__}{cmd}(_TestXenHypervisor, unittest.TestCase):
942
      TARGET = {cls}
943
      CMD = {cmd}
944
      HVNAME = {Hypervisor name retrieved using class}
945

946
  @type cls: class
947
  @param cls: Hypervisor class to be tested
948
  @type cmd: string
949
  @param cmd: Hypervisor command
950
  @rtype: tuple
951
  @return: Class name and class object (not instance)
952

953
  """
954
  name = "Test%sCmd%s" % (cls.__name__, cmd.title())
955
  bases = (_TestXenHypervisor, unittest.TestCase)
956
  hvname = HVCLASS_TO_HVNAME[cls]
957

    
958
  return (name, type(name, bases, dict(TARGET=cls, CMD=cmd, HVNAME=hvname)))
959

    
960

    
961
# Create test classes programmatically instead of manually to reduce the risk
962
# of forgetting some combinations
963
for cls in [hv_xen.XenPvmHypervisor, hv_xen.XenHvmHypervisor]:
964
  for cmd in constants.KNOWN_XEN_COMMANDS:
965
    (name, testcls) = _MakeTestClass(cls, cmd)
966

    
967
    assert name not in locals()
968

    
969
    locals()[name] = testcls
970

    
971

    
972
if __name__ == "__main__":
973
  testutils.GanetiTestProgram()