Statistics
| Branch: | Tag: | Revision:

root / test / py / ganeti.hypervisor.hv_xen_unittest.py @ 3eb073a6

History | View | Annotate | Download (24.9 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

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

    
39
from ganeti.hypervisor import hv_xen
40

    
41
import testutils
42

    
43

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

    
47

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

    
59

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

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

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

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

    
78

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

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

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

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

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

    
100

    
101
class TestParseXmList(testutils.GanetiTestCase):
102
  def test(self):
103
    data = testutils.ReadTestData("xen-xm-list-4.0.1-dom0-only.txt")
104

    
105
    # Exclude node
106
    self.assertEqual(hv_xen._ParseXmList(data.splitlines(), False), [])
107

    
108
    # Include node
109
    result = hv_xen._ParseXmList(data.splitlines(), True)
110
    self.assertEqual(len(result), 1)
111
    self.assertEqual(len(result[0]), 6)
112

    
113
    # Name
114
    self.assertEqual(result[0][0], hv_xen._DOM0_NAME)
115

    
116
    # ID
117
    self.assertEqual(result[0][1], 0)
118

    
119
    # Memory
120
    self.assertEqual(result[0][2], 1023)
121

    
122
    # VCPUs
123
    self.assertEqual(result[0][3], 1)
124

    
125
    # State
126
    self.assertEqual(result[0][4], "r-----")
127

    
128
    # Time
129
    self.assertAlmostEqual(result[0][5], 121152.6)
130

    
131
  def testWrongLineFormat(self):
132
    tests = [
133
      ["three fields only"],
134
      ["name InvalidID 128 1 r----- 12345"],
135
      ]
136

    
137
    for lines in tests:
138
      try:
139
        hv_xen._ParseXmList(["Header would be here"] + lines, False)
140
      except errors.HypervisorError, err:
141
        self.assertTrue("Can't parse output of xm list" in str(err))
142
      else:
143
        self.fail("Exception was not raised")
144

    
145

    
146
class TestGetXmList(testutils.GanetiTestCase):
147
  def _Fail(self):
148
    return utils.RunResult(constants.EXIT_FAILURE, None,
149
                           "stdout", "stderr", None,
150
                           NotImplemented, NotImplemented)
151

    
152
  def testTimeout(self):
153
    fn = testutils.CallCounter(self._Fail)
154
    try:
155
      hv_xen._GetXmList(fn, False, _timeout=0.1)
156
    except errors.HypervisorError, err:
157
      self.assertTrue("timeout exceeded" in str(err))
158
    else:
159
      self.fail("Exception was not raised")
160

    
161
    self.assertTrue(fn.Count() < 10,
162
                    msg="'xm list' was called too many times")
163

    
164
  def _Success(self, stdout):
165
    return utils.RunResult(constants.EXIT_SUCCESS, None, stdout, "", None,
166
                           NotImplemented, NotImplemented)
167

    
168
  def testSuccess(self):
169
    data = testutils.ReadTestData("xen-xm-list-4.0.1-four-instances.txt")
170

    
171
    fn = testutils.CallCounter(compat.partial(self._Success, data))
172

    
173
    result = hv_xen._GetXmList(fn, True, _timeout=0.1)
174

    
175
    self.assertEqual(len(result), 4)
176

    
177
    self.assertEqual(map(compat.fst, result), [
178
      "Domain-0",
179
      "server01.example.com",
180
      "web3106215069.example.com",
181
      "testinstance.example.com",
182
      ])
183

    
184
    self.assertEqual(fn.Count(), 1)
185

    
186

    
187
class TestParseNodeInfo(testutils.GanetiTestCase):
188
  def testEmpty(self):
189
    self.assertEqual(hv_xen._ParseNodeInfo(""), {})
190

    
191
  def testUnknownInput(self):
192
    data = "\n".join([
193
      "foo bar",
194
      "something else goes",
195
      "here",
196
      ])
197
    self.assertEqual(hv_xen._ParseNodeInfo(data), {})
198

    
199
  def testBasicInfo(self):
200
    data = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
201
    result = hv_xen._ParseNodeInfo(data)
202
    self.assertEqual(result, {
203
      "cpu_nodes": 1,
204
      "cpu_sockets": 2,
205
      "cpu_total": 4,
206
      "hv_version": (4, 0),
207
      "memory_free": 8004,
208
      "memory_total": 16378,
209
      })
210

    
211

    
212
class TestMergeInstanceInfo(testutils.GanetiTestCase):
213
  def testEmpty(self):
214
    self.assertEqual(hv_xen._MergeInstanceInfo({}, lambda _: []), {})
215

    
216
  def _FakeXmList(self, include_node):
217
    self.assertTrue(include_node)
218
    return [
219
      (hv_xen._DOM0_NAME, NotImplemented, 4096, 7, NotImplemented,
220
       NotImplemented),
221
      ("inst1.example.com", NotImplemented, 2048, 4, NotImplemented,
222
       NotImplemented),
223
      ]
224

    
225
  def testMissingNodeInfo(self):
226
    result = hv_xen._MergeInstanceInfo({}, self._FakeXmList)
227
    self.assertEqual(result, {
228
      "memory_dom0": 4096,
229
      "dom0_cpus": 7,
230
      })
231

    
232
  def testWithNodeInfo(self):
233
    info = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
234
    result = hv_xen._GetNodeInfo(info, self._FakeXmList)
235
    self.assertEqual(result, {
236
      "cpu_nodes": 1,
237
      "cpu_sockets": 2,
238
      "cpu_total": 4,
239
      "dom0_cpus": 7,
240
      "hv_version": (4, 0),
241
      "memory_dom0": 4096,
242
      "memory_free": 8004,
243
      "memory_hv": 2230,
244
      "memory_total": 16378,
245
      })
246

    
247

    
248
class TestGetConfigFileDiskData(unittest.TestCase):
249
  def testLetterCount(self):
250
    self.assertEqual(len(hv_xen._DISK_LETTERS), 26)
251

    
252
  def testNoDisks(self):
253
    self.assertEqual(hv_xen._GetConfigFileDiskData([], "hd"), [])
254

    
255
  def testManyDisks(self):
256
    for offset in [0, 1, 10]:
257
      disks = [(objects.Disk(dev_type=constants.LD_LV), "/tmp/disk/%s" % idx)
258
               for idx in range(len(hv_xen._DISK_LETTERS) + offset)]
259

    
260
      if offset == 0:
261
        result = hv_xen._GetConfigFileDiskData(disks, "hd")
262
        self.assertEqual(result, [
263
          "'phy:/tmp/disk/%s,hd%s,r'" % (idx, string.ascii_lowercase[idx])
264
          for idx in range(len(hv_xen._DISK_LETTERS) + offset)
265
          ])
266
      else:
267
        try:
268
          hv_xen._GetConfigFileDiskData(disks, "hd")
269
        except errors.HypervisorError, err:
270
          self.assertEqual(str(err), "Too many disks")
271
        else:
272
          self.fail("Exception was not raised")
273

    
274
  def testTwoLvDisksWithMode(self):
275
    disks = [
276
      (objects.Disk(dev_type=constants.LD_LV, mode=constants.DISK_RDWR),
277
       "/tmp/diskFirst"),
278
      (objects.Disk(dev_type=constants.LD_LV, mode=constants.DISK_RDONLY),
279
       "/tmp/diskLast"),
280
      ]
281

    
282
    result = hv_xen._GetConfigFileDiskData(disks, "hd")
283
    self.assertEqual(result, [
284
      "'phy:/tmp/diskFirst,hda,w'",
285
      "'phy:/tmp/diskLast,hdb,r'",
286
      ])
287

    
288
  def testFileDisks(self):
289
    disks = [
290
      (objects.Disk(dev_type=constants.LD_FILE, mode=constants.DISK_RDWR,
291
                    physical_id=[constants.FD_LOOP]),
292
       "/tmp/diskFirst"),
293
      (objects.Disk(dev_type=constants.LD_FILE, mode=constants.DISK_RDONLY,
294
                    physical_id=[constants.FD_BLKTAP]),
295
       "/tmp/diskTwo"),
296
      (objects.Disk(dev_type=constants.LD_FILE, mode=constants.DISK_RDWR,
297
                    physical_id=[constants.FD_LOOP]),
298
       "/tmp/diskThree"),
299
      (objects.Disk(dev_type=constants.LD_FILE, mode=constants.DISK_RDWR,
300
                    physical_id=[constants.FD_BLKTAP]),
301
       "/tmp/diskLast"),
302
      ]
303

    
304
    result = hv_xen._GetConfigFileDiskData(disks, "sd")
305
    self.assertEqual(result, [
306
      "'file:/tmp/diskFirst,sda,w'",
307
      "'tap:aio:/tmp/diskTwo,sdb,r'",
308
      "'file:/tmp/diskThree,sdc,w'",
309
      "'tap:aio:/tmp/diskLast,sdd,w'",
310
      ])
311

    
312
  def testInvalidFileDisk(self):
313
    disks = [
314
      (objects.Disk(dev_type=constants.LD_FILE, mode=constants.DISK_RDWR,
315
                    physical_id=["#unknown#"]),
316
       "/tmp/diskinvalid"),
317
      ]
318

    
319
    self.assertRaises(KeyError, hv_xen._GetConfigFileDiskData, disks, "sd")
320

    
321

    
322
class TestXenHypervisorUnknownCommand(unittest.TestCase):
323
  def test(self):
324
    cmd = "#unknown command#"
325
    self.assertFalse(cmd in constants.KNOWN_XEN_COMMANDS)
326
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
327
                              _run_cmd_fn=NotImplemented,
328
                              _cmd=cmd)
329
    self.assertRaises(errors.ProgrammerError, hv._RunXen, [])
330

    
331

    
332
class TestXenHypervisorWriteConfigFile(unittest.TestCase):
333
  def setUp(self):
334
    self.tmpdir = tempfile.mkdtemp()
335

    
336
  def tearDown(self):
337
    shutil.rmtree(self.tmpdir)
338

    
339
  def testWriteError(self):
340
    cfgdir = utils.PathJoin(self.tmpdir, "foobar")
341

    
342
    hv = hv_xen.XenHypervisor(_cfgdir=cfgdir,
343
                              _run_cmd_fn=NotImplemented,
344
                              _cmd=NotImplemented)
345

    
346
    self.assertFalse(os.path.exists(cfgdir))
347

    
348
    try:
349
      hv._WriteConfigFile("name", "data")
350
    except errors.HypervisorError, err:
351
      self.assertTrue(str(err).startswith("Cannot write Xen instance"))
352
    else:
353
      self.fail("Exception was not raised")
354

    
355

    
356
class _TestXenHypervisor(object):
357
  TARGET = NotImplemented
358
  CMD = NotImplemented
359
  HVNAME = NotImplemented
360

    
361
  def setUp(self):
362
    super(_TestXenHypervisor, self).setUp()
363

    
364
    self.tmpdir = tempfile.mkdtemp()
365

    
366
    self.vncpw = "".join(random.sample(string.ascii_letters, 10))
367

    
368
    self.vncpw_path = utils.PathJoin(self.tmpdir, "vncpw")
369
    utils.WriteFile(self.vncpw_path, data=self.vncpw)
370

    
371
  def tearDown(self):
372
    super(_TestXenHypervisor, self).tearDown()
373

    
374
    shutil.rmtree(self.tmpdir)
375

    
376
  def _GetHv(self, run_cmd=NotImplemented):
377
    return self.TARGET(_cfgdir=self.tmpdir, _run_cmd_fn=run_cmd, _cmd=self.CMD)
378

    
379
  def _SuccessCommand(self, stdout, cmd):
380
    self.assertEqual(cmd[0], self.CMD)
381

    
382
    return utils.RunResult(constants.EXIT_SUCCESS, None, stdout, "", None,
383
                           NotImplemented, NotImplemented)
384

    
385
  def _FailingCommand(self, cmd):
386
    self.assertEqual(cmd[0], self.CMD)
387

    
388
    return utils.RunResult(constants.EXIT_FAILURE, None,
389
                           "", "This command failed", None,
390
                           NotImplemented, NotImplemented)
391

    
392
  def _FakeTcpPing(self, expected, result, target, port, **kwargs):
393
    self.assertEqual((target, port), expected)
394
    return result
395

    
396
  def testReadingNonExistentConfigFile(self):
397
    hv = self._GetHv()
398

    
399
    try:
400
      hv._ReadConfigFile("inst15780.example.com")
401
    except errors.HypervisorError, err:
402
      self.assertTrue(str(err).startswith("Failed to load Xen config file:"))
403
    else:
404
      self.fail("Exception was not raised")
405

    
406
  def testRemovingAutoConfigFile(self):
407
    name = "inst8206.example.com"
408
    cfgfile = utils.PathJoin(self.tmpdir, name)
409
    autodir = utils.PathJoin(self.tmpdir, "auto")
410
    autocfgfile = utils.PathJoin(autodir, name)
411

    
412
    os.mkdir(autodir)
413

    
414
    utils.WriteFile(autocfgfile, data="")
415

    
416
    hv = self._GetHv()
417

    
418
    self.assertTrue(os.path.isfile(autocfgfile))
419
    hv._WriteConfigFile(name, "content")
420
    self.assertFalse(os.path.exists(autocfgfile))
421
    self.assertEqual(utils.ReadFile(cfgfile), "content")
422

    
423
  def _XenList(self, cmd):
424
    self.assertEqual(cmd, [self.CMD, "list"])
425

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

    
429
    return self._SuccessCommand(output, cmd)
430

    
431
  def testGetInstanceInfo(self):
432
    hv = self._GetHv(run_cmd=self._XenList)
433

    
434
    (name, instid, memory, vcpus, state, runtime) = \
435
      hv.GetInstanceInfo("server01.example.com")
436

    
437
    self.assertEqual(name, "server01.example.com")
438
    self.assertEqual(instid, 1)
439
    self.assertEqual(memory, 1024)
440
    self.assertEqual(vcpus, 1)
441
    self.assertEqual(state, "-b----")
442
    self.assertAlmostEqual(runtime, 167643.2)
443

    
444
  def testGetInstanceInfoDom0(self):
445
    hv = self._GetHv(run_cmd=self._XenList)
446

    
447
    # TODO: Not sure if this is actually used anywhere (can't find it), but the
448
    # code supports querying for Dom0
449
    (name, instid, memory, vcpus, state, runtime) = \
450
      hv.GetInstanceInfo(hv_xen._DOM0_NAME)
451

    
452
    self.assertEqual(name, "Domain-0")
453
    self.assertEqual(instid, 0)
454
    self.assertEqual(memory, 1023)
455
    self.assertEqual(vcpus, 1)
456
    self.assertEqual(state, "r-----")
457
    self.assertAlmostEqual(runtime, 154706.1)
458

    
459
  def testGetInstanceInfoUnknown(self):
460
    hv = self._GetHv(run_cmd=self._XenList)
461

    
462
    result = hv.GetInstanceInfo("unknown.example.com")
463
    self.assertTrue(result is None)
464

    
465
  def testGetAllInstancesInfo(self):
466
    hv = self._GetHv(run_cmd=self._XenList)
467

    
468
    result = hv.GetAllInstancesInfo()
469

    
470
    self.assertEqual(map(compat.fst, result), [
471
      "server01.example.com",
472
      "web3106215069.example.com",
473
      "testinstance.example.com",
474
      ])
475

    
476
  def testListInstances(self):
477
    hv = self._GetHv(run_cmd=self._XenList)
478

    
479
    self.assertEqual(hv.ListInstances(), [
480
      "server01.example.com",
481
      "web3106215069.example.com",
482
      "testinstance.example.com",
483
      ])
484

    
485
  def testVerify(self):
486
    output = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
487
    hv = self._GetHv(run_cmd=compat.partial(self._SuccessCommand,
488
                                            output))
489
    self.assertTrue(hv.Verify() is None)
490

    
491
  def testVerifyFailing(self):
492
    hv = self._GetHv(run_cmd=self._FailingCommand)
493
    self.assertTrue("failed:" in hv.Verify())
494

    
495
  def _StartInstanceCommand(self, inst, paused, failcreate, cmd):
496
    if cmd == [self.CMD, "info"]:
497
      output = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
498
    elif cmd == [self.CMD, "list"]:
499
      output = testutils.ReadTestData("xen-xm-list-4.0.1-dom0-only.txt")
500
    elif cmd[:2] == [self.CMD, "create"]:
501
      args = cmd[2:]
502
      cfgfile = utils.PathJoin(self.tmpdir, inst.name)
503

    
504
      if paused:
505
        self.assertEqual(args, ["-p", cfgfile])
506
      else:
507
        self.assertEqual(args, [cfgfile])
508

    
509
      if failcreate:
510
        return self._FailingCommand(cmd)
511

    
512
      output = ""
513
    else:
514
      self.fail("Unhandled command: %s" % (cmd, ))
515

    
516
    return self._SuccessCommand(output, cmd)
517

    
518
  def _MakeInstance(self):
519
    # Copy default parameters
520
    bep = objects.FillDict(constants.BEC_DEFAULTS, {})
521
    hvp = objects.FillDict(constants.HVC_DEFAULTS[self.HVNAME], {})
522

    
523
    # Override default VNC password file path
524
    if constants.HV_VNC_PASSWORD_FILE in hvp:
525
      hvp[constants.HV_VNC_PASSWORD_FILE] = self.vncpw_path
526

    
527
    disks = [
528
      (objects.Disk(dev_type=constants.LD_LV, mode=constants.DISK_RDWR),
529
       utils.PathJoin(self.tmpdir, "disk0")),
530
      (objects.Disk(dev_type=constants.LD_LV, mode=constants.DISK_RDONLY),
531
       utils.PathJoin(self.tmpdir, "disk1")),
532
      ]
533

    
534
    inst = objects.Instance(name="server01.example.com",
535
                            hvparams=hvp, beparams=bep,
536
                            osparams={}, nics=[], os="deb1",
537
                            disks=map(compat.fst, disks))
538
    inst.UpgradeConfig()
539

    
540
    return (inst, disks)
541

    
542
  def testStartInstance(self):
543
    (inst, disks) = self._MakeInstance()
544
    pathutils.LOG_XEN_DIR = self.tmpdir
545

    
546
    for failcreate in [False, True]:
547
      for paused in [False, True]:
548
        run_cmd = compat.partial(self._StartInstanceCommand,
549
                                 inst, paused, failcreate)
550

    
551
        hv = self._GetHv(run_cmd=run_cmd)
552

    
553
        # Ensure instance is not listed
554
        self.assertTrue(inst.name not in hv.ListInstances())
555

    
556
        # Remove configuration
557
        cfgfile = utils.PathJoin(self.tmpdir, inst.name)
558
        utils.RemoveFile(cfgfile)
559

    
560
        if failcreate:
561
          self.assertRaises(errors.HypervisorError, hv.StartInstance,
562
                            inst, disks, paused)
563
          # Check whether a stale config file is left behind
564
          self.assertFalse(os.path.exists(cfgfile))
565
        else:
566
          hv.StartInstance(inst, disks, paused)
567
          # Check if configuration was updated
568
          lines = utils.ReadFile(cfgfile).splitlines()
569

    
570
        if constants.HV_VNC_PASSWORD_FILE in inst.hvparams:
571
          self.assertTrue(("vncpasswd = '%s'" % self.vncpw) in lines)
572
        else:
573
          extra = inst.hvparams[constants.HV_KERNEL_ARGS]
574
          self.assertTrue(("extra = '%s'" % extra) in lines)
575

    
576
  def _StopInstanceCommand(self, instance_name, force, fail, cmd):
577
    if ((force and cmd[:2] == [self.CMD, "destroy"]) or
578
        (not force and cmd[:2] == [self.CMD, "shutdown"])):
579
      self.assertEqual(cmd[2:], [instance_name])
580
      output = ""
581
    else:
582
      self.fail("Unhandled command: %s" % (cmd, ))
583

    
584
    if fail:
585
      # Simulate a failing command
586
      return self._FailingCommand(cmd)
587
    else:
588
      return self._SuccessCommand(output, cmd)
589

    
590
  def testStopInstance(self):
591
    name = "inst4284.example.com"
592
    cfgfile = utils.PathJoin(self.tmpdir, name)
593
    cfgdata = "config file content\n"
594

    
595
    for force in [False, True]:
596
      for fail in [False, True]:
597
        utils.WriteFile(cfgfile, data=cfgdata)
598

    
599
        run_cmd = compat.partial(self._StopInstanceCommand, name, force, fail)
600

    
601
        hv = self._GetHv(run_cmd=run_cmd)
602

    
603
        self.assertTrue(os.path.isfile(cfgfile))
604

    
605
        if fail:
606
          try:
607
            hv._StopInstance(name, force)
608
          except errors.HypervisorError, err:
609
            self.assertTrue(str(err).startswith("Failed to stop instance"))
610
          else:
611
            self.fail("Exception was not raised")
612
          self.assertEqual(utils.ReadFile(cfgfile), cfgdata,
613
                           msg=("Configuration was removed when stopping"
614
                                " instance failed"))
615
        else:
616
          hv._StopInstance(name, force)
617
          self.assertFalse(os.path.exists(cfgfile))
618

    
619
  def _MigrateNonRunningInstCmd(self, cmd):
620
    if cmd == [self.CMD, "list"]:
621
      output = testutils.ReadTestData("xen-xm-list-4.0.1-dom0-only.txt")
622
    else:
623
      self.fail("Unhandled command: %s" % (cmd, ))
624

    
625
    return self._SuccessCommand(output, cmd)
626

    
627
  def testMigrateInstanceNotRunning(self):
628
    name = "nonexistinginstance.example.com"
629
    target = constants.IP4_ADDRESS_LOCALHOST
630
    port = 14618
631

    
632
    hv = self._GetHv(run_cmd=self._MigrateNonRunningInstCmd)
633

    
634
    for live in [False, True]:
635
      try:
636
        hv._MigrateInstance(NotImplemented, name, target, port, live,
637
                            _ping_fn=NotImplemented)
638
      except errors.HypervisorError, err:
639
        self.assertEqual(str(err), "Instance not running, cannot migrate")
640
      else:
641
        self.fail("Exception was not raised")
642

    
643
  def _MigrateInstTargetUnreachCmd(self, cmd):
644
    if cmd == [self.CMD, "list"]:
645
      output = testutils.ReadTestData("xen-xm-list-4.0.1-four-instances.txt")
646
    else:
647
      self.fail("Unhandled command: %s" % (cmd, ))
648

    
649
    return self._SuccessCommand(output, cmd)
650

    
651
  def testMigrateTargetUnreachable(self):
652
    name = "server01.example.com"
653
    target = constants.IP4_ADDRESS_LOCALHOST
654
    port = 28349
655

    
656
    hv = self._GetHv(run_cmd=self._MigrateInstTargetUnreachCmd)
657

    
658
    for live in [False, True]:
659
      if self.CMD == constants.XEN_CMD_XL:
660
        # TODO: Detect unreachable targets
661
        pass
662
      else:
663
        try:
664
          hv._MigrateInstance(NotImplemented, name, target, port, live,
665
                              _ping_fn=compat.partial(self._FakeTcpPing,
666
                                                      (target, port), False))
667
        except errors.HypervisorError, err:
668
          wanted = "Remote host %s not" % target
669
          self.assertTrue(str(err).startswith(wanted))
670
        else:
671
          self.fail("Exception was not raised")
672

    
673
  def _MigrateInstanceCmd(self, cluster_name, instance_name, target, port,
674
                          live, fail, cmd):
675
    if cmd == [self.CMD, "list"]:
676
      output = testutils.ReadTestData("xen-xm-list-4.0.1-four-instances.txt")
677
    elif cmd[:2] == [self.CMD, "migrate"]:
678
      if self.CMD == constants.XEN_CMD_XM:
679
        args = ["-p", str(port)]
680

    
681
        if live:
682
          args.append("-l")
683

    
684
      elif self.CMD == constants.XEN_CMD_XL:
685
        args = [
686
          "-s", constants.XL_SSH_CMD % cluster_name,
687
          "-C", utils.PathJoin(self.tmpdir, instance_name),
688
          ]
689

    
690
      else:
691
        self.fail("Unknown Xen command '%s'" % self.CMD)
692

    
693
      args.extend([instance_name, target])
694
      self.assertEqual(cmd[2:], args)
695

    
696
      if fail:
697
        return self._FailingCommand(cmd)
698

    
699
      output = ""
700
    else:
701
      self.fail("Unhandled command: %s" % (cmd, ))
702

    
703
    return self._SuccessCommand(output, cmd)
704

    
705
  def testMigrateInstance(self):
706
    clustername = "cluster.example.com"
707
    instname = "server01.example.com"
708
    target = constants.IP4_ADDRESS_LOCALHOST
709
    port = 22364
710

    
711
    for live in [False, True]:
712
      for fail in [False, True]:
713
        ping_fn = \
714
          testutils.CallCounter(compat.partial(self._FakeTcpPing,
715
                                               (target, port), True))
716

    
717
        run_cmd = \
718
          compat.partial(self._MigrateInstanceCmd,
719
                         clustername, instname, target, port, live,
720
                         fail)
721

    
722
        hv = self._GetHv(run_cmd=run_cmd)
723

    
724
        if fail:
725
          try:
726
            hv._MigrateInstance(clustername, instname, target, port, live,
727
                                _ping_fn=ping_fn)
728
          except errors.HypervisorError, err:
729
            self.assertTrue(str(err).startswith("Failed to migrate instance"))
730
          else:
731
            self.fail("Exception was not raised")
732
        else:
733
          hv._MigrateInstance(clustername, instname, target, port, live,
734
                              _ping_fn=ping_fn)
735

    
736
        if self.CMD == constants.XEN_CMD_XM:
737
          expected_pings = 1
738
        else:
739
          expected_pings = 0
740

    
741
        self.assertEqual(ping_fn.Count(), expected_pings)
742

    
743
  def _GetNodeInfoCmd(self, fail, cmd):
744
    if cmd == [self.CMD, "info"]:
745
      if fail:
746
        return self._FailingCommand(cmd)
747
      else:
748
        output = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
749
    elif cmd == [self.CMD, "list"]:
750
      if fail:
751
        self.fail("'xm list' shouldn't be called when 'xm info' failed")
752
      else:
753
        output = testutils.ReadTestData("xen-xm-list-4.0.1-four-instances.txt")
754
    else:
755
      self.fail("Unhandled command: %s" % (cmd, ))
756

    
757
    return self._SuccessCommand(output, cmd)
758

    
759
  def testGetNodeInfo(self):
760
    run_cmd = compat.partial(self._GetNodeInfoCmd, False)
761
    hv = self._GetHv(run_cmd=run_cmd)
762
    result = hv.GetNodeInfo()
763

    
764
    self.assertEqual(result["hv_version"], (4, 0))
765
    self.assertEqual(result["memory_free"], 8004)
766

    
767
  def testGetNodeInfoFailing(self):
768
    run_cmd = compat.partial(self._GetNodeInfoCmd, True)
769
    hv = self._GetHv(run_cmd=run_cmd)
770
    self.assertTrue(hv.GetNodeInfo() is None)
771

    
772

    
773
def _MakeTestClass(cls, cmd):
774
  """Makes a class for testing.
775

776
  The returned class has structure as shown in the following pseudo code:
777

778
    class Test{cls.__name__}{cmd}(_TestXenHypervisor, unittest.TestCase):
779
      TARGET = {cls}
780
      CMD = {cmd}
781
      HVNAME = {Hypervisor name retrieved using class}
782

783
  @type cls: class
784
  @param cls: Hypervisor class to be tested
785
  @type cmd: string
786
  @param cmd: Hypervisor command
787
  @rtype: tuple
788
  @return: Class name and class object (not instance)
789

790
  """
791
  name = "Test%sCmd%s" % (cls.__name__, cmd.title())
792
  bases = (_TestXenHypervisor, unittest.TestCase)
793
  hvname = HVCLASS_TO_HVNAME[cls]
794

    
795
  return (name, type(name, bases, dict(TARGET=cls, CMD=cmd, HVNAME=hvname)))
796

    
797

    
798
# Create test classes programmatically instead of manually to reduce the risk
799
# of forgetting some combinations
800
for cls in [hv_xen.XenPvmHypervisor, hv_xen.XenHvmHypervisor]:
801
  for cmd in constants.KNOWN_XEN_COMMANDS:
802
    (name, testcls) = _MakeTestClass(cls, cmd)
803

    
804
    assert name not in locals()
805

    
806
    locals()[name] = testcls
807

    
808

    
809
if __name__ == "__main__":
810
  testutils.GanetiTestProgram()