Statistics
| Branch: | Tag: | Revision:

root / test / py / ganeti.hypervisor.hv_xen_unittest.py @ 398fd4f6

History | View | Annotate | Download (30.8 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 testCommandExplicit(self):
82
    """Test the case when the command is given as class parameter explicitly.
83

84
    """
85
    expected_cmd = "xl"
86
    hv = hv_xen.XenHypervisor(_cmd=constants.XEN_CMD_XL)
87
    self.assertEqual(hv._GetCommand(None), expected_cmd)
88

    
89
  def testCommandInvalid(self):
90
    """Test the case an invalid command is given as class parameter explicitly.
91

92
    """
93
    hv = hv_xen.XenHypervisor(_cmd="invalidcommand")
94
    self.assertRaises(errors.ProgrammerError, hv._GetCommand, None)
95

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

    
102
  def testCommandHvparamsInvalid(self):
103
    test_hvparams = {}
104
    hv = hv_xen.XenHypervisor()
105
    self.assertRaises(errors.HypervisorError, hv._GetCommand, test_hvparams)
106

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

    
112

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

    
117
    # Exclude node
118
    self.assertEqual(hv_xen._ParseInstanceList(data.splitlines(), False), [])
119

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

    
125
    # Name
126
    self.assertEqual(result[0][0], hv_xen._DOM0_NAME)
127

    
128
    # ID
129
    self.assertEqual(result[0][1], 0)
130

    
131
    # Memory
132
    self.assertEqual(result[0][2], 1023)
133

    
134
    # VCPUs
135
    self.assertEqual(result[0][3], 1)
136

    
137
    # State
138
    self.assertEqual(result[0][4], "r-----")
139

    
140
    # Time
141
    self.assertAlmostEqual(result[0][5], 121152.6)
142

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

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

    
157

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

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

    
173
    self.assertTrue(fn.Count() < 10,
174
                    msg="'xm list' was called too many times")
175

    
176
  def _Success(self, stdout):
177
    return utils.RunResult(constants.EXIT_SUCCESS, None, stdout, "", None,
178
                           NotImplemented, NotImplemented)
179

    
180
  def testSuccess(self):
181
    data = testutils.ReadTestData("xen-xm-list-4.0.1-four-instances.txt")
182

    
183
    fn = testutils.CallCounter(compat.partial(self._Success, data))
184

    
185
    result = hv_xen._GetInstanceList(fn, True, _timeout=0.1)
186

    
187
    self.assertEqual(len(result), 4)
188

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

    
196
    self.assertEqual(fn.Count(), 1)
197

    
198

    
199
class TestParseNodeInfo(testutils.GanetiTestCase):
200
  def testEmpty(self):
201
    self.assertEqual(hv_xen._ParseNodeInfo(""), {})
202

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

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

    
223

    
224
class TestMergeInstanceInfo(testutils.GanetiTestCase):
225
  def testEmpty(self):
226
    self.assertEqual(hv_xen._MergeInstanceInfo({}, []), {})
227

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

    
236
  def testMissingNodeInfo(self):
237
    instance_list = self._FakeXmList(True)
238
    result = hv_xen._MergeInstanceInfo({}, instance_list)
239
    self.assertEqual(result, {
240
      "memory_dom0": 4096,
241
      "dom0_cpus": 7,
242
      })
243

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

    
260

    
261
class TestGetConfigFileDiskData(unittest.TestCase):
262
  def testLetterCount(self):
263
    self.assertEqual(len(hv_xen._DISK_LETTERS), 26)
264

    
265
  def testNoDisks(self):
266
    self.assertEqual(hv_xen._GetConfigFileDiskData([], "hd"), [])
267

    
268
  def testManyDisks(self):
269
    for offset in [0, 1, 10]:
270
      disks = [(objects.Disk(dev_type=constants.LD_LV), "/tmp/disk/%s" % idx)
271
               for idx in range(len(hv_xen._DISK_LETTERS) + offset)]
272

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

    
287
  def testTwoLvDisksWithMode(self):
288
    disks = [
289
      (objects.Disk(dev_type=constants.LD_LV, mode=constants.DISK_RDWR),
290
       "/tmp/diskFirst"),
291
      (objects.Disk(dev_type=constants.LD_LV, mode=constants.DISK_RDONLY),
292
       "/tmp/diskLast"),
293
      ]
294

    
295
    result = hv_xen._GetConfigFileDiskData(disks, "hd")
296
    self.assertEqual(result, [
297
      "'phy:/tmp/diskFirst,hda,w'",
298
      "'phy:/tmp/diskLast,hdb,r'",
299
      ])
300

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

    
317
    result = hv_xen._GetConfigFileDiskData(disks, "sd")
318
    self.assertEqual(result, [
319
      "'file:/tmp/diskFirst,sda,w'",
320
      "'tap:aio:/tmp/diskTwo,sdb,r'",
321
      "'file:/tmp/diskThree,sdc,w'",
322
      "'tap:aio:/tmp/diskLast,sdd,w'",
323
      ])
324

    
325
  def testInvalidFileDisk(self):
326
    disks = [
327
      (objects.Disk(dev_type=constants.LD_FILE, mode=constants.DISK_RDWR,
328
                    physical_id=["#unknown#"]),
329
       "/tmp/diskinvalid"),
330
      ]
331

    
332
    self.assertRaises(KeyError, hv_xen._GetConfigFileDiskData, disks, "sd")
333

    
334

    
335
class TestXenHypervisorRunXen(unittest.TestCase):
336

    
337
  XEN_SUB_CMD = "help"
338

    
339
  def testCommandUnknown(self):
340
    cmd = "#unknown command#"
341
    self.assertFalse(cmd in constants.KNOWN_XEN_COMMANDS)
342
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
343
                              _run_cmd_fn=NotImplemented,
344
                              _cmd=cmd)
345
    self.assertRaises(errors.ProgrammerError, hv._RunXen, [], None)
346

    
347
  def testCommandNoHvparams(self):
348
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
349
                              _run_cmd_fn=NotImplemented)
350
    hvparams = None
351
    self.assertRaises(errors.HypervisorError, hv._RunXen, [self.XEN_SUB_CMD],
352
                      hvparams)
353

    
354
  def testCommandFromHvparams(self):
355
    expected_xen_cmd = "xl"
356
    hvparams = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
357
    mock_run_cmd = mock.Mock()
358
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
359
                              _run_cmd_fn=mock_run_cmd)
360
    hv._RunXen([self.XEN_SUB_CMD], hvparams=hvparams)
361
    mock_run_cmd.assert_called_with([expected_xen_cmd, self.XEN_SUB_CMD])
362

    
363

    
364
class TestXenHypervisorGetInstanceList(unittest.TestCase):
365

    
366
  RESULT_OK = utils.RunResult(0, None, "", "", "", None, None)
367
  XEN_LIST = "list"
368

    
369
  def testNoHvparams(self):
370
    expected_xen_cmd = "xm"
371
    mock_run_cmd = mock.Mock( return_value=self.RESULT_OK )
372
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
373
                              _run_cmd_fn=mock_run_cmd)
374
    self.assertRaises(errors.HypervisorError, hv._GetInstanceList, True, None)
375

    
376
  def testFromHvparams(self):
377
    expected_xen_cmd = "xl"
378
    hvparams = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
379
    mock_run_cmd = mock.Mock( return_value=self.RESULT_OK )
380
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
381
                              _run_cmd_fn=mock_run_cmd)
382
    hv._GetInstanceList(True, hvparams)
383
    mock_run_cmd.assert_called_with([expected_xen_cmd, self.XEN_LIST])
384

    
385

    
386
class TestXenHypervisorListInstances(unittest.TestCase):
387

    
388
  RESULT_OK = utils.RunResult(0, None, "", "", "", None, None)
389
  XEN_LIST = "list"
390

    
391
  def testNoHvparams(self):
392
    expected_xen_cmd = "xm"
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
    self.assertRaises(errors.HypervisorError, hv.ListInstances)
397

    
398
  def testHvparamsXl(self):
399
    expected_xen_cmd = "xl"
400
    hvparams = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
401
    mock_run_cmd = mock.Mock( return_value=self.RESULT_OK )
402
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
403
                              _run_cmd_fn=mock_run_cmd)
404
    hv.ListInstances(hvparams=hvparams)
405
    mock_run_cmd.assert_called_with([expected_xen_cmd, self.XEN_LIST])
406

    
407

    
408
class TestXenHypervisorCheckToolstack(unittest.TestCase):
409

    
410
  def setUp(self):
411
    self.tmpdir = tempfile.mkdtemp()
412
    self.cfg_name = "xen_config"
413
    self.cfg_path = utils.PathJoin(self.tmpdir, self.cfg_name)
414
    self.hv = hv_xen.XenHypervisor()
415

    
416
  def tearDown(self):
417
    shutil.rmtree(self.tmpdir)
418

    
419
  def testBinaryNotFound(self):
420
    RESULT_FAILED = utils.RunResult(1, None, "", "", "", None, None)
421
    mock_run_cmd = mock.Mock(return_value=RESULT_FAILED)
422
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
423
                              _run_cmd_fn=mock_run_cmd)
424
    result = hv._CheckToolstackBinary("xl")
425
    self.assertFalse(result)
426

    
427
  def testCheckToolstackXlConfigured(self):
428
    RESULT_OK = utils.RunResult(0, None, "", "", "", None, None)
429
    mock_run_cmd = mock.Mock(return_value=RESULT_OK)
430
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
431
                              _run_cmd_fn=mock_run_cmd)
432
    result = hv._CheckToolstackXlConfigured()
433
    self.assertTrue(result)
434

    
435
  def testCheckToolstackXlNotConfigured(self):
436
    RESULT_FAILED = utils.RunResult(
437
        1, None, "",
438
        "ERROR:  A different toolstack (xm) have been selected!",
439
        "", None, None)
440
    mock_run_cmd = mock.Mock(return_value=RESULT_FAILED)
441
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
442
                              _run_cmd_fn=mock_run_cmd)
443
    result = hv._CheckToolstackXlConfigured()
444
    self.assertFalse(result)
445

    
446
  def testCheckToolstackXlFails(self):
447
    RESULT_FAILED = utils.RunResult(
448
        1, None, "",
449
        "ERROR: The pink bunny hid the binary.",
450
        "", None, None)
451
    mock_run_cmd = mock.Mock(return_value=RESULT_FAILED)
452
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
453
                              _run_cmd_fn=mock_run_cmd)
454
    self.assertRaises(errors.HypervisorError, hv._CheckToolstackXlConfigured)
455

    
456

    
457
class TestXenHypervisorWriteConfigFile(unittest.TestCase):
458
  def setUp(self):
459
    self.tmpdir = tempfile.mkdtemp()
460

    
461
  def tearDown(self):
462
    shutil.rmtree(self.tmpdir)
463

    
464
  def testWriteError(self):
465
    cfgdir = utils.PathJoin(self.tmpdir, "foobar")
466

    
467
    hv = hv_xen.XenHypervisor(_cfgdir=cfgdir,
468
                              _run_cmd_fn=NotImplemented,
469
                              _cmd=NotImplemented)
470

    
471
    self.assertFalse(os.path.exists(cfgdir))
472

    
473
    try:
474
      hv._WriteConfigFile("name", "data")
475
    except errors.HypervisorError, err:
476
      self.assertTrue(str(err).startswith("Cannot write Xen instance"))
477
    else:
478
      self.fail("Exception was not raised")
479

    
480

    
481
class TestXenHypervisorVerify(unittest.TestCase):
482

    
483
  def setUp(self):
484
    output = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
485
    self._result_ok = utils.RunResult(0, None, output, "", "", None, None)
486

    
487
  def testVerify(self):
488
    hvparams = {constants.HV_XEN_CMD : constants.XEN_CMD_XL}
489
    mock_run_cmd = mock.Mock(return_value=self._result_ok)
490
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
491
                              _run_cmd_fn=mock_run_cmd)
492
    hv._CheckToolstack = mock.Mock(return_value=True)
493
    result = hv.Verify(hvparams)
494
    self.assertTrue(result is None)
495

    
496
  def testVerifyToolstackNotOk(self):
497
    hvparams = {constants.HV_XEN_CMD : constants.XEN_CMD_XL}
498
    mock_run_cmd = mock.Mock(return_value=self._result_ok)
499
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
500
                              _run_cmd_fn=mock_run_cmd)
501
    hv._CheckToolstack = mock.Mock()
502
    hv._CheckToolstack.side_effect = errors.HypervisorError("foo")
503
    result = hv.Verify(hvparams)
504
    self.assertTrue(result is not None)
505

    
506
  def testVerifyFailing(self):
507
    result_failed = utils.RunResult(1, None, "", "", "", None, None)
508
    mock_run_cmd = mock.Mock(return_value=result_failed)
509
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
510
                              _run_cmd_fn=mock_run_cmd)
511
    hv._CheckToolstack = mock.Mock(return_value=True)
512
    result = hv.Verify()
513
    self.assertTrue(result is not None)
514

    
515

    
516
class _TestXenHypervisor(object):
517
  TARGET = NotImplemented
518
  CMD = NotImplemented
519
  HVNAME = NotImplemented
520
  VALID_HVPARAMS = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
521

    
522
  def setUp(self):
523
    super(_TestXenHypervisor, self).setUp()
524

    
525
    self.tmpdir = tempfile.mkdtemp()
526

    
527
    self.vncpw = "".join(random.sample(string.ascii_letters, 10))
528

    
529
    self.vncpw_path = utils.PathJoin(self.tmpdir, "vncpw")
530
    utils.WriteFile(self.vncpw_path, data=self.vncpw)
531

    
532
  def tearDown(self):
533
    super(_TestXenHypervisor, self).tearDown()
534

    
535
    shutil.rmtree(self.tmpdir)
536

    
537
  def _GetHv(self, run_cmd=NotImplemented):
538
    return self.TARGET(_cfgdir=self.tmpdir, _run_cmd_fn=run_cmd, _cmd=self.CMD)
539

    
540
  def _SuccessCommand(self, stdout, cmd):
541
    self.assertEqual(cmd[0], self.CMD)
542

    
543
    return utils.RunResult(constants.EXIT_SUCCESS, None, stdout, "", None,
544
                           NotImplemented, NotImplemented)
545

    
546
  def _FailingCommand(self, cmd):
547
    self.assertEqual(cmd[0], self.CMD)
548

    
549
    return utils.RunResult(constants.EXIT_FAILURE, None,
550
                           "", "This command failed", None,
551
                           NotImplemented, NotImplemented)
552

    
553
  def _FakeTcpPing(self, expected, result, target, port, **kwargs):
554
    self.assertEqual((target, port), expected)
555
    return result
556

    
557
  def testReadingNonExistentConfigFile(self):
558
    hv = self._GetHv()
559

    
560
    try:
561
      hv._ReadConfigFile("inst15780.example.com")
562
    except errors.HypervisorError, err:
563
      self.assertTrue(str(err).startswith("Failed to load Xen config file:"))
564
    else:
565
      self.fail("Exception was not raised")
566

    
567
  def testRemovingAutoConfigFile(self):
568
    name = "inst8206.example.com"
569
    cfgfile = utils.PathJoin(self.tmpdir, name)
570
    autodir = utils.PathJoin(self.tmpdir, "auto")
571
    autocfgfile = utils.PathJoin(autodir, name)
572

    
573
    os.mkdir(autodir)
574

    
575
    utils.WriteFile(autocfgfile, data="")
576

    
577
    hv = self._GetHv()
578

    
579
    self.assertTrue(os.path.isfile(autocfgfile))
580
    hv._WriteConfigFile(name, "content")
581
    self.assertFalse(os.path.exists(autocfgfile))
582
    self.assertEqual(utils.ReadFile(cfgfile), "content")
583

    
584
  def _XenList(self, cmd):
585
    self.assertEqual(cmd, [self.CMD, "list"])
586

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

    
590
    return self._SuccessCommand(output, cmd)
591

    
592
  def testGetInstanceInfo(self):
593
    hv = self._GetHv(run_cmd=self._XenList)
594

    
595
    (name, instid, memory, vcpus, state, runtime) = \
596
      hv.GetInstanceInfo("server01.example.com")
597

    
598
    self.assertEqual(name, "server01.example.com")
599
    self.assertEqual(instid, 1)
600
    self.assertEqual(memory, 1024)
601
    self.assertEqual(vcpus, 1)
602
    self.assertEqual(state, "-b----")
603
    self.assertAlmostEqual(runtime, 167643.2)
604

    
605
  def testGetInstanceInfoDom0(self):
606
    hv = self._GetHv(run_cmd=self._XenList)
607

    
608
    # TODO: Not sure if this is actually used anywhere (can't find it), but the
609
    # code supports querying for Dom0
610
    (name, instid, memory, vcpus, state, runtime) = \
611
      hv.GetInstanceInfo(hv_xen._DOM0_NAME)
612

    
613
    self.assertEqual(name, "Domain-0")
614
    self.assertEqual(instid, 0)
615
    self.assertEqual(memory, 1023)
616
    self.assertEqual(vcpus, 1)
617
    self.assertEqual(state, "r-----")
618
    self.assertAlmostEqual(runtime, 154706.1)
619

    
620
  def testGetInstanceInfoUnknown(self):
621
    hv = self._GetHv(run_cmd=self._XenList)
622

    
623
    result = hv.GetInstanceInfo("unknown.example.com")
624
    self.assertTrue(result is None)
625

    
626
  def testGetAllInstancesInfo(self):
627
    hv = self._GetHv(run_cmd=self._XenList)
628

    
629
    result = hv.GetAllInstancesInfo()
630

    
631
    self.assertEqual(map(compat.fst, result), [
632
      "server01.example.com",
633
      "web3106215069.example.com",
634
      "testinstance.example.com",
635
      ])
636

    
637
  def testListInstances(self):
638
    hv = self._GetHv(run_cmd=self._XenList)
639

    
640
    self.assertEqual(hv.ListInstances(), [
641
      "server01.example.com",
642
      "web3106215069.example.com",
643
      "testinstance.example.com",
644
      ])
645

    
646
  def _StartInstanceCommand(self, inst, paused, failcreate, cmd):
647
    if cmd == [self.CMD, "info"]:
648
      output = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
649
    elif cmd == [self.CMD, "list"]:
650
      output = testutils.ReadTestData("xen-xm-list-4.0.1-dom0-only.txt")
651
    elif cmd[:2] == [self.CMD, "create"]:
652
      args = cmd[2:]
653
      cfgfile = utils.PathJoin(self.tmpdir, inst.name)
654

    
655
      if paused:
656
        self.assertEqual(args, ["-p", cfgfile])
657
      else:
658
        self.assertEqual(args, [cfgfile])
659

    
660
      if failcreate:
661
        return self._FailingCommand(cmd)
662

    
663
      output = ""
664
    else:
665
      self.fail("Unhandled command: %s" % (cmd, ))
666

    
667
    return self._SuccessCommand(output, cmd)
668

    
669
  def _MakeInstance(self):
670
    # Copy default parameters
671
    bep = objects.FillDict(constants.BEC_DEFAULTS, {})
672
    hvp = objects.FillDict(constants.HVC_DEFAULTS[self.HVNAME], {})
673

    
674
    # Override default VNC password file path
675
    if constants.HV_VNC_PASSWORD_FILE in hvp:
676
      hvp[constants.HV_VNC_PASSWORD_FILE] = self.vncpw_path
677

    
678
    disks = [
679
      (objects.Disk(dev_type=constants.LD_LV, mode=constants.DISK_RDWR),
680
       utils.PathJoin(self.tmpdir, "disk0")),
681
      (objects.Disk(dev_type=constants.LD_LV, mode=constants.DISK_RDONLY),
682
       utils.PathJoin(self.tmpdir, "disk1")),
683
      ]
684

    
685
    inst = objects.Instance(name="server01.example.com",
686
                            hvparams=hvp, beparams=bep,
687
                            osparams={}, nics=[], os="deb1",
688
                            disks=map(compat.fst, disks))
689
    inst.UpgradeConfig()
690

    
691
    return (inst, disks)
692

    
693
  def testStartInstance(self):
694
    (inst, disks) = self._MakeInstance()
695
    pathutils.LOG_XEN_DIR = self.tmpdir
696

    
697
    for failcreate in [False, True]:
698
      for paused in [False, True]:
699
        run_cmd = compat.partial(self._StartInstanceCommand,
700
                                 inst, paused, failcreate)
701

    
702
        hv = self._GetHv(run_cmd=run_cmd)
703

    
704
        # Ensure instance is not listed
705
        self.assertTrue(inst.name not in hv.ListInstances())
706

    
707
        # Remove configuration
708
        cfgfile = utils.PathJoin(self.tmpdir, inst.name)
709
        utils.RemoveFile(cfgfile)
710

    
711
        if failcreate:
712
          self.assertRaises(errors.HypervisorError, hv.StartInstance,
713
                            inst, disks, paused)
714
          # Check whether a stale config file is left behind
715
          self.assertFalse(os.path.exists(cfgfile))
716
        else:
717
          hv.StartInstance(inst, disks, paused)
718
          # Check if configuration was updated
719
          lines = utils.ReadFile(cfgfile).splitlines()
720

    
721
        if constants.HV_VNC_PASSWORD_FILE in inst.hvparams:
722
          self.assertTrue(("vncpasswd = '%s'" % self.vncpw) in lines)
723
        else:
724
          extra = inst.hvparams[constants.HV_KERNEL_ARGS]
725
          self.assertTrue(("extra = '%s'" % extra) in lines)
726

    
727
  def _StopInstanceCommand(self, instance_name, force, fail, cmd):
728
    if ((force and cmd[:2] == [self.CMD, "destroy"]) or
729
        (not force and cmd[:2] == [self.CMD, "shutdown"])):
730
      self.assertEqual(cmd[2:], [instance_name])
731
      output = ""
732
    else:
733
      self.fail("Unhandled command: %s" % (cmd, ))
734

    
735
    if fail:
736
      # Simulate a failing command
737
      return self._FailingCommand(cmd)
738
    else:
739
      return self._SuccessCommand(output, cmd)
740

    
741
  def testStopInstance(self):
742
    name = "inst4284.example.com"
743
    cfgfile = utils.PathJoin(self.tmpdir, name)
744
    cfgdata = "config file content\n"
745

    
746
    for force in [False, True]:
747
      for fail in [False, True]:
748
        utils.WriteFile(cfgfile, data=cfgdata)
749

    
750
        run_cmd = compat.partial(self._StopInstanceCommand, name, force, fail)
751

    
752
        hv = self._GetHv(run_cmd=run_cmd)
753

    
754
        self.assertTrue(os.path.isfile(cfgfile))
755

    
756
        if fail:
757
          try:
758
            hv._StopInstance(name, force, None)
759
          except errors.HypervisorError, err:
760
            self.assertTrue(str(err).startswith("Failed to stop instance"))
761
          else:
762
            self.fail("Exception was not raised")
763
          self.assertEqual(utils.ReadFile(cfgfile), cfgdata,
764
                           msg=("Configuration was removed when stopping"
765
                                " instance failed"))
766
        else:
767
          hv._StopInstance(name, force, None)
768
          self.assertFalse(os.path.exists(cfgfile))
769

    
770
  def _MigrateNonRunningInstCmd(self, cmd):
771
    if cmd == [self.CMD, "list"]:
772
      output = testutils.ReadTestData("xen-xm-list-4.0.1-dom0-only.txt")
773
    else:
774
      self.fail("Unhandled command: %s" % (cmd, ))
775

    
776
    return self._SuccessCommand(output, cmd)
777

    
778
  def testMigrateInstanceNotRunning(self):
779
    name = "nonexistinginstance.example.com"
780
    target = constants.IP4_ADDRESS_LOCALHOST
781
    port = 14618
782

    
783
    hv = self._GetHv(run_cmd=self._MigrateNonRunningInstCmd)
784

    
785
    for live in [False, True]:
786
      try:
787
        hv._MigrateInstance(NotImplemented, name, target, port, live,
788
                            self.VALID_HVPARAMS, _ping_fn=NotImplemented)
789
      except errors.HypervisorError, err:
790
        self.assertEqual(str(err), "Instance not running, cannot migrate")
791
      else:
792
        self.fail("Exception was not raised")
793

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

    
800
    return self._SuccessCommand(output, cmd)
801

    
802
  def testMigrateTargetUnreachable(self):
803
    name = "server01.example.com"
804
    target = constants.IP4_ADDRESS_LOCALHOST
805
    port = 28349
806

    
807
    hv = self._GetHv(run_cmd=self._MigrateInstTargetUnreachCmd)
808
    hvparams = {constants.HV_XEN_CMD: self.CMD}
809

    
810
    for live in [False, True]:
811
      if self.CMD == constants.XEN_CMD_XL:
812
        # TODO: Detect unreachable targets
813
        pass
814
      else:
815
        try:
816
          hv._MigrateInstance(NotImplemented, name, target, port, live,
817
                              hvparams,
818
                              _ping_fn=compat.partial(self._FakeTcpPing,
819
                                                      (target, port), False))
820
        except errors.HypervisorError, err:
821
          wanted = "Remote host %s not" % target
822
          self.assertTrue(str(err).startswith(wanted))
823
        else:
824
          self.fail("Exception was not raised")
825

    
826
  def _MigrateInstanceCmd(self, cluster_name, instance_name, target, port,
827
                          live, fail, cmd):
828
    if cmd == [self.CMD, "list"]:
829
      output = testutils.ReadTestData("xen-xm-list-4.0.1-four-instances.txt")
830
    elif cmd[:2] == [self.CMD, "migrate"]:
831
      if self.CMD == constants.XEN_CMD_XM:
832
        args = ["-p", str(port)]
833

    
834
        if live:
835
          args.append("-l")
836

    
837
      elif self.CMD == constants.XEN_CMD_XL:
838
        args = [
839
          "-s", constants.XL_SSH_CMD % cluster_name,
840
          "-C", utils.PathJoin(self.tmpdir, instance_name),
841
          ]
842

    
843
      else:
844
        self.fail("Unknown Xen command '%s'" % self.CMD)
845

    
846
      args.extend([instance_name, target])
847
      self.assertEqual(cmd[2:], args)
848

    
849
      if fail:
850
        return self._FailingCommand(cmd)
851

    
852
      output = ""
853
    else:
854
      self.fail("Unhandled command: %s" % (cmd, ))
855

    
856
    return self._SuccessCommand(output, cmd)
857

    
858
  def testMigrateInstance(self):
859
    clustername = "cluster.example.com"
860
    instname = "server01.example.com"
861
    target = constants.IP4_ADDRESS_LOCALHOST
862
    port = 22364
863

    
864
    hvparams = {constants.HV_XEN_CMD: self.CMD}
865

    
866
    for live in [False, True]:
867
      for fail in [False, True]:
868
        ping_fn = \
869
          testutils.CallCounter(compat.partial(self._FakeTcpPing,
870
                                               (target, port), True))
871

    
872
        run_cmd = \
873
          compat.partial(self._MigrateInstanceCmd,
874
                         clustername, instname, target, port, live,
875
                         fail)
876

    
877
        hv = self._GetHv(run_cmd=run_cmd)
878

    
879
        if fail:
880
          try:
881
            hv._MigrateInstance(clustername, instname, target, port, live,
882
                                hvparams, _ping_fn=ping_fn)
883
          except errors.HypervisorError, err:
884
            self.assertTrue(str(err).startswith("Failed to migrate instance"))
885
          else:
886
            self.fail("Exception was not raised")
887
        else:
888
          hv._MigrateInstance(clustername, instname, target, port, live,
889
                              hvparams, _ping_fn=ping_fn)
890

    
891
        if self.CMD == constants.XEN_CMD_XM:
892
          expected_pings = 1
893
        else:
894
          expected_pings = 0
895

    
896
        self.assertEqual(ping_fn.Count(), expected_pings)
897

    
898
  def _GetNodeInfoCmd(self, fail, cmd):
899
    if cmd == [self.CMD, "info"]:
900
      if fail:
901
        return self._FailingCommand(cmd)
902
      else:
903
        output = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
904
    elif cmd == [self.CMD, "list"]:
905
      if fail:
906
        self.fail("'xm list' shouldn't be called when 'xm info' failed")
907
      else:
908
        output = testutils.ReadTestData("xen-xm-list-4.0.1-four-instances.txt")
909
    else:
910
      self.fail("Unhandled command: %s" % (cmd, ))
911

    
912
    return self._SuccessCommand(output, cmd)
913

    
914
  def testGetNodeInfo(self):
915
    run_cmd = compat.partial(self._GetNodeInfoCmd, False)
916
    hv = self._GetHv(run_cmd=run_cmd)
917
    result = hv.GetNodeInfo()
918

    
919
    self.assertEqual(result["hv_version"], (4, 0))
920
    self.assertEqual(result["memory_free"], 8004)
921

    
922
  def testGetNodeInfoFailing(self):
923
    run_cmd = compat.partial(self._GetNodeInfoCmd, True)
924
    hv = self._GetHv(run_cmd=run_cmd)
925
    self.assertTrue(hv.GetNodeInfo() is None)
926

    
927

    
928
def _MakeTestClass(cls, cmd):
929
  """Makes a class for testing.
930

931
  The returned class has structure as shown in the following pseudo code:
932

933
    class Test{cls.__name__}{cmd}(_TestXenHypervisor, unittest.TestCase):
934
      TARGET = {cls}
935
      CMD = {cmd}
936
      HVNAME = {Hypervisor name retrieved using class}
937

938
  @type cls: class
939
  @param cls: Hypervisor class to be tested
940
  @type cmd: string
941
  @param cmd: Hypervisor command
942
  @rtype: tuple
943
  @return: Class name and class object (not instance)
944

945
  """
946
  name = "Test%sCmd%s" % (cls.__name__, cmd.title())
947
  bases = (_TestXenHypervisor, unittest.TestCase)
948
  hvname = HVCLASS_TO_HVNAME[cls]
949

    
950
  return (name, type(name, bases, dict(TARGET=cls, CMD=cmd, HVNAME=hvname)))
951

    
952

    
953
# Create test classes programmatically instead of manually to reduce the risk
954
# of forgetting some combinations
955
for cls in [hv_xen.XenPvmHypervisor, hv_xen.XenHvmHypervisor]:
956
  for cmd in constants.KNOWN_XEN_COMMANDS:
957
    (name, testcls) = _MakeTestClass(cls, cmd)
958

    
959
    assert name not in locals()
960

    
961
    locals()[name] = testcls
962

    
963

    
964
if __name__ == "__main__":
965
  testutils.GanetiTestProgram()