Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (31.7 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_base
41
from ganeti.hypervisor import hv_xen
42

    
43
import testutils
44

    
45

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

    
49

    
50
class TestConsole(unittest.TestCase):
51
  def test(self):
52
    hvparams = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
53
    for cls in [hv_xen.XenPvmHypervisor(), hv_xen.XenHvmHypervisor()]:
54
      instance = objects.Instance(name="xen.example.com",
55
                                  primary_node="node24828-uuid")
56
      node = objects.Node(name="node24828", uuid="node24828-uuid",
57
                          ndparams={})
58
      group = objects.NodeGroup(name="group52341", ndparams={})
59
      cons = cls.GetInstanceConsole(instance, node, group, hvparams, {})
60
      self.assertTrue(cons.Validate())
61
      self.assertEqual(cons.kind, constants.CONS_SSH)
62
      self.assertEqual(cons.host, node.name)
63
      self.assertEqual(cons.command[-1], instance.name)
64

    
65

    
66
class TestCreateConfigCpus(unittest.TestCase):
67
  def testEmpty(self):
68
    for cpu_mask in [None, ""]:
69
      self.assertEqual(hv_xen._CreateConfigCpus(cpu_mask),
70
                       "cpus = [  ]")
71

    
72
  def testAll(self):
73
    self.assertEqual(hv_xen._CreateConfigCpus(constants.CPU_PINNING_ALL),
74
                     None)
75

    
76
  def testOne(self):
77
    self.assertEqual(hv_xen._CreateConfigCpus("9"), "cpu = \"9\"")
78

    
79
  def testMultiple(self):
80
    self.assertEqual(hv_xen._CreateConfigCpus("0-2,4,5-5:3:all"),
81
                     ("cpus = [ \"0,1,2,4,5\", \"3\", \"%s\" ]" %
82
                      constants.CPU_PINNING_ALL_XEN))
83

    
84

    
85
class TestGetCommand(testutils.GanetiTestCase):
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(None), 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(test_hvparams), expected_cmd)
106

    
107
  def testCommandHvparamsInvalid(self):
108
    test_hvparams = {}
109
    hv = hv_xen.XenHypervisor()
110
    self.assertRaises(errors.HypervisorError, 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], hv_base.HvInstanceState.RUNNING)
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._GetRunningInstanceList(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._GetRunningInstanceList(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
      "cpu_dom0": 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
      "cpu_dom0": 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.DT_PLAIN),
276
               "/tmp/disk/%s" % idx,
277
               NotImplemented)
278
               for idx in range(len(hv_xen._DISK_LETTERS) + offset)]
279

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

    
294
  def testTwoLvDisksWithMode(self):
295
    disks = [
296
      (objects.Disk(dev_type=constants.DT_PLAIN, mode=constants.DISK_RDWR),
297
       "/tmp/diskFirst",
298
       NotImplemented),
299
      (objects.Disk(dev_type=constants.DT_PLAIN, mode=constants.DISK_RDONLY),
300
       "/tmp/diskLast",
301
       NotImplemented),
302
      ]
303

    
304
    result = hv_xen._GetConfigFileDiskData(disks, "hd")
305
    self.assertEqual(result, [
306
      "'phy:/tmp/diskFirst,hda,w'",
307
      "'phy:/tmp/diskLast,hdb,r'",
308
      ])
309

    
310
  def testFileDisks(self):
311
    disks = [
312
      (objects.Disk(dev_type=constants.DT_FILE, mode=constants.DISK_RDWR,
313
                    logical_id=[constants.FD_LOOP]),
314
       "/tmp/diskFirst",
315
       NotImplemented),
316
      (objects.Disk(dev_type=constants.DT_FILE, mode=constants.DISK_RDONLY,
317
                    logical_id=[constants.FD_BLKTAP]),
318
       "/tmp/diskTwo",
319
       NotImplemented),
320
      (objects.Disk(dev_type=constants.DT_FILE, mode=constants.DISK_RDWR,
321
                    logical_id=[constants.FD_LOOP]),
322
       "/tmp/diskThree",
323
       NotImplemented),
324
      (objects.Disk(dev_type=constants.DT_FILE, mode=constants.DISK_RDWR,
325
                    logical_id=[constants.FD_BLKTAP]),
326
       "/tmp/diskLast",
327
       NotImplemented),
328
      ]
329

    
330
    result = hv_xen._GetConfigFileDiskData(disks, "sd")
331
    self.assertEqual(result, [
332
      "'file:/tmp/diskFirst,sda,w'",
333
      "'tap:aio:/tmp/diskTwo,sdb,r'",
334
      "'file:/tmp/diskThree,sdc,w'",
335
      "'tap:aio:/tmp/diskLast,sdd,w'",
336
      ])
337

    
338
  def testInvalidFileDisk(self):
339
    disks = [
340
      (objects.Disk(dev_type=constants.DT_FILE, mode=constants.DISK_RDWR,
341
                    logical_id=["#unknown#"]),
342
       "/tmp/diskinvalid",
343
       NotImplemented),
344
      ]
345

    
346
    self.assertRaises(KeyError, hv_xen._GetConfigFileDiskData, disks, "sd")
347

    
348

    
349
class TestXenHypervisorRunXen(unittest.TestCase):
350

    
351
  XEN_SUB_CMD = "help"
352

    
353
  def testCommandUnknown(self):
354
    cmd = "#unknown command#"
355
    self.assertFalse(cmd in constants.KNOWN_XEN_COMMANDS)
356
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
357
                              _run_cmd_fn=NotImplemented,
358
                              _cmd=cmd)
359
    self.assertRaises(errors.ProgrammerError, hv._RunXen, [], None)
360

    
361
  def testCommandNoHvparams(self):
362
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
363
                              _run_cmd_fn=NotImplemented)
364
    hvparams = None
365
    self.assertRaises(errors.HypervisorError, hv._RunXen, [self.XEN_SUB_CMD],
366
                      hvparams)
367

    
368
  def testCommandFromHvparams(self):
369
    expected_xen_cmd = "xl"
370
    hvparams = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
371
    mock_run_cmd = mock.Mock()
372
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
373
                              _run_cmd_fn=mock_run_cmd)
374
    hv._RunXen([self.XEN_SUB_CMD], hvparams=hvparams)
375
    mock_run_cmd.assert_called_with([expected_xen_cmd, self.XEN_SUB_CMD])
376

    
377

    
378
class TestXenHypervisorGetInstanceList(unittest.TestCase):
379

    
380
  RESULT_OK = utils.RunResult(0, None, "", "", "", None, None)
381
  XEN_LIST = "list"
382

    
383
  def testNoHvparams(self):
384
    expected_xen_cmd = "xm"
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
    self.assertRaises(errors.HypervisorError, hv._GetInstanceList, True, None)
389

    
390
  def testFromHvparams(self):
391
    expected_xen_cmd = "xl"
392
    hvparams = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
393
    mock_run_cmd = mock.Mock(return_value=self.RESULT_OK)
394
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
395
                              _run_cmd_fn=mock_run_cmd)
396
    hv._GetInstanceList(True, hvparams)
397
    mock_run_cmd.assert_called_with([expected_xen_cmd, self.XEN_LIST])
398

    
399

    
400
class TestXenHypervisorListInstances(unittest.TestCase):
401

    
402
  RESULT_OK = utils.RunResult(0, None, "", "", "", None, None)
403
  XEN_LIST = "list"
404

    
405
  def testNoHvparams(self):
406
    expected_xen_cmd = "xm"
407
    mock_run_cmd = mock.Mock(return_value=self.RESULT_OK)
408
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
409
                              _run_cmd_fn=mock_run_cmd)
410
    self.assertRaises(errors.HypervisorError, hv.ListInstances)
411

    
412
  def testHvparamsXl(self):
413
    expected_xen_cmd = "xl"
414
    hvparams = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
415
    mock_run_cmd = mock.Mock(return_value=self.RESULT_OK)
416
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
417
                              _run_cmd_fn=mock_run_cmd)
418
    hv.ListInstances(hvparams=hvparams)
419
    mock_run_cmd.assert_called_with([expected_xen_cmd, self.XEN_LIST])
420

    
421

    
422
class TestXenHypervisorCheckToolstack(unittest.TestCase):
423

    
424
  def setUp(self):
425
    self.tmpdir = tempfile.mkdtemp()
426
    self.cfg_name = "xen_config"
427
    self.cfg_path = utils.PathJoin(self.tmpdir, self.cfg_name)
428
    self.hv = hv_xen.XenHypervisor()
429

    
430
  def tearDown(self):
431
    shutil.rmtree(self.tmpdir)
432

    
433
  def testBinaryNotFound(self):
434
    RESULT_FAILED = utils.RunResult(1, None, "", "", "", None, None)
435
    mock_run_cmd = mock.Mock(return_value=RESULT_FAILED)
436
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
437
                              _run_cmd_fn=mock_run_cmd)
438
    result = hv._CheckToolstackBinary("xl")
439
    self.assertFalse(result)
440

    
441
  def testCheckToolstackXlConfigured(self):
442
    RESULT_OK = utils.RunResult(0, None, "", "", "", None, None)
443
    mock_run_cmd = mock.Mock(return_value=RESULT_OK)
444
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
445
                              _run_cmd_fn=mock_run_cmd)
446
    result = hv._CheckToolstackXlConfigured()
447
    self.assertTrue(result)
448

    
449
  def testCheckToolstackXlNotConfigured(self):
450
    RESULT_FAILED = utils.RunResult(
451
        1, None, "",
452
        "ERROR:  A different toolstack (xm) has been selected!",
453
        "", None, None)
454
    mock_run_cmd = mock.Mock(return_value=RESULT_FAILED)
455
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
456
                              _run_cmd_fn=mock_run_cmd)
457
    result = hv._CheckToolstackXlConfigured()
458
    self.assertFalse(result)
459

    
460
  def testCheckToolstackXlFails(self):
461
    RESULT_FAILED = utils.RunResult(
462
        1, None, "",
463
        "ERROR: The pink bunny hid the binary.",
464
        "", None, None)
465
    mock_run_cmd = mock.Mock(return_value=RESULT_FAILED)
466
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
467
                              _run_cmd_fn=mock_run_cmd)
468
    self.assertRaises(errors.HypervisorError, hv._CheckToolstackXlConfigured)
469

    
470

    
471
class TestXenHypervisorWriteConfigFile(unittest.TestCase):
472
  def setUp(self):
473
    self.tmpdir = tempfile.mkdtemp()
474

    
475
  def tearDown(self):
476
    shutil.rmtree(self.tmpdir)
477

    
478
  def testWriteError(self):
479
    cfgdir = utils.PathJoin(self.tmpdir, "foobar")
480

    
481
    hv = hv_xen.XenHypervisor(_cfgdir=cfgdir,
482
                              _run_cmd_fn=NotImplemented,
483
                              _cmd=NotImplemented)
484

    
485
    self.assertFalse(os.path.exists(cfgdir))
486

    
487
    try:
488
      hv._WriteConfigFile("name", "data")
489
    except errors.HypervisorError, err:
490
      self.assertTrue(str(err).startswith("Cannot write Xen instance"))
491
    else:
492
      self.fail("Exception was not raised")
493

    
494

    
495
class TestXenHypervisorVerify(unittest.TestCase):
496

    
497
  def setUp(self):
498
    output = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
499
    self._result_ok = utils.RunResult(0, None, output, "", "", None, None)
500

    
501
  def testVerify(self):
502
    hvparams = {constants.HV_XEN_CMD : constants.XEN_CMD_XL}
503
    mock_run_cmd = mock.Mock(return_value=self._result_ok)
504
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
505
                              _run_cmd_fn=mock_run_cmd)
506
    hv._CheckToolstack = mock.Mock(return_value=True)
507
    result = hv.Verify(hvparams)
508
    self.assertTrue(result is None)
509

    
510
  def testVerifyToolstackNotOk(self):
511
    hvparams = {constants.HV_XEN_CMD : constants.XEN_CMD_XL}
512
    mock_run_cmd = mock.Mock(return_value=self._result_ok)
513
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
514
                              _run_cmd_fn=mock_run_cmd)
515
    hv._CheckToolstack = mock.Mock()
516
    hv._CheckToolstack.side_effect = errors.HypervisorError("foo")
517
    result = hv.Verify(hvparams)
518
    self.assertTrue(result is not None)
519

    
520
  def testVerifyFailing(self):
521
    result_failed = utils.RunResult(1, None, "", "", "", None, None)
522
    mock_run_cmd = mock.Mock(return_value=result_failed)
523
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
524
                              _run_cmd_fn=mock_run_cmd)
525
    hv._CheckToolstack = mock.Mock(return_value=True)
526
    result = hv.Verify()
527
    self.assertTrue(result is not None)
528

    
529

    
530
class _TestXenHypervisor(object):
531
  TARGET = NotImplemented
532
  CMD = NotImplemented
533
  HVNAME = NotImplemented
534
  VALID_HVPARAMS = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
535

    
536
  def setUp(self):
537
    super(_TestXenHypervisor, self).setUp()
538

    
539
    self.tmpdir = tempfile.mkdtemp()
540

    
541
    self.vncpw = "".join(random.sample(string.ascii_letters, 10))
542

    
543
    self.vncpw_path = utils.PathJoin(self.tmpdir, "vncpw")
544
    utils.WriteFile(self.vncpw_path, data=self.vncpw)
545

    
546
  def tearDown(self):
547
    super(_TestXenHypervisor, self).tearDown()
548

    
549
    shutil.rmtree(self.tmpdir)
550

    
551
  def _GetHv(self, run_cmd=NotImplemented):
552
    return self.TARGET(_cfgdir=self.tmpdir, _run_cmd_fn=run_cmd, _cmd=self.CMD)
553

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

    
557
    return utils.RunResult(constants.EXIT_SUCCESS, None, stdout, "", None,
558
                           NotImplemented, NotImplemented)
559

    
560
  def _FailingCommand(self, cmd):
561
    self.assertEqual(cmd[0], self.CMD)
562

    
563
    return utils.RunResult(constants.EXIT_FAILURE, None,
564
                           "", "This command failed", None,
565
                           NotImplemented, NotImplemented)
566

    
567
  def _FakeTcpPing(self, expected, result, target, port, **kwargs):
568
    self.assertEqual((target, port), expected)
569
    return result
570

    
571
  def testReadingNonExistentConfigFile(self):
572
    hv = self._GetHv()
573

    
574
    try:
575
      hv._ReadConfigFile("inst15780.example.com")
576
    except errors.HypervisorError, err:
577
      self.assertTrue(str(err).startswith("Failed to load Xen config file:"))
578
    else:
579
      self.fail("Exception was not raised")
580

    
581
  def testRemovingAutoConfigFile(self):
582
    name = "inst8206.example.com"
583
    cfgfile = utils.PathJoin(self.tmpdir, name)
584
    autodir = utils.PathJoin(self.tmpdir, "auto")
585
    autocfgfile = utils.PathJoin(autodir, name)
586

    
587
    os.mkdir(autodir)
588

    
589
    utils.WriteFile(autocfgfile, data="")
590

    
591
    hv = self._GetHv()
592

    
593
    self.assertTrue(os.path.isfile(autocfgfile))
594
    hv._WriteConfigFile(name, "content")
595
    self.assertFalse(os.path.exists(autocfgfile))
596
    self.assertEqual(utils.ReadFile(cfgfile), "content")
597

    
598
  def _XenList(self, cmd):
599
    self.assertEqual(cmd, [self.CMD, "list"])
600

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

    
604
    return self._SuccessCommand(output, cmd)
605

    
606
  def testGetInstanceInfo(self):
607
    hv = self._GetHv(run_cmd=self._XenList)
608

    
609
    (name, instid, memory, vcpus, state, runtime) = \
610
      hv.GetInstanceInfo("server01.example.com")
611

    
612
    self.assertEqual(name, "server01.example.com")
613
    self.assertEqual(instid, 1)
614
    self.assertEqual(memory, 1024)
615
    self.assertEqual(vcpus, 1)
616
    self.assertEqual(state, hv_base.HvInstanceState.RUNNING)
617
    self.assertAlmostEqual(runtime, 167643.2)
618

    
619
  def testGetInstanceInfoDom0(self):
620
    hv = self._GetHv(run_cmd=self._XenList)
621

    
622
    # TODO: Not sure if this is actually used anywhere (can't find it), but the
623
    # code supports querying for Dom0
624
    (name, instid, memory, vcpus, state, runtime) = \
625
      hv.GetInstanceInfo(hv_xen._DOM0_NAME)
626

    
627
    self.assertEqual(name, "Domain-0")
628
    self.assertEqual(instid, 0)
629
    self.assertEqual(memory, 1023)
630
    self.assertEqual(vcpus, 1)
631
    self.assertEqual(state, hv_base.HvInstanceState.RUNNING)
632
    self.assertAlmostEqual(runtime, 154706.1)
633

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

    
637
    result = hv.GetInstanceInfo("unknown.example.com")
638
    self.assertTrue(result is None)
639

    
640
  def testGetAllInstancesInfo(self):
641
    hv = self._GetHv(run_cmd=self._XenList)
642

    
643
    result = hv.GetAllInstancesInfo()
644

    
645
    self.assertEqual(map(compat.fst, result), [
646
      "server01.example.com",
647
      "web3106215069.example.com",
648
      "testinstance.example.com",
649
      ])
650

    
651
  def testListInstances(self):
652
    hv = self._GetHv(run_cmd=self._XenList)
653

    
654
    self.assertEqual(hv.ListInstances(), [
655
      "server01.example.com",
656
      "web3106215069.example.com",
657
      "testinstance.example.com",
658
      ])
659

    
660
  def _StartInstanceCommand(self, inst, paused, failcreate, cmd):
661
    if cmd == [self.CMD, "info"]:
662
      output = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
663
    elif cmd == [self.CMD, "list"]:
664
      output = testutils.ReadTestData("xen-xm-list-4.0.1-dom0-only.txt")
665
    elif cmd[:2] == [self.CMD, "create"]:
666
      args = cmd[2:]
667
      cfgfile = utils.PathJoin(self.tmpdir, inst.name)
668

    
669
      if paused:
670
        self.assertEqual(args, ["-p", cfgfile])
671
      else:
672
        self.assertEqual(args, [cfgfile])
673

    
674
      if failcreate:
675
        return self._FailingCommand(cmd)
676

    
677
      output = ""
678
    else:
679
      self.fail("Unhandled command: %s" % (cmd, ))
680

    
681
    return self._SuccessCommand(output, cmd)
682

    
683
  def _MakeInstance(self):
684
    # Copy default parameters
685
    bep = objects.FillDict(constants.BEC_DEFAULTS, {})
686
    hvp = objects.FillDict(constants.HVC_DEFAULTS[self.HVNAME], {})
687

    
688
    # Override default VNC password file path
689
    if constants.HV_VNC_PASSWORD_FILE in hvp:
690
      hvp[constants.HV_VNC_PASSWORD_FILE] = self.vncpw_path
691

    
692
    disks = [
693
      (objects.Disk(dev_type=constants.DT_PLAIN, mode=constants.DISK_RDWR),
694
       utils.PathJoin(self.tmpdir, "disk0"),
695
       NotImplemented),
696
      (objects.Disk(dev_type=constants.DT_PLAIN, mode=constants.DISK_RDONLY),
697
       utils.PathJoin(self.tmpdir, "disk1"),
698
       NotImplemented),
699
      ]
700

    
701
    inst = objects.Instance(name="server01.example.com",
702
                            hvparams=hvp, beparams=bep,
703
                            osparams={}, nics=[], os="deb1",
704
                            disks=map(compat.fst, disks))
705
    inst.UpgradeConfig()
706

    
707
    return (inst, disks)
708

    
709
  def testStartInstance(self):
710
    (inst, disks) = self._MakeInstance()
711
    pathutils.LOG_XEN_DIR = self.tmpdir
712

    
713
    for failcreate in [False, True]:
714
      for paused in [False, True]:
715
        run_cmd = compat.partial(self._StartInstanceCommand,
716
                                 inst, paused, failcreate)
717

    
718
        hv = self._GetHv(run_cmd=run_cmd)
719

    
720
        # Ensure instance is not listed
721
        self.assertTrue(inst.name not in hv.ListInstances())
722

    
723
        # Remove configuration
724
        cfgfile = utils.PathJoin(self.tmpdir, inst.name)
725
        utils.RemoveFile(cfgfile)
726

    
727
        if failcreate:
728
          self.assertRaises(errors.HypervisorError, hv.StartInstance,
729
                            inst, disks, paused)
730
          # Check whether a stale config file is left behind
731
          self.assertFalse(os.path.exists(cfgfile))
732
        else:
733
          hv.StartInstance(inst, disks, paused)
734
          # Check if configuration was updated
735
          lines = utils.ReadFile(cfgfile).splitlines()
736

    
737
        if constants.HV_VNC_PASSWORD_FILE in inst.hvparams:
738
          self.assertTrue(("vncpasswd = '%s'" % self.vncpw) in lines)
739
        else:
740
          extra = inst.hvparams[constants.HV_KERNEL_ARGS]
741
          self.assertTrue(("extra = '%s'" % extra) in lines)
742

    
743
  def _StopInstanceCommand(self, instance_name, force, fail, cmd):
744
    if (cmd == [self.CMD, "list"]):
745
      output = "Name  ID  Mem  VCPUs  State  Time(s)\n" \
746
        "Domain-0  0  1023  1  r-----  142691.0\n" \
747
        "%s  417  128  1  r-----  3.2\n" % instance_name
748
    elif cmd[:2] == [self.CMD, "destroy"]:
749
      self.assertEqual(cmd[2:], [instance_name])
750
      output = ""
751
    elif not force and cmd[:3] == [self.CMD, "shutdown", "-w"]:
752
      self.assertEqual(cmd[3:], [instance_name])
753
      output = ""
754
    else:
755
      self.fail("Unhandled command: %s" % (cmd, ))
756

    
757
    if fail:
758
      # Simulate a failing command
759
      return self._FailingCommand(cmd)
760
    else:
761
      return self._SuccessCommand(output, cmd)
762

    
763
  def testStopInstance(self):
764
    name = "inst4284.example.com"
765
    cfgfile = utils.PathJoin(self.tmpdir, name)
766
    cfgdata = "config file content\n"
767

    
768
    for force in [False, True]:
769
      for fail in [False, True]:
770
        utils.WriteFile(cfgfile, data=cfgdata)
771

    
772
        run_cmd = compat.partial(self._StopInstanceCommand, name, force, fail)
773

    
774
        hv = self._GetHv(run_cmd=run_cmd)
775

    
776
        self.assertTrue(os.path.isfile(cfgfile))
777

    
778
        if fail:
779
          try:
780
            hv._StopInstance(name, force, None)
781
          except errors.HypervisorError, err:
782
            self.assertTrue(str(err).startswith("listing instances failed"),
783
                            msg=str(err))
784
          else:
785
            self.fail("Exception was not raised")
786
          self.assertEqual(utils.ReadFile(cfgfile), cfgdata,
787
                           msg=("Configuration was removed when stopping"
788
                                " instance failed"))
789
        else:
790
          hv._StopInstance(name, force, None)
791
          self.assertFalse(os.path.exists(cfgfile))
792

    
793
  def _MigrateNonRunningInstCmd(self, cmd):
794
    if cmd == [self.CMD, "list"]:
795
      output = testutils.ReadTestData("xen-xm-list-4.0.1-dom0-only.txt")
796
    else:
797
      self.fail("Unhandled command: %s" % (cmd, ))
798

    
799
    return self._SuccessCommand(output, cmd)
800

    
801
  def testMigrateInstanceNotRunning(self):
802
    name = "nonexistinginstance.example.com"
803
    target = constants.IP4_ADDRESS_LOCALHOST
804
    port = 14618
805

    
806
    hv = self._GetHv(run_cmd=self._MigrateNonRunningInstCmd)
807

    
808
    for live in [False, True]:
809
      try:
810
        hv._MigrateInstance(NotImplemented, name, target, port, live,
811
                            self.VALID_HVPARAMS, _ping_fn=NotImplemented)
812
      except errors.HypervisorError, err:
813
        self.assertEqual(str(err), "Instance not running, cannot migrate")
814
      else:
815
        self.fail("Exception was not raised")
816

    
817
  def _MigrateInstTargetUnreachCmd(self, cmd):
818
    if cmd == [self.CMD, "list"]:
819
      output = testutils.ReadTestData("xen-xm-list-4.0.1-four-instances.txt")
820
    else:
821
      self.fail("Unhandled command: %s" % (cmd, ))
822

    
823
    return self._SuccessCommand(output, cmd)
824

    
825
  def testMigrateTargetUnreachable(self):
826
    name = "server01.example.com"
827
    target = constants.IP4_ADDRESS_LOCALHOST
828
    port = 28349
829

    
830
    hv = self._GetHv(run_cmd=self._MigrateInstTargetUnreachCmd)
831
    hvparams = {constants.HV_XEN_CMD: self.CMD}
832

    
833
    for live in [False, True]:
834
      if self.CMD == constants.XEN_CMD_XL:
835
        # TODO: Detect unreachable targets
836
        pass
837
      else:
838
        try:
839
          hv._MigrateInstance(NotImplemented, name, target, port, live,
840
                              hvparams,
841
                              _ping_fn=compat.partial(self._FakeTcpPing,
842
                                                      (target, port), False))
843
        except errors.HypervisorError, err:
844
          wanted = "Remote host %s not" % target
845
          self.assertTrue(str(err).startswith(wanted))
846
        else:
847
          self.fail("Exception was not raised")
848

    
849
  def _MigrateInstanceCmd(self, cluster_name, instance_name, target, port,
850
                          live, fail, cmd):
851
    if cmd == [self.CMD, "list"]:
852
      output = testutils.ReadTestData("xen-xm-list-4.0.1-four-instances.txt")
853
    elif cmd[:2] == [self.CMD, "migrate"]:
854
      if self.CMD == constants.XEN_CMD_XM:
855
        args = ["-p", str(port)]
856

    
857
        if live:
858
          args.append("-l")
859

    
860
      elif self.CMD == constants.XEN_CMD_XL:
861
        args = [
862
          "-s", constants.XL_SSH_CMD % cluster_name,
863
          "-C", utils.PathJoin(self.tmpdir, instance_name),
864
          ]
865

    
866
      else:
867
        self.fail("Unknown Xen command '%s'" % self.CMD)
868

    
869
      args.extend([instance_name, target])
870
      self.assertEqual(cmd[2:], args)
871

    
872
      if fail:
873
        return self._FailingCommand(cmd)
874

    
875
      output = ""
876
    else:
877
      self.fail("Unhandled command: %s" % (cmd, ))
878

    
879
    return self._SuccessCommand(output, cmd)
880

    
881
  def testMigrateInstance(self):
882
    clustername = "cluster.example.com"
883
    instname = "server01.example.com"
884
    target = constants.IP4_ADDRESS_LOCALHOST
885
    port = 22364
886

    
887
    hvparams = {constants.HV_XEN_CMD: self.CMD}
888

    
889
    for live in [False, True]:
890
      for fail in [False, True]:
891
        ping_fn = \
892
          testutils.CallCounter(compat.partial(self._FakeTcpPing,
893
                                               (target, port), True))
894

    
895
        run_cmd = \
896
          compat.partial(self._MigrateInstanceCmd,
897
                         clustername, instname, target, port, live,
898
                         fail)
899

    
900
        hv = self._GetHv(run_cmd=run_cmd)
901

    
902
        if fail:
903
          try:
904
            hv._MigrateInstance(clustername, instname, target, port, live,
905
                                hvparams, _ping_fn=ping_fn)
906
          except errors.HypervisorError, err:
907
            self.assertTrue(str(err).startswith("Failed to migrate instance"))
908
          else:
909
            self.fail("Exception was not raised")
910
        else:
911
          hv._MigrateInstance(clustername, instname, target, port, live,
912
                              hvparams, _ping_fn=ping_fn)
913

    
914
        if self.CMD == constants.XEN_CMD_XM:
915
          expected_pings = 1
916
        else:
917
          expected_pings = 0
918

    
919
        self.assertEqual(ping_fn.Count(), expected_pings)
920

    
921
  def _GetNodeInfoCmd(self, fail, cmd):
922
    if cmd == [self.CMD, "info"]:
923
      if fail:
924
        return self._FailingCommand(cmd)
925
      else:
926
        output = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
927
    elif cmd == [self.CMD, "list"]:
928
      if fail:
929
        self.fail("'xm list' shouldn't be called when 'xm info' failed")
930
      else:
931
        output = testutils.ReadTestData("xen-xm-list-4.0.1-four-instances.txt")
932
    else:
933
      self.fail("Unhandled command: %s" % (cmd, ))
934

    
935
    return self._SuccessCommand(output, cmd)
936

    
937
  def testGetNodeInfo(self):
938
    run_cmd = compat.partial(self._GetNodeInfoCmd, False)
939
    hv = self._GetHv(run_cmd=run_cmd)
940
    result = hv.GetNodeInfo()
941

    
942
    self.assertEqual(result["hv_version"], (4, 0))
943
    self.assertEqual(result["memory_free"], 8004)
944

    
945
  def testGetNodeInfoFailing(self):
946
    run_cmd = compat.partial(self._GetNodeInfoCmd, True)
947
    hv = self._GetHv(run_cmd=run_cmd)
948
    self.assertTrue(hv.GetNodeInfo() is None)
949

    
950

    
951
def _MakeTestClass(cls, cmd):
952
  """Makes a class for testing.
953

954
  The returned class has structure as shown in the following pseudo code:
955

956
    class Test{cls.__name__}{cmd}(_TestXenHypervisor, unittest.TestCase):
957
      TARGET = {cls}
958
      CMD = {cmd}
959
      HVNAME = {Hypervisor name retrieved using class}
960

961
  @type cls: class
962
  @param cls: Hypervisor class to be tested
963
  @type cmd: string
964
  @param cmd: Hypervisor command
965
  @rtype: tuple
966
  @return: Class name and class object (not instance)
967

968
  """
969
  name = "Test%sCmd%s" % (cls.__name__, cmd.title())
970
  bases = (_TestXenHypervisor, unittest.TestCase)
971
  hvname = HVCLASS_TO_HVNAME[cls]
972

    
973
  return (name, type(name, bases, dict(TARGET=cls, CMD=cmd, HVNAME=hvname)))
974

    
975

    
976
# Create test classes programmatically instead of manually to reduce the risk
977
# of forgetting some combinations
978
for cls in [hv_xen.XenPvmHypervisor, hv_xen.XenHvmHypervisor]:
979
  for cmd in constants.KNOWN_XEN_COMMANDS:
980
    (name, testcls) = _MakeTestClass(cls, cmd)
981

    
982
    assert name not in locals()
983

    
984
    locals()[name] = testcls
985

    
986

    
987
if __name__ == "__main__":
988
  testutils.GanetiTestProgram()