Statistics
| Branch: | Tag: | Revision:

root / test / py / ganeti.hypervisor.hv_xen_unittest.py @ 7352d33b

History | View | Annotate | Download (24 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_lxc"""
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 hypervisor
34
from ganeti import utils
35
from ganeti import errors
36
from ganeti import compat
37

    
38
from ganeti.hypervisor import hv_xen
39

    
40
import testutils
41

    
42

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

    
46

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

    
58

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

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

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

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

    
77

    
78
class TestParseXmList(testutils.GanetiTestCase):
79
  def test(self):
80
    data = testutils.ReadTestData("xen-xm-list-4.0.1-dom0-only.txt")
81

    
82
    # Exclude node
83
    self.assertEqual(hv_xen._ParseXmList(data.splitlines(), False), [])
84

    
85
    # Include node
86
    result = hv_xen._ParseXmList(data.splitlines(), True)
87
    self.assertEqual(len(result), 1)
88
    self.assertEqual(len(result[0]), 6)
89

    
90
    # Name
91
    self.assertEqual(result[0][0], hv_xen._DOM0_NAME)
92

    
93
    # ID
94
    self.assertEqual(result[0][1], 0)
95

    
96
    # Memory
97
    self.assertEqual(result[0][2], 1023)
98

    
99
    # VCPUs
100
    self.assertEqual(result[0][3], 1)
101

    
102
    # State
103
    self.assertEqual(result[0][4], "r-----")
104

    
105
    # Time
106
    self.assertAlmostEqual(result[0][5], 121152.6)
107

    
108
  def testWrongLineFormat(self):
109
    tests = [
110
      ["three fields only"],
111
      ["name InvalidID 128 1 r----- 12345"],
112
      ]
113

    
114
    for lines in tests:
115
      try:
116
        hv_xen._ParseXmList(["Header would be here"] + lines, False)
117
      except errors.HypervisorError, err:
118
        self.assertTrue("Can't parse output of xm list" in str(err))
119
      else:
120
        self.fail("Exception was not raised")
121

    
122

    
123
class TestGetXmList(testutils.GanetiTestCase):
124
  def _Fail(self):
125
    return utils.RunResult(constants.EXIT_FAILURE, None,
126
                           "stdout", "stderr", None,
127
                           NotImplemented, NotImplemented)
128

    
129
  def testTimeout(self):
130
    fn = testutils.CallCounter(self._Fail)
131
    try:
132
      hv_xen._GetXmList(fn, False, _timeout=0.1)
133
    except errors.HypervisorError, err:
134
      self.assertTrue("timeout exceeded" in str(err))
135
    else:
136
      self.fail("Exception was not raised")
137

    
138
    self.assertTrue(fn.Count() < 10,
139
                    msg="'xm list' was called too many times")
140

    
141
  def _Success(self, stdout):
142
    return utils.RunResult(constants.EXIT_SUCCESS, None, stdout, "", None,
143
                           NotImplemented, NotImplemented)
144

    
145
  def testSuccess(self):
146
    data = testutils.ReadTestData("xen-xm-list-4.0.1-four-instances.txt")
147

    
148
    fn = testutils.CallCounter(compat.partial(self._Success, data))
149

    
150
    result = hv_xen._GetXmList(fn, True, _timeout=0.1)
151

    
152
    self.assertEqual(len(result), 4)
153

    
154
    self.assertEqual(map(compat.fst, result), [
155
      "Domain-0",
156
      "server01.example.com",
157
      "web3106215069.example.com",
158
      "testinstance.example.com",
159
      ])
160

    
161
    self.assertEqual(fn.Count(), 1)
162

    
163

    
164
class TestParseNodeInfo(testutils.GanetiTestCase):
165
  def testEmpty(self):
166
    self.assertEqual(hv_xen._ParseNodeInfo(""), {})
167

    
168
  def testUnknownInput(self):
169
    data = "\n".join([
170
      "foo bar",
171
      "something else goes",
172
      "here",
173
      ])
174
    self.assertEqual(hv_xen._ParseNodeInfo(data), {})
175

    
176
  def testBasicInfo(self):
177
    data = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
178
    result = hv_xen._ParseNodeInfo(data)
179
    self.assertEqual(result, {
180
      "cpu_nodes": 1,
181
      "cpu_sockets": 2,
182
      "cpu_total": 4,
183
      "hv_version": (4, 0),
184
      "memory_free": 8004,
185
      "memory_total": 16378,
186
      })
187

    
188

    
189
class TestMergeInstanceInfo(testutils.GanetiTestCase):
190
  def testEmpty(self):
191
    self.assertEqual(hv_xen._MergeInstanceInfo({}, lambda _: []), {})
192

    
193
  def _FakeXmList(self, include_node):
194
    self.assertTrue(include_node)
195
    return [
196
      (hv_xen._DOM0_NAME, NotImplemented, 4096, 7, NotImplemented,
197
       NotImplemented),
198
      ("inst1.example.com", NotImplemented, 2048, 4, NotImplemented,
199
       NotImplemented),
200
      ]
201

    
202
  def testMissingNodeInfo(self):
203
    result = hv_xen._MergeInstanceInfo({}, self._FakeXmList)
204
    self.assertEqual(result, {
205
      "memory_dom0": 4096,
206
      "dom0_cpus": 7,
207
      })
208

    
209
  def testWithNodeInfo(self):
210
    info = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
211
    result = hv_xen._GetNodeInfo(info, self._FakeXmList)
212
    self.assertEqual(result, {
213
      "cpu_nodes": 1,
214
      "cpu_sockets": 2,
215
      "cpu_total": 4,
216
      "dom0_cpus": 7,
217
      "hv_version": (4, 0),
218
      "memory_dom0": 4096,
219
      "memory_free": 8004,
220
      "memory_hv": 2230,
221
      "memory_total": 16378,
222
      })
223

    
224

    
225
class TestGetConfigFileDiskData(unittest.TestCase):
226
  def testLetterCount(self):
227
    self.assertEqual(len(hv_xen._DISK_LETTERS), 26)
228

    
229
  def testNoDisks(self):
230
    self.assertEqual(hv_xen._GetConfigFileDiskData([], "hd"), [])
231

    
232
  def testManyDisks(self):
233
    for offset in [0, 1, 10]:
234
      disks = [(objects.Disk(dev_type=constants.LD_LV), "/tmp/disk/%s" % idx)
235
               for idx in range(len(hv_xen._DISK_LETTERS) + offset)]
236

    
237
      if offset == 0:
238
        result = hv_xen._GetConfigFileDiskData(disks, "hd")
239
        self.assertEqual(result, [
240
          "'phy:/tmp/disk/%s,hd%s,r'" % (idx, string.ascii_lowercase[idx])
241
          for idx in range(len(hv_xen._DISK_LETTERS) + offset)
242
          ])
243
      else:
244
        try:
245
          hv_xen._GetConfigFileDiskData(disks, "hd")
246
        except errors.HypervisorError, err:
247
          self.assertEqual(str(err), "Too many disks")
248
        else:
249
          self.fail("Exception was not raised")
250

    
251
  def testTwoLvDisksWithMode(self):
252
    disks = [
253
      (objects.Disk(dev_type=constants.LD_LV, mode=constants.DISK_RDWR),
254
       "/tmp/diskFirst"),
255
      (objects.Disk(dev_type=constants.LD_LV, mode=constants.DISK_RDONLY),
256
       "/tmp/diskLast"),
257
      ]
258

    
259
    result = hv_xen._GetConfigFileDiskData(disks, "hd")
260
    self.assertEqual(result, [
261
      "'phy:/tmp/diskFirst,hda,w'",
262
      "'phy:/tmp/diskLast,hdb,r'",
263
      ])
264

    
265
  def testFileDisks(self):
266
    disks = [
267
      (objects.Disk(dev_type=constants.LD_FILE, mode=constants.DISK_RDWR,
268
                    physical_id=[constants.FD_LOOP]),
269
       "/tmp/diskFirst"),
270
      (objects.Disk(dev_type=constants.LD_FILE, mode=constants.DISK_RDONLY,
271
                    physical_id=[constants.FD_BLKTAP]),
272
       "/tmp/diskTwo"),
273
      (objects.Disk(dev_type=constants.LD_FILE, mode=constants.DISK_RDWR,
274
                    physical_id=[constants.FD_LOOP]),
275
       "/tmp/diskThree"),
276
      (objects.Disk(dev_type=constants.LD_FILE, mode=constants.DISK_RDWR,
277
                    physical_id=[constants.FD_BLKTAP]),
278
       "/tmp/diskLast"),
279
      ]
280

    
281
    result = hv_xen._GetConfigFileDiskData(disks, "sd")
282
    self.assertEqual(result, [
283
      "'file:/tmp/diskFirst,sda,w'",
284
      "'tap:aio:/tmp/diskTwo,sdb,r'",
285
      "'file:/tmp/diskThree,sdc,w'",
286
      "'tap:aio:/tmp/diskLast,sdd,w'",
287
      ])
288

    
289
  def testInvalidFileDisk(self):
290
    disks = [
291
      (objects.Disk(dev_type=constants.LD_FILE, mode=constants.DISK_RDWR,
292
                    physical_id=["#unknown#"]),
293
       "/tmp/diskinvalid"),
294
      ]
295

    
296
    self.assertRaises(KeyError, hv_xen._GetConfigFileDiskData, disks, "sd")
297

    
298

    
299
class TestXenHypervisorUnknownCommand(unittest.TestCase):
300
  def test(self):
301
    cmd = "#unknown command#"
302
    self.assertFalse(cmd in constants.KNOWN_XEN_COMMANDS)
303
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
304
                              _run_cmd_fn=NotImplemented,
305
                              _cmd=cmd)
306
    self.assertRaises(errors.ProgrammerError, hv._RunXen, [])
307

    
308

    
309
class TestXenHypervisorWriteConfigFile(unittest.TestCase):
310
  def setUp(self):
311
    self.tmpdir = tempfile.mkdtemp()
312

    
313
  def tearDown(self):
314
    shutil.rmtree(self.tmpdir)
315

    
316
  def testWriteError(self):
317
    cfgdir = utils.PathJoin(self.tmpdir, "foobar")
318

    
319
    hv = hv_xen.XenHypervisor(_cfgdir=cfgdir,
320
                              _run_cmd_fn=NotImplemented,
321
                              _cmd=NotImplemented)
322

    
323
    self.assertFalse(os.path.exists(cfgdir))
324

    
325
    try:
326
      hv._WriteConfigFile("name", "data")
327
    except errors.HypervisorError, err:
328
      self.assertTrue(str(err).startswith("Cannot write Xen instance"))
329
    else:
330
      self.fail("Exception was not raised")
331

    
332

    
333
class _TestXenHypervisor(object):
334
  TARGET = NotImplemented
335
  CMD = NotImplemented
336
  HVNAME = NotImplemented
337

    
338
  def setUp(self):
339
    super(_TestXenHypervisor, self).setUp()
340

    
341
    self.tmpdir = tempfile.mkdtemp()
342

    
343
    self.vncpw = "".join(random.sample(string.ascii_letters, 10))
344

    
345
    self.vncpw_path = utils.PathJoin(self.tmpdir, "vncpw")
346
    utils.WriteFile(self.vncpw_path, data=self.vncpw)
347

    
348
  def tearDown(self):
349
    super(_TestXenHypervisor, self).tearDown()
350

    
351
    shutil.rmtree(self.tmpdir)
352

    
353
  def _GetHv(self, run_cmd=NotImplemented):
354
    return self.TARGET(_cfgdir=self.tmpdir, _run_cmd_fn=run_cmd, _cmd=self.CMD)
355

    
356
  def _SuccessCommand(self, stdout, cmd):
357
    self.assertEqual(cmd[0], self.CMD)
358

    
359
    return utils.RunResult(constants.EXIT_SUCCESS, None, stdout, "", None,
360
                           NotImplemented, NotImplemented)
361

    
362
  def _FailingCommand(self, cmd):
363
    self.assertEqual(cmd[0], self.CMD)
364

    
365
    return utils.RunResult(constants.EXIT_FAILURE, None,
366
                           "", "This command failed", None,
367
                           NotImplemented, NotImplemented)
368

    
369
  def _FakeTcpPing(self, expected, result, target, port, **kwargs):
370
    self.assertEqual((target, port), expected)
371
    return result
372

    
373
  def testReadingNonExistentConfigFile(self):
374
    hv = self._GetHv()
375

    
376
    try:
377
      hv._ReadConfigFile("inst15780.example.com")
378
    except errors.HypervisorError, err:
379
      self.assertTrue(str(err).startswith("Failed to load Xen config file:"))
380
    else:
381
      self.fail("Exception was not raised")
382

    
383
  def testRemovingAutoConfigFile(self):
384
    name = "inst8206.example.com"
385
    cfgfile = utils.PathJoin(self.tmpdir, name)
386
    autodir = utils.PathJoin(self.tmpdir, "auto")
387
    autocfgfile = utils.PathJoin(autodir, name)
388

    
389
    os.mkdir(autodir)
390

    
391
    utils.WriteFile(autocfgfile, data="")
392

    
393
    hv = self._GetHv()
394

    
395
    self.assertTrue(os.path.isfile(autocfgfile))
396
    hv._WriteConfigFile(name, "content")
397
    self.assertFalse(os.path.exists(autocfgfile))
398
    self.assertEqual(utils.ReadFile(cfgfile), "content")
399

    
400
  def _XenList(self, cmd):
401
    self.assertEqual(cmd, [self.CMD, "list"])
402

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

    
406
    return self._SuccessCommand(output, cmd)
407

    
408
  def testGetInstanceInfo(self):
409
    hv = self._GetHv(run_cmd=self._XenList)
410

    
411
    (name, instid, memory, vcpus, state, runtime) = \
412
      hv.GetInstanceInfo("server01.example.com")
413

    
414
    self.assertEqual(name, "server01.example.com")
415
    self.assertEqual(instid, 1)
416
    self.assertEqual(memory, 1024)
417
    self.assertEqual(vcpus, 1)
418
    self.assertEqual(state, "-b----")
419
    self.assertAlmostEqual(runtime, 167643.2)
420

    
421
  def testGetInstanceInfoDom0(self):
422
    hv = self._GetHv(run_cmd=self._XenList)
423

    
424
    # TODO: Not sure if this is actually used anywhere (can't find it), but the
425
    # code supports querying for Dom0
426
    (name, instid, memory, vcpus, state, runtime) = \
427
      hv.GetInstanceInfo(hv_xen._DOM0_NAME)
428

    
429
    self.assertEqual(name, "Domain-0")
430
    self.assertEqual(instid, 0)
431
    self.assertEqual(memory, 1023)
432
    self.assertEqual(vcpus, 1)
433
    self.assertEqual(state, "r-----")
434
    self.assertAlmostEqual(runtime, 154706.1)
435

    
436
  def testGetInstanceInfoUnknown(self):
437
    hv = self._GetHv(run_cmd=self._XenList)
438

    
439
    result = hv.GetInstanceInfo("unknown.example.com")
440
    self.assertTrue(result is None)
441

    
442
  def testGetAllInstancesInfo(self):
443
    hv = self._GetHv(run_cmd=self._XenList)
444

    
445
    result = hv.GetAllInstancesInfo()
446

    
447
    self.assertEqual(map(compat.fst, result), [
448
      "server01.example.com",
449
      "web3106215069.example.com",
450
      "testinstance.example.com",
451
      ])
452

    
453
  def testListInstances(self):
454
    hv = self._GetHv(run_cmd=self._XenList)
455

    
456
    self.assertEqual(hv.ListInstances(), [
457
      "server01.example.com",
458
      "web3106215069.example.com",
459
      "testinstance.example.com",
460
      ])
461

    
462
  def testVerify(self):
463
    output = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
464
    hv = self._GetHv(run_cmd=compat.partial(self._SuccessCommand,
465
                                            output))
466
    self.assertTrue(hv.Verify() is None)
467

    
468
  def testVerifyFailing(self):
469
    hv = self._GetHv(run_cmd=self._FailingCommand)
470
    self.assertTrue("failed:" in hv.Verify())
471

    
472
  def _StartInstanceCommand(self, inst, paused, failcreate, cmd):
473
    if cmd == [self.CMD, "info"]:
474
      output = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
475
    elif cmd == [self.CMD, "list"]:
476
      output = testutils.ReadTestData("xen-xm-list-4.0.1-dom0-only.txt")
477
    elif cmd[:2] == [self.CMD, "create"]:
478
      args = cmd[2:]
479
      cfgfile = utils.PathJoin(self.tmpdir, inst.name)
480

    
481
      if paused:
482
        self.assertEqual(args, ["-p", cfgfile])
483
      else:
484
        self.assertEqual(args, [cfgfile])
485

    
486
      if failcreate:
487
        return self._FailingCommand(cmd)
488

    
489
      output = ""
490
    else:
491
      self.fail("Unhandled command: %s" % (cmd, ))
492

    
493
    return self._SuccessCommand(output, cmd)
494
    #return self._FailingCommand(cmd)
495

    
496
  def _MakeInstance(self):
497
    # Copy default parameters
498
    bep = objects.FillDict(constants.BEC_DEFAULTS, {})
499
    hvp = objects.FillDict(constants.HVC_DEFAULTS[self.HVNAME], {})
500

    
501
    # Override default VNC password file path
502
    if constants.HV_VNC_PASSWORD_FILE in hvp:
503
      hvp[constants.HV_VNC_PASSWORD_FILE] = self.vncpw_path
504

    
505
    disks = [
506
      (objects.Disk(dev_type=constants.LD_LV, mode=constants.DISK_RDWR),
507
       utils.PathJoin(self.tmpdir, "disk0")),
508
      (objects.Disk(dev_type=constants.LD_LV, mode=constants.DISK_RDONLY),
509
       utils.PathJoin(self.tmpdir, "disk1")),
510
      ]
511

    
512
    inst = objects.Instance(name="server01.example.com",
513
                            hvparams=hvp, beparams=bep,
514
                            osparams={}, nics=[], os="deb1",
515
                            disks=map(compat.fst, disks))
516
    inst.UpgradeConfig()
517

    
518
    return (inst, disks)
519

    
520
  def testStartInstance(self):
521
    (inst, disks) = self._MakeInstance()
522

    
523
    for failcreate in [False, True]:
524
      for paused in [False, True]:
525
        run_cmd = compat.partial(self._StartInstanceCommand,
526
                                 inst, paused, failcreate)
527

    
528
        hv = self._GetHv(run_cmd=run_cmd)
529

    
530
        # Ensure instance is not listed
531
        self.assertTrue(inst.name not in hv.ListInstances())
532

    
533
        # Remove configuration
534
        cfgfile = utils.PathJoin(self.tmpdir, inst.name)
535
        utils.RemoveFile(cfgfile)
536

    
537
        if failcreate:
538
          self.assertRaises(errors.HypervisorError, hv.StartInstance,
539
                            inst, disks, paused)
540
        else:
541
          hv.StartInstance(inst, disks, paused)
542

    
543
        # Check if configuration was updated
544
        lines = utils.ReadFile(cfgfile).splitlines()
545

    
546
        if constants.HV_VNC_PASSWORD_FILE in inst.hvparams:
547
          self.assertTrue(("vncpasswd = '%s'" % self.vncpw) in lines)
548
        else:
549
          extra = inst.hvparams[constants.HV_KERNEL_ARGS]
550
          self.assertTrue(("extra = '%s'" % extra) in lines)
551

    
552
  def _StopInstanceCommand(self, instance_name, force, fail, cmd):
553
    if ((force and cmd[:2] == [self.CMD, "destroy"]) or
554
        (not force and cmd[:2] == [self.CMD, "shutdown"])):
555
      self.assertEqual(cmd[2:], [instance_name])
556
      output = ""
557
    else:
558
      self.fail("Unhandled command: %s" % (cmd, ))
559

    
560
    if fail:
561
      # Simulate a failing command
562
      return self._FailingCommand(cmd)
563
    else:
564
      return self._SuccessCommand(output, cmd)
565

    
566
  def testStopInstance(self):
567
    name = "inst4284.example.com"
568
    cfgfile = utils.PathJoin(self.tmpdir, name)
569
    cfgdata = "config file content\n"
570

    
571
    for force in [False, True]:
572
      for fail in [False, True]:
573
        utils.WriteFile(cfgfile, data=cfgdata)
574

    
575
        run_cmd = compat.partial(self._StopInstanceCommand, name, force, fail)
576

    
577
        hv = self._GetHv(run_cmd=run_cmd)
578

    
579
        self.assertTrue(os.path.isfile(cfgfile))
580

    
581
        if fail:
582
          try:
583
            hv._StopInstance(name, force)
584
          except errors.HypervisorError, err:
585
            self.assertTrue(str(err).startswith("Failed to stop instance"))
586
          else:
587
            self.fail("Exception was not raised")
588
          self.assertEqual(utils.ReadFile(cfgfile), cfgdata,
589
                           msg=("Configuration was removed when stopping"
590
                                " instance failed"))
591
        else:
592
          hv._StopInstance(name, force)
593
          self.assertFalse(os.path.exists(cfgfile))
594

    
595
  def _MigrateNonRunningInstCmd(self, cmd):
596
    if cmd == [self.CMD, "list"]:
597
      output = testutils.ReadTestData("xen-xm-list-4.0.1-dom0-only.txt")
598
    else:
599
      self.fail("Unhandled command: %s" % (cmd, ))
600

    
601
    return self._SuccessCommand(output, cmd)
602

    
603
  def testMigrateInstanceNotRunning(self):
604
    name = "nonexistinginstance.example.com"
605
    target = constants.IP4_ADDRESS_LOCALHOST
606
    port = 14618
607

    
608
    hv = self._GetHv(run_cmd=self._MigrateNonRunningInstCmd)
609

    
610
    for live in [False, True]:
611
      try:
612
        hv._MigrateInstance(NotImplemented, name, target, port, live,
613
                            _ping_fn=NotImplemented)
614
      except errors.HypervisorError, err:
615
        self.assertEqual(str(err), "Instance not running, cannot migrate")
616
      else:
617
        self.fail("Exception was not raised")
618

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

    
625
    return self._SuccessCommand(output, cmd)
626

    
627
  def testMigrateTargetUnreachable(self):
628
    name = "server01.example.com"
629
    target = constants.IP4_ADDRESS_LOCALHOST
630
    port = 28349
631

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

    
634
    for live in [False, True]:
635
      if self.CMD == constants.XEN_CMD_XL:
636
        # TODO: Detect unreachable targets
637
        pass
638
      else:
639
        try:
640
          hv._MigrateInstance(NotImplemented, name, target, port, live,
641
                              _ping_fn=compat.partial(self._FakeTcpPing,
642
                                                      (target, port), False))
643
        except errors.HypervisorError, err:
644
          wanted = "Remote host %s not" % target
645
          self.assertTrue(str(err).startswith(wanted))
646
        else:
647
          self.fail("Exception was not raised")
648

    
649
  def _MigrateInstanceCmd(self, cluster_name, instance_name, target, port,
650
                          live, fail, cmd):
651
    if cmd == [self.CMD, "list"]:
652
      output = testutils.ReadTestData("xen-xm-list-4.0.1-four-instances.txt")
653
    elif cmd[:2] == [self.CMD, "migrate"]:
654
      if self.CMD == constants.XEN_CMD_XM:
655
        args = ["-p", str(port)]
656

    
657
        if live:
658
          args.append("-l")
659

    
660
      elif self.CMD == constants.XEN_CMD_XL:
661
        args = [
662
          "-s", constants.XL_SSH_CMD % cluster_name,
663
          "-C", utils.PathJoin(self.tmpdir, instance_name),
664
          ]
665

    
666
      else:
667
        self.fail("Unknown Xen command '%s'" % self.CMD)
668

    
669
      args.extend([instance_name, target])
670
      self.assertEqual(cmd[2:], args)
671

    
672
      if fail:
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 testMigrateInstance(self):
682
    clustername = "cluster.example.com"
683
    instname = "server01.example.com"
684
    target = constants.IP4_ADDRESS_LOCALHOST
685
    port = 22364
686

    
687
    for live in [False, True]:
688
      for fail in [False, True]:
689
        ping_fn = \
690
          testutils.CallCounter(compat.partial(self._FakeTcpPing,
691
                                               (target, port), True))
692

    
693
        run_cmd = \
694
          compat.partial(self._MigrateInstanceCmd,
695
                         clustername, instname, target, port, live,
696
                         fail)
697

    
698
        hv = self._GetHv(run_cmd=run_cmd)
699

    
700
        if fail:
701
          try:
702
            hv._MigrateInstance(clustername, instname, target, port, live,
703
                                _ping_fn=ping_fn)
704
          except errors.HypervisorError, err:
705
            self.assertTrue(str(err).startswith("Failed to migrate instance"))
706
          else:
707
            self.fail("Exception was not raised")
708
        else:
709
          hv._MigrateInstance(clustername, instname, target, port, live,
710
                              _ping_fn=ping_fn)
711

    
712
        if self.CMD == constants.XEN_CMD_XM:
713
          expected_pings = 1
714
        else:
715
          expected_pings = 0
716

    
717
        self.assertEqual(ping_fn.Count(), expected_pings)
718

    
719
  def _GetNodeInfoCmd(self, fail, cmd):
720
    if cmd == [self.CMD, "info"]:
721
      if fail:
722
        return self._FailingCommand(cmd)
723
      else:
724
        output = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
725
    elif cmd == [self.CMD, "list"]:
726
      if fail:
727
        self.fail("'xm list' shouldn't be called when 'xm info' failed")
728
      else:
729
        output = testutils.ReadTestData("xen-xm-list-4.0.1-four-instances.txt")
730
    else:
731
      self.fail("Unhandled command: %s" % (cmd, ))
732

    
733
    return self._SuccessCommand(output, cmd)
734

    
735
  def testGetNodeInfo(self):
736
    run_cmd = compat.partial(self._GetNodeInfoCmd, False)
737
    hv = self._GetHv(run_cmd=run_cmd)
738
    result = hv.GetNodeInfo()
739

    
740
    self.assertEqual(result["hv_version"], (4, 0))
741
    self.assertEqual(result["memory_free"], 8004)
742

    
743
  def testGetNodeInfoFailing(self):
744
    run_cmd = compat.partial(self._GetNodeInfoCmd, True)
745
    hv = self._GetHv(run_cmd=run_cmd)
746
    self.assertTrue(hv.GetNodeInfo() is None)
747

    
748

    
749
def _MakeTestClass(cls, cmd):
750
  """Makes a class for testing.
751

752
  The returned class has structure as shown in the following pseudo code:
753

754
    class Test{cls.__name__}{cmd}(_TestXenHypervisor, unittest.TestCase):
755
      TARGET = {cls}
756
      CMD = {cmd}
757
      HVNAME = {Hypervisor name retrieved using class}
758

759
  @type cls: class
760
  @param cls: Hypervisor class to be tested
761
  @type cmd: string
762
  @param cmd: Hypervisor command
763
  @rtype: tuple
764
  @return: Class name and class object (not instance)
765

766
  """
767
  name = "Test%sCmd%s" % (cls.__name__, cmd.title())
768
  bases = (_TestXenHypervisor, unittest.TestCase)
769
  hvname = HVCLASS_TO_HVNAME[cls]
770

    
771
  return (name, type(name, bases, dict(TARGET=cls, CMD=cmd, HVNAME=hvname)))
772

    
773

    
774
# Create test classes programmatically instead of manually to reduce the risk
775
# of forgetting some combinations
776
for cls in [hv_xen.XenPvmHypervisor, hv_xen.XenHvmHypervisor]:
777
  for cmd in constants.KNOWN_XEN_COMMANDS:
778
    (name, testcls) = _MakeTestClass(cls, cmd)
779

    
780
    assert name not in locals()
781

    
782
    locals()[name] = testcls
783

    
784

    
785
if __name__ == "__main__":
786
  testutils.GanetiTestProgram()