Statistics
| Branch: | Tag: | Revision:

root / test / py / ganeti.hypervisor.hv_xen_unittest.py @ 14933c17

History | View | Annotate | Download (31.2 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
    hvparams = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
52
    for cls in [hv_xen.XenPvmHypervisor(), hv_xen.XenHvmHypervisor()]:
53
      instance = objects.Instance(name="xen.example.com",
54
                                  primary_node="node24828-uuid")
55
      node = objects.Node(name="node24828", uuid="node24828-uuid")
56
      cons = cls.GetInstanceConsole(instance, node, hvparams, {})
57
      self.assertTrue(cons.Validate())
58
      self.assertEqual(cons.kind, constants.CONS_SSH)
59
      self.assertEqual(cons.host, node.name)
60
      self.assertEqual(cons.command[-1], instance.name)
61

    
62

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

    
69
  def testAll(self):
70
    self.assertEqual(hv_xen._CreateConfigCpus(constants.CPU_PINNING_ALL),
71
                     None)
72

    
73
  def testOne(self):
74
    self.assertEqual(hv_xen._CreateConfigCpus("9"), "cpu = \"9\"")
75

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

    
81

    
82
class TestGetCommand(testutils.GanetiTestCase):
83
  def testCommandExplicit(self):
84
    """Test the case when the command is given as class parameter explicitly.
85

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

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

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

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

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

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

    
114

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

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

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

    
127
    # Name
128
    self.assertEqual(result[0][0], hv_xen._DOM0_NAME)
129

    
130
    # ID
131
    self.assertEqual(result[0][1], 0)
132

    
133
    # Memory
134
    self.assertEqual(result[0][2], 1023)
135

    
136
    # VCPUs
137
    self.assertEqual(result[0][3], 1)
138

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

    
142
    # Time
143
    self.assertAlmostEqual(result[0][5], 121152.6)
144

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

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

    
159

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

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

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

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

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

    
185
    fn = testutils.CallCounter(compat.partial(self._Success, data))
186

    
187
    result = hv_xen._GetInstanceList(fn, True, _timeout=0.1)
188

    
189
    self.assertEqual(len(result), 4)
190

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

    
198
    self.assertEqual(fn.Count(), 1)
199

    
200

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

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

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

    
225

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

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

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

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

    
262

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

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

    
270
  def testManyDisks(self):
271
    for offset in [0, 1, 10]:
272
      disks = [(objects.Disk(dev_type=constants.DT_PLAIN),
273
               "/tmp/disk/%s" % idx,
274
               NotImplemented)
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.DT_PLAIN, mode=constants.DISK_RDWR),
294
       "/tmp/diskFirst",
295
       NotImplemented),
296
      (objects.Disk(dev_type=constants.DT_PLAIN, mode=constants.DISK_RDONLY),
297
       "/tmp/diskLast",
298
       NotImplemented),
299
      ]
300

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

    
307
  def testFileDisks(self):
308
    disks = [
309
      (objects.Disk(dev_type=constants.DT_FILE, mode=constants.DISK_RDWR,
310
                    logical_id=[constants.FD_LOOP]),
311
       "/tmp/diskFirst",
312
       NotImplemented),
313
      (objects.Disk(dev_type=constants.DT_FILE, mode=constants.DISK_RDONLY,
314
                    logical_id=[constants.FD_BLKTAP]),
315
       "/tmp/diskTwo",
316
       NotImplemented),
317
      (objects.Disk(dev_type=constants.DT_FILE, mode=constants.DISK_RDWR,
318
                    logical_id=[constants.FD_LOOP]),
319
       "/tmp/diskThree",
320
       NotImplemented),
321
      (objects.Disk(dev_type=constants.DT_FILE, mode=constants.DISK_RDWR,
322
                    logical_id=[constants.FD_BLKTAP]),
323
       "/tmp/diskLast",
324
       NotImplemented),
325
      ]
326

    
327
    result = hv_xen._GetConfigFileDiskData(disks, "sd")
328
    self.assertEqual(result, [
329
      "'file:/tmp/diskFirst,sda,w'",
330
      "'tap:aio:/tmp/diskTwo,sdb,r'",
331
      "'file:/tmp/diskThree,sdc,w'",
332
      "'tap:aio:/tmp/diskLast,sdd,w'",
333
      ])
334

    
335
  def testInvalidFileDisk(self):
336
    disks = [
337
      (objects.Disk(dev_type=constants.DT_FILE, mode=constants.DISK_RDWR,
338
                    logical_id=["#unknown#"]),
339
       "/tmp/diskinvalid",
340
       NotImplemented),
341
      ]
342

    
343
    self.assertRaises(KeyError, hv_xen._GetConfigFileDiskData, disks, "sd")
344

    
345

    
346
class TestXenHypervisorRunXen(unittest.TestCase):
347

    
348
  XEN_SUB_CMD = "help"
349

    
350
  def testCommandUnknown(self):
351
    cmd = "#unknown command#"
352
    self.assertFalse(cmd in constants.KNOWN_XEN_COMMANDS)
353
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
354
                              _run_cmd_fn=NotImplemented,
355
                              _cmd=cmd)
356
    self.assertRaises(errors.ProgrammerError, hv._RunXen, [], None)
357

    
358
  def testCommandNoHvparams(self):
359
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
360
                              _run_cmd_fn=NotImplemented)
361
    hvparams = None
362
    self.assertRaises(errors.HypervisorError, hv._RunXen, [self.XEN_SUB_CMD],
363
                      hvparams)
364

    
365
  def testCommandFromHvparams(self):
366
    expected_xen_cmd = "xl"
367
    hvparams = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
368
    mock_run_cmd = mock.Mock()
369
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
370
                              _run_cmd_fn=mock_run_cmd)
371
    hv._RunXen([self.XEN_SUB_CMD], hvparams=hvparams)
372
    mock_run_cmd.assert_called_with([expected_xen_cmd, self.XEN_SUB_CMD])
373

    
374

    
375
class TestXenHypervisorGetInstanceList(unittest.TestCase):
376

    
377
  RESULT_OK = utils.RunResult(0, None, "", "", "", None, None)
378
  XEN_LIST = "list"
379

    
380
  def testNoHvparams(self):
381
    expected_xen_cmd = "xm"
382
    mock_run_cmd = mock.Mock( return_value=self.RESULT_OK )
383
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
384
                              _run_cmd_fn=mock_run_cmd)
385
    self.assertRaises(errors.HypervisorError, hv._GetInstanceList, True, None)
386

    
387
  def testFromHvparams(self):
388
    expected_xen_cmd = "xl"
389
    hvparams = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
390
    mock_run_cmd = mock.Mock( return_value=self.RESULT_OK )
391
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
392
                              _run_cmd_fn=mock_run_cmd)
393
    hv._GetInstanceList(True, hvparams)
394
    mock_run_cmd.assert_called_with([expected_xen_cmd, self.XEN_LIST])
395

    
396

    
397
class TestXenHypervisorListInstances(unittest.TestCase):
398

    
399
  RESULT_OK = utils.RunResult(0, None, "", "", "", None, None)
400
  XEN_LIST = "list"
401

    
402
  def testNoHvparams(self):
403
    expected_xen_cmd = "xm"
404
    mock_run_cmd = mock.Mock( return_value=self.RESULT_OK )
405
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
406
                              _run_cmd_fn=mock_run_cmd)
407
    self.assertRaises(errors.HypervisorError, hv.ListInstances)
408

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

    
418

    
419
class TestXenHypervisorCheckToolstack(unittest.TestCase):
420

    
421
  def setUp(self):
422
    self.tmpdir = tempfile.mkdtemp()
423
    self.cfg_name = "xen_config"
424
    self.cfg_path = utils.PathJoin(self.tmpdir, self.cfg_name)
425
    self.hv = hv_xen.XenHypervisor()
426

    
427
  def tearDown(self):
428
    shutil.rmtree(self.tmpdir)
429

    
430
  def testBinaryNotFound(self):
431
    RESULT_FAILED = utils.RunResult(1, None, "", "", "", None, None)
432
    mock_run_cmd = mock.Mock(return_value=RESULT_FAILED)
433
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
434
                              _run_cmd_fn=mock_run_cmd)
435
    result = hv._CheckToolstackBinary("xl")
436
    self.assertFalse(result)
437

    
438
  def testCheckToolstackXlConfigured(self):
439
    RESULT_OK = utils.RunResult(0, None, "", "", "", None, None)
440
    mock_run_cmd = mock.Mock(return_value=RESULT_OK)
441
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
442
                              _run_cmd_fn=mock_run_cmd)
443
    result = hv._CheckToolstackXlConfigured()
444
    self.assertTrue(result)
445

    
446
  def testCheckToolstackXlNotConfigured(self):
447
    RESULT_FAILED = utils.RunResult(
448
        1, None, "",
449
        "ERROR:  A different toolstack (xm) have been selected!",
450
        "", None, None)
451
    mock_run_cmd = mock.Mock(return_value=RESULT_FAILED)
452
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
453
                              _run_cmd_fn=mock_run_cmd)
454
    result = hv._CheckToolstackXlConfigured()
455
    self.assertFalse(result)
456

    
457
  def testCheckToolstackXlFails(self):
458
    RESULT_FAILED = utils.RunResult(
459
        1, None, "",
460
        "ERROR: The pink bunny hid the binary.",
461
        "", None, None)
462
    mock_run_cmd = mock.Mock(return_value=RESULT_FAILED)
463
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
464
                              _run_cmd_fn=mock_run_cmd)
465
    self.assertRaises(errors.HypervisorError, hv._CheckToolstackXlConfigured)
466

    
467

    
468
class TestXenHypervisorWriteConfigFile(unittest.TestCase):
469
  def setUp(self):
470
    self.tmpdir = tempfile.mkdtemp()
471

    
472
  def tearDown(self):
473
    shutil.rmtree(self.tmpdir)
474

    
475
  def testWriteError(self):
476
    cfgdir = utils.PathJoin(self.tmpdir, "foobar")
477

    
478
    hv = hv_xen.XenHypervisor(_cfgdir=cfgdir,
479
                              _run_cmd_fn=NotImplemented,
480
                              _cmd=NotImplemented)
481

    
482
    self.assertFalse(os.path.exists(cfgdir))
483

    
484
    try:
485
      hv._WriteConfigFile("name", "data")
486
    except errors.HypervisorError, err:
487
      self.assertTrue(str(err).startswith("Cannot write Xen instance"))
488
    else:
489
      self.fail("Exception was not raised")
490

    
491

    
492
class TestXenHypervisorVerify(unittest.TestCase):
493

    
494
  def setUp(self):
495
    output = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
496
    self._result_ok = utils.RunResult(0, None, output, "", "", None, None)
497

    
498
  def testVerify(self):
499
    hvparams = {constants.HV_XEN_CMD : constants.XEN_CMD_XL}
500
    mock_run_cmd = mock.Mock(return_value=self._result_ok)
501
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
502
                              _run_cmd_fn=mock_run_cmd)
503
    hv._CheckToolstack = mock.Mock(return_value=True)
504
    result = hv.Verify(hvparams)
505
    self.assertTrue(result is None)
506

    
507
  def testVerifyToolstackNotOk(self):
508
    hvparams = {constants.HV_XEN_CMD : constants.XEN_CMD_XL}
509
    mock_run_cmd = mock.Mock(return_value=self._result_ok)
510
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
511
                              _run_cmd_fn=mock_run_cmd)
512
    hv._CheckToolstack = mock.Mock()
513
    hv._CheckToolstack.side_effect = errors.HypervisorError("foo")
514
    result = hv.Verify(hvparams)
515
    self.assertTrue(result is not None)
516

    
517
  def testVerifyFailing(self):
518
    result_failed = utils.RunResult(1, None, "", "", "", None, None)
519
    mock_run_cmd = mock.Mock(return_value=result_failed)
520
    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
521
                              _run_cmd_fn=mock_run_cmd)
522
    hv._CheckToolstack = mock.Mock(return_value=True)
523
    result = hv.Verify()
524
    self.assertTrue(result is not None)
525

    
526

    
527
class _TestXenHypervisor(object):
528
  TARGET = NotImplemented
529
  CMD = NotImplemented
530
  HVNAME = NotImplemented
531
  VALID_HVPARAMS = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
532

    
533
  def setUp(self):
534
    super(_TestXenHypervisor, self).setUp()
535

    
536
    self.tmpdir = tempfile.mkdtemp()
537

    
538
    self.vncpw = "".join(random.sample(string.ascii_letters, 10))
539

    
540
    self.vncpw_path = utils.PathJoin(self.tmpdir, "vncpw")
541
    utils.WriteFile(self.vncpw_path, data=self.vncpw)
542

    
543
  def tearDown(self):
544
    super(_TestXenHypervisor, self).tearDown()
545

    
546
    shutil.rmtree(self.tmpdir)
547

    
548
  def _GetHv(self, run_cmd=NotImplemented):
549
    return self.TARGET(_cfgdir=self.tmpdir, _run_cmd_fn=run_cmd, _cmd=self.CMD)
550

    
551
  def _SuccessCommand(self, stdout, cmd):
552
    self.assertEqual(cmd[0], self.CMD)
553

    
554
    return utils.RunResult(constants.EXIT_SUCCESS, None, stdout, "", None,
555
                           NotImplemented, NotImplemented)
556

    
557
  def _FailingCommand(self, cmd):
558
    self.assertEqual(cmd[0], self.CMD)
559

    
560
    return utils.RunResult(constants.EXIT_FAILURE, None,
561
                           "", "This command failed", None,
562
                           NotImplemented, NotImplemented)
563

    
564
  def _FakeTcpPing(self, expected, result, target, port, **kwargs):
565
    self.assertEqual((target, port), expected)
566
    return result
567

    
568
  def testReadingNonExistentConfigFile(self):
569
    hv = self._GetHv()
570

    
571
    try:
572
      hv._ReadConfigFile("inst15780.example.com")
573
    except errors.HypervisorError, err:
574
      self.assertTrue(str(err).startswith("Failed to load Xen config file:"))
575
    else:
576
      self.fail("Exception was not raised")
577

    
578
  def testRemovingAutoConfigFile(self):
579
    name = "inst8206.example.com"
580
    cfgfile = utils.PathJoin(self.tmpdir, name)
581
    autodir = utils.PathJoin(self.tmpdir, "auto")
582
    autocfgfile = utils.PathJoin(autodir, name)
583

    
584
    os.mkdir(autodir)
585

    
586
    utils.WriteFile(autocfgfile, data="")
587

    
588
    hv = self._GetHv()
589

    
590
    self.assertTrue(os.path.isfile(autocfgfile))
591
    hv._WriteConfigFile(name, "content")
592
    self.assertFalse(os.path.exists(autocfgfile))
593
    self.assertEqual(utils.ReadFile(cfgfile), "content")
594

    
595
  def _XenList(self, cmd):
596
    self.assertEqual(cmd, [self.CMD, "list"])
597

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

    
601
    return self._SuccessCommand(output, cmd)
602

    
603
  def testGetInstanceInfo(self):
604
    hv = self._GetHv(run_cmd=self._XenList)
605

    
606
    (name, instid, memory, vcpus, state, runtime) = \
607
      hv.GetInstanceInfo("server01.example.com")
608

    
609
    self.assertEqual(name, "server01.example.com")
610
    self.assertEqual(instid, 1)
611
    self.assertEqual(memory, 1024)
612
    self.assertEqual(vcpus, 1)
613
    self.assertEqual(state, "-b----")
614
    self.assertAlmostEqual(runtime, 167643.2)
615

    
616
  def testGetInstanceInfoDom0(self):
617
    hv = self._GetHv(run_cmd=self._XenList)
618

    
619
    # TODO: Not sure if this is actually used anywhere (can't find it), but the
620
    # code supports querying for Dom0
621
    (name, instid, memory, vcpus, state, runtime) = \
622
      hv.GetInstanceInfo(hv_xen._DOM0_NAME)
623

    
624
    self.assertEqual(name, "Domain-0")
625
    self.assertEqual(instid, 0)
626
    self.assertEqual(memory, 1023)
627
    self.assertEqual(vcpus, 1)
628
    self.assertEqual(state, "r-----")
629
    self.assertAlmostEqual(runtime, 154706.1)
630

    
631
  def testGetInstanceInfoUnknown(self):
632
    hv = self._GetHv(run_cmd=self._XenList)
633

    
634
    result = hv.GetInstanceInfo("unknown.example.com")
635
    self.assertTrue(result is None)
636

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

    
640
    result = hv.GetAllInstancesInfo()
641

    
642
    self.assertEqual(map(compat.fst, result), [
643
      "server01.example.com",
644
      "web3106215069.example.com",
645
      "testinstance.example.com",
646
      ])
647

    
648
  def testListInstances(self):
649
    hv = self._GetHv(run_cmd=self._XenList)
650

    
651
    self.assertEqual(hv.ListInstances(), [
652
      "server01.example.com",
653
      "web3106215069.example.com",
654
      "testinstance.example.com",
655
      ])
656

    
657
  def _StartInstanceCommand(self, inst, paused, failcreate, cmd):
658
    if cmd == [self.CMD, "info"]:
659
      output = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
660
    elif cmd == [self.CMD, "list"]:
661
      output = testutils.ReadTestData("xen-xm-list-4.0.1-dom0-only.txt")
662
    elif cmd[:2] == [self.CMD, "create"]:
663
      args = cmd[2:]
664
      cfgfile = utils.PathJoin(self.tmpdir, inst.name)
665

    
666
      if paused:
667
        self.assertEqual(args, ["-p", cfgfile])
668
      else:
669
        self.assertEqual(args, [cfgfile])
670

    
671
      if failcreate:
672
        return self._FailingCommand(cmd)
673

    
674
      output = ""
675
    else:
676
      self.fail("Unhandled command: %s" % (cmd, ))
677

    
678
    return self._SuccessCommand(output, cmd)
679

    
680
  def _MakeInstance(self):
681
    # Copy default parameters
682
    bep = objects.FillDict(constants.BEC_DEFAULTS, {})
683
    hvp = objects.FillDict(constants.HVC_DEFAULTS[self.HVNAME], {})
684

    
685
    # Override default VNC password file path
686
    if constants.HV_VNC_PASSWORD_FILE in hvp:
687
      hvp[constants.HV_VNC_PASSWORD_FILE] = self.vncpw_path
688

    
689
    disks = [
690
      (objects.Disk(dev_type=constants.DT_PLAIN, mode=constants.DISK_RDWR),
691
       utils.PathJoin(self.tmpdir, "disk0"),
692
       NotImplemented),
693
      (objects.Disk(dev_type=constants.DT_PLAIN, mode=constants.DISK_RDONLY),
694
       utils.PathJoin(self.tmpdir, "disk1"),
695
       NotImplemented),
696
      ]
697

    
698
    inst = objects.Instance(name="server01.example.com",
699
                            hvparams=hvp, beparams=bep,
700
                            osparams={}, nics=[], os="deb1",
701
                            disks=map(compat.fst, disks))
702
    inst.UpgradeConfig()
703

    
704
    return (inst, disks)
705

    
706
  def testStartInstance(self):
707
    (inst, disks) = self._MakeInstance()
708
    pathutils.LOG_XEN_DIR = self.tmpdir
709

    
710
    for failcreate in [False, True]:
711
      for paused in [False, True]:
712
        run_cmd = compat.partial(self._StartInstanceCommand,
713
                                 inst, paused, failcreate)
714

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

    
717
        # Ensure instance is not listed
718
        self.assertTrue(inst.name not in hv.ListInstances())
719

    
720
        # Remove configuration
721
        cfgfile = utils.PathJoin(self.tmpdir, inst.name)
722
        utils.RemoveFile(cfgfile)
723

    
724
        if failcreate:
725
          self.assertRaises(errors.HypervisorError, hv.StartInstance,
726
                            inst, disks, paused)
727
          # Check whether a stale config file is left behind
728
          self.assertFalse(os.path.exists(cfgfile))
729
        else:
730
          hv.StartInstance(inst, disks, paused)
731
          # Check if configuration was updated
732
          lines = utils.ReadFile(cfgfile).splitlines()
733

    
734
        if constants.HV_VNC_PASSWORD_FILE in inst.hvparams:
735
          self.assertTrue(("vncpasswd = '%s'" % self.vncpw) in lines)
736
        else:
737
          extra = inst.hvparams[constants.HV_KERNEL_ARGS]
738
          self.assertTrue(("extra = '%s'" % extra) in lines)
739

    
740
  def _StopInstanceCommand(self, instance_name, force, fail, cmd):
741
    if ((force and cmd[:2] == [self.CMD, "destroy"]) or
742
        (not force and cmd[:2] == [self.CMD, "shutdown"])):
743
      self.assertEqual(cmd[2:], [instance_name])
744
      output = ""
745
    else:
746
      self.fail("Unhandled command: %s" % (cmd, ))
747

    
748
    if fail:
749
      # Simulate a failing command
750
      return self._FailingCommand(cmd)
751
    else:
752
      return self._SuccessCommand(output, cmd)
753

    
754
  def testStopInstance(self):
755
    name = "inst4284.example.com"
756
    cfgfile = utils.PathJoin(self.tmpdir, name)
757
    cfgdata = "config file content\n"
758

    
759
    for force in [False, True]:
760
      for fail in [False, True]:
761
        utils.WriteFile(cfgfile, data=cfgdata)
762

    
763
        run_cmd = compat.partial(self._StopInstanceCommand, name, force, fail)
764

    
765
        hv = self._GetHv(run_cmd=run_cmd)
766

    
767
        self.assertTrue(os.path.isfile(cfgfile))
768

    
769
        if fail:
770
          try:
771
            hv._StopInstance(name, force, None)
772
          except errors.HypervisorError, err:
773
            self.assertTrue(str(err).startswith("Failed to stop instance"))
774
          else:
775
            self.fail("Exception was not raised")
776
          self.assertEqual(utils.ReadFile(cfgfile), cfgdata,
777
                           msg=("Configuration was removed when stopping"
778
                                " instance failed"))
779
        else:
780
          hv._StopInstance(name, force, None)
781
          self.assertFalse(os.path.exists(cfgfile))
782

    
783
  def _MigrateNonRunningInstCmd(self, cmd):
784
    if cmd == [self.CMD, "list"]:
785
      output = testutils.ReadTestData("xen-xm-list-4.0.1-dom0-only.txt")
786
    else:
787
      self.fail("Unhandled command: %s" % (cmd, ))
788

    
789
    return self._SuccessCommand(output, cmd)
790

    
791
  def testMigrateInstanceNotRunning(self):
792
    name = "nonexistinginstance.example.com"
793
    target = constants.IP4_ADDRESS_LOCALHOST
794
    port = 14618
795

    
796
    hv = self._GetHv(run_cmd=self._MigrateNonRunningInstCmd)
797

    
798
    for live in [False, True]:
799
      try:
800
        hv._MigrateInstance(NotImplemented, name, target, port, live,
801
                            self.VALID_HVPARAMS, _ping_fn=NotImplemented)
802
      except errors.HypervisorError, err:
803
        self.assertEqual(str(err), "Instance not running, cannot migrate")
804
      else:
805
        self.fail("Exception was not raised")
806

    
807
  def _MigrateInstTargetUnreachCmd(self, cmd):
808
    if cmd == [self.CMD, "list"]:
809
      output = testutils.ReadTestData("xen-xm-list-4.0.1-four-instances.txt")
810
    else:
811
      self.fail("Unhandled command: %s" % (cmd, ))
812

    
813
    return self._SuccessCommand(output, cmd)
814

    
815
  def testMigrateTargetUnreachable(self):
816
    name = "server01.example.com"
817
    target = constants.IP4_ADDRESS_LOCALHOST
818
    port = 28349
819

    
820
    hv = self._GetHv(run_cmd=self._MigrateInstTargetUnreachCmd)
821
    hvparams = {constants.HV_XEN_CMD: self.CMD}
822

    
823
    for live in [False, True]:
824
      if self.CMD == constants.XEN_CMD_XL:
825
        # TODO: Detect unreachable targets
826
        pass
827
      else:
828
        try:
829
          hv._MigrateInstance(NotImplemented, name, target, port, live,
830
                              hvparams,
831
                              _ping_fn=compat.partial(self._FakeTcpPing,
832
                                                      (target, port), False))
833
        except errors.HypervisorError, err:
834
          wanted = "Remote host %s not" % target
835
          self.assertTrue(str(err).startswith(wanted))
836
        else:
837
          self.fail("Exception was not raised")
838

    
839
  def _MigrateInstanceCmd(self, cluster_name, instance_name, target, port,
840
                          live, fail, cmd):
841
    if cmd == [self.CMD, "list"]:
842
      output = testutils.ReadTestData("xen-xm-list-4.0.1-four-instances.txt")
843
    elif cmd[:2] == [self.CMD, "migrate"]:
844
      if self.CMD == constants.XEN_CMD_XM:
845
        args = ["-p", str(port)]
846

    
847
        if live:
848
          args.append("-l")
849

    
850
      elif self.CMD == constants.XEN_CMD_XL:
851
        args = [
852
          "-s", constants.XL_SSH_CMD % cluster_name,
853
          "-C", utils.PathJoin(self.tmpdir, instance_name),
854
          ]
855

    
856
      else:
857
        self.fail("Unknown Xen command '%s'" % self.CMD)
858

    
859
      args.extend([instance_name, target])
860
      self.assertEqual(cmd[2:], args)
861

    
862
      if fail:
863
        return self._FailingCommand(cmd)
864

    
865
      output = ""
866
    else:
867
      self.fail("Unhandled command: %s" % (cmd, ))
868

    
869
    return self._SuccessCommand(output, cmd)
870

    
871
  def testMigrateInstance(self):
872
    clustername = "cluster.example.com"
873
    instname = "server01.example.com"
874
    target = constants.IP4_ADDRESS_LOCALHOST
875
    port = 22364
876

    
877
    hvparams = {constants.HV_XEN_CMD: self.CMD}
878

    
879
    for live in [False, True]:
880
      for fail in [False, True]:
881
        ping_fn = \
882
          testutils.CallCounter(compat.partial(self._FakeTcpPing,
883
                                               (target, port), True))
884

    
885
        run_cmd = \
886
          compat.partial(self._MigrateInstanceCmd,
887
                         clustername, instname, target, port, live,
888
                         fail)
889

    
890
        hv = self._GetHv(run_cmd=run_cmd)
891

    
892
        if fail:
893
          try:
894
            hv._MigrateInstance(clustername, instname, target, port, live,
895
                                hvparams, _ping_fn=ping_fn)
896
          except errors.HypervisorError, err:
897
            self.assertTrue(str(err).startswith("Failed to migrate instance"))
898
          else:
899
            self.fail("Exception was not raised")
900
        else:
901
          hv._MigrateInstance(clustername, instname, target, port, live,
902
                              hvparams, _ping_fn=ping_fn)
903

    
904
        if self.CMD == constants.XEN_CMD_XM:
905
          expected_pings = 1
906
        else:
907
          expected_pings = 0
908

    
909
        self.assertEqual(ping_fn.Count(), expected_pings)
910

    
911
  def _GetNodeInfoCmd(self, fail, cmd):
912
    if cmd == [self.CMD, "info"]:
913
      if fail:
914
        return self._FailingCommand(cmd)
915
      else:
916
        output = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
917
    elif cmd == [self.CMD, "list"]:
918
      if fail:
919
        self.fail("'xm list' shouldn't be called when 'xm info' failed")
920
      else:
921
        output = testutils.ReadTestData("xen-xm-list-4.0.1-four-instances.txt")
922
    else:
923
      self.fail("Unhandled command: %s" % (cmd, ))
924

    
925
    return self._SuccessCommand(output, cmd)
926

    
927
  def testGetNodeInfo(self):
928
    run_cmd = compat.partial(self._GetNodeInfoCmd, False)
929
    hv = self._GetHv(run_cmd=run_cmd)
930
    result = hv.GetNodeInfo()
931

    
932
    self.assertEqual(result["hv_version"], (4, 0))
933
    self.assertEqual(result["memory_free"], 8004)
934

    
935
  def testGetNodeInfoFailing(self):
936
    run_cmd = compat.partial(self._GetNodeInfoCmd, True)
937
    hv = self._GetHv(run_cmd=run_cmd)
938
    self.assertTrue(hv.GetNodeInfo() is None)
939

    
940

    
941
def _MakeTestClass(cls, cmd):
942
  """Makes a class for testing.
943

944
  The returned class has structure as shown in the following pseudo code:
945

946
    class Test{cls.__name__}{cmd}(_TestXenHypervisor, unittest.TestCase):
947
      TARGET = {cls}
948
      CMD = {cmd}
949
      HVNAME = {Hypervisor name retrieved using class}
950

951
  @type cls: class
952
  @param cls: Hypervisor class to be tested
953
  @type cmd: string
954
  @param cmd: Hypervisor command
955
  @rtype: tuple
956
  @return: Class name and class object (not instance)
957

958
  """
959
  name = "Test%sCmd%s" % (cls.__name__, cmd.title())
960
  bases = (_TestXenHypervisor, unittest.TestCase)
961
  hvname = HVCLASS_TO_HVNAME[cls]
962

    
963
  return (name, type(name, bases, dict(TARGET=cls, CMD=cmd, HVNAME=hvname)))
964

    
965

    
966
# Create test classes programmatically instead of manually to reduce the risk
967
# of forgetting some combinations
968
for cls in [hv_xen.XenPvmHypervisor, hv_xen.XenHvmHypervisor]:
969
  for cmd in constants.KNOWN_XEN_COMMANDS:
970
    (name, testcls) = _MakeTestClass(cls, cmd)
971

    
972
    assert name not in locals()
973

    
974
    locals()[name] = testcls
975

    
976

    
977
if __name__ == "__main__":
978
  testutils.GanetiTestProgram()