Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (31.6 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
      cons = cls.GetInstanceConsole(instance, node, hvparams, {})
58
      self.assertTrue(cons.Validate())
59
      self.assertEqual(cons.kind, constants.CONS_SSH)
60
      self.assertEqual(cons.host, node.name)
61
      self.assertEqual(cons.command[-1], instance.name)
62

    
63

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

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

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

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

    
82

    
83
class TestGetCommand(testutils.GanetiTestCase):
84
  def testCommandExplicit(self):
85
    """Test the case when the command is given as class parameter explicitly.
86

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

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

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

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

    
105
  def testCommandHvparamsInvalid(self):
106
    test_hvparams = {}
107
    hv = hv_xen.XenHypervisor()
108
    self.assertRaises(errors.HypervisorError, hv._GetCommand, test_hvparams)
109

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

    
115

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

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

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

    
128
    # Name
129
    self.assertEqual(result[0][0], hv_xen._DOM0_NAME)
130

    
131
    # ID
132
    self.assertEqual(result[0][1], 0)
133

    
134
    # Memory
135
    self.assertEqual(result[0][2], 1023)
136

    
137
    # VCPUs
138
    self.assertEqual(result[0][3], 1)
139

    
140
    # State
141
    self.assertEqual(result[0][4], hv_base.HvInstanceState.RUNNING)
142

    
143
    # Time
144
    self.assertAlmostEqual(result[0][5], 121152.6)
145

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

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

    
160

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

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

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

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

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

    
186
    fn = testutils.CallCounter(compat.partial(self._Success, data))
187

    
188
    result = hv_xen._GetRunningInstanceList(fn, True, _timeout=0.1)
189

    
190
    self.assertEqual(len(result), 4)
191

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

    
199
    self.assertEqual(fn.Count(), 1)
200

    
201

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

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

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

    
226

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

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

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

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

    
263

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

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

    
271
  def testManyDisks(self):
272
    for offset in [0, 1, 10]:
273
      disks = [(objects.Disk(dev_type=constants.DT_PLAIN),
274
               "/tmp/disk/%s" % idx,
275
               NotImplemented)
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.DT_PLAIN, mode=constants.DISK_RDWR),
295
       "/tmp/diskFirst",
296
       NotImplemented),
297
      (objects.Disk(dev_type=constants.DT_PLAIN, mode=constants.DISK_RDONLY),
298
       "/tmp/diskLast",
299
       NotImplemented),
300
      ]
301

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

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

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

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

    
344
    self.assertRaises(KeyError, hv_xen._GetConfigFileDiskData, disks, "sd")
345

    
346

    
347
class TestXenHypervisorRunXen(unittest.TestCase):
348

    
349
  XEN_SUB_CMD = "help"
350

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

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

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

    
375

    
376
class TestXenHypervisorGetInstanceList(unittest.TestCase):
377

    
378
  RESULT_OK = utils.RunResult(0, None, "", "", "", None, None)
379
  XEN_LIST = "list"
380

    
381
  def testNoHvparams(self):
382
    expected_xen_cmd = "xm"
383
    mock_run_cmd = mock.Mock(return_value=self.RESULT_OK)
384
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
385
                              _run_cmd_fn=mock_run_cmd)
386
    self.assertRaises(errors.HypervisorError, hv._GetInstanceList, True, None)
387

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

    
397

    
398
class TestXenHypervisorListInstances(unittest.TestCase):
399

    
400
  RESULT_OK = utils.RunResult(0, None, "", "", "", None, None)
401
  XEN_LIST = "list"
402

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

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

    
419

    
420
class TestXenHypervisorCheckToolstack(unittest.TestCase):
421

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

    
428
  def tearDown(self):
429
    shutil.rmtree(self.tmpdir)
430

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

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

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

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

    
468

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

    
473
  def tearDown(self):
474
    shutil.rmtree(self.tmpdir)
475

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

    
479
    hv = hv_xen.XenHypervisor(_cfgdir=cfgdir,
480
                              _run_cmd_fn=NotImplemented,
481
                              _cmd=NotImplemented)
482

    
483
    self.assertFalse(os.path.exists(cfgdir))
484

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

    
492

    
493
class TestXenHypervisorVerify(unittest.TestCase):
494

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

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

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

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

    
527

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

    
534
  def setUp(self):
535
    super(_TestXenHypervisor, self).setUp()
536

    
537
    self.tmpdir = tempfile.mkdtemp()
538

    
539
    self.vncpw = "".join(random.sample(string.ascii_letters, 10))
540

    
541
    self.vncpw_path = utils.PathJoin(self.tmpdir, "vncpw")
542
    utils.WriteFile(self.vncpw_path, data=self.vncpw)
543

    
544
  def tearDown(self):
545
    super(_TestXenHypervisor, self).tearDown()
546

    
547
    shutil.rmtree(self.tmpdir)
548

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

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

    
555
    return utils.RunResult(constants.EXIT_SUCCESS, None, stdout, "", None,
556
                           NotImplemented, NotImplemented)
557

    
558
  def _FailingCommand(self, cmd):
559
    self.assertEqual(cmd[0], self.CMD)
560

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

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

    
569
  def testReadingNonExistentConfigFile(self):
570
    hv = self._GetHv()
571

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

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

    
585
    os.mkdir(autodir)
586

    
587
    utils.WriteFile(autocfgfile, data="")
588

    
589
    hv = self._GetHv()
590

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

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

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

    
602
    return self._SuccessCommand(output, cmd)
603

    
604
  def testGetInstanceInfo(self):
605
    hv = self._GetHv(run_cmd=self._XenList)
606

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

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

    
617
  def testGetInstanceInfoDom0(self):
618
    hv = self._GetHv(run_cmd=self._XenList)
619

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

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

    
632
  def testGetInstanceInfoUnknown(self):
633
    hv = self._GetHv(run_cmd=self._XenList)
634

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

    
638
  def testGetAllInstancesInfo(self):
639
    hv = self._GetHv(run_cmd=self._XenList)
640

    
641
    result = hv.GetAllInstancesInfo()
642

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

    
649
  def testListInstances(self):
650
    hv = self._GetHv(run_cmd=self._XenList)
651

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

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

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

    
672
      if failcreate:
673
        return self._FailingCommand(cmd)
674

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

    
679
    return self._SuccessCommand(output, cmd)
680

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

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

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

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

    
705
    return (inst, disks)
706

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

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

    
716
        hv = self._GetHv(run_cmd=run_cmd)
717

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

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

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

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

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

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

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

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

    
770
        run_cmd = compat.partial(self._StopInstanceCommand, name, force, fail)
771

    
772
        hv = self._GetHv(run_cmd=run_cmd)
773

    
774
        self.assertTrue(os.path.isfile(cfgfile))
775

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

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

    
797
    return self._SuccessCommand(output, cmd)
798

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

    
804
    hv = self._GetHv(run_cmd=self._MigrateNonRunningInstCmd)
805

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

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

    
821
    return self._SuccessCommand(output, cmd)
822

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

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

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

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

    
855
        if live:
856
          args.append("-l")
857

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

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

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

    
870
      if fail:
871
        return self._FailingCommand(cmd)
872

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

    
877
    return self._SuccessCommand(output, cmd)
878

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

    
885
    hvparams = {constants.HV_XEN_CMD: self.CMD}
886

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

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

    
898
        hv = self._GetHv(run_cmd=run_cmd)
899

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

    
912
        if self.CMD == constants.XEN_CMD_XM:
913
          expected_pings = 1
914
        else:
915
          expected_pings = 0
916

    
917
        self.assertEqual(ping_fn.Count(), expected_pings)
918

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

    
933
    return self._SuccessCommand(output, cmd)
934

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

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

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

    
948

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

952
  The returned class has structure as shown in the following pseudo code:
953

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

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

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

    
971
  return (name, type(name, bases, dict(TARGET=cls, CMD=cmd, HVNAME=hvname)))
972

    
973

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

    
980
    assert name not in locals()
981

    
982
    locals()[name] = testcls
983

    
984

    
985
if __name__ == "__main__":
986
  testutils.GanetiTestProgram()