diff --git a/Makefile.am b/Makefile.am
index 89fbaca6fd99e9d93ace14ad8d0134bff8e8c99c..67f453bdcae82f7a88da209b140fa6b282346839 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -95,6 +95,7 @@ pkgpython_PYTHON = \
 	lib/bootstrap.py \
 	lib/cli.py \
 	lib/cmdlib.py \
+	lib/compat.py \
 	lib/config.py \
 	lib/constants.py \
 	lib/daemon.py \
@@ -335,6 +336,7 @@ python_tests = \
 	test/ganeti.bdev_unittest.py \
 	test/ganeti.cli_unittest.py \
 	test/ganeti.cmdlib_unittest.py \
+	test/ganeti.compat_unittest.py \
 	test/ganeti.confd.client_unittest.py \
 	test/ganeti.config_unittest.py \
 	test/ganeti.constants_unittest.py \
@@ -582,4 +584,6 @@ coverage: $(BUILT_SOURCES) $(python_tests)
 	$(PLAIN_TESTS_ENVIRONMENT) $(abs_top_srcdir)/autotools/gen-coverage \
 	$(python_tests)
 
+commit-check: distcheck lint apidoc
+
 # vim: set noet :
diff --git a/lib/bdev.py b/lib/bdev.py
index 5783a211e6d5c1bdde72915d2d4fef8390a4eacd..5469283ccc3ff3ab224697010eef7f2bbc504088 100644
--- a/lib/bdev.py
+++ b/lib/bdev.py
@@ -32,6 +32,7 @@ from ganeti import utils
 from ganeti import errors
 from ganeti import constants
 from ganeti import objects
+from ganeti import compat
 
 
 # Size of reads in _CanReadDevice
@@ -388,7 +389,7 @@ class LogicalVolume(BlockDev):
     pvs_info.reverse()
 
     pvlist = [ pv[1] for pv in pvs_info ]
-    if utils.any(pvlist, lambda v: ":" in v):
+    if compat.any(pvlist, lambda v: ":" in v):
       _ThrowError("Some of your PVs have the invalid character ':' in their"
                   " name, this is not supported - please filter them out"
                   " in lvm.conf using either 'filter' or 'preferred_names'")
@@ -462,7 +463,7 @@ class LogicalVolume(BlockDev):
     """
     if (not cls._VALID_NAME_RE.match(name) or
         name in cls._INVALID_NAMES or
-        utils.any(cls._INVALID_SUBSTRINGS, lambda x: x in name)):
+        compat.any(cls._INVALID_SUBSTRINGS, lambda x: x in name)):
       _ThrowError("Invalid LVM name '%s'", name)
 
   def Remove(self):
diff --git a/lib/cli.py b/lib/cli.py
index 9a40c48c471ff410136ee393b062948a86a30a69..b7722e20955b93196ed782f19fc9602d1e6679d8 100644
--- a/lib/cli.py
+++ b/lib/cli.py
@@ -37,6 +37,7 @@ from ganeti import luxi
 from ganeti import ssconf
 from ganeti import rpc
 from ganeti import ssh
+from ganeti import compat
 
 from optparse import (OptionParser, TitledHelpFormatter,
                       Option, OptionValueError)
@@ -2143,7 +2144,7 @@ class JobExecutor(object):
         ToStdout("Submitted jobs %s", utils.CommaJoin(ok_jobs))
 
     # first, remove any non-submitted jobs
-    self.jobs, failures = utils.partition(self.jobs, lambda x: x[1])
+    self.jobs, failures = compat.partition(self.jobs, lambda x: x[1])
     for idx, _, jid, name in failures:
       ToStderr("Failed to submit job for %s: %s", name, jid)
       results.append((idx, False, jid))
diff --git a/lib/cmdlib.py b/lib/cmdlib.py
index 2945c9ef0323d8527b2ce13c4a904bace6a7e143..506c49a5c0c109448585273d3787f252cc35996e 100644
--- a/lib/cmdlib.py
+++ b/lib/cmdlib.py
@@ -2255,8 +2255,11 @@ class LUSetClusterParams(LogicalUnit):
     """Check parameters
 
     """
-    if not hasattr(self.op, "candidate_pool_size"):
-      self.op.candidate_pool_size = None
+    for attr in ["candidate_pool_size",
+                 "uid_pool", "add_uids", "remove_uids"]:
+      if not hasattr(self.op, attr):
+        setattr(self.op, attr, None)
+
     if self.op.candidate_pool_size is not None:
       try:
         self.op.candidate_pool_size = int(self.op.candidate_pool_size)
diff --git a/lib/compat.py b/lib/compat.py
new file mode 100644
index 0000000000000000000000000000000000000000..692ab246f9845ec7f48379049e2e835a8b325c2d
--- /dev/null
+++ b/lib/compat.py
@@ -0,0 +1,88 @@
+#
+#
+
+# Copyright (C) 2010 Google Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+
+
+"""Module containing backported language/library functionality.
+
+"""
+
+import itertools
+
+try:
+  import functools
+except ImportError:
+  functools = None
+
+
+def all(seq, pred=bool): # pylint: disable-msg=W0622
+  """Returns True if pred(x) is True for every element in the iterable.
+
+  Please note that this function provides a C{pred} parameter which isn't
+  available in the version included in Python 2.5 and above.
+
+  """
+  for _ in itertools.ifilterfalse(pred, seq):
+    return False
+  return True
+
+
+def any(seq, pred=bool): # pylint: disable-msg=W0622
+  """Returns True if pred(x) is True for at least one element in the iterable.
+
+  Please note that this function provides a C{pred} parameter which isn't
+  available in the version included in Python 2.5 and above.
+
+  """
+  for _ in itertools.ifilter(pred, seq):
+    return True
+  return False
+
+
+def partition(seq, pred=bool): # pylint: disable-msg=W0622
+  """Partition a list in two, based on the given predicate.
+
+  """
+  return (list(itertools.ifilter(pred, seq)),
+          list(itertools.ifilterfalse(pred, seq)))
+
+
+# Even though we're using Python's built-in "partial" function if available,
+# this one is always defined for testing.
+def _partial(func, *args, **keywords): # pylint: disable-msg=W0622
+  """Decorator with partial application of arguments and keywords.
+
+  This function was copied from Python's documentation.
+
+  """
+  def newfunc(*fargs, **fkeywords):
+    newkeywords = keywords.copy()
+    newkeywords.update(fkeywords)
+    return func(*(args + fargs), **newkeywords) # pylint: disable-msg=W0142
+
+  newfunc.func = func
+  newfunc.args = args
+  newfunc.keywords = keywords
+  return newfunc
+
+
+if functools is None:
+  partial = _partial
+else:
+  partial = functools.partial
diff --git a/lib/confd/client.py b/lib/confd/client.py
index 9bd9ef7fc692194b0e782bbf676a43519ba50b07..e2e0a13b80a46f86bfbe3bec8e6b3cffbdd8a892 100644
--- a/lib/confd/client.py
+++ b/lib/confd/client.py
@@ -61,6 +61,7 @@ from ganeti import daemon # contains AsyncUDPSocket
 from ganeti import errors
 from ganeti import confd
 from ganeti import ssconf
+from ganeti import compat
 
 
 class ConfdAsyncUDPClient(daemon.AsyncUDPSocket):
@@ -581,7 +582,7 @@ class ConfdCountingCallback:
     """Have all the registered queries received at least an answer?
 
     """
-    return utils.all(self._answers.values())
+    return compat.all(self._answers.values())
 
   def _HandleExpire(self, up):
     # if we have no answer we have received none, before the expiration.
diff --git a/lib/hypervisor/hv_base.py b/lib/hypervisor/hv_base.py
index 58db39599a3165d22317ed0f64445c642cb1df76..1465da2401a570e4232604403e295acca1d4dc45 100644
--- a/lib/hypervisor/hv_base.py
+++ b/lib/hypervisor/hv_base.py
@@ -101,10 +101,14 @@ class BaseHypervisor(object):
           - a function to check for parameter validity on the remote node,
             in the L{ValidateParameters} function
           - an error message for the above function
+  @type CAN_MIGRATE: boolean
+  @cvar CAN_MIGRATE: whether this hypervisor can do migration (either
+      live or non-live)
 
   """
   PARAMETERS = {}
   ANCILLARY_FILES = []
+  CAN_MIGRATE = False
 
   def __init__(self):
     pass
diff --git a/lib/hypervisor/hv_chroot.py b/lib/hypervisor/hv_chroot.py
index 36b37a8eb1261f9e13c2133dc653d147cad7f7f4..c495c9e4dbb30dacd181b9ad7832338cadaf461b 100644
--- a/lib/hypervisor/hv_chroot.py
+++ b/lib/hypervisor/hv_chroot.py
@@ -212,6 +212,13 @@ class ChrootManager(hv_base.BaseHypervisor):
         raise HypervisorError("Can't stop the processes using the chroot")
       return
 
+  def CleanupInstance(self, instance_name):
+    """Cleanup after a stopped instance
+
+    """
+    if self._IsDirLive(root_dir):
+      raise HypervisorError("Processes are still using the chroot")
+
     for mpath in self._GetMountSubdirs(root_dir):
       utils.RunCmd(["umount", mpath])
 
diff --git a/lib/hypervisor/hv_fake.py b/lib/hypervisor/hv_fake.py
index cea05d61ef7c2cdd8e629915cdf4d5e3b26c5b27..a97bebe299c502406e692cc712108b041882d9c6 100644
--- a/lib/hypervisor/hv_fake.py
+++ b/lib/hypervisor/hv_fake.py
@@ -40,6 +40,8 @@ class FakeHypervisor(hv_base.BaseHypervisor):
   a real virtualisation software installed.
 
   """
+  CAN_MIGRATE = True
+
   _ROOT_DIR = constants.RUN_DIR + "/ganeti-fake-hypervisor"
 
   def __init__(self):
diff --git a/lib/hypervisor/hv_kvm.py b/lib/hypervisor/hv_kvm.py
index 6b7483338422d9a587162bf8a39568577fae23ef..13c09ddce547c8bfb9da5f096075a97d58641afb 100644
--- a/lib/hypervisor/hv_kvm.py
+++ b/lib/hypervisor/hv_kvm.py
@@ -44,6 +44,7 @@ from ganeti.hypervisor import hv_base
 
 class KVMHypervisor(hv_base.BaseHypervisor):
   """KVM hypervisor interface"""
+  CAN_MIGRATE = True
 
   _ROOT_DIR = constants.RUN_GANETI_DIR + "/kvm-hypervisor"
   _PIDS_DIR = _ROOT_DIR + "/pid" # contains live instances pids
@@ -235,13 +236,12 @@ class KVMHypervisor(hv_base.BaseHypervisor):
     if os.path.exists(uid_file):
       try:
         uid = int(utils.ReadFile(uid_file))
+        return uid
       except EnvironmentError:
         logging.warning("Can't read uid file", exc_info=True)
-        return None
       except (TypeError, ValueError):
         logging.warning("Can't parse uid file contents", exc_info=True)
-        return None
-    return uid
+    return None
 
   @classmethod
   def _RemoveInstanceRuntimeFiles(cls, pidfile, instance_name):
@@ -425,7 +425,10 @@ class KVMHypervisor(hv_base.BaseHypervisor):
       # TODO: handle FD_LOOP and FD_BLKTAP (?)
       if boot_disk:
         kvm_cmd.extend(['-boot', 'c'])
-        boot_val = ',boot=on'
+        if disk_type != constants.HT_DISK_IDE:
+          boot_val = ',boot=on'
+        else:
+          boot_val = ''
         # We only boot from the first disk
         boot_disk = False
       else:
@@ -440,9 +443,14 @@ class KVMHypervisor(hv_base.BaseHypervisor):
       options = ',format=raw,media=cdrom'
       if boot_cdrom:
         kvm_cmd.extend(['-boot', 'd'])
-        options = '%s,boot=on' % options
+        if disk_type != constants.HT_DISK_IDE:
+          options = '%s,boot=on' % options
       else:
-        options = '%s,if=virtio' % options
+        if disk_type == constants.HT_DISK_PARAVIRTUAL:
+          if_val = ',if=virtio'
+        else:
+          if_val = ',if=%s' % disk_type
+        options = '%s%s' % (options, if_val)
       drive_val = 'file=%s%s' % (iso_image, options)
       kvm_cmd.extend(['-drive', drive_val])
 
@@ -498,6 +506,10 @@ class KVMHypervisor(hv_base.BaseHypervisor):
         vnc_arg = 'unix:%s/%s.vnc' % (vnc_bind_address, instance.name)
 
       kvm_cmd.extend(['-vnc', vnc_arg])
+
+      # Also add a tablet USB device to act as a mouse
+      # This solves various mouse alignment issues
+      kvm_cmd.extend(['-usbdevice', 'tablet'])
     else:
       kvm_cmd.extend(['-nographic'])
 
diff --git a/lib/hypervisor/hv_xen.py b/lib/hypervisor/hv_xen.py
index 575e3b9404e990bd1de7589c1da3939ba8958897..a281e6a9b41d9434162a10847bfe010494b2e8f7 100644
--- a/lib/hypervisor/hv_xen.py
+++ b/lib/hypervisor/hv_xen.py
@@ -39,6 +39,7 @@ class XenHypervisor(hv_base.BaseHypervisor):
   all the functionality that is identical for both.
 
   """
+  CAN_MIGRATE = True
   REBOOT_RETRY_COUNT = 60
   REBOOT_RETRY_INTERVAL = 10
 
diff --git a/lib/locking.py b/lib/locking.py
index aeafd3f7911db3830ff4b22c9d945fcd0185a067..3e25917c40f6e7388017e5a848f8f72933edb8ac 100644
--- a/lib/locking.py
+++ b/lib/locking.py
@@ -33,6 +33,7 @@ import errno
 
 from ganeti import errors
 from ganeti import utils
+from ganeti import compat
 
 
 def ssynchronized(lock, shared=0):
@@ -1216,7 +1217,7 @@ class GanetiLockManager:
     """
     # This way of checking only works if LEVELS[i] = i, which we check for in
     # the test cases.
-    return utils.any((self._is_owned(l) for l in LEVELS[level + 1:]))
+    return compat.any((self._is_owned(l) for l in LEVELS[level + 1:]))
 
   def _BGL_owned(self): # pylint: disable-msg=C0103
     """Check if the current thread owns the BGL.
diff --git a/lib/rapi/client.py b/lib/rapi/client.py
index 5a1b555a965735e19a2f93f52adaf419bed73815..b1f3d104538b8b1561544ecbb6762feb6fef916f 100644
--- a/lib/rapi/client.py
+++ b/lib/rapi/client.py
@@ -137,7 +137,7 @@ class GanetiRapiClient(object):
     @param query: query arguments to pass to urllib.urlencode
     @type prepend_version: bool
     @param prepend_version: whether to automatically fetch and prepend the
-        Ganeti version to the URL path
+        Ganeti RAPI version to the URL path
 
     @rtype:  str
     @return: URL path
@@ -171,7 +171,7 @@ class GanetiRapiClient(object):
     @param content: HTTP body content
     @type prepend_version: bool
     @param prepend_version: whether to automatically fetch and prepend the
-        Ganeti version to the URL path
+        Ganeti RAPI version to the URL path
 
     @rtype: str
     @return: JSON-Decoded response
@@ -213,7 +213,7 @@ class GanetiRapiClient(object):
     return self._version
 
   def GetVersion(self):
-    """Gets the ganeti version running on the cluster.
+    """Gets the Remote API version running on the cluster.
 
     @rtype: int
     @return: Ganeti version
@@ -266,7 +266,7 @@ class GanetiRapiClient(object):
     if dry_run:
       query.append(("dry-run", 1))
 
-    self._SendRequest(HTTP_PUT, "/tags", query)
+    return self._SendRequest(HTTP_PUT, "/tags", query)
 
   def DeleteClusterTags(self, tags, dry_run=False):
     """Deletes tags from the cluster.
@@ -339,12 +339,15 @@ class GanetiRapiClient(object):
     @type instance: str
     @param instance: the instance to delete
 
+    @rtype: int
+    @return: job id
+
     """
     query = []
     if dry_run:
       query.append(("dry-run", 1))
 
-    self._SendRequest(HTTP_DELETE, "/instances/%s" % instance, query)
+    return self._SendRequest(HTTP_DELETE, "/instances/%s" % instance, query)
 
   def GetInstanceTags(self, instance):
     """Gets tags for an instance.
@@ -376,7 +379,7 @@ class GanetiRapiClient(object):
     if dry_run:
       query.append(("dry-run", 1))
 
-    self._SendRequest(HTTP_PUT, "/instances/%s/tags" % instance, query)
+    return self._SendRequest(HTTP_PUT, "/instances/%s/tags" % instance, query)
 
   def DeleteInstanceTags(self, instance, tags, dry_run=False):
     """Deletes tags from an instance.
diff --git a/lib/ssh.py b/lib/ssh.py
index b3116d3a09aded5fda42473ead72a96bc915a091..ba4c3eb33ac9888e281e53165d4600b8f4cbd1f6 100644
--- a/lib/ssh.py
+++ b/lib/ssh.py
@@ -95,6 +95,7 @@ class SshRunner:
       "-oHashKnownHosts=no",
       "-oGlobalKnownHostsFile=%s" % constants.SSH_KNOWN_HOSTS_FILE,
       "-oUserKnownHostsFile=/dev/null",
+      "-oCheckHostIp=no",
       ]
 
     if use_cluster_key:
diff --git a/lib/utils.py b/lib/utils.py
index 48b427ac35be424deda322ab59092d1da58ee9bb..32ebd875b21dce356d98570cefb137ed799ae509 100644
--- a/lib/utils.py
+++ b/lib/utils.py
@@ -1781,20 +1781,6 @@ def FirstFree(seq, base=0):
   return None
 
 
-def all(seq, pred=bool): # pylint: disable-msg=W0622
-  "Returns True if pred(x) is True for every element in the iterable"
-  for _ in itertools.ifilterfalse(pred, seq):
-    return False
-  return True
-
-
-def any(seq, pred=bool): # pylint: disable-msg=W0622
-  "Returns True if pred(x) is True for at least one element in the iterable"
-  for _ in itertools.ifilter(pred, seq):
-    return True
-  return False
-
-
 def SingleWaitForFdCondition(fdobj, event, timeout):
   """Waits for a condition to occur on the socket.
 
@@ -1886,12 +1872,6 @@ def WaitForFdCondition(fdobj, event, timeout):
   return result
 
 
-def partition(seq, pred=bool): # # pylint: disable-msg=W0622
-  "Partition a list in two, based on the given predicate"
-  return (list(itertools.ifilter(pred, seq)),
-          list(itertools.ifilterfalse(pred, seq)))
-
-
 def UniqueSequence(seq):
   """Returns a list with unique elements.
 
diff --git a/scripts/gnt-cluster b/scripts/gnt-cluster
index 8a3ba82c065d2f992aae4e2071dec0dd6460c1ed..4bde5b91f3affb1ce78b368e5499173d473c013c 100755
--- a/scripts/gnt-cluster
+++ b/scripts/gnt-cluster
@@ -40,6 +40,7 @@ from ganeti import bootstrap
 from ganeti import ssh
 from ganeti import objects
 from ganeti import uidpool
+from ganeti import compat
 
 
 @UsesRPC
@@ -423,7 +424,7 @@ def VerifyDisks(opts, args):
 
   if missing:
     for iname, ival in missing.iteritems():
-      all_missing = utils.all(ival, lambda x: x[0] in bad_nodes)
+      all_missing = compat.all(ival, lambda x: x[0] in bad_nodes)
       if all_missing:
         ToStdout("Instance %s cannot be verified as it lives on"
                  " broken nodes", iname)
diff --git a/scripts/gnt-instance b/scripts/gnt-instance
index 4f6ddc8040099385c673aaf56debd9ba9adc8016..3dfcd344a0cc059b03f97358cc54cceae6d39b96 100755
--- a/scripts/gnt-instance
+++ b/scripts/gnt-instance
@@ -1175,7 +1175,7 @@ def ShowInstanceConfig(opts, args):
                                       vnc_bind_address)
       buf.write("    - console connection: vnc to %s\n" % vnc_console_port)
 
-    for key in instance["hv_actual"]:
+    for key in sorted(instance["hv_actual"]):
       if key in instance["hv_instance"]:
         val = instance["hv_instance"][key]
       else:
diff --git a/test/ganeti.compat_unittest.py b/test/ganeti.compat_unittest.py
new file mode 100755
index 0000000000000000000000000000000000000000..19f82b7a8f26b8d225aea2c3dc8364afcc630719
--- /dev/null
+++ b/test/ganeti.compat_unittest.py
@@ -0,0 +1,62 @@
+#!/usr/bin/python
+#
+
+# Copyright (C) 2010 Google Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+
+
+"""Script for unittesting the compat module"""
+
+import unittest
+
+from ganeti import compat
+
+import testutils
+
+
+class TestPartial(testutils.GanetiTestCase):
+  def test(self):
+    # Test standard version
+    self._Test(compat.partial)
+
+    # Test our version
+    self._Test(compat._partial)
+
+  def _Test(self, fn):
+    def _TestFunc1(x, power=2):
+      return x ** power
+
+    cubic = fn(_TestFunc1, power=3)
+    self.assertEqual(cubic(1), 1)
+    self.assertEqual(cubic(3), 27)
+    self.assertEqual(cubic(4), 64)
+
+    def _TestFunc2(*args, **kwargs):
+      return (args, kwargs)
+
+    self.assertEqualValues(fn(_TestFunc2, "Hello", "World")("Foo"),
+                           (("Hello", "World", "Foo"), {}))
+
+    self.assertEqualValues(fn(_TestFunc2, "Hello", xyz=123)("Foo"),
+                           (("Hello", "Foo"), {"xyz": 123}))
+
+    self.assertEqualValues(fn(_TestFunc2, xyz=123)("Foo", xyz=999),
+                           (("Foo", ), {"xyz": 999,}))
+
+
+if __name__ == "__main__":
+  testutils.GanetiTestProgram()
diff --git a/test/ganeti.rapi.client_unittest.py b/test/ganeti.rapi.client_unittest.py
index 66f5fbbd19600c82a1109ce46b72532353f92d12..d7ccf54e76f989fff6b341f63bd3cfda39e194b3 100755
--- a/test/ganeti.rapi.client_unittest.py
+++ b/test/ganeti.rapi.client_unittest.py
@@ -192,7 +192,9 @@ class GanetiRapiClientTests(unittest.TestCase):
     self.assertHandler(rlib2.R_2_tags)
 
   def testAddClusterTags(self):
-    self.client.AddClusterTags(["awesome"], dry_run=True)
+    self.rapi.AddResponse("1234")
+    self.assertEqual(1234,
+        self.client.AddClusterTags(["awesome"], dry_run=True))
     self.assertHandler(rlib2.R_2_tags)
     self.assertDryRun()
     self.assertQuery("tag", ["awesome"])
@@ -227,7 +229,8 @@ class GanetiRapiClientTests(unittest.TestCase):
     self.assertDryRun()
 
   def testDeleteInstance(self):
-    self.client.DeleteInstance("instance", dry_run=True)
+    self.rapi.AddResponse("1234")
+    self.assertEqual(1234, self.client.DeleteInstance("instance", dry_run=True))
     self.assertHandler(rlib2.R_2_instances_name)
     self.assertItems(["instance"])
     self.assertDryRun()
@@ -239,7 +242,9 @@ class GanetiRapiClientTests(unittest.TestCase):
     self.assertItems(["fooinstance"])
 
   def testAddInstanceTags(self):
-    self.client.AddInstanceTags("fooinstance", ["awesome"], dry_run=True)
+    self.rapi.AddResponse("1234")
+    self.assertEqual(1234,
+        self.client.AddInstanceTags("fooinstance", ["awesome"], dry_run=True))
     self.assertHandler(rlib2.R_2_instances_name_tags)
     self.assertItems(["fooinstance"])
     self.assertDryRun()
@@ -425,7 +430,9 @@ class GanetiRapiClientTests(unittest.TestCase):
     self.assertItems(["node-k"])
 
   def testAddNodeTags(self):
-    self.client.AddNodeTags("node-v", ["awesome"], dry_run=True)
+    self.rapi.AddResponse("1234")
+    self.assertEqual(1234,
+        self.client.AddNodeTags("node-v", ["awesome"], dry_run=True))
     self.assertHandler(rlib2.R_2_nodes_name_tags)
     self.assertItems(["node-v"])
     self.assertDryRun()
diff --git a/tools/burnin b/tools/burnin
index a1dfc91880617beae752ddc6459eb72384dd2e9c..17e58500105a558eac84964b9af246346bb5ec15 100755
--- a/tools/burnin
+++ b/tools/burnin
@@ -36,6 +36,8 @@ from ganeti import constants
 from ganeti import cli
 from ganeti import errors
 from ganeti import utils
+from ganeti import hypervisor
+from ganeti import compat
 
 from ganeti.confd import client as confd_client
 
@@ -111,6 +113,7 @@ OPTIONS = [
                  help="OS to use during burnin",
                  metavar="<OS>",
                  completion_suggest=cli.OPT_COMPL_ONE_OS),
+  cli.HYPERVISOR_OPT,
   cli.cli_option("--disk-size", dest="disk_size",
                  help="Disk size (determines disk count)",
                  default="128m", type="string", metavar="<size,size,...>",
@@ -473,7 +476,11 @@ class Burner(object):
       constants.BE_MEMORY: options.mem_size,
       constants.BE_VCPUS: 1,
       }
+
+    self.hypervisor = None
     self.hvp = {}
+    if options.hypervisor:
+      self.hypervisor, self.hvp = options.hypervisor
 
     socket.setdefaulttimeout(options.net_timeout)
 
@@ -515,6 +522,9 @@ class Burner(object):
 
     default_nic_params = self.cluster_info["nicparams"][constants.PP_DEFAULT]
     self.cluster_default_nicparams = default_nic_params
+    if self.hypervisor is None:
+      self.hypervisor = self.cluster_info["default_hypervisor"]
+    self.hv_class = hypervisor.GetHypervisorClass(self.hypervisor)
 
   @_DoCheckInstances
   @_DoBatch(False)
@@ -559,6 +569,7 @@ class Burner(object):
                                     iallocator=self.opts.iallocator,
                                     beparams=self.bep,
                                     hvparams=self.hvp,
+                                    hypervisor=self.hypervisor,
                                     )
       remove_instance = lambda name: lambda: self.to_rem.append(name)
       self.ExecOrQueue(instance, [op], post_process=remove_instance(instance))
@@ -972,14 +983,20 @@ class Burner(object):
         self.BurnReplaceDisks2()
 
       if (opts.disk_template in constants.DTS_GROWABLE and
-          utils.any(self.disk_growth, lambda n: n > 0)):
+          compat.any(self.disk_growth, lambda n: n > 0)):
         self.BurnGrowDisks()
 
       if opts.do_failover and opts.disk_template in constants.DTS_NET_MIRROR:
         self.BurnFailover()
 
-      if opts.do_migrate and opts.disk_template == constants.DT_DRBD8:
-        self.BurnMigrate()
+      if opts.do_migrate:
+        if opts.disk_template != constants.DT_DRBD8:
+          Log("Skipping migration (disk template not DRBD8)")
+        elif not self.hv_class.CAN_MIGRATE:
+          Log("Skipping migration (hypervisor %s does not support it)",
+              self.hypervisor)
+        else:
+          self.BurnMigrate()
 
       if (opts.do_move and len(self.nodes) > 1 and
           opts.disk_template in [constants.DT_PLAIN, constants.DT_FILE]):