Statistics
| Branch: | Tag: | Revision:

root / test / py / ganeti.hypervisor.hv_xen_unittest.py @ 58e356a9

History | View | Annotate | Download (27.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
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 testDefault(self):
82
    expected_cmd = "xm"
83
    hv = hv_xen.XenHypervisor()
84
    self.assertEqual(hv._GetCommand(), expected_cmd)
85

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

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

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

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

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

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

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

    
117

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

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

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

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

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

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

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

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

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

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

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

    
162

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

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

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

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

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

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

    
190
    result = hv_xen._GetInstanceList(fn, True, _timeout=0.1)
191

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

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

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

    
203

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

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

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

    
228

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

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

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

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

    
264

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

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

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

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

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

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

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

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

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

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

    
338

    
339
class TestXenHypervisorRunXen(unittest.TestCase):
340

    
341
  XEN_SUB_CMD = "help"
342

    
343
  def testCommandUnknown(self):
344
    cmd = "#unknown command#"
345
    self.assertFalse(cmd in constants.KNOWN_XEN_COMMANDS)
346
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
347
                              _run_cmd_fn=NotImplemented,
348
                              _cmd=cmd)
349
    self.assertRaises(errors.ProgrammerError, hv._RunXen, [])
350

    
351
  def testCommandValid(self):
352
    xen_cmd = "xm"
353
    mock_run_cmd = mock.Mock()
354
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
355
                              _run_cmd_fn=mock_run_cmd)
356
    hv._RunXen([self.XEN_SUB_CMD])
357
    mock_run_cmd.assert_called_with([xen_cmd, self.XEN_SUB_CMD])
358

    
359
  def testCommandFromHvparams(self):
360
    expected_xen_cmd = "xl"
361
    hvparams = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
362
    mock_run_cmd = mock.Mock()
363
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
364
                              _run_cmd_fn=mock_run_cmd)
365
    hv._RunXen([self.XEN_SUB_CMD], hvparams=hvparams)
366
    mock_run_cmd.assert_called_with([expected_xen_cmd, self.XEN_SUB_CMD])
367

    
368

    
369
class TestXenHypervisorGetInstanceList(unittest.TestCase):
370

    
371
  RESULT_OK = utils.RunResult(0, None, "", "", "", None, None)
372
  XEN_LIST = "list"
373

    
374
  def testOk(self):
375
    expected_xen_cmd = "xm"
376
    mock_run_cmd = mock.Mock( return_value=self.RESULT_OK )
377
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
378
                              _run_cmd_fn=mock_run_cmd)
379
    hv._GetInstanceList(True)
380
    mock_run_cmd.assert_called_with([expected_xen_cmd, self.XEN_LIST])
381

    
382
  def testFromHvparams(self):
383
    expected_xen_cmd = "xl"
384
    hvparams = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
385
    mock_run_cmd = mock.Mock( return_value=self.RESULT_OK )
386
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
387
                              _run_cmd_fn=mock_run_cmd)
388
    hv._GetInstanceList(True, hvparams=hvparams)
389
    mock_run_cmd.assert_called_with([expected_xen_cmd, self.XEN_LIST])
390

    
391

    
392
class TestXenHypervisorListInstances(unittest.TestCase):
393

    
394
  RESULT_OK = utils.RunResult(0, None, "", "", "", None, None)
395
  XEN_LIST = "list"
396

    
397
  def testDefaultXm(self):
398
    expected_xen_cmd = "xm"
399
    mock_run_cmd = mock.Mock( return_value=self.RESULT_OK )
400
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
401
                              _run_cmd_fn=mock_run_cmd)
402
    hv.ListInstances()
403
    mock_run_cmd.assert_called_with([expected_xen_cmd, self.XEN_LIST])
404

    
405
  def testHvparamsXl(self):
406
    expected_xen_cmd = "xl"
407
    hvparams = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
408
    mock_run_cmd = mock.Mock( return_value=self.RESULT_OK )
409
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
410
                              _run_cmd_fn=mock_run_cmd)
411
    hv.ListInstances(hvparams=hvparams)
412
    mock_run_cmd.assert_called_with([expected_xen_cmd, self.XEN_LIST])
413

    
414

    
415
class TestXenHypervisorWriteConfigFile(unittest.TestCase):
416
  def setUp(self):
417
    self.tmpdir = tempfile.mkdtemp()
418

    
419
  def tearDown(self):
420
    shutil.rmtree(self.tmpdir)
421

    
422
  def testWriteError(self):
423
    cfgdir = utils.PathJoin(self.tmpdir, "foobar")
424

    
425
    hv = hv_xen.XenHypervisor(_cfgdir=cfgdir,
426
                              _run_cmd_fn=NotImplemented,
427
                              _cmd=NotImplemented)
428

    
429
    self.assertFalse(os.path.exists(cfgdir))
430

    
431
    try:
432
      hv._WriteConfigFile("name", "data")
433
    except errors.HypervisorError, err:
434
      self.assertTrue(str(err).startswith("Cannot write Xen instance"))
435
    else:
436
      self.fail("Exception was not raised")
437

    
438

    
439
class _TestXenHypervisor(object):
440
  TARGET = NotImplemented
441
  CMD = NotImplemented
442
  HVNAME = NotImplemented
443

    
444
  def setUp(self):
445
    super(_TestXenHypervisor, self).setUp()
446

    
447
    self.tmpdir = tempfile.mkdtemp()
448

    
449
    self.vncpw = "".join(random.sample(string.ascii_letters, 10))
450

    
451
    self.vncpw_path = utils.PathJoin(self.tmpdir, "vncpw")
452
    utils.WriteFile(self.vncpw_path, data=self.vncpw)
453

    
454
  def tearDown(self):
455
    super(_TestXenHypervisor, self).tearDown()
456

    
457
    shutil.rmtree(self.tmpdir)
458

    
459
  def _GetHv(self, run_cmd=NotImplemented):
460
    return self.TARGET(_cfgdir=self.tmpdir, _run_cmd_fn=run_cmd, _cmd=self.CMD)
461

    
462
  def _SuccessCommand(self, stdout, cmd):
463
    self.assertEqual(cmd[0], self.CMD)
464

    
465
    return utils.RunResult(constants.EXIT_SUCCESS, None, stdout, "", None,
466
                           NotImplemented, NotImplemented)
467

    
468
  def _FailingCommand(self, cmd):
469
    self.assertEqual(cmd[0], self.CMD)
470

    
471
    return utils.RunResult(constants.EXIT_FAILURE, None,
472
                           "", "This command failed", None,
473
                           NotImplemented, NotImplemented)
474

    
475
  def _FakeTcpPing(self, expected, result, target, port, **kwargs):
476
    self.assertEqual((target, port), expected)
477
    return result
478

    
479
  def testReadingNonExistentConfigFile(self):
480
    hv = self._GetHv()
481

    
482
    try:
483
      hv._ReadConfigFile("inst15780.example.com")
484
    except errors.HypervisorError, err:
485
      self.assertTrue(str(err).startswith("Failed to load Xen config file:"))
486
    else:
487
      self.fail("Exception was not raised")
488

    
489
  def testRemovingAutoConfigFile(self):
490
    name = "inst8206.example.com"
491
    cfgfile = utils.PathJoin(self.tmpdir, name)
492
    autodir = utils.PathJoin(self.tmpdir, "auto")
493
    autocfgfile = utils.PathJoin(autodir, name)
494

    
495
    os.mkdir(autodir)
496

    
497
    utils.WriteFile(autocfgfile, data="")
498

    
499
    hv = self._GetHv()
500

    
501
    self.assertTrue(os.path.isfile(autocfgfile))
502
    hv._WriteConfigFile(name, "content")
503
    self.assertFalse(os.path.exists(autocfgfile))
504
    self.assertEqual(utils.ReadFile(cfgfile), "content")
505

    
506
  def _XenList(self, cmd):
507
    self.assertEqual(cmd, [self.CMD, "list"])
508

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

    
512
    return self._SuccessCommand(output, cmd)
513

    
514
  def testGetInstanceInfo(self):
515
    hv = self._GetHv(run_cmd=self._XenList)
516

    
517
    (name, instid, memory, vcpus, state, runtime) = \
518
      hv.GetInstanceInfo("server01.example.com")
519

    
520
    self.assertEqual(name, "server01.example.com")
521
    self.assertEqual(instid, 1)
522
    self.assertEqual(memory, 1024)
523
    self.assertEqual(vcpus, 1)
524
    self.assertEqual(state, "-b----")
525
    self.assertAlmostEqual(runtime, 167643.2)
526

    
527
  def testGetInstanceInfoDom0(self):
528
    hv = self._GetHv(run_cmd=self._XenList)
529

    
530
    # TODO: Not sure if this is actually used anywhere (can't find it), but the
531
    # code supports querying for Dom0
532
    (name, instid, memory, vcpus, state, runtime) = \
533
      hv.GetInstanceInfo(hv_xen._DOM0_NAME)
534

    
535
    self.assertEqual(name, "Domain-0")
536
    self.assertEqual(instid, 0)
537
    self.assertEqual(memory, 1023)
538
    self.assertEqual(vcpus, 1)
539
    self.assertEqual(state, "r-----")
540
    self.assertAlmostEqual(runtime, 154706.1)
541

    
542
  def testGetInstanceInfoUnknown(self):
543
    hv = self._GetHv(run_cmd=self._XenList)
544

    
545
    result = hv.GetInstanceInfo("unknown.example.com")
546
    self.assertTrue(result is None)
547

    
548
  def testGetAllInstancesInfo(self):
549
    hv = self._GetHv(run_cmd=self._XenList)
550

    
551
    result = hv.GetAllInstancesInfo()
552

    
553
    self.assertEqual(map(compat.fst, result), [
554
      "server01.example.com",
555
      "web3106215069.example.com",
556
      "testinstance.example.com",
557
      ])
558

    
559
  def testListInstances(self):
560
    hv = self._GetHv(run_cmd=self._XenList)
561

    
562
    self.assertEqual(hv.ListInstances(), [
563
      "server01.example.com",
564
      "web3106215069.example.com",
565
      "testinstance.example.com",
566
      ])
567

    
568
  def testVerify(self):
569
    output = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
570
    hv = self._GetHv(run_cmd=compat.partial(self._SuccessCommand,
571
                                            output))
572
    self.assertTrue(hv.Verify() is None)
573

    
574
  def testVerifyFailing(self):
575
    hv = self._GetHv(run_cmd=self._FailingCommand)
576
    self.assertTrue("failed:" in hv.Verify())
577

    
578
  def _StartInstanceCommand(self, inst, paused, failcreate, cmd):
579
    if cmd == [self.CMD, "info"]:
580
      output = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
581
    elif cmd == [self.CMD, "list"]:
582
      output = testutils.ReadTestData("xen-xm-list-4.0.1-dom0-only.txt")
583
    elif cmd[:2] == [self.CMD, "create"]:
584
      args = cmd[2:]
585
      cfgfile = utils.PathJoin(self.tmpdir, inst.name)
586

    
587
      if paused:
588
        self.assertEqual(args, ["-p", cfgfile])
589
      else:
590
        self.assertEqual(args, [cfgfile])
591

    
592
      if failcreate:
593
        return self._FailingCommand(cmd)
594

    
595
      output = ""
596
    else:
597
      self.fail("Unhandled command: %s" % (cmd, ))
598

    
599
    return self._SuccessCommand(output, cmd)
600

    
601
  def _MakeInstance(self):
602
    # Copy default parameters
603
    bep = objects.FillDict(constants.BEC_DEFAULTS, {})
604
    hvp = objects.FillDict(constants.HVC_DEFAULTS[self.HVNAME], {})
605

    
606
    # Override default VNC password file path
607
    if constants.HV_VNC_PASSWORD_FILE in hvp:
608
      hvp[constants.HV_VNC_PASSWORD_FILE] = self.vncpw_path
609

    
610
    disks = [
611
      (objects.Disk(dev_type=constants.LD_LV, mode=constants.DISK_RDWR),
612
       utils.PathJoin(self.tmpdir, "disk0")),
613
      (objects.Disk(dev_type=constants.LD_LV, mode=constants.DISK_RDONLY),
614
       utils.PathJoin(self.tmpdir, "disk1")),
615
      ]
616

    
617
    inst = objects.Instance(name="server01.example.com",
618
                            hvparams=hvp, beparams=bep,
619
                            osparams={}, nics=[], os="deb1",
620
                            disks=map(compat.fst, disks))
621
    inst.UpgradeConfig()
622

    
623
    return (inst, disks)
624

    
625
  def testStartInstance(self):
626
    (inst, disks) = self._MakeInstance()
627
    pathutils.LOG_XEN_DIR = self.tmpdir
628

    
629
    for failcreate in [False, True]:
630
      for paused in [False, True]:
631
        run_cmd = compat.partial(self._StartInstanceCommand,
632
                                 inst, paused, failcreate)
633

    
634
        hv = self._GetHv(run_cmd=run_cmd)
635

    
636
        # Ensure instance is not listed
637
        self.assertTrue(inst.name not in hv.ListInstances())
638

    
639
        # Remove configuration
640
        cfgfile = utils.PathJoin(self.tmpdir, inst.name)
641
        utils.RemoveFile(cfgfile)
642

    
643
        if failcreate:
644
          self.assertRaises(errors.HypervisorError, hv.StartInstance,
645
                            inst, disks, paused)
646
          # Check whether a stale config file is left behind
647
          self.assertFalse(os.path.exists(cfgfile))
648
        else:
649
          hv.StartInstance(inst, disks, paused)
650
          # Check if configuration was updated
651
          lines = utils.ReadFile(cfgfile).splitlines()
652

    
653
        if constants.HV_VNC_PASSWORD_FILE in inst.hvparams:
654
          self.assertTrue(("vncpasswd = '%s'" % self.vncpw) in lines)
655
        else:
656
          extra = inst.hvparams[constants.HV_KERNEL_ARGS]
657
          self.assertTrue(("extra = '%s'" % extra) in lines)
658

    
659
  def _StopInstanceCommand(self, instance_name, force, fail, cmd):
660
    if ((force and cmd[:2] == [self.CMD, "destroy"]) or
661
        (not force and cmd[:2] == [self.CMD, "shutdown"])):
662
      self.assertEqual(cmd[2:], [instance_name])
663
      output = ""
664
    else:
665
      self.fail("Unhandled command: %s" % (cmd, ))
666

    
667
    if fail:
668
      # Simulate a failing command
669
      return self._FailingCommand(cmd)
670
    else:
671
      return self._SuccessCommand(output, cmd)
672

    
673
  def testStopInstance(self):
674
    name = "inst4284.example.com"
675
    cfgfile = utils.PathJoin(self.tmpdir, name)
676
    cfgdata = "config file content\n"
677

    
678
    for force in [False, True]:
679
      for fail in [False, True]:
680
        utils.WriteFile(cfgfile, data=cfgdata)
681

    
682
        run_cmd = compat.partial(self._StopInstanceCommand, name, force, fail)
683

    
684
        hv = self._GetHv(run_cmd=run_cmd)
685

    
686
        self.assertTrue(os.path.isfile(cfgfile))
687

    
688
        if fail:
689
          try:
690
            hv._StopInstance(name, force, None)
691
          except errors.HypervisorError, err:
692
            self.assertTrue(str(err).startswith("Failed to stop instance"))
693
          else:
694
            self.fail("Exception was not raised")
695
          self.assertEqual(utils.ReadFile(cfgfile), cfgdata,
696
                           msg=("Configuration was removed when stopping"
697
                                " instance failed"))
698
        else:
699
          hv._StopInstance(name, force, None)
700
          self.assertFalse(os.path.exists(cfgfile))
701

    
702
  def _MigrateNonRunningInstCmd(self, cmd):
703
    if cmd == [self.CMD, "list"]:
704
      output = testutils.ReadTestData("xen-xm-list-4.0.1-dom0-only.txt")
705
    else:
706
      self.fail("Unhandled command: %s" % (cmd, ))
707

    
708
    return self._SuccessCommand(output, cmd)
709

    
710
  def testMigrateInstanceNotRunning(self):
711
    name = "nonexistinginstance.example.com"
712
    target = constants.IP4_ADDRESS_LOCALHOST
713
    port = 14618
714

    
715
    hv = self._GetHv(run_cmd=self._MigrateNonRunningInstCmd)
716

    
717
    for live in [False, True]:
718
      try:
719
        hv._MigrateInstance(NotImplemented, name, target, port, live,
720
                            _ping_fn=NotImplemented)
721
      except errors.HypervisorError, err:
722
        self.assertEqual(str(err), "Instance not running, cannot migrate")
723
      else:
724
        self.fail("Exception was not raised")
725

    
726
  def _MigrateInstTargetUnreachCmd(self, cmd):
727
    if cmd == [self.CMD, "list"]:
728
      output = testutils.ReadTestData("xen-xm-list-4.0.1-four-instances.txt")
729
    else:
730
      self.fail("Unhandled command: %s" % (cmd, ))
731

    
732
    return self._SuccessCommand(output, cmd)
733

    
734
  def testMigrateTargetUnreachable(self):
735
    name = "server01.example.com"
736
    target = constants.IP4_ADDRESS_LOCALHOST
737
    port = 28349
738

    
739
    hv = self._GetHv(run_cmd=self._MigrateInstTargetUnreachCmd)
740

    
741
    for live in [False, True]:
742
      if self.CMD == constants.XEN_CMD_XL:
743
        # TODO: Detect unreachable targets
744
        pass
745
      else:
746
        try:
747
          hv._MigrateInstance(NotImplemented, name, target, port, live,
748
                              _ping_fn=compat.partial(self._FakeTcpPing,
749
                                                      (target, port), False))
750
        except errors.HypervisorError, err:
751
          wanted = "Remote host %s not" % target
752
          self.assertTrue(str(err).startswith(wanted))
753
        else:
754
          self.fail("Exception was not raised")
755

    
756
  def _MigrateInstanceCmd(self, cluster_name, instance_name, target, port,
757
                          live, fail, cmd):
758
    if cmd == [self.CMD, "list"]:
759
      output = testutils.ReadTestData("xen-xm-list-4.0.1-four-instances.txt")
760
    elif cmd[:2] == [self.CMD, "migrate"]:
761
      if self.CMD == constants.XEN_CMD_XM:
762
        args = ["-p", str(port)]
763

    
764
        if live:
765
          args.append("-l")
766

    
767
      elif self.CMD == constants.XEN_CMD_XL:
768
        args = [
769
          "-s", constants.XL_SSH_CMD % cluster_name,
770
          "-C", utils.PathJoin(self.tmpdir, instance_name),
771
          ]
772

    
773
      else:
774
        self.fail("Unknown Xen command '%s'" % self.CMD)
775

    
776
      args.extend([instance_name, target])
777
      self.assertEqual(cmd[2:], args)
778

    
779
      if fail:
780
        return self._FailingCommand(cmd)
781

    
782
      output = ""
783
    else:
784
      self.fail("Unhandled command: %s" % (cmd, ))
785

    
786
    return self._SuccessCommand(output, cmd)
787

    
788
  def testMigrateInstance(self):
789
    clustername = "cluster.example.com"
790
    instname = "server01.example.com"
791
    target = constants.IP4_ADDRESS_LOCALHOST
792
    port = 22364
793

    
794
    for live in [False, True]:
795
      for fail in [False, True]:
796
        ping_fn = \
797
          testutils.CallCounter(compat.partial(self._FakeTcpPing,
798
                                               (target, port), True))
799

    
800
        run_cmd = \
801
          compat.partial(self._MigrateInstanceCmd,
802
                         clustername, instname, target, port, live,
803
                         fail)
804

    
805
        hv = self._GetHv(run_cmd=run_cmd)
806

    
807
        if fail:
808
          try:
809
            hv._MigrateInstance(clustername, instname, target, port, live,
810
                                _ping_fn=ping_fn)
811
          except errors.HypervisorError, err:
812
            self.assertTrue(str(err).startswith("Failed to migrate instance"))
813
          else:
814
            self.fail("Exception was not raised")
815
        else:
816
          hv._MigrateInstance(clustername, instname, target, port, live,
817
                              _ping_fn=ping_fn)
818

    
819
        if self.CMD == constants.XEN_CMD_XM:
820
          expected_pings = 1
821
        else:
822
          expected_pings = 0
823

    
824
        self.assertEqual(ping_fn.Count(), expected_pings)
825

    
826
  def _GetNodeInfoCmd(self, fail, cmd):
827
    if cmd == [self.CMD, "info"]:
828
      if fail:
829
        return self._FailingCommand(cmd)
830
      else:
831
        output = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
832
    elif cmd == [self.CMD, "list"]:
833
      if fail:
834
        self.fail("'xm list' shouldn't be called when 'xm info' failed")
835
      else:
836
        output = testutils.ReadTestData("xen-xm-list-4.0.1-four-instances.txt")
837
    else:
838
      self.fail("Unhandled command: %s" % (cmd, ))
839

    
840
    return self._SuccessCommand(output, cmd)
841

    
842
  def testGetNodeInfo(self):
843
    run_cmd = compat.partial(self._GetNodeInfoCmd, False)
844
    hv = self._GetHv(run_cmd=run_cmd)
845
    result = hv.GetNodeInfo()
846

    
847
    self.assertEqual(result["hv_version"], (4, 0))
848
    self.assertEqual(result["memory_free"], 8004)
849

    
850
  def testGetNodeInfoFailing(self):
851
    run_cmd = compat.partial(self._GetNodeInfoCmd, True)
852
    hv = self._GetHv(run_cmd=run_cmd)
853
    self.assertTrue(hv.GetNodeInfo() is None)
854

    
855

    
856
def _MakeTestClass(cls, cmd):
857
  """Makes a class for testing.
858

859
  The returned class has structure as shown in the following pseudo code:
860

861
    class Test{cls.__name__}{cmd}(_TestXenHypervisor, unittest.TestCase):
862
      TARGET = {cls}
863
      CMD = {cmd}
864
      HVNAME = {Hypervisor name retrieved using class}
865

866
  @type cls: class
867
  @param cls: Hypervisor class to be tested
868
  @type cmd: string
869
  @param cmd: Hypervisor command
870
  @rtype: tuple
871
  @return: Class name and class object (not instance)
872

873
  """
874
  name = "Test%sCmd%s" % (cls.__name__, cmd.title())
875
  bases = (_TestXenHypervisor, unittest.TestCase)
876
  hvname = HVCLASS_TO_HVNAME[cls]
877

    
878
  return (name, type(name, bases, dict(TARGET=cls, CMD=cmd, HVNAME=hvname)))
879

    
880

    
881
# Create test classes programmatically instead of manually to reduce the risk
882
# of forgetting some combinations
883
for cls in [hv_xen.XenPvmHypervisor, hv_xen.XenHvmHypervisor]:
884
  for cmd in constants.KNOWN_XEN_COMMANDS:
885
    (name, testcls) = _MakeTestClass(cls, cmd)
886

    
887
    assert name not in locals()
888

    
889
    locals()[name] = testcls
890

    
891

    
892
if __name__ == "__main__":
893
  testutils.GanetiTestProgram()