Merge branch 'stable-2.8' into stable-2.9
[ganeti-local] / test / py / ganeti.hypervisor.hv_xen_unittest.py
index 18b94a1..0c88891 100755 (executable)
@@ -19,7 +19,7 @@
 # 02110-1301, USA.
 
 
-"""Script for testing ganeti.hypervisor.hv_lxc"""
+"""Script for testing ganeti.hypervisor.hv_xen"""
 
 import string # pylint: disable=W0402
 import unittest
@@ -27,9 +27,11 @@ import tempfile
 import shutil
 import random
 import os
+import mock
 
 from ganeti import constants
 from ganeti import objects
+from ganeti import pathutils
 from ganeti import hypervisor
 from ganeti import utils
 from ganeti import errors
@@ -46,13 +48,15 @@ HVCLASS_TO_HVNAME = utils.InvertDict(hypervisor._HYPERVISOR_MAP)
 
 class TestConsole(unittest.TestCase):
   def test(self):
-    for cls in [hv_xen.XenPvmHypervisor, hv_xen.XenHvmHypervisor]:
+    hvparams = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
+    for cls in [hv_xen.XenPvmHypervisor(), hv_xen.XenHvmHypervisor()]:
       instance = objects.Instance(name="xen.example.com",
-                                  primary_node="node24828")
-      cons = cls.GetInstanceConsole(instance, {}, {})
+                                  primary_node="node24828-uuid")
+      node = objects.Node(name="node24828", uuid="node24828-uuid")
+      cons = cls.GetInstanceConsole(instance, node, hvparams, {})
       self.assertTrue(cons.Validate())
       self.assertEqual(cons.kind, constants.CONS_SSH)
-      self.assertEqual(cons.host, instance.primary_node)
+      self.assertEqual(cons.host, node.name)
       self.assertEqual(cons.command[-1], instance.name)
 
 
@@ -75,15 +79,48 @@ class TestCreateConfigCpus(unittest.TestCase):
                       constants.CPU_PINNING_ALL_XEN))
 
 
-class TestParseXmList(testutils.GanetiTestCase):
+class TestGetCommand(testutils.GanetiTestCase):
+  def testCommandExplicit(self):
+    """Test the case when the command is given as class parameter explicitly.
+
+    """
+    expected_cmd = "xl"
+    hv = hv_xen.XenHypervisor(_cmd=constants.XEN_CMD_XL)
+    self.assertEqual(hv._GetCommand(None), expected_cmd)
+
+  def testCommandInvalid(self):
+    """Test the case an invalid command is given as class parameter explicitly.
+
+    """
+    hv = hv_xen.XenHypervisor(_cmd="invalidcommand")
+    self.assertRaises(errors.ProgrammerError, hv._GetCommand, None)
+
+  def testCommandHvparams(self):
+    expected_cmd = "xl"
+    test_hvparams = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
+    hv = hv_xen.XenHypervisor()
+    self.assertEqual(hv._GetCommand(test_hvparams), expected_cmd)
+
+  def testCommandHvparamsInvalid(self):
+    test_hvparams = {}
+    hv = hv_xen.XenHypervisor()
+    self.assertRaises(errors.HypervisorError, hv._GetCommand, test_hvparams)
+
+  def testCommandHvparamsCmdInvalid(self):
+    test_hvparams = {constants.HV_XEN_CMD: "invalidcommand"}
+    hv = hv_xen.XenHypervisor()
+    self.assertRaises(errors.ProgrammerError, hv._GetCommand, test_hvparams)
+
+
+class TestParseInstanceList(testutils.GanetiTestCase):
   def test(self):
     data = testutils.ReadTestData("xen-xm-list-4.0.1-dom0-only.txt")
 
     # Exclude node
-    self.assertEqual(hv_xen._ParseXmList(data.splitlines(), False), [])
+    self.assertEqual(hv_xen._ParseInstanceList(data.splitlines(), False), [])
 
     # Include node
-    result = hv_xen._ParseXmList(data.splitlines(), True)
+    result = hv_xen._ParseInstanceList(data.splitlines(), True)
     self.assertEqual(len(result), 1)
     self.assertEqual(len(result[0]), 6)
 
@@ -113,14 +150,14 @@ class TestParseXmList(testutils.GanetiTestCase):
 
     for lines in tests:
       try:
-        hv_xen._ParseXmList(["Header would be here"] + lines, False)
+        hv_xen._ParseInstanceList(["Header would be here"] + lines, False)
       except errors.HypervisorError, err:
-        self.assertTrue("Can't parse output of xm list" in str(err))
+        self.assertTrue("Can't parse instance list" in str(err))
       else:
         self.fail("Exception was not raised")
 
 
-class TestGetXmList(testutils.GanetiTestCase):
+class TestGetInstanceList(testutils.GanetiTestCase):
   def _Fail(self):
     return utils.RunResult(constants.EXIT_FAILURE, None,
                            "stdout", "stderr", None,
@@ -129,7 +166,7 @@ class TestGetXmList(testutils.GanetiTestCase):
   def testTimeout(self):
     fn = testutils.CallCounter(self._Fail)
     try:
-      hv_xen._GetXmList(fn, False, _timeout=0.1)
+      hv_xen._GetInstanceList(fn, False, _timeout=0.1)
     except errors.HypervisorError, err:
       self.assertTrue("timeout exceeded" in str(err))
     else:
@@ -147,7 +184,7 @@ class TestGetXmList(testutils.GanetiTestCase):
 
     fn = testutils.CallCounter(compat.partial(self._Success, data))
 
-    result = hv_xen._GetXmList(fn, True, _timeout=0.1)
+    result = hv_xen._GetInstanceList(fn, True, _timeout=0.1)
 
     self.assertEqual(len(result), 4)
 
@@ -188,10 +225,9 @@ class TestParseNodeInfo(testutils.GanetiTestCase):
 
 class TestMergeInstanceInfo(testutils.GanetiTestCase):
   def testEmpty(self):
-    self.assertEqual(hv_xen._MergeInstanceInfo({}, lambda _: []), {})
+    self.assertEqual(hv_xen._MergeInstanceInfo({}, []), {})
 
   def _FakeXmList(self, include_node):
-    self.assertTrue(include_node)
     return [
       (hv_xen._DOM0_NAME, NotImplemented, 4096, 7, NotImplemented,
        NotImplemented),
@@ -200,20 +236,22 @@ class TestMergeInstanceInfo(testutils.GanetiTestCase):
       ]
 
   def testMissingNodeInfo(self):
-    result = hv_xen._MergeInstanceInfo({}, self._FakeXmList)
+    instance_list = self._FakeXmList(True)
+    result = hv_xen._MergeInstanceInfo({}, instance_list)
     self.assertEqual(result, {
       "memory_dom0": 4096,
-      "dom0_cpus": 7,
+      "cpu_dom0": 7,
       })
 
   def testWithNodeInfo(self):
     info = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
-    result = hv_xen._GetNodeInfo(info, self._FakeXmList)
+    instance_list = self._FakeXmList(True)
+    result = hv_xen._GetNodeInfo(info, instance_list)
     self.assertEqual(result, {
       "cpu_nodes": 1,
       "cpu_sockets": 2,
       "cpu_total": 4,
-      "dom0_cpus": 7,
+      "cpu_dom0": 7,
       "hv_version": (4, 0),
       "memory_dom0": 4096,
       "memory_free": 8004,
@@ -231,7 +269,7 @@ class TestGetConfigFileDiskData(unittest.TestCase):
 
   def testManyDisks(self):
     for offset in [0, 1, 10]:
-      disks = [(objects.Disk(dev_type=constants.LD_LV), "/tmp/disk/%s" % idx)
+      disks = [(objects.Disk(dev_type=constants.DT_PLAIN), "/tmp/disk/%s" % idx)
                for idx in range(len(hv_xen._DISK_LETTERS) + offset)]
 
       if offset == 0:
@@ -250,9 +288,9 @@ class TestGetConfigFileDiskData(unittest.TestCase):
 
   def testTwoLvDisksWithMode(self):
     disks = [
-      (objects.Disk(dev_type=constants.LD_LV, mode=constants.DISK_RDWR),
+      (objects.Disk(dev_type=constants.DT_PLAIN, mode=constants.DISK_RDWR),
        "/tmp/diskFirst"),
-      (objects.Disk(dev_type=constants.LD_LV, mode=constants.DISK_RDONLY),
+      (objects.Disk(dev_type=constants.DT_PLAIN, mode=constants.DISK_RDONLY),
        "/tmp/diskLast"),
       ]
 
@@ -264,16 +302,16 @@ class TestGetConfigFileDiskData(unittest.TestCase):
 
   def testFileDisks(self):
     disks = [
-      (objects.Disk(dev_type=constants.LD_FILE, mode=constants.DISK_RDWR,
+      (objects.Disk(dev_type=constants.DT_FILE, mode=constants.DISK_RDWR,
                     physical_id=[constants.FD_LOOP]),
        "/tmp/diskFirst"),
-      (objects.Disk(dev_type=constants.LD_FILE, mode=constants.DISK_RDONLY,
+      (objects.Disk(dev_type=constants.DT_FILE, mode=constants.DISK_RDONLY,
                     physical_id=[constants.FD_BLKTAP]),
        "/tmp/diskTwo"),
-      (objects.Disk(dev_type=constants.LD_FILE, mode=constants.DISK_RDWR,
+      (objects.Disk(dev_type=constants.DT_FILE, mode=constants.DISK_RDWR,
                     physical_id=[constants.FD_LOOP]),
        "/tmp/diskThree"),
-      (objects.Disk(dev_type=constants.LD_FILE, mode=constants.DISK_RDWR,
+      (objects.Disk(dev_type=constants.DT_FILE, mode=constants.DISK_RDWR,
                     physical_id=[constants.FD_BLKTAP]),
        "/tmp/diskLast"),
       ]
@@ -288,7 +326,7 @@ class TestGetConfigFileDiskData(unittest.TestCase):
 
   def testInvalidFileDisk(self):
     disks = [
-      (objects.Disk(dev_type=constants.LD_FILE, mode=constants.DISK_RDWR,
+      (objects.Disk(dev_type=constants.DT_FILE, mode=constants.DISK_RDWR,
                     physical_id=["#unknown#"]),
        "/tmp/diskinvalid"),
       ]
@@ -296,14 +334,126 @@ class TestGetConfigFileDiskData(unittest.TestCase):
     self.assertRaises(KeyError, hv_xen._GetConfigFileDiskData, disks, "sd")
 
 
-class TestXenHypervisorUnknownCommand(unittest.TestCase):
-  def test(self):
+class TestXenHypervisorRunXen(unittest.TestCase):
+
+  XEN_SUB_CMD = "help"
+
+  def testCommandUnknown(self):
     cmd = "#unknown command#"
     self.assertFalse(cmd in constants.KNOWN_XEN_COMMANDS)
     hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
                               _run_cmd_fn=NotImplemented,
                               _cmd=cmd)
-    self.assertRaises(errors.ProgrammerError, hv._RunXen, [])
+    self.assertRaises(errors.ProgrammerError, hv._RunXen, [], None)
+
+  def testCommandNoHvparams(self):
+    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
+                              _run_cmd_fn=NotImplemented)
+    hvparams = None
+    self.assertRaises(errors.HypervisorError, hv._RunXen, [self.XEN_SUB_CMD],
+                      hvparams)
+
+  def testCommandFromHvparams(self):
+    expected_xen_cmd = "xl"
+    hvparams = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
+    mock_run_cmd = mock.Mock()
+    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
+                              _run_cmd_fn=mock_run_cmd)
+    hv._RunXen([self.XEN_SUB_CMD], hvparams=hvparams)
+    mock_run_cmd.assert_called_with([expected_xen_cmd, self.XEN_SUB_CMD])
+
+
+class TestXenHypervisorGetInstanceList(unittest.TestCase):
+
+  RESULT_OK = utils.RunResult(0, None, "", "", "", None, None)
+  XEN_LIST = "list"
+
+  def testNoHvparams(self):
+    expected_xen_cmd = "xm"
+    mock_run_cmd = mock.Mock( return_value=self.RESULT_OK )
+    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
+                              _run_cmd_fn=mock_run_cmd)
+    self.assertRaises(errors.HypervisorError, hv._GetInstanceList, True, None)
+
+  def testFromHvparams(self):
+    expected_xen_cmd = "xl"
+    hvparams = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
+    mock_run_cmd = mock.Mock( return_value=self.RESULT_OK )
+    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
+                              _run_cmd_fn=mock_run_cmd)
+    hv._GetInstanceList(True, hvparams)
+    mock_run_cmd.assert_called_with([expected_xen_cmd, self.XEN_LIST])
+
+
+class TestXenHypervisorListInstances(unittest.TestCase):
+
+  RESULT_OK = utils.RunResult(0, None, "", "", "", None, None)
+  XEN_LIST = "list"
+
+  def testNoHvparams(self):
+    expected_xen_cmd = "xm"
+    mock_run_cmd = mock.Mock( return_value=self.RESULT_OK )
+    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
+                              _run_cmd_fn=mock_run_cmd)
+    self.assertRaises(errors.HypervisorError, hv.ListInstances)
+
+  def testHvparamsXl(self):
+    expected_xen_cmd = "xl"
+    hvparams = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
+    mock_run_cmd = mock.Mock( return_value=self.RESULT_OK )
+    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
+                              _run_cmd_fn=mock_run_cmd)
+    hv.ListInstances(hvparams=hvparams)
+    mock_run_cmd.assert_called_with([expected_xen_cmd, self.XEN_LIST])
+
+
+class TestXenHypervisorCheckToolstack(unittest.TestCase):
+
+  def setUp(self):
+    self.tmpdir = tempfile.mkdtemp()
+    self.cfg_name = "xen_config"
+    self.cfg_path = utils.PathJoin(self.tmpdir, self.cfg_name)
+    self.hv = hv_xen.XenHypervisor()
+
+  def tearDown(self):
+    shutil.rmtree(self.tmpdir)
+
+  def testBinaryNotFound(self):
+    RESULT_FAILED = utils.RunResult(1, None, "", "", "", None, None)
+    mock_run_cmd = mock.Mock(return_value=RESULT_FAILED)
+    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
+                              _run_cmd_fn=mock_run_cmd)
+    result = hv._CheckToolstackBinary("xl")
+    self.assertFalse(result)
+
+  def testCheckToolstackXlConfigured(self):
+    RESULT_OK = utils.RunResult(0, None, "", "", "", None, None)
+    mock_run_cmd = mock.Mock(return_value=RESULT_OK)
+    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
+                              _run_cmd_fn=mock_run_cmd)
+    result = hv._CheckToolstackXlConfigured()
+    self.assertTrue(result)
+
+  def testCheckToolstackXlNotConfigured(self):
+    RESULT_FAILED = utils.RunResult(
+        1, None, "",
+        "ERROR:  A different toolstack (xm) have been selected!",
+        "", None, None)
+    mock_run_cmd = mock.Mock(return_value=RESULT_FAILED)
+    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
+                              _run_cmd_fn=mock_run_cmd)
+    result = hv._CheckToolstackXlConfigured()
+    self.assertFalse(result)
+
+  def testCheckToolstackXlFails(self):
+    RESULT_FAILED = utils.RunResult(
+        1, None, "",
+        "ERROR: The pink bunny hid the binary.",
+        "", None, None)
+    mock_run_cmd = mock.Mock(return_value=RESULT_FAILED)
+    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
+                              _run_cmd_fn=mock_run_cmd)
+    self.assertRaises(errors.HypervisorError, hv._CheckToolstackXlConfigured)
 
 
 class TestXenHypervisorWriteConfigFile(unittest.TestCase):
@@ -330,10 +480,46 @@ class TestXenHypervisorWriteConfigFile(unittest.TestCase):
       self.fail("Exception was not raised")
 
 
+class TestXenHypervisorVerify(unittest.TestCase):
+
+  def setUp(self):
+    output = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
+    self._result_ok = utils.RunResult(0, None, output, "", "", None, None)
+
+  def testVerify(self):
+    hvparams = {constants.HV_XEN_CMD : constants.XEN_CMD_XL}
+    mock_run_cmd = mock.Mock(return_value=self._result_ok)
+    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
+                              _run_cmd_fn=mock_run_cmd)
+    hv._CheckToolstack = mock.Mock(return_value=True)
+    result = hv.Verify(hvparams)
+    self.assertTrue(result is None)
+
+  def testVerifyToolstackNotOk(self):
+    hvparams = {constants.HV_XEN_CMD : constants.XEN_CMD_XL}
+    mock_run_cmd = mock.Mock(return_value=self._result_ok)
+    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
+                              _run_cmd_fn=mock_run_cmd)
+    hv._CheckToolstack = mock.Mock()
+    hv._CheckToolstack.side_effect = errors.HypervisorError("foo")
+    result = hv.Verify(hvparams)
+    self.assertTrue(result is not None)
+
+  def testVerifyFailing(self):
+    result_failed = utils.RunResult(1, None, "", "", "", None, None)
+    mock_run_cmd = mock.Mock(return_value=result_failed)
+    hv = hv_xen.XenHypervisor(_cfgdir=NotImplemented,
+                              _run_cmd_fn=mock_run_cmd)
+    hv._CheckToolstack = mock.Mock(return_value=True)
+    result = hv.Verify()
+    self.assertTrue(result is not None)
+
+
 class _TestXenHypervisor(object):
   TARGET = NotImplemented
   CMD = NotImplemented
   HVNAME = NotImplemented
+  VALID_HVPARAMS = {constants.HV_XEN_CMD: constants.XEN_CMD_XL}
 
   def setUp(self):
     super(_TestXenHypervisor, self).setUp()
@@ -366,6 +552,10 @@ class _TestXenHypervisor(object):
                            "", "This command failed", None,
                            NotImplemented, NotImplemented)
 
+  def _FakeTcpPing(self, expected, result, target, port, **kwargs):
+    self.assertEqual((target, port), expected)
+    return result
+
   def testReadingNonExistentConfigFile(self):
     hv = self._GetHv()
 
@@ -393,15 +583,355 @@ class _TestXenHypervisor(object):
     self.assertFalse(os.path.exists(autocfgfile))
     self.assertEqual(utils.ReadFile(cfgfile), "content")
 
-  def testVerify(self):
-    output = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
-    hv = self._GetHv(run_cmd=compat.partial(self._SuccessCommand,
-                                            output))
-    self.assertTrue(hv.Verify() is None)
+  def _XenList(self, cmd):
+    self.assertEqual(cmd, [self.CMD, "list"])
 
-  def testVerifyFailing(self):
-    hv = self._GetHv(run_cmd=self._FailingCommand)
-    self.assertTrue("failed:" in hv.Verify())
+    # TODO: Use actual data from "xl" command
+    output = testutils.ReadTestData("xen-xm-list-4.0.1-four-instances.txt")
+
+    return self._SuccessCommand(output, cmd)
+
+  def testGetInstanceInfo(self):
+    hv = self._GetHv(run_cmd=self._XenList)
+
+    (name, instid, memory, vcpus, state, runtime) = \
+      hv.GetInstanceInfo("server01.example.com")
+
+    self.assertEqual(name, "server01.example.com")
+    self.assertEqual(instid, 1)
+    self.assertEqual(memory, 1024)
+    self.assertEqual(vcpus, 1)
+    self.assertEqual(state, "-b----")
+    self.assertAlmostEqual(runtime, 167643.2)
+
+  def testGetInstanceInfoDom0(self):
+    hv = self._GetHv(run_cmd=self._XenList)
+
+    # TODO: Not sure if this is actually used anywhere (can't find it), but the
+    # code supports querying for Dom0
+    (name, instid, memory, vcpus, state, runtime) = \
+      hv.GetInstanceInfo(hv_xen._DOM0_NAME)
+
+    self.assertEqual(name, "Domain-0")
+    self.assertEqual(instid, 0)
+    self.assertEqual(memory, 1023)
+    self.assertEqual(vcpus, 1)
+    self.assertEqual(state, "r-----")
+    self.assertAlmostEqual(runtime, 154706.1)
+
+  def testGetInstanceInfoUnknown(self):
+    hv = self._GetHv(run_cmd=self._XenList)
+
+    result = hv.GetInstanceInfo("unknown.example.com")
+    self.assertTrue(result is None)
+
+  def testGetAllInstancesInfo(self):
+    hv = self._GetHv(run_cmd=self._XenList)
+
+    result = hv.GetAllInstancesInfo()
+
+    self.assertEqual(map(compat.fst, result), [
+      "server01.example.com",
+      "web3106215069.example.com",
+      "testinstance.example.com",
+      ])
+
+  def testListInstances(self):
+    hv = self._GetHv(run_cmd=self._XenList)
+
+    self.assertEqual(hv.ListInstances(), [
+      "server01.example.com",
+      "web3106215069.example.com",
+      "testinstance.example.com",
+      ])
+
+  def _StartInstanceCommand(self, inst, paused, failcreate, cmd):
+    if cmd == [self.CMD, "info"]:
+      output = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
+    elif cmd == [self.CMD, "list"]:
+      output = testutils.ReadTestData("xen-xm-list-4.0.1-dom0-only.txt")
+    elif cmd[:2] == [self.CMD, "create"]:
+      args = cmd[2:]
+      cfgfile = utils.PathJoin(self.tmpdir, inst.name)
+
+      if paused:
+        self.assertEqual(args, ["-p", cfgfile])
+      else:
+        self.assertEqual(args, [cfgfile])
+
+      if failcreate:
+        return self._FailingCommand(cmd)
+
+      output = ""
+    else:
+      self.fail("Unhandled command: %s" % (cmd, ))
+
+    return self._SuccessCommand(output, cmd)
+
+  def _MakeInstance(self):
+    # Copy default parameters
+    bep = objects.FillDict(constants.BEC_DEFAULTS, {})
+    hvp = objects.FillDict(constants.HVC_DEFAULTS[self.HVNAME], {})
+
+    # Override default VNC password file path
+    if constants.HV_VNC_PASSWORD_FILE in hvp:
+      hvp[constants.HV_VNC_PASSWORD_FILE] = self.vncpw_path
+
+    disks = [
+      (objects.Disk(dev_type=constants.DT_PLAIN, mode=constants.DISK_RDWR),
+       utils.PathJoin(self.tmpdir, "disk0")),
+      (objects.Disk(dev_type=constants.DT_PLAIN, mode=constants.DISK_RDONLY),
+       utils.PathJoin(self.tmpdir, "disk1")),
+      ]
+
+    inst = objects.Instance(name="server01.example.com",
+                            hvparams=hvp, beparams=bep,
+                            osparams={}, nics=[], os="deb1",
+                            disks=map(compat.fst, disks))
+    inst.UpgradeConfig()
+
+    return (inst, disks)
+
+  def testStartInstance(self):
+    (inst, disks) = self._MakeInstance()
+    pathutils.LOG_XEN_DIR = self.tmpdir
+
+    for failcreate in [False, True]:
+      for paused in [False, True]:
+        run_cmd = compat.partial(self._StartInstanceCommand,
+                                 inst, paused, failcreate)
+
+        hv = self._GetHv(run_cmd=run_cmd)
+
+        # Ensure instance is not listed
+        self.assertTrue(inst.name not in hv.ListInstances())
+
+        # Remove configuration
+        cfgfile = utils.PathJoin(self.tmpdir, inst.name)
+        utils.RemoveFile(cfgfile)
+
+        if failcreate:
+          self.assertRaises(errors.HypervisorError, hv.StartInstance,
+                            inst, disks, paused)
+          # Check whether a stale config file is left behind
+          self.assertFalse(os.path.exists(cfgfile))
+        else:
+          hv.StartInstance(inst, disks, paused)
+          # Check if configuration was updated
+          lines = utils.ReadFile(cfgfile).splitlines()
+
+        if constants.HV_VNC_PASSWORD_FILE in inst.hvparams:
+          self.assertTrue(("vncpasswd = '%s'" % self.vncpw) in lines)
+        else:
+          extra = inst.hvparams[constants.HV_KERNEL_ARGS]
+          self.assertTrue(("extra = '%s'" % extra) in lines)
+
+  def _StopInstanceCommand(self, instance_name, force, fail, cmd):
+    if (cmd == [self.CMD, "list"]):
+      output = "Name  ID  Mem  VCPUs  State  Time(s)\n" \
+        "Domain-0  0  1023  1  r-----  142691.0\n" \
+        "%s  417  128  1  r-----  3.2\n" % instance_name
+    elif cmd[:2] == [self.CMD, "destroy"]:
+      self.assertEqual(cmd[2:], [instance_name])
+      output = ""
+    elif not force and cmd[:3] == [self.CMD, "shutdown", "-w"]:
+      self.assertEqual(cmd[3:], [instance_name])
+      output = ""
+    else:
+      self.fail("Unhandled command: %s" % (cmd, ))
+
+    if fail:
+      # Simulate a failing command
+      return self._FailingCommand(cmd)
+    else:
+      return self._SuccessCommand(output, cmd)
+
+  def testStopInstance(self):
+    name = "inst4284.example.com"
+    cfgfile = utils.PathJoin(self.tmpdir, name)
+    cfgdata = "config file content\n"
+
+    for force in [False, True]:
+      for fail in [False, True]:
+        utils.WriteFile(cfgfile, data=cfgdata)
+
+        run_cmd = compat.partial(self._StopInstanceCommand, name, force, fail)
+
+        hv = self._GetHv(run_cmd=run_cmd)
+
+        self.assertTrue(os.path.isfile(cfgfile))
+
+        if fail:
+          try:
+            hv._StopInstance(name, force, None)
+          except errors.HypervisorError, err:
+            self.assertTrue(str(err).startswith("listing instances failed"),
+                            msg=str(err))
+          else:
+            self.fail("Exception was not raised")
+          self.assertEqual(utils.ReadFile(cfgfile), cfgdata,
+                           msg=("Configuration was removed when stopping"
+                                " instance failed"))
+        else:
+          hv._StopInstance(name, force, None)
+          self.assertFalse(os.path.exists(cfgfile))
+
+  def _MigrateNonRunningInstCmd(self, cmd):
+    if cmd == [self.CMD, "list"]:
+      output = testutils.ReadTestData("xen-xm-list-4.0.1-dom0-only.txt")
+    else:
+      self.fail("Unhandled command: %s" % (cmd, ))
+
+    return self._SuccessCommand(output, cmd)
+
+  def testMigrateInstanceNotRunning(self):
+    name = "nonexistinginstance.example.com"
+    target = constants.IP4_ADDRESS_LOCALHOST
+    port = 14618
+
+    hv = self._GetHv(run_cmd=self._MigrateNonRunningInstCmd)
+
+    for live in [False, True]:
+      try:
+        hv._MigrateInstance(NotImplemented, name, target, port, live,
+                            self.VALID_HVPARAMS, _ping_fn=NotImplemented)
+      except errors.HypervisorError, err:
+        self.assertEqual(str(err), "Instance not running, cannot migrate")
+      else:
+        self.fail("Exception was not raised")
+
+  def _MigrateInstTargetUnreachCmd(self, cmd):
+    if cmd == [self.CMD, "list"]:
+      output = testutils.ReadTestData("xen-xm-list-4.0.1-four-instances.txt")
+    else:
+      self.fail("Unhandled command: %s" % (cmd, ))
+
+    return self._SuccessCommand(output, cmd)
+
+  def testMigrateTargetUnreachable(self):
+    name = "server01.example.com"
+    target = constants.IP4_ADDRESS_LOCALHOST
+    port = 28349
+
+    hv = self._GetHv(run_cmd=self._MigrateInstTargetUnreachCmd)
+    hvparams = {constants.HV_XEN_CMD: self.CMD}
+
+    for live in [False, True]:
+      if self.CMD == constants.XEN_CMD_XL:
+        # TODO: Detect unreachable targets
+        pass
+      else:
+        try:
+          hv._MigrateInstance(NotImplemented, name, target, port, live,
+                              hvparams,
+                              _ping_fn=compat.partial(self._FakeTcpPing,
+                                                      (target, port), False))
+        except errors.HypervisorError, err:
+          wanted = "Remote host %s not" % target
+          self.assertTrue(str(err).startswith(wanted))
+        else:
+          self.fail("Exception was not raised")
+
+  def _MigrateInstanceCmd(self, cluster_name, instance_name, target, port,
+                          live, fail, cmd):
+    if cmd == [self.CMD, "list"]:
+      output = testutils.ReadTestData("xen-xm-list-4.0.1-four-instances.txt")
+    elif cmd[:2] == [self.CMD, "migrate"]:
+      if self.CMD == constants.XEN_CMD_XM:
+        args = ["-p", str(port)]
+
+        if live:
+          args.append("-l")
+
+      elif self.CMD == constants.XEN_CMD_XL:
+        args = [
+          "-s", constants.XL_SSH_CMD % cluster_name,
+          "-C", utils.PathJoin(self.tmpdir, instance_name),
+          ]
+
+      else:
+        self.fail("Unknown Xen command '%s'" % self.CMD)
+
+      args.extend([instance_name, target])
+      self.assertEqual(cmd[2:], args)
+
+      if fail:
+        return self._FailingCommand(cmd)
+
+      output = ""
+    else:
+      self.fail("Unhandled command: %s" % (cmd, ))
+
+    return self._SuccessCommand(output, cmd)
+
+  def testMigrateInstance(self):
+    clustername = "cluster.example.com"
+    instname = "server01.example.com"
+    target = constants.IP4_ADDRESS_LOCALHOST
+    port = 22364
+
+    hvparams = {constants.HV_XEN_CMD: self.CMD}
+
+    for live in [False, True]:
+      for fail in [False, True]:
+        ping_fn = \
+          testutils.CallCounter(compat.partial(self._FakeTcpPing,
+                                               (target, port), True))
+
+        run_cmd = \
+          compat.partial(self._MigrateInstanceCmd,
+                         clustername, instname, target, port, live,
+                         fail)
+
+        hv = self._GetHv(run_cmd=run_cmd)
+
+        if fail:
+          try:
+            hv._MigrateInstance(clustername, instname, target, port, live,
+                                hvparams, _ping_fn=ping_fn)
+          except errors.HypervisorError, err:
+            self.assertTrue(str(err).startswith("Failed to migrate instance"))
+          else:
+            self.fail("Exception was not raised")
+        else:
+          hv._MigrateInstance(clustername, instname, target, port, live,
+                              hvparams, _ping_fn=ping_fn)
+
+        if self.CMD == constants.XEN_CMD_XM:
+          expected_pings = 1
+        else:
+          expected_pings = 0
+
+        self.assertEqual(ping_fn.Count(), expected_pings)
+
+  def _GetNodeInfoCmd(self, fail, cmd):
+    if cmd == [self.CMD, "info"]:
+      if fail:
+        return self._FailingCommand(cmd)
+      else:
+        output = testutils.ReadTestData("xen-xm-info-4.0.1.txt")
+    elif cmd == [self.CMD, "list"]:
+      if fail:
+        self.fail("'xm list' shouldn't be called when 'xm info' failed")
+      else:
+        output = testutils.ReadTestData("xen-xm-list-4.0.1-four-instances.txt")
+    else:
+      self.fail("Unhandled command: %s" % (cmd, ))
+
+    return self._SuccessCommand(output, cmd)
+
+  def testGetNodeInfo(self):
+    run_cmd = compat.partial(self._GetNodeInfoCmd, False)
+    hv = self._GetHv(run_cmd=run_cmd)
+    result = hv.GetNodeInfo()
+
+    self.assertEqual(result["hv_version"], (4, 0))
+    self.assertEqual(result["memory_free"], 8004)
+
+  def testGetNodeInfoFailing(self):
+    run_cmd = compat.partial(self._GetNodeInfoCmd, True)
+    hv = self._GetHv(run_cmd=run_cmd)
+    self.assertTrue(hv.GetNodeInfo() is None)
 
 
 def _MakeTestClass(cls, cmd):