Statistics
| Branch: | Tag: | Revision:

root / test / py / ganeti.hypervisor.hv_xen_unittest.py @ 51a95d00

History | View | Annotate | Download (25.4 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
  def testCommandHvparams(self):
101
    expected_cmd = "xl"
102
    test_hvparams = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
103
    hv = hv_xen.XenHypervisor()
104
    self.assertEqual(hv._GetCommand(hvparams=test_hvparams), expected_cmd)
105

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

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

    
116

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

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

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

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

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

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

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

    
141
    # State
142
    self.assertEqual(result[0][4], "r-----")
143

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

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

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

    
161

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

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

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

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

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

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

    
189
    result = hv_xen._GetXmList(fn, True, _timeout=0.1)
190

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

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

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

    
202

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

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

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

    
227

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

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

    
241
  def testMissingNodeInfo(self):
242
    result = hv_xen._MergeInstanceInfo({}, self._FakeXmList)
243
    self.assertEqual(result, {
244
      "memory_dom0": 4096,
245
      "dom0_cpus": 7,
246
      })
247

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

    
263

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

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

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

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

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

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

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

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

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

    
335
    self.assertRaises(KeyError, hv_xen._GetConfigFileDiskData, disks, "sd")
336

    
337

    
338
class TestXenHypervisorUnknownCommand(unittest.TestCase):
339
  def test(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, [])
346

    
347

    
348
class TestXenHypervisorWriteConfigFile(unittest.TestCase):
349
  def setUp(self):
350
    self.tmpdir = tempfile.mkdtemp()
351

    
352
  def tearDown(self):
353
    shutil.rmtree(self.tmpdir)
354

    
355
  def testWriteError(self):
356
    cfgdir = utils.PathJoin(self.tmpdir, "foobar")
357

    
358
    hv = hv_xen.XenHypervisor(_cfgdir=cfgdir,
359
                              _run_cmd_fn=NotImplemented,
360
                              _cmd=NotImplemented)
361

    
362
    self.assertFalse(os.path.exists(cfgdir))
363

    
364
    try:
365
      hv._WriteConfigFile("name", "data")
366
    except errors.HypervisorError, err:
367
      self.assertTrue(str(err).startswith("Cannot write Xen instance"))
368
    else:
369
      self.fail("Exception was not raised")
370

    
371

    
372
class _TestXenHypervisor(object):
373
  TARGET = NotImplemented
374
  CMD = NotImplemented
375
  HVNAME = NotImplemented
376

    
377
  def setUp(self):
378
    super(_TestXenHypervisor, self).setUp()
379

    
380
    self.tmpdir = tempfile.mkdtemp()
381

    
382
    self.vncpw = "".join(random.sample(string.ascii_letters, 10))
383

    
384
    self.vncpw_path = utils.PathJoin(self.tmpdir, "vncpw")
385
    utils.WriteFile(self.vncpw_path, data=self.vncpw)
386

    
387
  def tearDown(self):
388
    super(_TestXenHypervisor, self).tearDown()
389

    
390
    shutil.rmtree(self.tmpdir)
391

    
392
  def _GetHv(self, run_cmd=NotImplemented):
393
    return self.TARGET(_cfgdir=self.tmpdir, _run_cmd_fn=run_cmd, _cmd=self.CMD)
394

    
395
  def _SuccessCommand(self, stdout, cmd):
396
    self.assertEqual(cmd[0], self.CMD)
397

    
398
    return utils.RunResult(constants.EXIT_SUCCESS, None, stdout, "", None,
399
                           NotImplemented, NotImplemented)
400

    
401
  def _FailingCommand(self, cmd):
402
    self.assertEqual(cmd[0], self.CMD)
403

    
404
    return utils.RunResult(constants.EXIT_FAILURE, None,
405
                           "", "This command failed", None,
406
                           NotImplemented, NotImplemented)
407

    
408
  def _FakeTcpPing(self, expected, result, target, port, **kwargs):
409
    self.assertEqual((target, port), expected)
410
    return result
411

    
412
  def testReadingNonExistentConfigFile(self):
413
    hv = self._GetHv()
414

    
415
    try:
416
      hv._ReadConfigFile("inst15780.example.com")
417
    except errors.HypervisorError, err:
418
      self.assertTrue(str(err).startswith("Failed to load Xen config file:"))
419
    else:
420
      self.fail("Exception was not raised")
421

    
422
  def testRemovingAutoConfigFile(self):
423
    name = "inst8206.example.com"
424
    cfgfile = utils.PathJoin(self.tmpdir, name)
425
    autodir = utils.PathJoin(self.tmpdir, "auto")
426
    autocfgfile = utils.PathJoin(autodir, name)
427

    
428
    os.mkdir(autodir)
429

    
430
    utils.WriteFile(autocfgfile, data="")
431

    
432
    hv = self._GetHv()
433

    
434
    self.assertTrue(os.path.isfile(autocfgfile))
435
    hv._WriteConfigFile(name, "content")
436
    self.assertFalse(os.path.exists(autocfgfile))
437
    self.assertEqual(utils.ReadFile(cfgfile), "content")
438

    
439
  def _XenList(self, cmd):
440
    self.assertEqual(cmd, [self.CMD, "list"])
441

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

    
445
    return self._SuccessCommand(output, cmd)
446

    
447
  def testGetInstanceInfo(self):
448
    hv = self._GetHv(run_cmd=self._XenList)
449

    
450
    (name, instid, memory, vcpus, state, runtime) = \
451
      hv.GetInstanceInfo("server01.example.com")
452

    
453
    self.assertEqual(name, "server01.example.com")
454
    self.assertEqual(instid, 1)
455
    self.assertEqual(memory, 1024)
456
    self.assertEqual(vcpus, 1)
457
    self.assertEqual(state, "-b----")
458
    self.assertAlmostEqual(runtime, 167643.2)
459

    
460
  def testGetInstanceInfoDom0(self):
461
    hv = self._GetHv(run_cmd=self._XenList)
462

    
463
    # TODO: Not sure if this is actually used anywhere (can't find it), but the
464
    # code supports querying for Dom0
465
    (name, instid, memory, vcpus, state, runtime) = \
466
      hv.GetInstanceInfo(hv_xen._DOM0_NAME)
467

    
468
    self.assertEqual(name, "Domain-0")
469
    self.assertEqual(instid, 0)
470
    self.assertEqual(memory, 1023)
471
    self.assertEqual(vcpus, 1)
472
    self.assertEqual(state, "r-----")
473
    self.assertAlmostEqual(runtime, 154706.1)
474

    
475
  def testGetInstanceInfoUnknown(self):
476
    hv = self._GetHv(run_cmd=self._XenList)
477

    
478
    result = hv.GetInstanceInfo("unknown.example.com")
479
    self.assertTrue(result is None)
480

    
481
  def testGetAllInstancesInfo(self):
482
    hv = self._GetHv(run_cmd=self._XenList)
483

    
484
    result = hv.GetAllInstancesInfo()
485

    
486
    self.assertEqual(map(compat.fst, result), [
487
      "server01.example.com",
488
      "web3106215069.example.com",
489
      "testinstance.example.com",
490
      ])
491

    
492
  def testListInstances(self):
493
    hv = self._GetHv(run_cmd=self._XenList)
494

    
495
    self.assertEqual(hv.ListInstances(), [
496
      "server01.example.com",
497
      "web3106215069.example.com",
498
      "testinstance.example.com",
499
      ])
500

    
501
  def testVerify(self):
502
    output = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
503
    hv = self._GetHv(run_cmd=compat.partial(self._SuccessCommand,
504
                                            output))
505
    self.assertTrue(hv.Verify() is None)
506

    
507
  def testVerifyFailing(self):
508
    hv = self._GetHv(run_cmd=self._FailingCommand)
509
    self.assertTrue("failed:" in hv.Verify())
510

    
511
  def _StartInstanceCommand(self, inst, paused, failcreate, cmd):
512
    if cmd == [self.CMD, "info"]:
513
      output = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
514
    elif cmd == [self.CMD, "list"]:
515
      output = testutils.ReadTestData("xen-xm-list-4.0.1-dom0-only.txt")
516
    elif cmd[:2] == [self.CMD, "create"]:
517
      args = cmd[2:]
518
      cfgfile = utils.PathJoin(self.tmpdir, inst.name)
519

    
520
      if paused:
521
        self.assertEqual(args, ["-p", cfgfile])
522
      else:
523
        self.assertEqual(args, [cfgfile])
524

    
525
      if failcreate:
526
        return self._FailingCommand(cmd)
527

    
528
      output = ""
529
    else:
530
      self.fail("Unhandled command: %s" % (cmd, ))
531

    
532
    return self._SuccessCommand(output, cmd)
533

    
534
  def _MakeInstance(self):
535
    # Copy default parameters
536
    bep = objects.FillDict(constants.BEC_DEFAULTS, {})
537
    hvp = objects.FillDict(constants.HVC_DEFAULTS[self.HVNAME], {})
538

    
539
    # Override default VNC password file path
540
    if constants.HV_VNC_PASSWORD_FILE in hvp:
541
      hvp[constants.HV_VNC_PASSWORD_FILE] = self.vncpw_path
542

    
543
    disks = [
544
      (objects.Disk(dev_type=constants.LD_LV, mode=constants.DISK_RDWR),
545
       utils.PathJoin(self.tmpdir, "disk0")),
546
      (objects.Disk(dev_type=constants.LD_LV, mode=constants.DISK_RDONLY),
547
       utils.PathJoin(self.tmpdir, "disk1")),
548
      ]
549

    
550
    inst = objects.Instance(name="server01.example.com",
551
                            hvparams=hvp, beparams=bep,
552
                            osparams={}, nics=[], os="deb1",
553
                            disks=map(compat.fst, disks))
554
    inst.UpgradeConfig()
555

    
556
    return (inst, disks)
557

    
558
  def testStartInstance(self):
559
    (inst, disks) = self._MakeInstance()
560
    pathutils.LOG_XEN_DIR = self.tmpdir
561

    
562
    for failcreate in [False, True]:
563
      for paused in [False, True]:
564
        run_cmd = compat.partial(self._StartInstanceCommand,
565
                                 inst, paused, failcreate)
566

    
567
        hv = self._GetHv(run_cmd=run_cmd)
568

    
569
        # Ensure instance is not listed
570
        self.assertTrue(inst.name not in hv.ListInstances())
571

    
572
        # Remove configuration
573
        cfgfile = utils.PathJoin(self.tmpdir, inst.name)
574
        utils.RemoveFile(cfgfile)
575

    
576
        if failcreate:
577
          self.assertRaises(errors.HypervisorError, hv.StartInstance,
578
                            inst, disks, paused)
579
          # Check whether a stale config file is left behind
580
          self.assertFalse(os.path.exists(cfgfile))
581
        else:
582
          hv.StartInstance(inst, disks, paused)
583
          # Check if configuration was updated
584
          lines = utils.ReadFile(cfgfile).splitlines()
585

    
586
        if constants.HV_VNC_PASSWORD_FILE in inst.hvparams:
587
          self.assertTrue(("vncpasswd = '%s'" % self.vncpw) in lines)
588
        else:
589
          extra = inst.hvparams[constants.HV_KERNEL_ARGS]
590
          self.assertTrue(("extra = '%s'" % extra) in lines)
591

    
592
  def _StopInstanceCommand(self, instance_name, force, fail, cmd):
593
    if ((force and cmd[:2] == [self.CMD, "destroy"]) or
594
        (not force and cmd[:2] == [self.CMD, "shutdown"])):
595
      self.assertEqual(cmd[2:], [instance_name])
596
      output = ""
597
    else:
598
      self.fail("Unhandled command: %s" % (cmd, ))
599

    
600
    if fail:
601
      # Simulate a failing command
602
      return self._FailingCommand(cmd)
603
    else:
604
      return self._SuccessCommand(output, cmd)
605

    
606
  def testStopInstance(self):
607
    name = "inst4284.example.com"
608
    cfgfile = utils.PathJoin(self.tmpdir, name)
609
    cfgdata = "config file content\n"
610

    
611
    for force in [False, True]:
612
      for fail in [False, True]:
613
        utils.WriteFile(cfgfile, data=cfgdata)
614

    
615
        run_cmd = compat.partial(self._StopInstanceCommand, name, force, fail)
616

    
617
        hv = self._GetHv(run_cmd=run_cmd)
618

    
619
        self.assertTrue(os.path.isfile(cfgfile))
620

    
621
        if fail:
622
          try:
623
            hv._StopInstance(name, force)
624
          except errors.HypervisorError, err:
625
            self.assertTrue(str(err).startswith("Failed to stop instance"))
626
          else:
627
            self.fail("Exception was not raised")
628
          self.assertEqual(utils.ReadFile(cfgfile), cfgdata,
629
                           msg=("Configuration was removed when stopping"
630
                                " instance failed"))
631
        else:
632
          hv._StopInstance(name, force)
633
          self.assertFalse(os.path.exists(cfgfile))
634

    
635
  def _MigrateNonRunningInstCmd(self, cmd):
636
    if cmd == [self.CMD, "list"]:
637
      output = testutils.ReadTestData("xen-xm-list-4.0.1-dom0-only.txt")
638
    else:
639
      self.fail("Unhandled command: %s" % (cmd, ))
640

    
641
    return self._SuccessCommand(output, cmd)
642

    
643
  def testMigrateInstanceNotRunning(self):
644
    name = "nonexistinginstance.example.com"
645
    target = constants.IP4_ADDRESS_LOCALHOST
646
    port = 14618
647

    
648
    hv = self._GetHv(run_cmd=self._MigrateNonRunningInstCmd)
649

    
650
    for live in [False, True]:
651
      try:
652
        hv._MigrateInstance(NotImplemented, name, target, port, live,
653
                            _ping_fn=NotImplemented)
654
      except errors.HypervisorError, err:
655
        self.assertEqual(str(err), "Instance not running, cannot migrate")
656
      else:
657
        self.fail("Exception was not raised")
658

    
659
  def _MigrateInstTargetUnreachCmd(self, cmd):
660
    if cmd == [self.CMD, "list"]:
661
      output = testutils.ReadTestData("xen-xm-list-4.0.1-four-instances.txt")
662
    else:
663
      self.fail("Unhandled command: %s" % (cmd, ))
664

    
665
    return self._SuccessCommand(output, cmd)
666

    
667
  def testMigrateTargetUnreachable(self):
668
    name = "server01.example.com"
669
    target = constants.IP4_ADDRESS_LOCALHOST
670
    port = 28349
671

    
672
    hv = self._GetHv(run_cmd=self._MigrateInstTargetUnreachCmd)
673

    
674
    for live in [False, True]:
675
      if self.CMD == constants.XEN_CMD_XL:
676
        # TODO: Detect unreachable targets
677
        pass
678
      else:
679
        try:
680
          hv._MigrateInstance(NotImplemented, name, target, port, live,
681
                              _ping_fn=compat.partial(self._FakeTcpPing,
682
                                                      (target, port), False))
683
        except errors.HypervisorError, err:
684
          wanted = "Remote host %s not" % target
685
          self.assertTrue(str(err).startswith(wanted))
686
        else:
687
          self.fail("Exception was not raised")
688

    
689
  def _MigrateInstanceCmd(self, cluster_name, instance_name, target, port,
690
                          live, fail, cmd):
691
    if cmd == [self.CMD, "list"]:
692
      output = testutils.ReadTestData("xen-xm-list-4.0.1-four-instances.txt")
693
    elif cmd[:2] == [self.CMD, "migrate"]:
694
      if self.CMD == constants.XEN_CMD_XM:
695
        args = ["-p", str(port)]
696

    
697
        if live:
698
          args.append("-l")
699

    
700
      elif self.CMD == constants.XEN_CMD_XL:
701
        args = [
702
          "-s", constants.XL_SSH_CMD % cluster_name,
703
          "-C", utils.PathJoin(self.tmpdir, instance_name),
704
          ]
705

    
706
      else:
707
        self.fail("Unknown Xen command '%s'" % self.CMD)
708

    
709
      args.extend([instance_name, target])
710
      self.assertEqual(cmd[2:], args)
711

    
712
      if fail:
713
        return self._FailingCommand(cmd)
714

    
715
      output = ""
716
    else:
717
      self.fail("Unhandled command: %s" % (cmd, ))
718

    
719
    return self._SuccessCommand(output, cmd)
720

    
721
  def testMigrateInstance(self):
722
    clustername = "cluster.example.com"
723
    instname = "server01.example.com"
724
    target = constants.IP4_ADDRESS_LOCALHOST
725
    port = 22364
726

    
727
    for live in [False, True]:
728
      for fail in [False, True]:
729
        ping_fn = \
730
          testutils.CallCounter(compat.partial(self._FakeTcpPing,
731
                                               (target, port), True))
732

    
733
        run_cmd = \
734
          compat.partial(self._MigrateInstanceCmd,
735
                         clustername, instname, target, port, live,
736
                         fail)
737

    
738
        hv = self._GetHv(run_cmd=run_cmd)
739

    
740
        if fail:
741
          try:
742
            hv._MigrateInstance(clustername, instname, target, port, live,
743
                                _ping_fn=ping_fn)
744
          except errors.HypervisorError, err:
745
            self.assertTrue(str(err).startswith("Failed to migrate instance"))
746
          else:
747
            self.fail("Exception was not raised")
748
        else:
749
          hv._MigrateInstance(clustername, instname, target, port, live,
750
                              _ping_fn=ping_fn)
751

    
752
        if self.CMD == constants.XEN_CMD_XM:
753
          expected_pings = 1
754
        else:
755
          expected_pings = 0
756

    
757
        self.assertEqual(ping_fn.Count(), expected_pings)
758

    
759
  def _GetNodeInfoCmd(self, fail, cmd):
760
    if cmd == [self.CMD, "info"]:
761
      if fail:
762
        return self._FailingCommand(cmd)
763
      else:
764
        output = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
765
    elif cmd == [self.CMD, "list"]:
766
      if fail:
767
        self.fail("'xm list' shouldn't be called when 'xm info' failed")
768
      else:
769
        output = testutils.ReadTestData("xen-xm-list-4.0.1-four-instances.txt")
770
    else:
771
      self.fail("Unhandled command: %s" % (cmd, ))
772

    
773
    return self._SuccessCommand(output, cmd)
774

    
775
  def testGetNodeInfo(self):
776
    run_cmd = compat.partial(self._GetNodeInfoCmd, False)
777
    hv = self._GetHv(run_cmd=run_cmd)
778
    result = hv.GetNodeInfo()
779

    
780
    self.assertEqual(result["hv_version"], (4, 0))
781
    self.assertEqual(result["memory_free"], 8004)
782

    
783
  def testGetNodeInfoFailing(self):
784
    run_cmd = compat.partial(self._GetNodeInfoCmd, True)
785
    hv = self._GetHv(run_cmd=run_cmd)
786
    self.assertTrue(hv.GetNodeInfo() is None)
787

    
788

    
789
def _MakeTestClass(cls, cmd):
790
  """Makes a class for testing.
791

792
  The returned class has structure as shown in the following pseudo code:
793

794
    class Test{cls.__name__}{cmd}(_TestXenHypervisor, unittest.TestCase):
795
      TARGET = {cls}
796
      CMD = {cmd}
797
      HVNAME = {Hypervisor name retrieved using class}
798

799
  @type cls: class
800
  @param cls: Hypervisor class to be tested
801
  @type cmd: string
802
  @param cmd: Hypervisor command
803
  @rtype: tuple
804
  @return: Class name and class object (not instance)
805

806
  """
807
  name = "Test%sCmd%s" % (cls.__name__, cmd.title())
808
  bases = (_TestXenHypervisor, unittest.TestCase)
809
  hvname = HVCLASS_TO_HVNAME[cls]
810

    
811
  return (name, type(name, bases, dict(TARGET=cls, CMD=cmd, HVNAME=hvname)))
812

    
813

    
814
# Create test classes programmatically instead of manually to reduce the risk
815
# of forgetting some combinations
816
for cls in [hv_xen.XenPvmHypervisor, hv_xen.XenHvmHypervisor]:
817
  for cmd in constants.KNOWN_XEN_COMMANDS:
818
    (name, testcls) = _MakeTestClass(cls, cmd)
819

    
820
    assert name not in locals()
821

    
822
    locals()[name] = testcls
823

    
824

    
825
if __name__ == "__main__":
826
  testutils.GanetiTestProgram()