diff --git a/Makefile.am b/Makefile.am
index 915638c323169fcf3c4b76bbb84daadd3ab3dd86..fde2b660a64f3318d5c50926cc21f2e44d021c44 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -455,6 +455,7 @@ qa_scripts = \
 	qa/qa_daemon.py \
 	qa/qa_env.py \
 	qa/qa_error.py \
+	qa/qa_group.py \
 	qa/qa_instance.py \
 	qa/qa_node.py \
 	qa/qa_os.py \
@@ -633,6 +634,8 @@ TEST_FILES = \
 	test/data/proc_drbd8.txt \
 	test/data/proc_drbd80-emptyline.txt \
 	test/data/proc_drbd83.txt \
+	test/data/proc_drbd83_sync.txt \
+	test/data/proc_drbd83_sync_krnl2.6.39.txt \
 	test/data/sys_drbd_usermode_helper.txt \
 	test/import-export_unittest-helper
 
@@ -773,6 +776,17 @@ standalone_python_modules = \
 	lib/rapi/client.py \
 	tools/ganeti-listrunner
 
+pep8_python_code = \
+	ganeti \
+	ganeti/http/server.py \
+	$(dist_sbin_SCRIPTS) \
+	$(dist_tools_PYTHON) \
+	$(pkglib_python_scripts) \
+	$(BUILD_BASH_COMPLETION) \
+	$(DOCPP) \
+	$(PYTHON_BOOTSTRAP) \
+	qa
+
 test/daemon-util_unittest.bash: daemons/daemon-util
 
 test/ganeti-cleaner_unittest.bash: daemons/ganeti-cleaner
@@ -885,7 +899,7 @@ lib/_autoconf.py: Makefile | lib/.dir
 	  echo ''; \
 	  echo '"""'; \
 	  echo ''; \
-	  echo '# pylint: disable-msg=C0301,C0324'; \
+	  echo '# pylint: disable=C0301,C0324'; \
 	  echo '# because this is autogenerated, we do not want'; \
 	  echo '# style warnings' ; \
 	  echo ''; \
@@ -951,7 +965,7 @@ lib/_vcsversion.py: Makefile vcs-version | lib/.dir
 	  echo ''; \
 	  echo '"""'; \
 	  echo ''; \
-	  echo '# pylint: disable-msg=C0301,C0324'; \
+	  echo '# pylint: disable=C0301,C0324'; \
 	  echo '# because this is autogenerated, we do not want'; \
 	  echo '# style warnings' ; \
 	  echo ''; \
@@ -1000,7 +1014,7 @@ $(PYTHON_BOOTSTRAP): Makefile | $(all_dirfiles)
 	  echo; \
 	  echo '"""Bootstrap script for L{$(MODULE)}"""'; \
 	  echo; \
-	  echo '# pylint: disable-msg=C0103'; \
+	  echo '# pylint: disable=C0103'; \
 	  echo '# C0103: Invalid name'; \
 	  echo; \
 	  echo 'import sys'; \
@@ -1009,9 +1023,9 @@ $(PYTHON_BOOTSTRAP): Makefile | $(all_dirfiles)
 	  echo '# Temporarily alias commands until bash completion'; \
 	  echo '# generator is changed'; \
 	  echo 'if hasattr(main, "commands"):'; \
-	  echo '  commands = main.commands # pylint: disable-msg=E1101'; \
+	  echo '  commands = main.commands # pylint: disable=E1101'; \
 	  echo 'if hasattr(main, "aliases"):'; \
-	  echo '  aliases = main.aliases # pylint: disable-msg=E1101'; \
+	  echo '  aliases = main.aliases # pylint: disable=E1101'; \
 	  echo; \
 	  echo 'if __name__ == "__main__":'; \
 	  echo '  sys.exit(main.Main())'; \
@@ -1095,7 +1109,7 @@ lint: $(BUILT_SOURCES)
 		echo '"pep8" not found during configure' >&2; \
 	else \
 		$(PEP8) --repeat --ignore='$(PEP8_IGNORE)' --exclude='$(PEP8_EXCLUDE)' \
-			$(lint_python_code); \
+			$(pep8_python_code); \
 	fi
 	$(PYLINT) $(LINT_OPTS) $(lint_python_code)
 	cd $(top_srcdir)/qa && \
@@ -1187,7 +1201,7 @@ hs-apidoc: $(HS_BUILT_SRCS)
 	rm -rf $(APIDOC_HS_DIR)/*
 	@mkdir_p@ $(APIDOC_HS_DIR)/Ganeti/HTools/Program
 	$(HSCOLOUR) -print-css > $(APIDOC_HS_DIR)/Ganeti/hscolour.css
-	ln -s ../hscolour.css $(APIDOC_HS_DIR)/Ganeti/HTools/hscolour.css
+	$(LN_S) ../hscolour.css $(APIDOC_HS_DIR)/Ganeti/HTools/hscolour.css
 	set -e ; \
 	cd htools; \
 	if [ "$(HTOOLS_NOCURL)" ]; \
@@ -1239,7 +1253,7 @@ hs-coverage: $(haskell_tests)
 	@mkdir_p@ $(COVERAGE_HS_DIR)
 	hpc markup --destdir=$(COVERAGE_HS_DIR) htools/test $(HPCEXCL)
 	hpc report htools/test $(HPCEXCL)
-	ln -sf hpc_index.html $(COVERAGE_HS_DIR)/index.html
+	$(LN_S) -f hpc_index.html $(COVERAGE_HS_DIR)/index.html
 
 # Special "kind-of-QA" target for htools, needs special setup (all
 # tools compiled with -fhpc)
@@ -1247,7 +1261,7 @@ hs-coverage: $(haskell_tests)
 live-test: all
 	set -e ; \
 	cd htools; \
-	rm -f .hpc; ln -s ../.hpc .hpc; \
+	rm -f .hpc; $(LN_S) ../.hpc .hpc; \
 	rm -f *.tix *.mix; \
 	./live-test.sh; \
 	hpc sum --union $(HPCEXCL) $(addsuffix .tix,$(HS_PROGS:htools/%=%)) \
diff --git a/NEWS b/NEWS
index a6689a3ef173d835028c499f5f240bcd4eeb1a56..2e7d18f38fa30f3c8b3a378ed8cd201c1c3b6c24 100644
--- a/NEWS
+++ b/NEWS
@@ -1,10 +1,10 @@
 News
 ====
 
-Version 2.5.0 beta2
+Version 2.5.0 beta3
 -------------------
 
-*(Released Mon, 22 Aug 2011)*
+*(Released Wed, 31 Aug 2011)*
 
 Incompatible/important changes and bugfixes
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -85,6 +85,7 @@ New features
   is offline.
 - Opcodes have a new ``comment`` attribute.
 - Added basic SPICE support to KVM hypervisor.
+- ``tools/ganeti-listrunner`` allows passing of arguments to executable.
 
 Node group improvements
 ~~~~~~~~~~~~~~~~~~~~~~~
@@ -128,6 +129,14 @@ Misc
 - DRBD metadata volumes are overwritten with zeros during disk creation.
 
 
+Version 2.5.0 beta2
+-------------------
+
+*(Released Mon, 22 Aug 2011)*
+
+This was the second beta release of the 2.5 series.
+
+
 Version 2.5.0 beta1
 -------------------
 
@@ -136,6 +145,18 @@ Version 2.5.0 beta1
 This was the first beta release of the 2.5 series.
 
 
+Version 2.4.4
+-------------
+
+*(Released Tue, 23 Aug 2011)*
+
+Small bug-fixes:
+
+- Fixed documentation for importing with ``--src-dir`` option
+- Fixed a bug in ``ensure-dirs`` with queue/archive permissions
+- Fixed a parsing issue with DRBD 8.3.11 in the Linux kernel
+
+
 Version 2.4.3
 -------------
 
@@ -167,7 +188,7 @@ Many bug-fixes and a few small features:
 - Fixed off-by-one bug in job serial generation
 - ``gnt-node volumes``: Fix instance names
 - Fixed aliases in bash completion
-- Fixed a bug in reopening log files after beeing sent a SIGHUP
+- Fixed a bug in reopening log files after being sent a SIGHUP
 - Added a flag to burnin to allow specifying VCPU count
 - Bugfixes to non-root Ganeti configuration
 
diff --git a/autotools/build-bash-completion b/autotools/build-bash-completion
index eda56ab52213956b9d05a84c05e5022bf7f866f6..6eb0dbe7544d7a3cff14b43c344d371d45b880ce 100755
--- a/autotools/build-bash-completion
+++ b/autotools/build-bash-completion
@@ -23,7 +23,7 @@
 
 """
 
-# pylint: disable-msg=C0103
+# pylint: disable=C0103
 # [C0103] Invalid name build-bash-completion
 
 import os
@@ -246,7 +246,7 @@ class CompletionWriter:
 
     for opt in opts:
       # While documented, these variables aren't seen as public attributes by
-      # pylint. pylint: disable-msg=W0212
+      # pylint. pylint: disable=W0212
       opt.all_names = sorted(opt._short_opts + opt._long_opts)
 
       invalid = list(itertools.ifilterfalse(_OPT_NAME_RE.match, opt.all_names))
diff --git a/configure.ac b/configure.ac
index cb0a15318f0780a537be4893cbacc0d63e9dd648..dfcd932dc936f1599a924cc44cdf601a7764a2ef 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,7 +2,7 @@
 m4_define([gnt_version_major], [2])
 m4_define([gnt_version_minor], [5])
 m4_define([gnt_version_revision], [0])
-m4_define([gnt_version_suffix], [~beta2])
+m4_define([gnt_version_suffix], [~beta3])
 m4_define([gnt_version_full],
           m4_format([%d.%d.%d%s],
                     gnt_version_major, gnt_version_minor,
diff --git a/daemons/import-export b/daemons/import-export
index a63e4c24c55c93388210adad05ed75f7192a3f83..86034608de94418ff11e526138b92ee08eae0c1f 100755
--- a/daemons/import-export
+++ b/daemons/import-export
@@ -23,7 +23,7 @@
 
 """
 
-# pylint: disable-msg=C0103
+# pylint: disable=C0103
 # C0103: Invalid name import-export
 
 import errno
@@ -353,7 +353,7 @@ def ParseOptions():
   @return: Arguments to program
 
   """
-  global options # pylint: disable-msg=W0603
+  global options # pylint: disable=W0603
 
   parser = optparse.OptionParser(usage=("%%prog <status-file> {%s|%s}" %
                                         (constants.IEM_IMPORT,
@@ -587,7 +587,7 @@ def main():
         errmsg = "Exited with status %s" % (child.returncode, )
 
       status_file.SetExitStatus(child.returncode, errmsg)
-    except Exception, err: # pylint: disable-msg=W0703
+    except Exception, err: # pylint: disable=W0703
       logging.exception("Unhandled error occurred")
       status_file.SetExitStatus(constants.EXIT_FAILURE,
                                 "Unhandled error occurred: %s" % (err, ))
diff --git a/doc/admin.rst b/doc/admin.rst
index b366def1026000222299ec2fe4812113ac21ae8b..665c0e638ccbe82fc4432c5f55c20ab2ec23a568 100644
--- a/doc/admin.rst
+++ b/doc/admin.rst
@@ -391,16 +391,16 @@ parameters as such:
 - ``root_path`` to a valid setting (e.g. ``/dev/xvda1``)
 - ``bootloader_path`` and ``bootloader_args`` to empty
 
-Alternatively, you can delete the kernel management to instances, and
+Alternatively, you can delegate the kernel management to instances, and
 use either ``pvgrub`` or the deprecated ``pygrub``. For this, you must
-install the kernels and initrds in the instance, and create a valid grub
+install the kernels and initrds in the instance and create a valid GRUB
 v1 configuration file.
 
 For ``pvgrub`` (new in version 2.4.2), you need to set:
 
 - ``kernel_path`` to point to the ``pvgrub`` loader present on the node
   (e.g. ``/usr/lib/xen/boot/pv-grub-x86_32.gz``)
-- ``kernel_args`` to the path to the grub config file, relative to the
+- ``kernel_args`` to the path to the GRUB config file, relative to the
   instance (e.g. ``(hd0,0)/grub/menu.lst``)
 - ``root_path`` **must** be empty
 - ``bootloader_path`` and ``bootloader_args`` to empty
diff --git a/doc/design-cpu-pinning.rst b/doc/design-cpu-pinning.rst
index f1b3de140f325c592c3b68062a782b0831507ebb..a4c2eb96ca4bf0634cb2bf77ec6471070cf44a71 100644
--- a/doc/design-cpu-pinning.rst
+++ b/doc/design-cpu-pinning.rst
@@ -48,7 +48,7 @@ Here are some usage examples::
 
   # Pin vCPU 0 to any CPU, vCPU 1 to CPUs 1, 3, 4 or 5, and CPU 2 to
   # CPU 0
-  gnt-instance modify -H cpu_mask=all:1\\,3-4:0 my-inst
+  gnt-instance modify -H cpu_mask=all:1\\,3-5:0 my-inst
 
   # Pin entire VM to CPU 0
   gnt-instance modify -H cpu_mask=0 my-inst
@@ -56,13 +56,13 @@ Here are some usage examples::
   # Turn off CPU pinning (default setting)
   gnt-instance modify -H cpu_mask=all my-inst
 
-Assuming an instance has 2 vCPUs, the following commands will fail::
+Assuming an instance has 3 vCPUs, the following commands will fail::
 
   # not enough mappings
-  gnt-instance modify -H cpu_mask=0 my-inst
+  gnt-instance modify -H cpu_mask=0:1 my-inst
 
   # too many
-  gnt-instance modify -H cpu_mask=2:1:1 my-inst
+  gnt-instance modify -H cpu_mask=2:1:1:all my-inst
 
 Validation
 ----------
@@ -102,8 +102,8 @@ indicated by CPU pinning information, instance failover will fail.
 
 In case of emergency, to force failover to ignore mismatching CPU
 information, the following switch can be used:
-``gnt-instance failover --ignore-cpu-mismatch my-inst``.
-This command will try to fail the instance with the current cpu mask,
+``gnt-instance failover --fix-cpu-mismatch my-inst``.
+This command will try to failover the instance with the current cpu mask,
 but if that fails, it will change the mask to be "all".
 
 Migration
@@ -127,8 +127,7 @@ Configuration file
 ------------------
 
 The pinning information is kept for each instance's hypervisor
-params section of the configuration file as
-``cpu_mask: [ [ a ], [ b, c ], [ d ] ]``
+params section of the configuration file as the original string.
 
 Xen
 ---
diff --git a/doc/rapi.rst b/doc/rapi.rst
index 5ea41ca1ff58d85be802b9382cc6865278d2fe67..efb4a4b5ee44253a443cfc85a5d3a328796da60a 100644
--- a/doc/rapi.rst
+++ b/doc/rapi.rst
@@ -960,20 +960,23 @@ console. Contained keys:
      constants.CONS_MESSAGE,
      constants.CONS_SSH,
      constants.CONS_VNC,
+     constants.CONS_SPICE,
      ])
 
 ``instance``
   Instance name.
 ``kind``
   Console type, one of :pyeval:`constants.CONS_SSH`,
-  :pyeval:`constants.CONS_VNC` or :pyeval:`constants.CONS_MESSAGE`.
+  :pyeval:`constants.CONS_VNC`, :pyeval:`constants.CONS_SPICE`
+  or :pyeval:`constants.CONS_MESSAGE`.
 ``message``
   Message to display (:pyeval:`constants.CONS_MESSAGE` type only).
 ``host``
-  Host to connect to (:pyeval:`constants.CONS_SSH` and
-  :pyeval:`constants.CONS_VNC` only).
+  Host to connect to (:pyeval:`constants.CONS_SSH`,
+  :pyeval:`constants.CONS_VNC` or :pyeval:`constants.CONS_SPICE` only).
 ``port``
-  TCP port to connect to (:pyeval:`constants.CONS_VNC` only).
+  TCP port to connect to (:pyeval:`constants.CONS_VNC` or
+  :pyeval:`constants.CONS_SPICE` only).
 ``user``
   Username to use (:pyeval:`constants.CONS_SSH` only).
 ``command``
diff --git a/htools/Ganeti/HTools/Cluster.hs b/htools/Ganeti/HTools/Cluster.hs
index 9e946936658352701d4f56753686cb069f62a65f..3d1b9f23fc87ef72e39c4b46cb584efab5161340 100644
--- a/htools/Ganeti/HTools/Cluster.hs
+++ b/htools/Ganeti/HTools/Cluster.hs
@@ -61,7 +61,6 @@ module Ganeti.HTools.Cluster
     , tryAlloc
     , tryMGAlloc
     , tryReloc
-    , tryEvac
     , tryNodeEvac
     , tryChangeGroup
     , collapseFailures
@@ -811,45 +810,6 @@ tryReloc _ _ _ reqn _  = fail $ "Unsupported number of relocation \
                                 \destinations required (" ++ show reqn ++
                                                   "), only one supported"
 
--- | Change an instance's secondary node.
-evacInstance :: (Monad m) =>
-                [Ndx]                      -- ^ Excluded nodes
-             -> Instance.List              -- ^ The current instance list
-             -> (Node.List, AllocSolution) -- ^ The current state
-             -> Idx                        -- ^ The instance to evacuate
-             -> m (Node.List, AllocSolution)
-evacInstance ex_ndx il (nl, old_as) idx = do
-  -- FIXME: hardcoded one node here
-
-  -- Longer explanation: evacuation is currently hardcoded to DRBD
-  -- instances (which have one secondary); hence, even if the
-  -- IAllocator protocol can request N nodes for an instance, and all
-  -- the message parsing/loading pass this, this implementation only
-  -- supports one; this situation needs to be revisited if we ever
-  -- support more than one secondary, or if we change the storage
-  -- model
-  new_as <- tryReloc nl il idx 1 ex_ndx
-  case asSolutions new_as of
-    -- an individual relocation succeeded, we kind of compose the data
-    -- from the two solutions
-    csol@(nl', _, _, _):_ ->
-        return (nl', new_as { asSolutions = csol:asSolutions old_as })
-    -- this relocation failed, so we fail the entire evac
-    _ -> fail $ "Can't evacuate instance " ++
-         Instance.name (Container.find idx il) ++
-             ": " ++ describeSolution new_as
-
--- | Try to evacuate a list of nodes.
-tryEvac :: (Monad m) =>
-            Node.List       -- ^ The node list
-         -> Instance.List   -- ^ The instance list
-         -> [Idx]           -- ^ Instances to be evacuated
-         -> [Ndx]           -- ^ Restricted nodes (the ones being evacuated)
-         -> m AllocSolution -- ^ Solution list
-tryEvac nl il idxs ex_ndx = do
-  (_, sol) <- foldM (evacInstance ex_ndx il) (nl, emptyAllocSolution) idxs
-  return sol
-
 -- | Function which fails if the requested mode is change secondary.
 --
 -- This is useful since except DRBD, no other disk template can
@@ -1107,10 +1067,11 @@ tryNodeEvac _ ini_nl ini_il mode idxs =
                       splitCluster ini_nl ini_il
         (fin_nl, fin_il, esol) =
             foldl' (\state@(nl, il, _) inst ->
-                        let gdx = instancePriGroup nl inst in
+                        let gdx = instancePriGroup nl inst
+                            pdx = Instance.pNode inst in
                         updateEvacSolution state (Instance.idx inst) $
                         availableGroupNodes group_ndx
-                          excl_ndx gdx >>=
+                          (IntSet.insert pdx excl_ndx) gdx >>=
                         nodeEvacInstance nl il mode inst gdx
                    )
             (ini_nl, ini_il, emptyEvacSolution)
diff --git a/htools/Ganeti/HTools/IAlloc.hs b/htools/Ganeti/HTools/IAlloc.hs
index 81e6dfdf1be40878d42d3d258b6bf3718f529684..aabdd76f902b2523054454e7780cdeae3c546b43 100644
--- a/htools/Ganeti/HTools/IAlloc.hs
+++ b/htools/Ganeti/HTools/IAlloc.hs
@@ -26,6 +26,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 module Ganeti.HTools.IAlloc
     ( readRequest
     , runIAllocator
+    , processRelocate
     ) where
 
 import Data.Either ()
@@ -283,6 +284,8 @@ processRelocate gl nl il idx 1 exndx = do
       snode = Instance.sNode inst
   when (snode == sorig) $
        fail "Internal error: instance didn't change secondary node?!"
+  when (snode == pnode) $
+       fail "Internal error: selected primary as new secondary?!"
 
   nodes' <- if (nodes == [pnode, snode])
             then return [snode] -- only the new secondary is needed
diff --git a/htools/Ganeti/HTools/QC.hs b/htools/Ganeti/HTools/QC.hs
index 80551ebb74541291f5de2deb05ea2413ec8e53d2..305a2bee7e7114f7a229feb5d132e6ca587b963c 100644
--- a/htools/Ganeti/HTools/QC.hs
+++ b/htools/Ganeti/HTools/QC.hs
@@ -908,8 +908,9 @@ prop_ClusterAllocEvac node inst =
                (xnl, xi, _, _):[] ->
                    let sdx = Instance.sNode xi
                        il' = Container.add (Instance.idx xi) xi il
-                   in case Cluster.tryEvac xnl il' [Instance.idx xi] [sdx] of
-                        Just _ -> True
+                   in case IAlloc.processRelocate defGroupList xnl il'
+                          (Instance.idx xi) 1 [sdx] of
+                        Types.Ok _ -> True
                         _ -> False
                _ -> False
 
diff --git a/lib/asyncnotifier.py b/lib/asyncnotifier.py
index 6f74542a989d39244acff5a1b139d50da9b380d2..37a2fcbd8b0f191cd81c2d63d5bd1ddb3fc9c374 100644
--- a/lib/asyncnotifier.py
+++ b/lib/asyncnotifier.py
@@ -26,7 +26,7 @@ import asyncore
 import logging
 
 try:
-  # pylint: disable-msg=E0611
+  # pylint: disable=E0611
   from pyinotify import pyinotify
 except ImportError:
   import pyinotify
@@ -42,7 +42,7 @@ class AsyncNotifier(asyncore.file_dispatcher):
   """An asyncore dispatcher for inotify events.
 
   """
-  # pylint: disable-msg=W0622,W0212
+  # pylint: disable=W0622,W0212
   def __init__(self, watch_manager, default_proc_fun=None, map=None):
     """Initializes this class.
 
@@ -90,7 +90,7 @@ class FileEventHandlerBase(pyinotify.ProcessEvent):
     @param watch_manager: inotify watch manager
 
     """
-    # pylint: disable-msg=W0231
+    # pylint: disable=W0231
     # no need to call the parent's constructor
     self.watch_manager = watch_manager
 
@@ -168,7 +168,7 @@ class SingleFileEventHandler(FileEventHandlerBase):
     if self._watch_handle is not None and self.RemoveWatch(self._watch_handle):
       self._watch_handle = None
 
-  # pylint: disable-msg=C0103
+  # pylint: disable=C0103
   # this overrides a method in pyinotify.ProcessEvent
   def process_IN_IGNORED(self, event):
     # Since we monitor a single file rather than the directory it resides in,
@@ -182,7 +182,7 @@ class SingleFileEventHandler(FileEventHandlerBase):
     self._watch_handle = None
     self._callback(False)
 
-  # pylint: disable-msg=C0103
+  # pylint: disable=C0103
   # this overrides a method in pyinotify.ProcessEvent
   def process_IN_MODIFY(self, event):
     # This gets called when the monitored file is modified. Note that this
diff --git a/lib/backend.py b/lib/backend.py
index 84a39d07de6a1e6dff287d54108046e4eafbf3a6..36da855cc33448a052366b2d9e11eb4b20248a1b 100644
--- a/lib/backend.py
+++ b/lib/backend.py
@@ -28,7 +28,7 @@
 
 """
 
-# pylint: disable-msg=E1103
+# pylint: disable=E1103
 
 # E1103: %s %r has no %r member (but some types could not be
 # inferred), because the _TryOSFromDisk returns either (True, os_obj)
@@ -412,7 +412,7 @@ def LeaveCluster(modify_ssh_setup):
     utils.RemoveFile(constants.CONFD_HMAC_KEY)
     utils.RemoveFile(constants.RAPI_CERT_FILE)
     utils.RemoveFile(constants.NODED_CERT_FILE)
-  except: # pylint: disable-msg=W0702
+  except: # pylint: disable=W0702
     logging.exception("Error while removing cluster secrets")
 
   result = utils.RunCmd([constants.DAEMON_UTIL, "stop", constants.CONFD])
@@ -668,7 +668,7 @@ def GetBlockDevSizes(devices):
   blockdevs = {}
 
   for devpath in devices:
-    if os.path.commonprefix([DEV_PREFIX, devpath]) != DEV_PREFIX:
+    if not utils.IsBelowDir(DEV_PREFIX, devpath):
       continue
 
     try:
@@ -1340,7 +1340,7 @@ def BlockdevCreate(disk, size, owner, on_primary, info):
 
   """
   # TODO: remove the obsolete "size" argument
-  # pylint: disable-msg=W0613
+  # pylint: disable=W0613
   clist = []
   if disk.children:
     for child in disk.children:
@@ -1352,7 +1352,7 @@ def BlockdevCreate(disk, size, owner, on_primary, info):
         # we need the children open in case the device itself has to
         # be assembled
         try:
-          # pylint: disable-msg=E1103
+          # pylint: disable=E1103
           crdev.Open()
         except errors.BlockDeviceError, err:
           _Fail("Can't make child '%s' read-write: %s", child, err)
@@ -1568,7 +1568,7 @@ def BlockdevAssemble(disk, owner, as_primary, idx):
   try:
     result = _RecursiveAssembleBD(disk, owner, as_primary)
     if isinstance(result, bdev.BlockDev):
-      # pylint: disable-msg=E1103
+      # pylint: disable=E1103
       result = result.dev_path
       if as_primary:
         _SymlinkBlockDev(owner, result, idx)
@@ -2507,8 +2507,8 @@ def _TransformFileStorageDir(fs_dir):
   fs_dir = os.path.normpath(fs_dir)
   base_fstore = cfg.GetFileStorageDir()
   base_shared = cfg.GetSharedFileStorageDir()
-  if ((os.path.commonprefix([fs_dir, base_fstore]) != base_fstore) and
-      (os.path.commonprefix([fs_dir, base_shared]) != base_shared)):
+  if not (utils.IsBelowDir(base_fstore, fs_dir) or
+          utils.IsBelowDir(base_shared, fs_dir)):
     _Fail("File storage directory '%s' is not under base file"
           " storage directory '%s' or shared storage directory '%s'",
           fs_dir, base_fstore, base_shared)
@@ -2875,12 +2875,12 @@ def _GetImportExportIoCommand(instance, mode, ieio, ieargs):
     if not utils.IsNormAbsPath(filename):
       _Fail("Path '%s' is not normalized or absolute", filename)
 
-    directory = os.path.normpath(os.path.dirname(filename))
+    real_filename = os.path.realpath(filename)
+    directory = os.path.dirname(real_filename)
 
-    if (os.path.commonprefix([constants.EXPORT_DIR, directory]) !=
-        constants.EXPORT_DIR):
-      _Fail("File '%s' is not under exports directory '%s'",
-            filename, constants.EXPORT_DIR)
+    if not utils.IsBelowDir(constants.EXPORT_DIR, real_filename):
+      _Fail("File '%s' is not under exports directory '%s': %s",
+            filename, constants.EXPORT_DIR, real_filename)
 
     # Create directory
     utils.Makedirs(directory, mode=0750)
@@ -3322,7 +3322,7 @@ def PowercycleNode(hypervisor_type):
   # ensure the child is running on ram
   try:
     utils.Mlockall()
-  except Exception: # pylint: disable-msg=W0703
+  except Exception: # pylint: disable=W0703
     pass
   time.sleep(5)
   hyper.PowercycleNode()
@@ -3347,7 +3347,7 @@ class HooksRunner(object):
       hooks_base_dir = constants.HOOKS_BASE_DIR
     # yeah, _BASE_DIR is not valid for attributes, we use it like a
     # constant
-    self._BASE_DIR = hooks_base_dir # pylint: disable-msg=C0103
+    self._BASE_DIR = hooks_base_dir # pylint: disable=C0103
 
   def RunHooks(self, hpath, phase, env):
     """Run the scripts in the hooks directory.
diff --git a/lib/bdev.py b/lib/bdev.py
index 905ad1fc13b7f6490977776a986e6dd5d9ac95e2..d8603df8d13f31e57922423b538e945b12f5be5d 100644
--- a/lib/bdev.py
+++ b/lib/bdev.py
@@ -791,7 +791,10 @@ class DRBD8Status(object):
   LINE_RE = re.compile(r"\s*[0-9]+:\s*cs:(\S+)\s+(?:st|ro):([^/]+)/(\S+)"
                        "\s+ds:([^/]+)/(\S+)\s+.*$")
   SYNC_RE = re.compile(r"^.*\ssync'ed:\s*([0-9.]+)%.*"
-                       "\sfinish: ([0-9]+):([0-9]+):([0-9]+)\s.*$")
+                       # Due to a bug in drbd in the kernel, introduced in
+                       # commit 4b0715f096 (still unfixed as of 2011-08-22)
+                       "(?:\s|M)"
+                       "finish: ([0-9]+):([0-9]+):([0-9]+)\s.*$")
 
   CS_UNCONFIGURED = "Unconfigured"
   CS_STANDALONE = "StandAlone"
@@ -886,7 +889,7 @@ class DRBD8Status(object):
       self.est_time = None
 
 
-class BaseDRBD(BlockDev): # pylint: disable-msg=W0223
+class BaseDRBD(BlockDev): # pylint: disable=W0223
   """Base DRBD class.
 
   This class contains a few bits of common functionality between the
@@ -1762,7 +1765,7 @@ class DRBD8(BaseDRBD):
 
     """
     # TODO: Rewrite to not use a for loop just because there is 'break'
-    # pylint: disable-msg=W0631
+    # pylint: disable=W0631
     net_data = (self._lhost, self._lport, self._rhost, self._rport)
     for minor in (self._aminor,):
       info = self._GetDevInfo(self._GetShowData(minor))
diff --git a/lib/bootstrap.py b/lib/bootstrap.py
index 3e75dfa124e2b18e4cd92e651e374d4dd8d5711f..733a8ba98a27eab9b88e7062fc41e76629ded3c6 100644
--- a/lib/bootstrap.py
+++ b/lib/bootstrap.py
@@ -242,7 +242,7 @@ def _InitFileStorage(file_storage_dir):
   return file_storage_dir
 
 
-def InitCluster(cluster_name, mac_prefix, # pylint: disable-msg=R0913
+def InitCluster(cluster_name, mac_prefix, # pylint: disable=R0913
                 master_netdev, file_storage_dir, shared_file_storage_dir,
                 candidate_pool_size, secondary_ip=None, vg_name=None,
                 beparams=None, nicparams=None, ndparams=None, hvparams=None,
diff --git a/lib/build/sphinx_ext.py b/lib/build/sphinx_ext.py
index 474721dccdeec46626972b832f0a9d76c48ce2d5..0d2d2e3cff6ced73f9e8a32ad57787bd51bf01c2 100644
--- a/lib/build/sphinx_ext.py
+++ b/lib/build/sphinx_ext.py
@@ -42,7 +42,7 @@ from ganeti import opcodes
 from ganeti import ht
 from ganeti import rapi
 
-import ganeti.rapi.rlib2 # pylint: disable-msg=W0611
+import ganeti.rapi.rlib2 # pylint: disable=W0611
 
 
 COMMON_PARAM_NAMES = map(compat.fst, opcodes.OpCode.OP_PARAMS)
@@ -213,7 +213,7 @@ def PythonEvalRole(role, rawtext, text, lineno, inliner,
   The expression's result is included as a literal.
 
   """
-  # pylint: disable-msg=W0102,W0613,W0142
+  # pylint: disable=W0102,W0613,W0142
   # W0102: Dangerous default value as argument
   # W0142: Used * or ** magic
   # W0613: Unused argument
@@ -222,7 +222,7 @@ def PythonEvalRole(role, rawtext, text, lineno, inliner,
 
   try:
     result = eval(code, EVAL_NS)
-  except Exception, err: # pylint: disable-msg=W0703
+  except Exception, err: # pylint: disable=W0703
     msg = inliner.reporter.error("Failed to evaluate %r: %s" % (code, err),
                                  line=lineno)
     return ([inliner.problematic(rawtext, rawtext, msg)], [msg])
diff --git a/lib/cli.py b/lib/cli.py
index f55804d04bd6809e41af4f5079e96d70351a1ea1..d7e497e58b8dee66035564b6201bb83c11be4600 100644
--- a/lib/cli.py
+++ b/lib/cli.py
@@ -259,7 +259,7 @@ _PRIONAME_TO_VALUE = dict(_PRIORITY_NAMES)
 
 
 class _Argument:
-  def __init__(self, min=0, max=None): # pylint: disable-msg=W0622
+  def __init__(self, min=0, max=None): # pylint: disable=W0622
     self.min = min
     self.max = max
 
@@ -274,7 +274,7 @@ class ArgSuggest(_Argument):
   Value can be any of the ones passed to the constructor.
 
   """
-  # pylint: disable-msg=W0622
+  # pylint: disable=W0622
   def __init__(self, min=0, max=None, choices=None):
     _Argument.__init__(self, min=min, max=max)
     self.choices = choices
@@ -462,7 +462,7 @@ def RemoveTags(opts, args):
   SubmitOpCode(op, opts=opts)
 
 
-def check_unit(option, opt, value): # pylint: disable-msg=W0613
+def check_unit(option, opt, value): # pylint: disable=W0613
   """OptParsers custom converter for units.
 
   """
@@ -509,7 +509,7 @@ def _SplitKeyVal(opt, data):
   return kv_dict
 
 
-def check_ident_key_val(option, opt, value):  # pylint: disable-msg=W0613
+def check_ident_key_val(option, opt, value):  # pylint: disable=W0613
   """Custom parser for ident:key=val,key=val options.
 
   This will store the parsed values as a tuple (ident, {key: val}). As such,
@@ -537,7 +537,7 @@ def check_ident_key_val(option, opt, value):  # pylint: disable-msg=W0613
   return retval
 
 
-def check_key_val(option, opt, value):  # pylint: disable-msg=W0613
+def check_key_val(option, opt, value):  # pylint: disable=W0613
   """Custom parser class for key=val,key=val options.
 
   This will store the parsed values as a dict {key: val}.
@@ -546,7 +546,7 @@ def check_key_val(option, opt, value):  # pylint: disable-msg=W0613
   return _SplitKeyVal(opt, value)
 
 
-def check_bool(option, opt, value): # pylint: disable-msg=W0613
+def check_bool(option, opt, value): # pylint: disable=W0613
   """Custom parser for yes/no options.
 
   This will store the parsed value as either True or False.
@@ -2363,8 +2363,8 @@ def GenerateTable(headers, fields, separator, data,
   if unitfields is None:
     unitfields = []
 
-  numfields = utils.FieldSet(*numfields)   # pylint: disable-msg=W0142
-  unitfields = utils.FieldSet(*unitfields) # pylint: disable-msg=W0142
+  numfields = utils.FieldSet(*numfields)   # pylint: disable=W0142
+  unitfields = utils.FieldSet(*unitfields) # pylint: disable=W0142
 
   format_fields = []
   for field in fields:
diff --git a/lib/client/gnt_backup.py b/lib/client/gnt_backup.py
index 4b7f81bf68f6c6f12d82d194483668cd40436a11..5999e3267c5ddcdad493517097b2efc0d7825bdb 100644
--- a/lib/client/gnt_backup.py
+++ b/lib/client/gnt_backup.py
@@ -20,7 +20,7 @@
 
 """Backup related commands"""
 
-# pylint: disable-msg=W0401,W0613,W0614,C0103
+# pylint: disable=W0401,W0613,W0614,C0103
 # W0401: Wildcard import ganeti.cli
 # W0613: Unused argument, since all functions follow the same API
 # W0614: Unused import %s from wildcard import (since we need cli)
diff --git a/lib/client/gnt_cluster.py b/lib/client/gnt_cluster.py
index 7eb11e53e1c5d9bf397dbb2a9d3148bdfdad1444..b61e8e3417968616aa28e1c875ebcdaa789eae8e 100644
--- a/lib/client/gnt_cluster.py
+++ b/lib/client/gnt_cluster.py
@@ -20,7 +20,7 @@
 
 """Cluster related commands"""
 
-# pylint: disable-msg=W0401,W0613,W0614,C0103
+# pylint: disable=W0401,W0613,W0614,C0103
 # W0401: Wildcard import ganeti.cli
 # W0613: Unused argument, since all functions follow the same API
 # W0614: Unused import %s from wildcard import (since we need cli)
@@ -622,7 +622,7 @@ def MasterPing(opts, args):
     cl = GetClient()
     cl.QueryClusterInfo()
     return 0
-  except Exception: # pylint: disable-msg=W0703
+  except Exception: # pylint: disable=W0703
     return 1
 
 
@@ -668,7 +668,7 @@ def _RenewCrypto(new_cluster_cert, new_rapi_cert, rapi_cert_filename,
 
   """
   if new_rapi_cert and rapi_cert_filename:
-    ToStderr("Only one of the --new-rapi-certficate and --rapi-certificate"
+    ToStderr("Only one of the --new-rapi-certificate and --rapi-certificate"
              " options can be specified at the same time.")
     return 1
 
@@ -685,14 +685,14 @@ def _RenewCrypto(new_cluster_cert, new_rapi_cert, rapi_cert_filename,
 
       OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
                                       rapi_cert_pem)
-    except Exception, err: # pylint: disable-msg=W0703
+    except Exception, err: # pylint: disable=W0703
       ToStderr("Can't load new RAPI certificate from %s: %s" %
                (rapi_cert_filename, str(err)))
       return 1
 
     try:
       OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, rapi_cert_pem)
-    except Exception, err: # pylint: disable-msg=W0703
+    except Exception, err: # pylint: disable=W0703
       ToStderr("Can't load new RAPI private key from %s: %s" %
                (rapi_cert_filename, str(err)))
       return 1
@@ -703,7 +703,7 @@ def _RenewCrypto(new_cluster_cert, new_rapi_cert, rapi_cert_filename,
   if cds_filename:
     try:
       cds = utils.ReadFile(cds_filename)
-    except Exception, err: # pylint: disable-msg=W0703
+    except Exception, err: # pylint: disable=W0703
       ToStderr("Can't load new cluster domain secret from %s: %s" %
                (cds_filename, str(err)))
       return 1
diff --git a/lib/client/gnt_debug.py b/lib/client/gnt_debug.py
index 0a73525cf14f0c68d333cffd8f07df5e4064c8a4..228733b5d27b7ab02d0db59b1dc39d03db7d6bb7 100644
--- a/lib/client/gnt_debug.py
+++ b/lib/client/gnt_debug.py
@@ -20,7 +20,7 @@
 
 """Debugging commands"""
 
-# pylint: disable-msg=W0401,W0614,C0103
+# pylint: disable=W0401,W0614,C0103
 # W0401: Wildcard import ganeti.cli
 # W0614: Unused import %s from wildcard import (since we need cli)
 # C0103: Invalid name gnt-backup
@@ -90,7 +90,7 @@ def GenericOpCodes(opts, args):
     ToStdout("Loading...")
   for job_idx in range(opts.rep_job):
     for fname in args:
-      # pylint: disable-msg=W0142
+      # pylint: disable=W0142
       op_data = simplejson.loads(utils.ReadFile(fname))
       op_list = [opcodes.OpCode.LoadOpCode(val) for val in op_data]
       op_list = op_list * opts.rep_op
@@ -564,7 +564,7 @@ def TestJobqueue(opts, _):
   return 0
 
 
-def ListLocks(opts, args): # pylint: disable-msg=W0613
+def ListLocks(opts, args): # pylint: disable=W0613
   """List all locks.
 
   @param opts: the command line options selected by the user
diff --git a/lib/client/gnt_group.py b/lib/client/gnt_group.py
index 2fb73e1b07b62e0194dc6e9da08154f7159bf0b5..096bb9934bce1990e725d221c87606b175d77c94 100644
--- a/lib/client/gnt_group.py
+++ b/lib/client/gnt_group.py
@@ -20,7 +20,7 @@
 
 """Node group related commands"""
 
-# pylint: disable-msg=W0401,W0614
+# pylint: disable=W0401,W0614
 # W0401: Wildcard import ganeti.cli
 # W0614: Unused import %s from wildcard import (since we need cli)
 
diff --git a/lib/client/gnt_instance.py b/lib/client/gnt_instance.py
index 7f51327a57ee370cfbeff0ad017cfa4d50f351d3..84cc869af243e8c9d76bb5521f1b94d345d49d5c 100644
--- a/lib/client/gnt_instance.py
+++ b/lib/client/gnt_instance.py
@@ -20,7 +20,7 @@
 
 """Instance related commands"""
 
-# pylint: disable-msg=W0401,W0614,C0103
+# pylint: disable=W0401,W0614,C0103
 # W0401: Wildcard import ganeti.cli
 # W0614: Unused import %s from wildcard import (since we need cli)
 # C0103: Invalid name gnt-instance
@@ -87,7 +87,7 @@ def _ExpandMultiNames(mode, names, client=None):
   @raise errors.OpPrereqError: for invalid input parameters
 
   """
-  # pylint: disable-msg=W0142
+  # pylint: disable=W0142
 
   if client is None:
     client = GetClient()
@@ -322,7 +322,7 @@ def BatchCreate(opts, args):
   json_filename = args[0]
   try:
     instance_data = simplejson.loads(utils.ReadFile(json_filename))
-  except Exception, err: # pylint: disable-msg=W0703
+  except Exception, err: # pylint: disable=W0703
     ToStderr("Can't parse the instance definition file: %s" % str(err))
     return 1
 
@@ -335,7 +335,7 @@ def BatchCreate(opts, args):
   # Iterate over the instances and do:
   #  * Populate the specs with default value
   #  * Validate the instance specs
-  i_names = utils.NiceSort(instance_data.keys()) # pylint: disable-msg=E1103
+  i_names = utils.NiceSort(instance_data.keys()) # pylint: disable=E1103
   for name in i_names:
     specs = instance_data[name]
     specs = _PopulateWithDefaults(specs)
@@ -934,6 +934,9 @@ def _DoConsole(console, show_command, cluster_name, feedback_fn=ToStdout,
                 " URL <vnc://%s:%s/>",
                 console.instance, console.host, console.port,
                 console.display, console.host, console.port)
+  elif console.kind == constants.CONS_SPICE:
+    feedback_fn("Instance %s has SPICE listening on %s:%s", console.instance,
+                console.host, console.port)
   elif console.kind == constants.CONS_SSH:
     # Convert to string if not already one
     if isinstance(console.command, basestring):
diff --git a/lib/client/gnt_job.py b/lib/client/gnt_job.py
index 4e11eabb73cc69970a31317fea89256ec3f8a9a6..4d888ad835242b9f26469bf511caa47de9d80e74 100644
--- a/lib/client/gnt_job.py
+++ b/lib/client/gnt_job.py
@@ -20,7 +20,7 @@
 
 """Job related commands"""
 
-# pylint: disable-msg=W0401,W0613,W0614,C0103
+# pylint: disable=W0401,W0613,W0614,C0103
 # W0401: Wildcard import ganeti.cli
 # W0613: Unused argument, since all functions follow the same API
 # W0614: Unused import %s from wildcard import (since we need cli)
diff --git a/lib/client/gnt_node.py b/lib/client/gnt_node.py
index 1847b4162a43e566bc6a32615994b9758afbd429..270aff55eb63016d8c5531e0eba5f889ee0e41ea 100644
--- a/lib/client/gnt_node.py
+++ b/lib/client/gnt_node.py
@@ -20,7 +20,7 @@
 
 """Node related commands"""
 
-# pylint: disable-msg=W0401,W0613,W0614,C0103
+# pylint: disable=W0401,W0613,W0614,C0103
 # W0401: Wildcard import ganeti.cli
 # W0613: Unused argument, since all functions follow the same API
 # W0614: Unused import %s from wildcard import (since we need cli)
diff --git a/lib/client/gnt_os.py b/lib/client/gnt_os.py
index e80aa6513a66d985e4c5893c3fb3c07046f0b15e..3554524ff92c8a34df5b5a75d3166ffb2bbe7baf 100644
--- a/lib/client/gnt_os.py
+++ b/lib/client/gnt_os.py
@@ -20,7 +20,7 @@
 
 """OS scripts related commands"""
 
-# pylint: disable-msg=W0401,W0613,W0614,C0103
+# pylint: disable=W0401,W0613,W0614,C0103
 # W0401: Wildcard import ganeti.cli
 # W0613: Unused argument, since all functions follow the same API
 # W0614: Unused import %s from wildcard import (since we need cli)
diff --git a/lib/cmdlib.py b/lib/cmdlib.py
index 289f6005794e4bdd0a8ab3c4ef2948d7e3fa4074..1dae379d82d813545090f15d4610468dccecf958 100644
--- a/lib/cmdlib.py
+++ b/lib/cmdlib.py
@@ -21,7 +21,7 @@
 
 """Module implementing the master-side code."""
 
-# pylint: disable-msg=W0201,C0302
+# pylint: disable=W0201,C0302
 
 # W0201 since most LU attributes are defined in CheckPrereq or similar
 # functions
@@ -60,7 +60,7 @@ from ganeti import qlang
 from ganeti import opcodes
 from ganeti import ht
 
-import ganeti.masterd.instance # pylint: disable-msg=W0611
+import ganeti.masterd.instance # pylint: disable=W0611
 
 
 class ResultWithJobs:
@@ -131,10 +131,10 @@ class LogicalUnit(object):
     # Used to force good behavior when calling helper functions
     self.recalculate_locks = {}
     # logging
-    self.Log = processor.Log # pylint: disable-msg=C0103
-    self.LogWarning = processor.LogWarning # pylint: disable-msg=C0103
-    self.LogInfo = processor.LogInfo # pylint: disable-msg=C0103
-    self.LogStep = processor.LogStep # pylint: disable-msg=C0103
+    self.Log = processor.Log # pylint: disable=C0103
+    self.LogWarning = processor.LogWarning # pylint: disable=C0103
+    self.LogInfo = processor.LogInfo # pylint: disable=C0103
+    self.LogStep = processor.LogStep # pylint: disable=C0103
     # support for dry-run
     self.dry_run_result = None
     # support for generic debug attribute
@@ -322,7 +322,7 @@ class LogicalUnit(object):
     """
     # API must be kept, thus we ignore the unused argument and could
     # be a function warnings
-    # pylint: disable-msg=W0613,R0201
+    # pylint: disable=W0613,R0201
     return lu_result
 
   def _ExpandAndLockInstance(self):
@@ -390,7 +390,7 @@ class LogicalUnit(object):
     del self.recalculate_locks[locking.LEVEL_NODE]
 
 
-class NoHooksLU(LogicalUnit): # pylint: disable-msg=W0223
+class NoHooksLU(LogicalUnit): # pylint: disable=W0223
   """Simple LU which runs no hooks.
 
   This LU is intended as a parent for other LogicalUnits which will
@@ -757,7 +757,7 @@ def _RunPostHook(lu, node_name):
   try:
     hm.RunPhase(constants.HOOKS_PHASE_POST, nodes=[node_name])
   except:
-    # pylint: disable-msg=W0702
+    # pylint: disable=W0702
     lu.LogWarning("Errors occurred running hooks on %s" % node_name)
 
 
@@ -1087,7 +1087,7 @@ def _BuildInstanceHookEnvByObject(lu, instance, override=None):
   }
   if override:
     args.update(override)
-  return _BuildInstanceHookEnv(**args) # pylint: disable-msg=W0142
+  return _BuildInstanceHookEnv(**args) # pylint: disable=W0142
 
 
 def _AdjustCandidatePool(lu, exceptions):
@@ -1371,7 +1371,7 @@ def _VerifyCertificate(filename):
   try:
     cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
                                            utils.ReadFile(filename))
-  except Exception, err: # pylint: disable-msg=W0703
+  except Exception, err: # pylint: disable=W0703
     return (LUClusterVerifyConfig.ETYPE_ERROR,
             "Failed to load X509 certificate %s: %s" % (filename, err))
 
@@ -1486,7 +1486,7 @@ class _VerifyErrors(object):
     if args:
       msg = msg % args
     # then format the whole message
-    if self.op.error_codes: # This is a mix-in. pylint: disable-msg=E1101
+    if self.op.error_codes: # This is a mix-in. pylint: disable=E1101
       msg = "%s:%s:%s:%s:%s" % (ltype, etxt, itype, item, msg)
     else:
       if item:
@@ -1495,14 +1495,14 @@ class _VerifyErrors(object):
         item = ""
       msg = "%s: %s%s: %s" % (ltype, itype, item, msg)
     # and finally report it via the feedback_fn
-    self._feedback_fn("  - %s" % msg) # Mix-in. pylint: disable-msg=E1101
+    self._feedback_fn("  - %s" % msg) # Mix-in. pylint: disable=E1101
 
   def _ErrorIf(self, cond, *args, **kwargs):
     """Log an error message if the passed condition is True.
 
     """
     cond = (bool(cond)
-            or self.op.debug_simulate_errors) # pylint: disable-msg=E1101
+            or self.op.debug_simulate_errors) # pylint: disable=E1101
     if cond:
       self._Error(*args, **kwargs)
     # do not mark the operation as failed for WARN cases only
@@ -1539,7 +1539,7 @@ class LUClusterVerify(NoHooksLU):
                 for group in groups)
 
     # Fix up all parameters
-    for op in itertools.chain(*jobs): # pylint: disable-msg=W0142
+    for op in itertools.chain(*jobs): # pylint: disable=W0142
       op.debug_simulate_errors = self.op.debug_simulate_errors
       op.verbose = self.op.verbose
       op.error_codes = self.op.error_codes
@@ -1801,7 +1801,7 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
 
     """
     node = ninfo.name
-    _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
+    _ErrorIf = self._ErrorIf # pylint: disable=C0103
 
     # main result, nresult should be a non-empty dict
     test = not nresult or not isinstance(nresult, dict)
@@ -1870,7 +1870,7 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
 
     """
     node = ninfo.name
-    _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
+    _ErrorIf = self._ErrorIf # pylint: disable=C0103
 
     ntime = nresult.get(constants.NV_TIME, None)
     try:
@@ -1903,7 +1903,7 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
       return
 
     node = ninfo.name
-    _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
+    _ErrorIf = self._ErrorIf # pylint: disable=C0103
 
     # checks vg existence and size > 20G
     vglist = nresult.get(constants.NV_VGLIST, None)
@@ -1940,7 +1940,7 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
       return
 
     node = ninfo.name
-    _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
+    _ErrorIf = self._ErrorIf # pylint: disable=C0103
 
     missing = nresult.get(constants.NV_BRIDGES, None)
     test = not isinstance(missing, list)
@@ -1959,7 +1959,7 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
 
     """
     node = ninfo.name
-    _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
+    _ErrorIf = self._ErrorIf # pylint: disable=C0103
 
     test = constants.NV_NODELIST not in nresult
     _ErrorIf(test, self.ENODESSH, node,
@@ -2000,7 +2000,7 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
     available on the instance's node.
 
     """
-    _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
+    _ErrorIf = self._ErrorIf # pylint: disable=C0103
     node_current = instanceconfig.primary_node
 
     node_vol_should = {}
@@ -2200,7 +2200,7 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
 
     """
     node = ninfo.name
-    _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
+    _ErrorIf = self._ErrorIf # pylint: disable=C0103
 
     if drbd_helper:
       helper_result = nresult.get(constants.NV_DRBDHELPER, None)
@@ -2259,7 +2259,7 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
 
     """
     node = ninfo.name
-    _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
+    _ErrorIf = self._ErrorIf # pylint: disable=C0103
 
     remote_os = nresult.get(constants.NV_OSLIST, None)
     test = (not isinstance(remote_os, list) or
@@ -2300,7 +2300,7 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
 
     """
     node = ninfo.name
-    _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
+    _ErrorIf = self._ErrorIf # pylint: disable=C0103
 
     assert not nimg.os_fail, "Entered _VerifyNodeOS with failed OS rpc?"
 
@@ -2370,7 +2370,7 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
 
     """
     node = ninfo.name
-    _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
+    _ErrorIf = self._ErrorIf # pylint: disable=C0103
 
     nimg.lvm_fail = True
     lvdata = nresult.get(constants.NV_LVLIST, "Missing LV data")
@@ -2418,7 +2418,7 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
 
     """
     node = ninfo.name
-    _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
+    _ErrorIf = self._ErrorIf # pylint: disable=C0103
 
     # try to read free memory (from the hypervisor)
     hv_info = nresult.get(constants.NV_HVINFO, None)
@@ -2460,7 +2460,7 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
         list of tuples (success, payload)
 
     """
-    _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
+    _ErrorIf = self._ErrorIf # pylint: disable=C0103
 
     node_disks = {}
     node_disks_devonly = {}
@@ -2568,7 +2568,7 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
     """Verify integrity of the node group, performing various test on nodes.
 
     """
-    # This method has too many local variables. pylint: disable-msg=R0914
+    # This method has too many local variables. pylint: disable=R0914
     feedback_fn("* Verifying group '%s'" % self.group_info.name)
 
     if not self.my_node_names:
@@ -2577,7 +2577,7 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
       return True
 
     self.bad = False
-    _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
+    _ErrorIf = self._ErrorIf # pylint: disable=C0103
     verbose = self.op.verbose
     self._feedback_fn = feedback_fn
 
@@ -4419,7 +4419,7 @@ class LUNodeQuery(NoHooksLU):
   """Logical unit for querying nodes.
 
   """
-  # pylint: disable-msg=W0142
+  # pylint: disable=W0142
   REQ_BGL = False
 
   def CheckArguments(self):
@@ -4620,7 +4620,7 @@ class _InstanceQuery(_QueryBase):
               for instance_name in lu.owned_locks(locking.LEVEL_INSTANCE)
               for group_uuid in lu.cfg.GetInstanceNodeGroups(instance_name))
       elif level == locking.LEVEL_NODE:
-        lu._LockInstancesNodes() # pylint: disable-msg=W0212
+        lu._LockInstancesNodes() # pylint: disable=W0212
 
   @staticmethod
   def _CheckGroupLocks(lu):
@@ -4721,7 +4721,7 @@ class LUQuery(NoHooksLU):
   """Query for resources/items of a certain kind.
 
   """
-  # pylint: disable-msg=W0142
+  # pylint: disable=W0142
   REQ_BGL = False
 
   def CheckArguments(self):
@@ -4743,7 +4743,7 @@ class LUQueryFields(NoHooksLU):
   """Query for resources/items of a certain kind.
 
   """
-  # pylint: disable-msg=W0142
+  # pylint: disable=W0142
   REQ_BGL = False
 
   def CheckArguments(self):
@@ -4990,7 +4990,7 @@ class LUNodeAdd(LogicalUnit):
     # later in the procedure; this also means that if the re-add
     # fails, we are left with a non-offlined, broken node
     if self.op.readd:
-      new_node.drained = new_node.offline = False # pylint: disable-msg=W0201
+      new_node.drained = new_node.offline = False # pylint: disable=W0201
       self.LogInfo("Readding a node, the offline/drained flags were reset")
       # if we demote the node, we do cleanup later in the procedure
       new_node.master_candidate = self.master_candidate
@@ -6584,7 +6584,7 @@ class LUInstanceQuery(NoHooksLU):
   """Logical unit for querying instances.
 
   """
-  # pylint: disable-msg=W0142
+  # pylint: disable=W0142
   REQ_BGL = False
 
   def CheckArguments(self):
@@ -8555,7 +8555,7 @@ class LUInstanceCreate(LogicalUnit):
 
       joinargs.append(self.op.instance_name)
 
-      # pylint: disable-msg=W0142
+      # pylint: disable=W0142
       self.instance_file_storage_dir = utils.PathJoin(*joinargs)
 
   def CheckPrereq(self):
@@ -9705,7 +9705,7 @@ class TLReplaceDisks(Tasklet):
           self.lu.LogWarning("Can't remove old LV: %s" % msg,
                              hint="remove unused LVs manually")
 
-  def _ExecDrbd8DiskOnly(self, feedback_fn): # pylint: disable-msg=W0613
+  def _ExecDrbd8DiskOnly(self, feedback_fn): # pylint: disable=W0613
     """Replace a disk on the primary or secondary for DRBD 8.
 
     The algorithm for replace is quite complicated:
@@ -12497,7 +12497,7 @@ class LUGroupEvacuate(LogicalUnit):
     return ResultWithJobs(jobs)
 
 
-class TagsLU(NoHooksLU): # pylint: disable-msg=W0223
+class TagsLU(NoHooksLU): # pylint: disable=W0223
   """Generic tags LU.
 
   This is an abstract class which is the parent of all the other tags LUs.
@@ -12757,7 +12757,7 @@ class LUTestJqueue(NoHooksLU):
     # Wait for client to close
     try:
       try:
-        # pylint: disable-msg=E1101
+        # pylint: disable=E1101
         # Instance of '_socketobject' has no ... member
         conn.settimeout(cls._CLIENT_CONFIRM_TIMEOUT)
         conn.recv(1)
@@ -12854,7 +12854,7 @@ class IAllocator(object):
       easy usage
 
   """
-  # pylint: disable-msg=R0902
+  # pylint: disable=R0902
   # lots of instance attributes
 
   def __init__(self, cfg, rpc, mode, **kwargs):
@@ -13191,7 +13191,7 @@ class IAllocator(object):
 
   _STRING_LIST = ht.TListOf(ht.TString)
   _JOB_LIST = ht.TListOf(ht.TListOf(ht.TStrictDict(True, False, {
-     # pylint: disable-msg=E1101
+     # pylint: disable=E1101
      # Class '...' has no 'OP_ID' member
      "OP_ID": ht.TElemOf([opcodes.OpInstanceFailover.OP_ID,
                           opcodes.OpInstanceMigrate.OP_ID,
diff --git a/lib/compat.py b/lib/compat.py
index 90799205798aecfd1e1e093ed188c0e0fa14cdf5..53ee1fb801d5e96fbfc1353ada0def0ba7cd0818 100644
--- a/lib/compat.py
+++ b/lib/compat.py
@@ -27,13 +27,13 @@ import itertools
 import operator
 
 try:
-  # pylint: disable-msg=F0401
+  # pylint: disable=F0401
   import functools
 except ImportError:
   functools = None
 
 try:
-  # pylint: disable-msg=F0401
+  # pylint: disable=F0401
   import roman
 except ImportError:
   roman = None
@@ -45,9 +45,9 @@ except ImportError:
 # modules (hmac, for example) which have changed their behavior as well from
 # one version to the other.
 try:
-  # pylint: disable-msg=F0401
+  # pylint: disable=F0401
   # Yes, we're not using the imports in this module.
-  # pylint: disable-msg=W0611
+  # pylint: disable=W0611
   from hashlib import md5 as md5_hash
   from hashlib import sha1 as sha1_hash
   # this additional version is needed for compatibility with the hmac module
@@ -78,21 +78,21 @@ def _any(seq):
 
 
 try:
-  # pylint: disable-msg=E0601
-  # pylint: disable-msg=W0622
+  # pylint: disable=E0601
+  # pylint: disable=W0622
   all = all
 except NameError:
   all = _all
 
 try:
-  # pylint: disable-msg=E0601
-  # pylint: disable-msg=W0622
+  # pylint: disable=E0601
+  # pylint: disable=W0622
   any = any
 except NameError:
   any = _any
 
 
-def partition(seq, pred=bool): # pylint: disable-msg=W0622
+def partition(seq, pred=bool): # pylint: disable=W0622
   """Partition a list in two, based on the given predicate.
 
   """
@@ -102,7 +102,7 @@ def partition(seq, pred=bool): # pylint: disable-msg=W0622
 
 # 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
+def _partial(func, *args, **keywords): # pylint: disable=W0622
   """Decorator with partial application of arguments and keywords.
 
   This function was copied from Python's documentation.
@@ -111,7 +111,7 @@ def _partial(func, *args, **keywords): # pylint: disable-msg=W0622
   def newfunc(*fargs, **fkeywords):
     newkeywords = keywords.copy()
     newkeywords.update(fkeywords)
-    return func(*(args + fargs), **newkeywords) # pylint: disable-msg=W0142
+    return func(*(args + fargs), **newkeywords) # pylint: disable=W0142
 
   newfunc.func = func
   newfunc.args = args
diff --git a/lib/confd/client.py b/lib/confd/client.py
index aec2a7f5b0f0a61a7d3f77d50a29cace957a7730..11baa4d223ad6cea90d62f03aecddef8f70198fe 100644
--- a/lib/confd/client.py
+++ b/lib/confd/client.py
@@ -45,7 +45,7 @@ confirming what you already got.
 
 """
 
-# pylint: disable-msg=E0203
+# pylint: disable=E0203
 
 # E0203: Access to member %r before its definition, since we use
 # objects.py which doesn't explicitely initialise its members
@@ -156,7 +156,7 @@ class ConfdClient:
 
     """
     # we are actually called from init, so:
-    # pylint: disable-msg=W0201
+    # pylint: disable=W0201
     if not isinstance(peers, list):
       raise errors.ProgrammerError("peers must be a list")
     # make a copy of peers, since we're going to shuffle the list, later
diff --git a/lib/confd/querylib.py b/lib/confd/querylib.py
index 4ad51e78855e3bffbccd72042182b2744fcc7753..e231a14e49ec94a7032c8c5e6a0feeaef2e0a59e 100644
--- a/lib/confd/querylib.py
+++ b/lib/confd/querylib.py
@@ -50,7 +50,7 @@ class ConfdQuery(object):
     """
     self.reader = reader
 
-  def Exec(self, query): # pylint: disable-msg=R0201,W0613
+  def Exec(self, query): # pylint: disable=R0201,W0613
     """Process a single UDP request from a client.
 
     Different queries should override this function, which by defaults returns
diff --git a/lib/config.py b/lib/config.py
index df4f84fa6b79b0ec9f7e4ea73da4faf3f08a90d5..fdd77c0d918dcf43ae3628c1326376b5380d5742 100644
--- a/lib/config.py
+++ b/lib/config.py
@@ -31,7 +31,7 @@ much memory.
 
 """
 
-# pylint: disable-msg=R0904
+# pylint: disable=R0904
 # R0904: Too many public methods
 
 import os
@@ -374,7 +374,7 @@ class ConfigWriter:
         configuration errors
 
     """
-    # pylint: disable-msg=R0914
+    # pylint: disable=R0914
     result = []
     seen_macs = []
     ports = {}
diff --git a/lib/constants.py b/lib/constants.py
index 9e48997c66adf5e45060efec164812b1367a78c7..a1c70b2eca90ffb9367a80f35503760931fb2042 100644
--- a/lib/constants.py
+++ b/lib/constants.py
@@ -269,11 +269,14 @@ CONS_SSH = "ssh"
 #: Console as VNC server
 CONS_VNC = "vnc"
 
+#: Console as SPICE server
+CONS_SPICE = "spice"
+
 #: Display a message for console access
 CONS_MESSAGE = "msg"
 
 #: All console types
-CONS_ALL = frozenset([CONS_SSH, CONS_VNC, CONS_MESSAGE])
+CONS_ALL = frozenset([CONS_SSH, CONS_VNC, CONS_SPICE, CONS_MESSAGE])
 
 # For RSA keys more bits are better, but they also make operations more
 # expensive. NIST SP 800-131 recommends a minimum of 2048 bits from the year
@@ -684,6 +687,12 @@ HV_VNC_X509 = "vnc_x509_path"
 HV_VNC_X509_VERIFY = "vnc_x509_verify"
 HV_KVM_SPICE_BIND = "spice_bind"
 HV_KVM_SPICE_IP_VERSION = "spice_ip_version"
+HV_KVM_SPICE_PASSWORD_FILE = "spice_password_file"
+HV_KVM_SPICE_LOSSLESS_IMG_COMPR = "spice_image_compression"
+HV_KVM_SPICE_JPEG_IMG_COMPR = "spice_jpeg_wan_compression"
+HV_KVM_SPICE_ZLIB_GLZ_IMG_COMPR = "spice_zlib_glz_wan_compression"
+HV_KVM_SPICE_STREAMING_VIDEO_DETECTION = "spice_streaming_video"
+HV_KVM_SPICE_AUDIO_COMPR = "spice_playback_compression"
 HV_ACPI = "acpi"
 HV_PAE = "pae"
 HV_USE_BOOTLOADER = "use_bootloader"
@@ -729,6 +738,12 @@ HVS_PARAMETER_TYPES = {
   HV_VNC_X509_VERIFY: VTYPE_BOOL,
   HV_KVM_SPICE_BIND: VTYPE_STRING,
   HV_KVM_SPICE_IP_VERSION: VTYPE_INT,
+  HV_KVM_SPICE_PASSWORD_FILE: VTYPE_STRING,
+  HV_KVM_SPICE_LOSSLESS_IMG_COMPR: VTYPE_STRING,
+  HV_KVM_SPICE_JPEG_IMG_COMPR: VTYPE_STRING,
+  HV_KVM_SPICE_ZLIB_GLZ_IMG_COMPR: VTYPE_STRING,
+  HV_KVM_SPICE_STREAMING_VIDEO_DETECTION: VTYPE_STRING,
+  HV_KVM_SPICE_AUDIO_COMPR: VTYPE_BOOL,
   HV_ACPI: VTYPE_BOOL,
   HV_PAE: VTYPE_BOOL,
   HV_USE_BOOTLOADER: VTYPE_BOOL,
@@ -962,6 +977,45 @@ HT_KVM_VALID_BO_TYPES = frozenset([
   HT_BO_NETWORK
   ])
 
+# SPICE lossless image compression options
+HT_KVM_SPICE_LOSSLESS_IMG_COMPR_AUTO_GLZ = "auto_glz"
+HT_KVM_SPICE_LOSSLESS_IMG_COMPR_AUTO_LZ = "auto_lz"
+HT_KVM_SPICE_LOSSLESS_IMG_COMPR_QUIC = "quic"
+HT_KVM_SPICE_LOSSLESS_IMG_COMPR_GLZ = "glz"
+HT_KVM_SPICE_LOSSLESS_IMG_COMPR_LZ = "lz"
+HT_KVM_SPICE_LOSSLESS_IMG_COMPR_OFF = "off"
+
+HT_KVM_SPICE_VALID_LOSSLESS_IMG_COMPR_OPTIONS = frozenset([
+  HT_KVM_SPICE_LOSSLESS_IMG_COMPR_AUTO_GLZ,
+  HT_KVM_SPICE_LOSSLESS_IMG_COMPR_AUTO_LZ,
+  HT_KVM_SPICE_LOSSLESS_IMG_COMPR_QUIC,
+  HT_KVM_SPICE_LOSSLESS_IMG_COMPR_GLZ,
+  HT_KVM_SPICE_LOSSLESS_IMG_COMPR_LZ,
+  HT_KVM_SPICE_LOSSLESS_IMG_COMPR_OFF,
+  ])
+
+# SPICE lossy image compression options (valid for both jpeg and zlib-glz)
+HT_KVM_SPICE_LOSSY_IMG_COMPR_AUTO = "auto"
+HT_KVM_SPICE_LOSSY_IMG_COMPR_NEVER = "never"
+HT_KVM_SPICE_LOSSY_IMG_COMPR_ALWAYS = "always"
+
+HT_KVM_SPICE_VALID_LOSSY_IMG_COMPR_OPTIONS = frozenset([
+  HT_KVM_SPICE_LOSSY_IMG_COMPR_AUTO,
+  HT_KVM_SPICE_LOSSY_IMG_COMPR_NEVER,
+  HT_KVM_SPICE_LOSSY_IMG_COMPR_ALWAYS,
+  ])
+
+# SPICE video stream detection
+HT_KVM_SPICE_VIDEO_STREAM_DETECTION_OFF = "off"
+HT_KVM_SPICE_VIDEO_STREAM_DETECTION_ALL = "all"
+HT_KVM_SPICE_VIDEO_STREAM_DETECTION_FILTER = "filter"
+
+HT_KVM_SPICE_VALID_VIDEO_STREAM_DETECTION_OPTIONS = frozenset([
+  HT_KVM_SPICE_VIDEO_STREAM_DETECTION_OFF,
+  HT_KVM_SPICE_VIDEO_STREAM_DETECTION_ALL,
+  HT_KVM_SPICE_VIDEO_STREAM_DETECTION_FILTER,
+  ])
+
 # Security models
 HT_SM_NONE = "none"
 HT_SM_USER = "user"
@@ -1311,6 +1365,12 @@ HVC_DEFAULTS = {
     HV_VNC_PASSWORD_FILE: "",
     HV_KVM_SPICE_BIND: "",
     HV_KVM_SPICE_IP_VERSION: IFACE_NO_IP_VERSION_SPECIFIED,
+    HV_KVM_SPICE_PASSWORD_FILE: "",
+    HV_KVM_SPICE_LOSSLESS_IMG_COMPR: "",
+    HV_KVM_SPICE_JPEG_IMG_COMPR: "",
+    HV_KVM_SPICE_ZLIB_GLZ_IMG_COMPR: "",
+    HV_KVM_SPICE_STREAMING_VIDEO_DETECTION: "",
+    HV_KVM_SPICE_AUDIO_COMPR: True,
     HV_KVM_FLOPPY_IMAGE_PATH: "",
     HV_CDROM_IMAGE_PATH: "",
     HV_KVM_CDROM2_IMAGE_PATH: "",
diff --git a/lib/daemon.py b/lib/daemon.py
index 7f5e9efcfa7546281f59d63d81c4d6e1d4dfe692..f5e18a25bc0252b6320633f8403580fb23b02793 100644
--- a/lib/daemon.py
+++ b/lib/daemon.py
@@ -539,12 +539,12 @@ def _BeautifyError(err):
                                             err.errno)
     else:
       return str(err)
-  except Exception: # pylint: disable-msg=W0703
+  except Exception: # pylint: disable=W0703
     logging.exception("Error while handling existing error %s", err)
     return "%s" % str(err)
 
 
-def _HandleSigHup(reopen_fn, signum, frame): # pylint: disable-msg=W0613
+def _HandleSigHup(reopen_fn, signum, frame): # pylint: disable=W0613
   """Handler for SIGHUP.
 
   @param reopen_fn: List of callback functions for reopening log files
diff --git a/lib/http/__init__.py b/lib/http/__init__.py
index 9049829912cef21116b1a7302c17fd5cffe72b21..2719a38f0ff5722f80089f03ec875151f9c491ee 100644
--- a/lib/http/__init__.py
+++ b/lib/http/__init__.py
@@ -624,7 +624,7 @@ class HttpBase(object):
 
     return OpenSSL.SSL.Connection(ctx, sock)
 
-  def GetSslCiphers(self): # pylint: disable-msg=R0201
+  def GetSslCiphers(self): # pylint: disable=R0201
     """Returns the ciphers string for SSL.
 
     """
@@ -638,7 +638,7 @@ class HttpBase(object):
 
     """
     # some parameters are unused, but this is the API
-    # pylint: disable-msg=W0613
+    # pylint: disable=W0613
     assert self._ssl_params, "SSL not initialized"
 
     return (self._ssl_cert.digest("sha1") == cert.digest("sha1") and
diff --git a/lib/http/auth.py b/lib/http/auth.py
index 09b0ce72154ddb341adb7d834a14fcb8bdb84ad1..5a7bfac19b2b6d1d217fcc9d62cc3e7e93af3f5d 100644
--- a/lib/http/auth.py
+++ b/lib/http/auth.py
@@ -92,7 +92,7 @@ class HttpServerRequestAuthentication(object):
     """
     # today we don't have per-request filtering, but we might want to
     # add it in the future
-    # pylint: disable-msg=W0613
+    # pylint: disable=W0613
     return self.AUTH_REALM
 
   def AuthenticationRequired(self, req):
@@ -106,7 +106,7 @@ class HttpServerRequestAuthentication(object):
 
     """
     # Unused argument, method could be a function
-    # pylint: disable-msg=W0613,R0201
+    # pylint: disable=W0613,R0201
     return False
 
   def PreHandleRequest(self, req):
diff --git a/lib/http/server.py b/lib/http/server.py
index 52b1340648290c7229d87b5d54583356a5271faa..cb76185966add89cadd8e3e21bebab9dc7593efe 100644
--- a/lib/http/server.py
+++ b/lib/http/server.py
@@ -537,7 +537,7 @@ class HttpServer(http.HttpBase, asyncore.dispatcher):
     """Called for each incoming connection
 
     """
-    # pylint: disable-msg=W0212
+    # pylint: disable=W0212
     (connection, client_addr) = self.socket.accept()
 
     self._CollectChildren(False)
@@ -560,7 +560,7 @@ class HttpServer(http.HttpBase, asyncore.dispatcher):
         utils.ResetTempfileModule()
 
         self.request_executor(self, connection, client_addr)
-      except Exception: # pylint: disable-msg=W0703
+      except Exception: # pylint: disable=W0703
         logging.exception("Error while handling request from %s:%s",
                           client_addr[0], client_addr[1])
         os._exit(1)
diff --git a/lib/hypervisor/hv_base.py b/lib/hypervisor/hv_base.py
index 074017f75940f7943cd372ba60109f252efa2dd7..0886933c3d317cd920bbd2d7eeff236b506a94cd 100644
--- a/lib/hypervisor/hv_base.py
+++ b/lib/hypervisor/hv_base.py
@@ -263,7 +263,7 @@ class BaseHypervisor(object):
     """
     raise NotImplementedError
 
-  def MigrationInfo(self, instance): # pylint: disable-msg=R0201,W0613
+  def MigrationInfo(self, instance): # pylint: disable=R0201,W0613
     """Get instance information to perform a migration.
 
     By default assume no information is needed.
diff --git a/lib/hypervisor/hv_chroot.py b/lib/hypervisor/hv_chroot.py
index 8505cadc58c60d93c4af3a7326e4a7380cca31e9..3d3caeba7fa8578ff446d8e1c5ec0fa37fc05d44 100644
--- a/lib/hypervisor/hv_chroot.py
+++ b/lib/hypervisor/hv_chroot.py
@@ -29,7 +29,7 @@ import time
 import logging
 
 from ganeti import constants
-from ganeti import errors # pylint: disable-msg=W0611
+from ganeti import errors # pylint: disable=W0611
 from ganeti import utils
 from ganeti import objects
 from ganeti.hypervisor import hv_base
@@ -247,7 +247,7 @@ class ChrootManager(hv_base.BaseHypervisor):
     return self.GetLinuxNodeInfo()
 
   @classmethod
-  def GetInstanceConsole(cls, instance, # pylint: disable-msg=W0221
+  def GetInstanceConsole(cls, instance, # pylint: disable=W0221
                          hvparams, beparams, root_dir=None):
     """Return information for connecting to the console of an instance.
 
diff --git a/lib/hypervisor/hv_kvm.py b/lib/hypervisor/hv_kvm.py
index d5d057fc63fe2a4f4efd988bc089c872dc9e3adf..c32c8abfd6c3dfda2dea2ae381576f6abc620d53 100644
--- a/lib/hypervisor/hv_kvm.py
+++ b/lib/hypervisor/hv_kvm.py
@@ -34,6 +34,8 @@ import pwd
 import struct
 import fcntl
 import shutil
+import socket
+import StringIO
 
 from ganeti import utils
 from ganeti import constants
@@ -127,6 +129,251 @@ def _OpenTap(vnet_hdr=True):
   return (ifname, tapfd)
 
 
+class QmpMessage:
+  """QEMU Messaging Protocol (QMP) message.
+
+  """
+
+  def __init__(self, data):
+    """Creates a new QMP message based on the passed data.
+
+    """
+    if not isinstance(data, dict):
+      raise TypeError("QmpMessage must be initialized with a dict")
+
+    self.data = data
+
+  def __getitem__(self, field_name):
+    """Get the value of the required field if present, or None.
+
+    Overrides the [] operator to provide access to the message data,
+    returning None if the required item is not in the message
+    @return: the value of the field_name field, or None if field_name
+             is not contained in the message
+
+    """
+
+    if field_name in self.data:
+      return self.data[field_name]
+
+    return None
+
+  def __setitem__(self, field_name, field_value):
+    """Set the value of the required field_name to field_value.
+
+    """
+    self.data[field_name] = field_value
+
+  @staticmethod
+  def BuildFromJsonString(json_string):
+    """Build a QmpMessage from a JSON encoded string.
+
+    @type json_string: str
+    @param json_string: JSON string representing the message
+    @rtype: L{QmpMessage}
+    @return: a L{QmpMessage} built from json_string
+
+    """
+    # Parse the string
+    data = serializer.LoadJson(json_string)
+    return QmpMessage(data)
+
+  def __str__(self):
+    # The protocol expects the JSON object to be sent as a single
+    # line, hence the need for indent=False.
+    return serializer.DumpJson(self.data, indent=False)
+
+  def __eq__(self, other):
+    # When comparing two QmpMessages, we are interested in comparing
+    # their internal representation of the message data
+    return self.data == other.data
+
+
+class QmpConnection:
+  """Connection to the QEMU Monitor using the QEMU Monitor Protocol (QMP).
+
+  """
+  _FIRST_MESSAGE_KEY = "QMP"
+  _EVENT_KEY = "event"
+  _ERROR_KEY = "error"
+  _ERROR_CLASS_KEY = "class"
+  _ERROR_DATA_KEY = "data"
+  _ERROR_DESC_KEY = "desc"
+  _EXECUTE_KEY = "execute"
+  _ARGUMENTS_KEY = "arguments"
+  _CAPABILITIES_COMMAND = "qmp_capabilities"
+  _MESSAGE_END_TOKEN = "\r\n"
+  _SOCKET_TIMEOUT = 5
+
+  def __init__(self, monitor_filename):
+    """Instantiates the QmpConnection object.
+
+    @type monitor_filename: string
+    @param monitor_filename: the filename of the UNIX raw socket on which the
+                             QMP monitor is listening
+
+    """
+    self.monitor_filename = monitor_filename
+    self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+    # We want to fail if the server doesn't send a complete message
+    # in a reasonable amount of time
+    self.sock.settimeout(self._SOCKET_TIMEOUT)
+    self._connected = False
+    self._buf = ""
+
+  def _check_connection(self):
+    """Make sure that the connection is established.
+
+    """
+    if not self._connected:
+      raise errors.ProgrammerError("To use a QmpConnection you need to first"
+                                   " invoke connect() on it")
+
+  def connect(self):
+    """Connects to the QMP monitor.
+
+    Connects to the UNIX socket and makes sure that we can actually send and
+    receive data to the kvm instance via QMP.
+
+    @raise errors.HypervisorError: when there are communication errors
+    @raise errors.ProgrammerError: when there are data serialization errors
+
+    """
+    self.sock.connect(self.monitor_filename)
+    self._connected = True
+
+    # Check if we receive a correct greeting message from the server
+    # (As per the QEMU Protocol Specification 0.1 - section 2.2)
+    greeting = self._Recv()
+    if not greeting[self._FIRST_MESSAGE_KEY]:
+      self._connected = False
+      raise errors.HypervisorError("kvm: qmp communication error (wrong"
+                                   " server greeting")
+
+    # Let's put the monitor in command mode using the qmp_capabilities
+    # command, or else no command will be executable.
+    # (As per the QEMU Protocol Specification 0.1 - section 4)
+    self.Execute(self._CAPABILITIES_COMMAND)
+
+  def _ParseMessage(self, buf):
+    """Extract and parse a QMP message from the given buffer.
+
+    Seeks for a QMP message in the given buf. If found, it parses it and
+    returns it together with the rest of the characters in the buf.
+    If no message is found, returns None and the whole buffer.
+
+    @raise errors.ProgrammerError: when there are data serialization errors
+
+    """
+    message = None
+    # Check if we got the message end token (CRLF, as per the QEMU Protocol
+    # Specification 0.1 - Section 2.1.1)
+    pos = buf.find(self._MESSAGE_END_TOKEN)
+    if pos >= 0:
+      try:
+        message = QmpMessage.BuildFromJsonString(buf[:pos + 1])
+      except Exception, err:
+        raise errors.ProgrammerError("QMP data serialization error: %s" % err)
+      buf = buf[pos + 1:]
+
+    return (message, buf)
+
+  def _Recv(self):
+    """Receives a message from QMP and decodes the received JSON object.
+
+    @rtype: QmpMessage
+    @return: the received message
+    @raise errors.HypervisorError: when there are communication errors
+    @raise errors.ProgrammerError: when there are data serialization errors
+
+    """
+    self._check_connection()
+
+    # Check if there is already a message in the buffer
+    (message, self._buf) = self._ParseMessage(self._buf)
+    if message:
+      return message
+
+    recv_buffer = StringIO.StringIO(self._buf)
+    recv_buffer.seek(len(self._buf))
+    try:
+      while True:
+        data = self.sock.recv(4096)
+        if not data:
+          break
+        recv_buffer.write(data)
+
+        (message, self._buf) = self._ParseMessage(recv_buffer.getvalue())
+        if message:
+          return message
+
+    except socket.timeout, err:
+      raise errors.HypervisorError("Timeout while receiving a QMP message: "
+                                   "%s" % (err))
+    except socket.error, err:
+      raise errors.HypervisorError("Unable to receive data from KVM using the"
+                                   " QMP protocol: %s" % err)
+
+  def _Send(self, message):
+    """Encodes and sends a message to KVM using QMP.
+
+    @type message: QmpMessage
+    @param message: message to send to KVM
+    @raise errors.HypervisorError: when there are communication errors
+    @raise errors.ProgrammerError: when there are data serialization errors
+
+    """
+    self._check_connection()
+    try:
+      message_str = str(message)
+    except Exception, err:
+      raise errors.ProgrammerError("QMP data deserialization error: %s" % err)
+
+    try:
+      self.sock.sendall(message_str)
+    except socket.timeout, err:
+      raise errors.HypervisorError("Timeout while sending a QMP message: "
+                                   "%s (%s)" % (err.string, err.errno))
+    except socket.error, err:
+      raise errors.HypervisorError("Unable to send data from KVM using the"
+                                   " QMP protocol: %s" % err)
+
+  def Execute(self, command, arguments=None):
+    """Executes a QMP command and returns the response of the server.
+
+    @type command: str
+    @param command: the command to execute
+    @type arguments: dict
+    @param arguments: dictionary of arguments to be passed to the command
+    @rtype: dict
+    @return: dictionary representing the received JSON object
+    @raise errors.HypervisorError: when there are communication errors
+    @raise errors.ProgrammerError: when there are data serialization errors
+
+    """
+    self._check_connection()
+    message = QmpMessage({self._EXECUTE_KEY: command})
+    if arguments:
+      message[self._ARGUMENTS_KEY] = arguments
+    self._Send(message)
+
+    # Events can occur between the sending of the command and the reception
+    # of the response, so we need to filter out messages with the event key.
+    while True:
+      response = self._Recv()
+      err = response[self._ERROR_KEY]
+      if err:
+        raise errors.HypervisorError("kvm: error executing the %s"
+                                     " command: %s (%s, %s):" %
+                                     (command,
+                                      err[self._ERROR_DESC_KEY],
+                                      err[self._ERROR_CLASS_KEY],
+                                      err[self._ERROR_DATA_KEY]))
+
+      elif not response[self._EVENT_KEY]:
+        return response
+
+
 class KVMHypervisor(hv_base.BaseHypervisor):
   """KVM hypervisor interface"""
   CAN_MIGRATE = True
@@ -171,6 +418,20 @@ class KVMHypervisor(hv_base.BaseHypervisor):
                          x in constants.VALID_IP_VERSIONS),
        "the SPICE IP version should be 4 or 6",
        None, None),
+    constants.HV_KVM_SPICE_PASSWORD_FILE: hv_base.OPT_FILE_CHECK,
+    constants.HV_KVM_SPICE_LOSSLESS_IMG_COMPR:
+      hv_base.ParamInSet(False,
+        constants.HT_KVM_SPICE_VALID_LOSSLESS_IMG_COMPR_OPTIONS),
+    constants.HV_KVM_SPICE_JPEG_IMG_COMPR:
+      hv_base.ParamInSet(False,
+        constants.HT_KVM_SPICE_VALID_LOSSY_IMG_COMPR_OPTIONS),
+    constants.HV_KVM_SPICE_ZLIB_GLZ_IMG_COMPR:
+      hv_base.ParamInSet(False,
+        constants.HT_KVM_SPICE_VALID_LOSSY_IMG_COMPR_OPTIONS),
+    constants.HV_KVM_SPICE_STREAMING_VIDEO_DETECTION:
+      hv_base.ParamInSet(False,
+        constants.HT_KVM_SPICE_VALID_VIDEO_STREAM_DETECTION_OPTIONS),
+    constants.HV_KVM_SPICE_AUDIO_COMPR: hv_base.NO_CHECK,
     constants.HV_KVM_FLOPPY_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
     constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
     constants.HV_KVM_CDROM2_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
@@ -326,6 +587,13 @@ class KVMHypervisor(hv_base.BaseHypervisor):
     """
     return utils.PathJoin(cls._CTRL_DIR, "%s.serial" % instance_name)
 
+  @classmethod
+  def _InstanceQmpMonitor(cls, instance_name):
+    """Returns the instance serial QMP socket name
+
+    """
+    return utils.PathJoin(cls._CTRL_DIR, "%s.qmp" % instance_name)
+
   @staticmethod
   def _SocatUnixConsoleParams():
     """Returns the correct parameters for socat
@@ -397,6 +665,7 @@ class KVMHypervisor(hv_base.BaseHypervisor):
     utils.RemoveFile(pidfile)
     utils.RemoveFile(cls._InstanceMonitor(instance_name))
     utils.RemoveFile(cls._InstanceSerial(instance_name))
+    utils.RemoveFile(cls._InstanceQmpMonitor(instance_name))
     utils.RemoveFile(cls._InstanceKVMRuntime(instance_name))
     utils.RemoveFile(cls._InstanceKeymapFile(instance_name))
     uid_file = cls._InstanceUidFile(instance_name)
@@ -525,6 +794,7 @@ class KVMHypervisor(hv_base.BaseHypervisor):
     """Generate KVM information to start an instance.
 
     """
+    # pylint: disable=R0914
     _, v_major, v_min, _ = self._GetKVMVersion()
 
     pidfile = self._InstancePidFile(instance.name)
@@ -761,14 +1031,39 @@ class KVMHypervisor(hv_base.BaseHypervisor):
         # ValidateParameters checked it.
         spice_address = spice_bind
 
-      spice_arg = "addr=%s,port=%s,disable-ticketing" % (spice_address,
-                                                         instance.network_port)
+      spice_arg = "addr=%s,port=%s" % (spice_address, instance.network_port)
+      if not hvp[constants.HV_KVM_SPICE_PASSWORD_FILE]:
+        spice_arg = "%s,disable-ticketing" % spice_arg
+
       if spice_ip_version:
         spice_arg = "%s,ipv%s" % (spice_arg, spice_ip_version)
 
+      # Image compression options
+      img_lossless = hvp[constants.HV_KVM_SPICE_LOSSLESS_IMG_COMPR]
+      img_jpeg = hvp[constants.HV_KVM_SPICE_JPEG_IMG_COMPR]
+      img_zlib_glz = hvp[constants.HV_KVM_SPICE_ZLIB_GLZ_IMG_COMPR]
+      if img_lossless:
+        spice_arg = "%s,image-compression=%s" % (spice_arg, img_lossless)
+      if img_jpeg:
+        spice_arg = "%s,jpeg-wan-compression=%s" % (spice_arg, img_jpeg)
+      if img_zlib_glz:
+        spice_arg = "%s,zlib-glz-wan-compression=%s" % (spice_arg, img_zlib_glz)
+
+      # Video stream detection
+      video_streaming = hvp[constants.HV_KVM_SPICE_STREAMING_VIDEO_DETECTION]
+      if video_streaming:
+        spice_arg = "%s,streaming-video=%s" % (spice_arg, video_streaming)
+
+      # Audio compression, by default in qemu-kvm it is on
+      if not hvp[constants.HV_KVM_SPICE_AUDIO_COMPR]:
+        spice_arg = "%s,playback-compression=off" % spice_arg
+
       logging.info("KVM: SPICE will listen on port %s", instance.network_port)
       kvm_cmd.extend(["-spice", spice_arg])
 
+      # Tell kvm to use the paravirtualized graphic card, optimized for SPICE
+      kvm_cmd.extend(["-vga", "qxl"])
+
     if hvp[constants.HV_USE_LOCALTIME]:
       kvm_cmd.extend(["-localtime"])
 
@@ -940,6 +1235,12 @@ class KVMHypervisor(hv_base.BaseHypervisor):
       utils.EnsureDirs([(self._InstanceChrootDir(name),
                          constants.SECURE_DIR_MODE)])
 
+    # Automatically enable QMP if version is >= 0.14
+    if (v_major, v_min) >= (0, 14):
+      logging.debug("Enabling QMP")
+      kvm_cmd.extend(["-qmp", "unix:%s,server,nowait" %
+                    self._InstanceQmpMonitor(instance.name)])
+
     # Configure the network now for starting instances and bridged interfaces,
     # during FinalizeMigration for incoming instances' routed interfaces
     for nic_seq, nic in enumerate(kvm_nics):
@@ -976,6 +1277,26 @@ class KVMHypervisor(hv_base.BaseHypervisor):
       change_cmd = "change vnc password %s" % vnc_pwd
       self._CallMonitorCommand(instance.name, change_cmd)
 
+    # Setting SPICE password. We are not vulnerable to malicious passwordless
+    # connection attempts because SPICE by default does not allow connections
+    # if neither a password nor the "disable_ticketing" options are specified.
+    # As soon as we send the password via QMP, that password is a valid ticket
+    # for connection.
+    spice_password_file = conf_hvp[constants.HV_KVM_SPICE_PASSWORD_FILE]
+    if spice_password_file:
+      try:
+        spice_pwd = utils.ReadOneLineFile(spice_password_file, strict=True)
+        qmp = QmpConnection(self._InstanceQmpMonitor(instance.name))
+        qmp.connect()
+        arguments = {
+            "protocol": "spice",
+            "password": spice_pwd,
+        }
+        qmp.Execute("set_password", arguments)
+      except EnvironmentError, err:
+        raise errors.HypervisorError("Failed to open SPICE password file %s: %s"
+                                     % (spice_password_file, err))
+
     for filename in temp_files:
       utils.RemoveFile(filename)
 
@@ -1241,6 +1562,13 @@ class KVMHypervisor(hv_base.BaseHypervisor):
                                      port=instance.network_port,
                                      display=display)
 
+    spice_bind = hvparams[constants.HV_KVM_SPICE_BIND]
+    if spice_bind:
+      return objects.InstanceConsole(instance=instance.name,
+                                     kind=constants.CONS_SPICE,
+                                     host=spice_bind,
+                                     port=instance.network_port)
+
     return objects.InstanceConsole(instance=instance.name,
                                    kind=constants.CONS_MESSAGE,
                                    message=("No serial shell for instance %s" %
@@ -1298,8 +1626,8 @@ class KVMHypervisor(hv_base.BaseHypervisor):
                                      " security model is 'none' or 'pool'")
 
     spice_bind = hvparams[constants.HV_KVM_SPICE_BIND]
+    spice_ip_version = hvparams[constants.HV_KVM_SPICE_IP_VERSION]
     if spice_bind:
-      spice_ip_version = hvparams[constants.HV_KVM_SPICE_IP_VERSION]
       if spice_ip_version != constants.IFACE_NO_IP_VERSION_SPECIFIED:
         # if an IP version is specified, the spice_bind parameter must be an
         # IP of that family
@@ -1314,6 +1642,21 @@ class KVMHypervisor(hv_base.BaseHypervisor):
           raise errors.HypervisorError("spice: got an IPv6 address (%s), but"
                                        " the specified IP version is %s" %
                                        (spice_bind, spice_ip_version))
+    else:
+      # All the other SPICE parameters depend on spice_bind being set. Raise an
+      # error if any of them is set without it.
+      spice_additional_params = frozenset([
+        constants.HV_KVM_SPICE_IP_VERSION,
+        constants.HV_KVM_SPICE_PASSWORD_FILE,
+        constants.HV_KVM_SPICE_LOSSLESS_IMG_COMPR,
+        constants.HV_KVM_SPICE_JPEG_IMG_COMPR,
+        constants.HV_KVM_SPICE_ZLIB_GLZ_IMG_COMPR,
+        constants.HV_KVM_SPICE_STREAMING_VIDEO_DETECTION,
+        ])
+      for param in spice_additional_params:
+        if hvparams[param]:
+          raise errors.HypervisorError("spice: %s requires %s to be set" %
+                                       (param, constants.HV_KVM_SPICE_BIND))
 
   @classmethod
   def ValidateParameters(cls, hvparams):
diff --git a/lib/hypervisor/hv_lxc.py b/lib/hypervisor/hv_lxc.py
index 9d56bd2fc77b820d56010bc3c028df83c72fdb30..8fdc52f1a6e3935dd6307e6804f9a8ced51ae8bd 100644
--- a/lib/hypervisor/hv_lxc.py
+++ b/lib/hypervisor/hv_lxc.py
@@ -29,7 +29,7 @@ import time
 import logging
 
 from ganeti import constants
-from ganeti import errors # pylint: disable-msg=W0611
+from ganeti import errors # pylint: disable=W0611
 from ganeti import utils
 from ganeti import objects
 from ganeti.hypervisor import hv_base
diff --git a/lib/jqueue.py b/lib/jqueue.py
index bf65d5bc71fe6c4ce870ef8d8617ece5173bbaec..3cf3b428762be1b26b4e509fc63fb34c2e819d33 100644
--- a/lib/jqueue.py
+++ b/lib/jqueue.py
@@ -37,7 +37,7 @@ import threading
 import itertools
 
 try:
-  # pylint: disable-msg=E0611
+  # pylint: disable=E0611
   from pyinotify import pyinotify
 except ImportError:
   import pyinotify
@@ -177,7 +177,7 @@ class _QueuedJob(object):
   @ivar writable: Whether the job is allowed to be modified
 
   """
-  # pylint: disable-msg=W0212
+  # pylint: disable=W0212
   __slots__ = ["queue", "id", "ops", "log_serial", "ops_iter", "cur_opctx",
                "received_timestamp", "start_timestamp", "end_timestamp",
                "__weakref__", "processor_lock", "writable"]
@@ -1048,7 +1048,7 @@ class _JobProcessor(object):
       logging.exception("%s: Canceling job", opctx.log_prefix)
       assert op.status == constants.OP_STATUS_CANCELING
       return (constants.OP_STATUS_CANCELING, None)
-    except Exception, err: # pylint: disable-msg=W0703
+    except Exception, err: # pylint: disable=W0703
       logging.exception("%s: Caught exception in %s",
                         opctx.log_prefix, opctx.summary)
       return (constants.OP_STATUS_ERROR, _EncodeOpError(err))
@@ -1241,7 +1241,7 @@ class _JobQueueWorker(workerpool.BaseWorker):
   """The actual job workers.
 
   """
-  def RunTask(self, job): # pylint: disable-msg=W0221
+  def RunTask(self, job): # pylint: disable=W0221
     """Job executor.
 
     @type job: L{_QueuedJob}
@@ -1358,7 +1358,7 @@ class _JobDependencyManager:
     self._lock = locking.SharedLock("JobDepMgr")
 
   @locking.ssynchronized(_LOCK, shared=1)
-  def GetLockInfo(self, requested): # pylint: disable-msg=W0613
+  def GetLockInfo(self, requested): # pylint: disable=W0613
     """Retrieves information about waiting jobs.
 
     @type requested: set
@@ -1481,7 +1481,7 @@ def _RequireOpenQueue(fn):
 
   """
   def wrapper(self, *args, **kwargs):
-    # pylint: disable-msg=W0212
+    # pylint: disable=W0212
     assert self._queue_filelock is not None, "Queue should be open"
     return fn(self, *args, **kwargs)
   return wrapper
@@ -1945,7 +1945,7 @@ class JobQueue(object):
     try:
       data = serializer.LoadJson(raw_data)
       job = _QueuedJob.Restore(self, data, writable)
-    except Exception, err: # pylint: disable-msg=W0703
+    except Exception, err: # pylint: disable=W0703
       raise errors.JobFileCorrupted(err)
 
     return job
@@ -2187,7 +2187,7 @@ class JobQueue(object):
     # Try to load from disk
     job = self.SafeLoadJobFromDisk(job_id, True, writable=False)
 
-    assert not job.writable, "Got writable job"
+    assert not job.writable, "Got writable job" # pylint: disable=E1101
 
     if job:
       return job.CalcStatus()
diff --git a/lib/locking.py b/lib/locking.py
index 9f24771500db9135724ae4f53ebe658ec24a00a8..26345632e400df71a04978ec7271cec4d6eac420 100644
--- a/lib/locking.py
+++ b/lib/locking.py
@@ -20,7 +20,7 @@
 
 """Module implementing the Ganeti locking code."""
 
-# pylint: disable-msg=W0212
+# pylint: disable=W0212
 
 # W0212 since e.g. LockSet methods use (a lot) the internals of
 # SharedLock
@@ -276,7 +276,7 @@ class SingleNotifyPipeCondition(_BaseCondition):
       if self._nwaiters == 0:
         self._Cleanup()
 
-  def notifyAll(self): # pylint: disable-msg=C0103
+  def notifyAll(self): # pylint: disable=C0103
     """Close the writing side of the pipe to notify all waiters.
 
     """
@@ -333,7 +333,7 @@ class PipeCondition(_BaseCondition):
       self._check_owned()
       self._waiters.remove(threading.currentThread())
 
-  def notifyAll(self): # pylint: disable-msg=C0103
+  def notifyAll(self): # pylint: disable=C0103
     """Notify all currently waiting threads.
 
     """
@@ -1507,7 +1507,7 @@ class GanetiLockManager:
     # the test cases.
     return compat.any((self._is_owned(l) for l in LEVELS[level + 1:]))
 
-  def _BGL_owned(self): # pylint: disable-msg=C0103
+  def _BGL_owned(self): # pylint: disable=C0103
     """Check if the current thread owns the BGL.
 
     Both an exclusive or a shared acquisition work.
@@ -1516,7 +1516,7 @@ class GanetiLockManager:
     return BGL in self.__keyring[LEVEL_CLUSTER]._list_owned()
 
   @staticmethod
-  def _contains_BGL(level, names): # pylint: disable-msg=C0103
+  def _contains_BGL(level, names): # pylint: disable=C0103
     """Check if the level contains the BGL.
 
     Check if acting on the given level and set of names will change
diff --git a/lib/luxi.py b/lib/luxi.py
index cfde21e4ec372c68a4ec7aa30c597f5b57675a5d..4371613a1193254ae3e74f717f723799ec2bbd60 100644
--- a/lib/luxi.py
+++ b/lib/luxi.py
@@ -278,9 +278,9 @@ def ParseRequest(msg):
     logging.error("LUXI request not a dict: %r", msg)
     raise ProtocolError("Invalid LUXI request (not a dict)")
 
-  method = request.get(KEY_METHOD, None) # pylint: disable-msg=E1103
-  args = request.get(KEY_ARGS, None) # pylint: disable-msg=E1103
-  version = request.get(KEY_VERSION, None) # pylint: disable-msg=E1103
+  method = request.get(KEY_METHOD, None) # pylint: disable=E1103
+  args = request.get(KEY_ARGS, None) # pylint: disable=E1103
+  version = request.get(KEY_VERSION, None) # pylint: disable=E1103
 
   if method is None or args is None:
     logging.error("LUXI request missing method or arguments: %r", msg)
@@ -309,7 +309,7 @@ def ParseResponse(msg):
     raise ProtocolError("Invalid response from server: %r" % data)
 
   return (data[KEY_SUCCESS], data[KEY_RESULT],
-          data.get(KEY_VERSION, None)) # pylint: disable-msg=E1103
+          data.get(KEY_VERSION, None)) # pylint: disable=E1103
 
 
 def FormatResponse(success, result, version=None):
@@ -417,7 +417,7 @@ class Client(object):
       old_transp = self.transport
       self.transport = None
       old_transp.Close()
-    except Exception: # pylint: disable-msg=W0703
+    except Exception: # pylint: disable=W0703
       pass
 
   def _SendMethodCall(self, data):
diff --git a/lib/mcpu.py b/lib/mcpu.py
index 4fcd6fdcb8b93bc25a9223da482709d691d205dd..89046c60cdf64e575cce43b592969530b790e0ea 100644
--- a/lib/mcpu.py
+++ b/lib/mcpu.py
@@ -122,7 +122,7 @@ class LockAttemptTimeoutStrategy(object):
     return timeout
 
 
-class OpExecCbBase: # pylint: disable-msg=W0232
+class OpExecCbBase: # pylint: disable=W0232
   """Base class for OpCode execution callbacks.
 
   """
diff --git a/lib/netutils.py b/lib/netutils.py
index 7a1b6d032013409eb417592f56dfc5195c2ccff7..80c0219bc13b9a853a4520e41d90717f89b25821 100644
--- a/lib/netutils.py
+++ b/lib/netutils.py
@@ -583,7 +583,7 @@ class IP6Address(IPAddress):
       twoparts = address.split("::")
       sep = len(twoparts[0].split(":")) + len(twoparts[1].split(":"))
       parts = twoparts[0].split(":")
-      [parts.append("0") for _ in range(8 - sep)]
+      parts.extend(["0"] * (8 - sep))
       parts += twoparts[1].split(":")
     else:
       parts = address.split(":")
diff --git a/lib/objects.py b/lib/objects.py
index 4e298c6a1761f97b5b1fcc04a293e7ec4da6e2f3..8cc7ac36b29276b46e4536d88fe2878a57540b5a 100644
--- a/lib/objects.py
+++ b/lib/objects.py
@@ -26,7 +26,7 @@ pass to and from external parties.
 
 """
 
-# pylint: disable-msg=E0203,W0201
+# pylint: disable=E0203,W0201
 
 # E0203: Access to member %r before its definition, since we use
 # objects.py which doesn't explicitely initialise its members
@@ -170,7 +170,7 @@ class ConfigObject(object):
       raise errors.ConfigurationError("Invalid object passed to FromDict:"
                                       " expected dict, got %s" % type(val))
     val_str = dict([(str(k), v) for k, v in val.iteritems()])
-    obj = cls(**val_str) # pylint: disable-msg=W0142
+    obj = cls(**val_str) # pylint: disable=W0142
     return obj
 
   @staticmethod
@@ -966,7 +966,7 @@ class Node(TaggableObject):
     """Fill defaults for missing configuration values.
 
     """
-    # pylint: disable-msg=E0203
+    # pylint: disable=E0203
     # because these are "defined" via slots, not manually
     if self.master_capable is None:
       self.master_capable = True
@@ -1096,7 +1096,7 @@ class Cluster(TaggableObject):
     """Fill defaults for missing configuration values.
 
     """
-    # pylint: disable-msg=E0203
+    # pylint: disable=E0203
     # because these are "defined" via slots, not manually
     if self.hvparams is None:
       self.hvparams = constants.HVC_DEFAULTS
@@ -1524,15 +1524,20 @@ class InstanceConsole(ConfigObject):
     """
     assert self.kind in constants.CONS_ALL, "Unknown console type"
     assert self.instance, "Missing instance name"
-    assert self.message or self.kind in [constants.CONS_SSH, constants.CONS_VNC]
+    assert self.message or self.kind in [constants.CONS_SSH,
+                                         constants.CONS_SPICE,
+                                         constants.CONS_VNC]
     assert self.host or self.kind == constants.CONS_MESSAGE
     assert self.port or self.kind in [constants.CONS_MESSAGE,
                                       constants.CONS_SSH]
     assert self.user or self.kind in [constants.CONS_MESSAGE,
+                                      constants.CONS_SPICE,
                                       constants.CONS_VNC]
     assert self.command or self.kind in [constants.CONS_MESSAGE,
+                                         constants.CONS_SPICE,
                                          constants.CONS_VNC]
     assert self.display or self.kind in [constants.CONS_MESSAGE,
+                                         constants.CONS_SPICE,
                                          constants.CONS_SSH]
     return True
 
diff --git a/lib/opcodes.py b/lib/opcodes.py
index 14a132b1aefb2afb4506805c416c8099fb85ac62..36104f7c7147792a8d6bc7f919ea4ed67f93b5f3 100644
--- a/lib/opcodes.py
+++ b/lib/opcodes.py
@@ -31,7 +31,7 @@ opcodes.
 
 # this are practically structures, so disable the message about too
 # few public methods:
-# pylint: disable-msg=R0903
+# pylint: disable=R0903
 
 import logging
 import re
@@ -311,7 +311,7 @@ class BaseOpCode(object):
   field handling.
 
   """
-  # pylint: disable-msg=E1101
+  # pylint: disable=E1101
   # as OP_ID is dynamically defined
   __metaclass__ = _AutoOpParamSlots
 
@@ -485,7 +485,7 @@ class OpCode(BaseOpCode):
   @ivar priority: Opcode priority for queue
 
   """
-  # pylint: disable-msg=E1101
+  # pylint: disable=E1101
   # as OP_ID is dynamically defined
   WITH_LU = True
   OP_PARAMS = [
diff --git a/lib/qlang.py b/lib/qlang.py
index db0a59d357cd52115b555a2b6664f324bdc68444..bd2a3fd5f75046ec9c42852bf35132f5033f9d91 100644
--- a/lib/qlang.py
+++ b/lib/qlang.py
@@ -32,7 +32,7 @@ converted to callable functions by L{query._CompileFilter}.
 """
 
 import re
-import string # pylint: disable-msg=W0402
+import string # pylint: disable=W0402
 import logging
 
 import pyparsing as pyp
diff --git a/lib/query.py b/lib/query.py
index ae7da5aa6195a66d534c31cf6f29c088bca4c709..7a9360c8726f9aaa1305d61cd2815d08153e66a7 100644
--- a/lib/query.py
+++ b/lib/query.py
@@ -137,7 +137,7 @@ _VTToQFT = {
 _SERIAL_NO_DOC = "%s object serial number, incremented on each modification"
 
 
-def _GetUnknownField(ctx, item): # pylint: disable-msg=W0613
+def _GetUnknownField(ctx, item): # pylint: disable=W0613
   """Gets the contents of an unknown field.
 
   """
@@ -260,7 +260,7 @@ class _FilterHints:
     if op != qlang.OP_OR:
       self._NeedAllNames()
 
-  def NoteUnaryOp(self, op): # pylint: disable-msg=W0613
+  def NoteUnaryOp(self, op): # pylint: disable=W0613
     """Called when handling an unary operation.
 
     @type op: string
@@ -337,7 +337,7 @@ class _FilterCompilerHelper:
   """Converts a query filter to a callable usable for filtering.
 
   """
-  # String statement has no effect, pylint: disable-msg=W0105
+  # String statement has no effect, pylint: disable=W0105
 
   #: How deep filters can be nested
   _LEVELS_MAX = 10
@@ -546,7 +546,7 @@ class _FilterCompilerHelper:
     @param operands: List of operands
 
     """
-    # Unused arguments, pylint: disable-msg=W0613
+    # Unused arguments, pylint: disable=W0613
     try:
       (name, value) = operands
     except (ValueError, TypeError):
@@ -1062,7 +1062,7 @@ def _GetGroup(cb):
   return fn
 
 
-def _GetNodeGroup(ctx, node, ng): # pylint: disable-msg=W0613
+def _GetNodeGroup(ctx, node, ng): # pylint: disable=W0613
   """Returns the name of a node's group.
 
   @type ctx: L{NodeQueryData}
@@ -1408,7 +1408,7 @@ def _GetInstNic(index, cb):
   return fn
 
 
-def _GetInstNicIp(ctx, _, nic): # pylint: disable-msg=W0613
+def _GetInstNicIp(ctx, _, nic): # pylint: disable=W0613
   """Get a NIC's IP address.
 
   @type ctx: L{InstanceQueryData}
diff --git a/lib/rapi/baserlib.py b/lib/rapi/baserlib.py
index af799c4684b0edfbae2bc6f3c41cd020240d0119..3e467394f0c45e727a541a55c8884af62d754a35 100644
--- a/lib/rapi/baserlib.py
+++ b/lib/rapi/baserlib.py
@@ -23,7 +23,7 @@
 
 """
 
-# pylint: disable-msg=C0103
+# pylint: disable=C0103
 
 # C0103: Invalid name, since the R_* names are not conforming
 
@@ -198,7 +198,7 @@ def FillOpcode(opcls, body, static, rename=None):
   params = dict((str(key), value) for (key, value) in params.items())
 
   try:
-    op = opcls(**params) # pylint: disable-msg=W0142
+    op = opcls(**params) # pylint: disable=W0142
     op.Validate(False)
   except (errors.OpPrereqError, TypeError), err:
     raise http.HttpBadRequest("Invalid body parameters: %s" % err)
diff --git a/lib/rapi/client.py b/lib/rapi/client.py
index a4be072dbb23af782528c208b10179ff85521249..dac670b1b187818531d51bfdd720d3fe2c16fe22 100644
--- a/lib/rapi/client.py
+++ b/lib/rapi/client.py
@@ -261,7 +261,7 @@ def GenericCurlConfig(verbose=False, use_signal=False,
   return _ConfigCurl
 
 
-class GanetiRapiClient(object): # pylint: disable-msg=R0904
+class GanetiRapiClient(object): # pylint: disable=R0904
   """Ganeti RAPI client.
 
   """
diff --git a/lib/rapi/connector.py b/lib/rapi/connector.py
index fad46e46e9bcca7f8b59e6906b49b7d302bf46a0..d8524efad75201675dd98ab476053ac52a2d331f 100644
--- a/lib/rapi/connector.py
+++ b/lib/rapi/connector.py
@@ -22,7 +22,7 @@
 
 """
 
-# pylint: disable-msg=C0103
+# pylint: disable=C0103
 
 # C0103: Invalid name, since the R_* names are not conforming
 
diff --git a/lib/rapi/rlib2.py b/lib/rapi/rlib2.py
index 049b97eb87c14de41d67d65bb4c757dbeab09c95..76d640b12b8a19b13bceddde163ab4f3bb4a06a1 100644
--- a/lib/rapi/rlib2.py
+++ b/lib/rapi/rlib2.py
@@ -51,7 +51,7 @@ PUT should be prefered over POST.
 
 """
 
-# pylint: disable-msg=C0103
+# pylint: disable=C0103
 
 # C0103: Invalid name, since the R_* names are not conforming
 
diff --git a/lib/rpc.py b/lib/rpc.py
index c93c16d2b6045f1564d9b1ee8712596aa3706672..60a30ab0e45f2541a10f82f10796f65f019dd040 100644
--- a/lib/rpc.py
+++ b/lib/rpc.py
@@ -23,7 +23,7 @@
 
 """
 
-# pylint: disable-msg=C0103,R0201,R0904
+# pylint: disable=C0103,R0201,R0904
 # C0103: Invalid name, since call_ are not valid
 # R0201: Method could be a function, we keep all rpcs instance methods
 # as not to change them back and forth between static/instance methods
@@ -48,7 +48,7 @@ from ganeti import ssconf
 from ganeti import runtime
 
 # pylint has a bug here, doesn't see this import
-import ganeti.http.client  # pylint: disable-msg=W0611
+import ganeti.http.client  # pylint: disable=W0611
 
 
 # Timeout for connecting to nodes (seconds)
@@ -262,7 +262,7 @@ class RpcResult(object):
       args = (msg, ecode)
     else:
       args = (msg, )
-    raise ec(*args) # pylint: disable-msg=W0142
+    raise ec(*args) # pylint: disable=W0142
 
 
 def _AddressLookup(node_list,
diff --git a/lib/runtime.py b/lib/runtime.py
index 6108e2671b6ab574e3770bd127452ef239fae81a..9e478ec5e5731cf62432fd06988840a287b49576 100644
--- a/lib/runtime.py
+++ b/lib/runtime.py
@@ -175,14 +175,14 @@ def GetEnts(resolver=GetentResolver):
 
   """
   # We need to use the global keyword here
-  global _priv # pylint: disable-msg=W0603
+  global _priv # pylint: disable=W0603
 
   if not _priv:
     _priv_lock.acquire()
     try:
       if not _priv:
         # W0621: Redefine '_priv' from outer scope (used for singleton)
-        _priv = resolver() # pylint: disable-msg=W0621
+        _priv = resolver() # pylint: disable=W0621
     finally:
       _priv_lock.release()
 
diff --git a/lib/serializer.py b/lib/serializer.py
index 81f1add970f237a69ffcf0a156af131884547891..ff27261aab11c1ee4a04a48c6587ba45107b68da 100644
--- a/lib/serializer.py
+++ b/lib/serializer.py
@@ -24,7 +24,7 @@ This module introduces a simple abstraction over the serialization
 backend (currently json).
 
 """
-# pylint: disable-msg=C0103
+# pylint: disable=C0103
 
 # C0103: Invalid name, since pylint doesn't see that Dump points to a
 # function and not a constant
@@ -147,7 +147,7 @@ def LoadSignedJson(txt, key):
     raise errors.SignatureError("Invalid external message")
 
   if callable(key):
-    # pylint: disable-msg=E1103
+    # pylint: disable=E1103
     key_selector = signed_dict.get("key_selector", None)
     hmac_key = key(key_selector)
     if not hmac_key:
diff --git a/lib/server/confd.py b/lib/server/confd.py
index fe5f7e11b62f4559cc509611d65cf837e7529f35..d25a67715f44f6ce6f786b3afd2457d581e197e6 100644
--- a/lib/server/confd.py
+++ b/lib/server/confd.py
@@ -26,7 +26,7 @@ It uses UDP+HMAC for authentication with a global cluster key.
 
 """
 
-# pylint: disable-msg=C0103
+# pylint: disable=C0103
 # C0103: Invalid name ganeti-confd
 
 import os
@@ -35,7 +35,7 @@ import logging
 import time
 
 try:
-  # pylint: disable-msg=E0611
+  # pylint: disable=E0611
   from pyinotify import pyinotify
 except ImportError:
   import pyinotify
@@ -265,7 +265,7 @@ def PrepConfd(options, _):
   # TODO: clarify how the server and reloader variables work (they are
   # not used)
 
-  # pylint: disable-msg=W0612
+  # pylint: disable=W0612
   mainloop = daemon.Mainloop()
 
   # Asyncronous confd UDP server
@@ -285,7 +285,7 @@ def PrepConfd(options, _):
   return mainloop
 
 
-def ExecConfd(options, args, prep_data): # pylint: disable-msg=W0613
+def ExecConfd(options, args, prep_data): # pylint: disable=W0613
   """Main confd function, executed with PID file held
 
   """
diff --git a/lib/server/masterd.py b/lib/server/masterd.py
index 17eabf85fefde7bbe7a717eb2a1a10903f5f6268..27bf561c4bb681cd287110594c0db1a025bfc692 100644
--- a/lib/server/masterd.py
+++ b/lib/server/masterd.py
@@ -26,7 +26,7 @@ inheritance from parent classes requires it.
 
 """
 
-# pylint: disable-msg=C0103
+# pylint: disable=C0103
 # C0103: Invalid name ganeti-masterd
 
 import grp
@@ -66,7 +66,7 @@ EXIT_NODESETUP_ERROR = constants.EXIT_NODESETUP_ERROR
 
 
 class ClientRequestWorker(workerpool.BaseWorker):
-  # pylint: disable-msg=W0221
+  # pylint: disable=W0221
   def RunTask(self, server, message, client):
     """Process the request.
 
@@ -103,7 +103,7 @@ class ClientRequestWorker(workerpool.BaseWorker):
       client.send_message(reply)
       # awake the main thread so that it can write out the data.
       server.awaker.signal()
-    except: # pylint: disable-msg=W0702
+    except: # pylint: disable=W0702
       logging.exception("Send error")
       client.close_log()
 
@@ -189,7 +189,7 @@ class ClientOps:
   def __init__(self, server):
     self.server = server
 
-  def handle_request(self, method, args): # pylint: disable-msg=R0911
+  def handle_request(self, method, args): # pylint: disable=R0911
     queue = self.server.context.jobqueue
 
     # TODO: Parameter validation
@@ -374,7 +374,7 @@ class GanetiContext(object):
   This class creates and holds common objects shared by all threads.
 
   """
-  # pylint: disable-msg=W0212
+  # pylint: disable=W0212
   # we do want to ensure a singleton here
   _instance = None
 
@@ -613,7 +613,7 @@ def PrepMasterd(options, _):
   return (mainloop, master)
 
 
-def ExecMasterd(options, args, prep_data): # pylint: disable-msg=W0613
+def ExecMasterd(options, args, prep_data): # pylint: disable=W0613
   """Main master daemon function, executed with the PID file held.
 
   """
diff --git a/lib/server/noded.py b/lib/server/noded.py
index 580467ae975c3b009369fa02fff00c4d15f09bd4..e852ca90d3eb9eb3aa3a0226627e3448d75d35d4 100644
--- a/lib/server/noded.py
+++ b/lib/server/noded.py
@@ -21,7 +21,7 @@
 
 """Ganeti node daemon"""
 
-# pylint: disable-msg=C0103,W0142
+# pylint: disable=C0103,W0142
 
 # C0103: Functions in this module need to have a given name structure,
 # and the name of the daemon doesn't match
@@ -49,7 +49,7 @@ from ganeti import storage
 from ganeti import serializer
 from ganeti import netutils
 
-import ganeti.http.server # pylint: disable-msg=W0611
+import ganeti.http.server # pylint: disable=W0611
 
 
 queue_lock = None
@@ -61,7 +61,7 @@ def _PrepareQueueLock():
   @return: None for success, otherwise an exception object
 
   """
-  global queue_lock # pylint: disable-msg=W0603
+  global queue_lock # pylint: disable=W0603
 
   if queue_lock is not None:
     return None
@@ -129,7 +129,7 @@ class NodeHttpServer(http.server.HttpServer):
   """
   # too many public methods, and unused args - all methods get params
   # due to the API
-  # pylint: disable-msg=R0904,W0613
+  # pylint: disable=R0904,W0613
   def __init__(self, *args, **kwargs):
     http.server.HttpServer.__init__(self, *args, **kwargs)
     self.noded_pid = os.getpid()
@@ -1019,7 +1019,7 @@ def PrepNoded(options, _):
   return (mainloop, server)
 
 
-def ExecNoded(options, args, prep_data): # pylint: disable-msg=W0613
+def ExecNoded(options, args, prep_data): # pylint: disable=W0613
   """Main node daemon function, executed with the PID file held.
 
   """
diff --git a/lib/server/rapi.py b/lib/server/rapi.py
index b3a71aa08d96435b4f388a954af9df721c284f09..6a8a76aee19fd1ad3ced1c09cd1013278aca08eb 100644
--- a/lib/server/rapi.py
+++ b/lib/server/rapi.py
@@ -22,7 +22,7 @@
 
 """
 
-# pylint: disable-msg=C0103,W0142
+# pylint: disable=C0103,W0142
 
 # C0103: Invalid name ganeti-watcher
 
@@ -34,7 +34,7 @@ import os.path
 import errno
 
 try:
-  from pyinotify import pyinotify # pylint: disable-msg=E0611
+  from pyinotify import pyinotify # pylint: disable=E0611
 except ImportError:
   import pyinotify
 
@@ -49,7 +49,7 @@ from ganeti import compat
 from ganeti import utils
 from ganeti.rapi import connector
 
-import ganeti.http.auth   # pylint: disable-msg=W0611
+import ganeti.http.auth   # pylint: disable=W0611
 import ganeti.http.server
 
 
@@ -90,7 +90,7 @@ class RemoteApiHttpServer(http.auth.HttpServerRequestAuthentication,
   AUTH_REALM = "Ganeti Remote API"
 
   def __init__(self, *args, **kwargs):
-    # pylint: disable-msg=W0233
+    # pylint: disable=W0233
     # it seems pylint doesn't see the second parent class there
     http.server.HttpServer.__init__(self, *args, **kwargs)
     http.auth.HttpServerRequestAuthentication.__init__(self)
@@ -118,7 +118,7 @@ class RemoteApiHttpServer(http.auth.HttpServerRequestAuthentication,
 
       users = http.auth.ParsePasswordFile(contents)
 
-    except Exception, err: # pylint: disable-msg=W0703
+    except Exception, err: # pylint: disable=W0703
       # We don't care about the type of exception
       logging.error("Error while parsing %s: %s", filename, err)
       return False
@@ -321,14 +321,14 @@ def PrepRapi(options, _):
 
   server.LoadUsers(constants.RAPI_USERS_FILE)
 
-  # pylint: disable-msg=E1101
+  # pylint: disable=E1101
   # it seems pylint doesn't see the second parent class there
   server.Start()
 
   return (mainloop, server)
 
 
-def ExecRapi(options, args, prep_data): # pylint: disable-msg=W0613
+def ExecRapi(options, args, prep_data): # pylint: disable=W0613
   """Main remote API function, executed with the PID file held.
 
   """
diff --git a/lib/storage.py b/lib/storage.py
index cf8768636c911023c30793608a1a8d25aeef4650..d382be53c8e249e1d186226a23133f1dff3632f5 100644
--- a/lib/storage.py
+++ b/lib/storage.py
@@ -23,7 +23,7 @@
 
 """
 
-# pylint: disable-msg=W0232,R0201
+# pylint: disable=W0232,R0201
 
 # W0232, since we use these as singletons rather than object holding
 # data
@@ -58,7 +58,7 @@ class _Base:
     """
     raise NotImplementedError()
 
-  def Modify(self, name, changes): # pylint: disable-msg=W0613
+  def Modify(self, name, changes): # pylint: disable=W0613
     """Modifies an entity within the storage unit.
 
     @type name: string
@@ -84,7 +84,7 @@ class _Base:
     raise NotImplementedError()
 
 
-class FileStorage(_Base): # pylint: disable-msg=W0223
+class FileStorage(_Base): # pylint: disable=W0223
   """File storage unit.
 
   """
@@ -161,7 +161,7 @@ class FileStorage(_Base): # pylint: disable-msg=W0223
     return values
 
 
-class _LvmBase(_Base): # pylint: disable-msg=W0223
+class _LvmBase(_Base): # pylint: disable=W0223
   """Base class for LVM storage containers.
 
   @cvar LIST_FIELDS: list of tuples consisting of three elements: SF_*
@@ -256,7 +256,7 @@ class _LvmBase(_Base): # pylint: disable-msg=W0223
 
         if callable(mapper):
           # we got a function, call it with all the declared fields
-          val = mapper(*values) # pylint: disable-msg=W0142
+          val = mapper(*values) # pylint: disable=W0142
         elif len(values) == 1:
           assert mapper is None, ("Invalid mapper value (neither callable"
                                   " nor None) for one-element fields")
@@ -347,7 +347,7 @@ def _LvmPvGetAllocatable(attr):
     return False
 
 
-class LvmPvStorage(_LvmBase): # pylint: disable-msg=W0223
+class LvmPvStorage(_LvmBase): # pylint: disable=W0223
   """LVM Physical Volume storage unit.
 
   """
diff --git a/lib/utils/__init__.py b/lib/utils/__init__.py
index 2f2da51e23dd33de47400ff02b879af7457c63c4..fd7ab70fc4d19da57a0787d179c097f1a6588b02 100644
--- a/lib/utils/__init__.py
+++ b/lib/utils/__init__.py
@@ -26,7 +26,7 @@ the command line scripts.
 
 """
 
-# Allow wildcard import in pylint: disable-msg=W0401
+# Allow wildcard import in pylint: disable=W0401
 
 import os
 import re
@@ -599,7 +599,7 @@ class SignalWakeupFd(object):
     _set_wakeup_fd_fn = signal.set_wakeup_fd
   except AttributeError:
     # Not supported
-    def _SetWakeupFd(self, _): # pylint: disable-msg=R0201
+    def _SetWakeupFd(self, _): # pylint: disable=R0201
       return -1
   else:
     def _SetWakeupFd(self, fd):
diff --git a/lib/utils/io.py b/lib/utils/io.py
index c64c125d1de97f65dbaf168b9116c7d8133bb69f..91899a210f4d3a70607f51d1549066af8acbb319 100644
--- a/lib/utils/io.py
+++ b/lib/utils/io.py
@@ -465,6 +465,20 @@ def IsNormAbsPath(path):
   return os.path.normpath(path) == path and os.path.isabs(path)
 
 
+def IsBelowDir(root, other_path):
+  """Check whether a path is below a root dir.
+
+  This works around the nasty byte-byte comparisation of commonprefix.
+
+  """
+  if not (os.path.isabs(root) and os.path.isabs(other_path)):
+    raise ValueError("Provided paths '%s' and '%s' are not absolute" %
+                     (root, other_path))
+  prepared_root = "%s%s" % (os.path.normpath(root), os.sep)
+  return os.path.commonprefix([prepared_root,
+                               os.path.normpath(other_path)]) == prepared_root
+
+
 def PathJoin(*args):
   """Safe-join a list of path components.
 
@@ -488,10 +502,9 @@ def PathJoin(*args):
   if not IsNormAbsPath(result):
     raise ValueError("Invalid parameters to PathJoin: '%s'" % str(args))
   # check that we're still under the original prefix
-  prefix = os.path.commonprefix([root, result])
-  if prefix != root:
+  if not IsBelowDir(root, result):
     raise ValueError("Error: path joining resulted in different prefix"
-                     " (%s != %s)" % (prefix, root))
+                     " (%s != %s)" % (result, root))
   return result
 
 
diff --git a/lib/utils/log.py b/lib/utils/log.py
index 1cc6d82c75a44248aa3b2f30cafffe4aa211809c..281f59045ac8e7e7ae2505e154d55f6fdf4f1d84 100644
--- a/lib/utils/log.py
+++ b/lib/utils/log.py
@@ -51,13 +51,13 @@ class _ReopenableLogHandler(logging.handlers.BaseRotatingHandler):
 
     self._reopen = False
 
-  def shouldRollover(self, _): # pylint: disable-msg=C0103
+  def shouldRollover(self, _): # pylint: disable=C0103
     """Determine whether log file should be reopened.
 
     """
     return self._reopen or not self.stream
 
-  def doRollover(self): # pylint: disable-msg=C0103
+  def doRollover(self): # pylint: disable=C0103
     """Reopens the log file.
 
     """
@@ -88,7 +88,7 @@ def _LogErrorsToConsole(base):
   This needs to be in a function for unittesting.
 
   """
-  class wrapped(base): # pylint: disable-msg=C0103
+  class wrapped(base): # pylint: disable=C0103
     """Log handler that doesn't fallback to stderr.
 
     When an error occurs while writing on the logfile, logging.FileHandler
@@ -108,7 +108,7 @@ def _LogErrorsToConsole(base):
       assert not hasattr(self, "_console")
       self._console = console
 
-    def handleError(self, record): # pylint: disable-msg=C0103
+    def handleError(self, record): # pylint: disable=C0103
       """Handle errors which occur during an emit() call.
 
       Try to handle errors with FileHandler method, if it fails write to
@@ -117,13 +117,13 @@ def _LogErrorsToConsole(base):
       """
       try:
         base.handleError(record)
-      except Exception: # pylint: disable-msg=W0703
+      except Exception: # pylint: disable=W0703
         if self._console:
           try:
-            # Ignore warning about "self.format", pylint: disable-msg=E1101
+            # Ignore warning about "self.format", pylint: disable=E1101
             self._console.write("Cannot log message:\n%s\n" %
                                 self.format(record))
-          except Exception: # pylint: disable-msg=W0703
+          except Exception: # pylint: disable=W0703
             # Log handler tried everything it could, now just give up
             pass
 
diff --git a/lib/utils/mlock.py b/lib/utils/mlock.py
index a8fe5b70e1c92d34e9816033e5e2bb7c57541db3..ea14d684800c8a39810f9ef127acbc3b4aa39715 100644
--- a/lib/utils/mlock.py
+++ b/lib/utils/mlock.py
@@ -28,7 +28,7 @@ import logging
 from ganeti import errors
 
 try:
-  # pylint: disable-msg=F0401
+  # pylint: disable=F0401
   import ctypes
 except ImportError:
   ctypes = None
@@ -65,11 +65,11 @@ def Mlockall(_ctypes=ctypes):
   # By declaring this variable as a pointer to an integer we can then access
   # its value correctly, should the mlockall call fail, in order to see what
   # the actual error code was.
-  # pylint: disable-msg=W0212
+  # pylint: disable=W0212
   libc.__errno_location.restype = _ctypes.POINTER(_ctypes.c_int)
 
   if libc.mlockall(_MCL_CURRENT | _MCL_FUTURE):
-    # pylint: disable-msg=W0212
+    # pylint: disable=W0212
     logging.error("Cannot set memory lock: %s",
                   os.strerror(libc.__errno_location().contents.value))
     return
diff --git a/lib/utils/process.py b/lib/utils/process.py
index 78937610127153058cbc773a202a63666401a417..a4587553239121a4dd1970591cfca97b1c3cf8ca 100644
--- a/lib/utils/process.py
+++ b/lib/utils/process.py
@@ -57,7 +57,7 @@ def DisableFork():
   """Disables the use of fork(2).
 
   """
-  global _no_fork # pylint: disable-msg=W0603
+  global _no_fork # pylint: disable=W0603
 
   _no_fork = True
 
@@ -342,7 +342,7 @@ def StartDaemon(cmd, env=None, cwd="/", output=None, output_fd=None,
                                 output, output_fd, pidfile)
             finally:
               # Well, maybe child process failed
-              os._exit(1) # pylint: disable-msg=W0212
+              os._exit(1) # pylint: disable=W0212
         finally:
           utils_wrapper.CloseFdNoError(errpipe_write)
 
@@ -396,7 +396,7 @@ def _StartDaemonChild(errpipe_read, errpipe_write,
     pid = os.fork()
     if pid != 0:
       # Exit first child process
-      os._exit(0) # pylint: disable-msg=W0212
+      os._exit(0) # pylint: disable=W0212
 
     # Make sure pipe is closed on execv* (and thereby notifies
     # original process)
@@ -431,15 +431,15 @@ def _StartDaemonChild(errpipe_read, errpipe_write,
       os.execvp(args[0], args)
     else:
       os.execvpe(args[0], args, env)
-  except: # pylint: disable-msg=W0702
+  except: # pylint: disable=W0702
     try:
       # Report errors to original process
       WriteErrorToFD(errpipe_write, str(sys.exc_info()[1]))
-    except: # pylint: disable-msg=W0702
+    except: # pylint: disable=W0702
       # Ignore errors in error handling
       pass
 
-  os._exit(1) # pylint: disable-msg=W0212
+  os._exit(1) # pylint: disable=W0212
 
 
 def WriteErrorToFD(fd, err):
@@ -699,7 +699,7 @@ def RunParts(dir_name, env=None, reset_env=False):
     else:
       try:
         result = RunCmd([fname], env=env, reset_env=reset_env)
-      except Exception, err: # pylint: disable-msg=W0703
+      except Exception, err: # pylint: disable=W0703
         rr.append((relname, constants.RUNPARTS_ERR, str(err)))
       else:
         rr.append((relname, constants.RUNPARTS_RUN, result))
@@ -850,7 +850,7 @@ def Daemonize(logfile):
     process and a callable to reopen log files
 
   """
-  # pylint: disable-msg=W0212
+  # pylint: disable=W0212
   # yes, we really want os._exit
 
   # TODO: do another attempt to merge Daemonize and StartDaemon, or at
@@ -975,12 +975,12 @@ def RunInSeparateProcess(fn, *args):
       # Call function
       result = int(bool(fn(*args)))
       assert result in (0, 1)
-    except: # pylint: disable-msg=W0702
+    except: # pylint: disable=W0702
       logging.exception("Error while calling function in separate process")
       # 0 and 1 are reserved for the return value
       result = 33
 
-    os._exit(result) # pylint: disable-msg=W0212
+    os._exit(result) # pylint: disable=W0212
 
   # Parent process
 
diff --git a/lib/utils/retry.py b/lib/utils/retry.py
index 4e36332e089c2e338be697eb3f37e6baa8afe943..cc7541cffbb85dbbed20a249a99e7041e9bf471f 100644
--- a/lib/utils/retry.py
+++ b/lib/utils/retry.py
@@ -160,7 +160,7 @@ def Retry(fn, delay, timeout, args=None, wait_fn=time.sleep,
   while True:
     retry_args = []
     try:
-      # pylint: disable-msg=W0142
+      # pylint: disable=W0142
       return fn(*args)
     except RetryAgain, err:
       retry_args = err.args
@@ -171,7 +171,7 @@ def Retry(fn, delay, timeout, args=None, wait_fn=time.sleep,
     remaining_time = end_time - _time_fn()
 
     if remaining_time < 0.0:
-      # pylint: disable-msg=W0142
+      # pylint: disable=W0142
       raise RetryTimeout(*retry_args)
 
     assert remaining_time >= 0.0
@@ -208,7 +208,7 @@ def SimpleRetry(expected, fn, delay, timeout, args=None, wait_fn=time.sleep,
   rdict = {}
 
   def helper(*innerargs):
-    # pylint: disable-msg=W0142
+    # pylint: disable=W0142
     result = rdict["result"] = fn(*innerargs)
     if not ((callable(expected) and expected(result)) or result == expected):
       raise RetryAgain()
diff --git a/lib/utils/text.py b/lib/utils/text.py
index a4c2777ecda452f022cd63551be8a89b36f1846d..7ec049e951a21b7717bcec91d5b3aa680fc5025d 100644
--- a/lib/utils/text.py
+++ b/lib/utils/text.py
@@ -381,7 +381,7 @@ def UnescapeAndSplit(text, sep=","):
     e1 = slist.pop(0)
     if e1.endswith("\\"):
       num_b = len(e1) - len(e1.rstrip("\\"))
-      if num_b % 2 == 1:
+      if num_b % 2 == 1 and slist:
         e2 = slist.pop(0)
         # here the backslashes remain (all), and will be reduced in
         # the next step
@@ -463,7 +463,7 @@ class LineSplitter:
     if args:
       # Python 2.4 doesn't have functools.partial yet
       self._line_fn = \
-        lambda line: line_fn(line, *args) # pylint: disable-msg=W0142
+        lambda line: line_fn(line, *args) # pylint: disable=W0142
     else:
       self._line_fn = line_fn
 
diff --git a/lib/utils/wrapper.py b/lib/utils/wrapper.py
index 6cf16541f4edbe45747a00ea8f169b39a8cd2b66..256d2f1d6d1f1ad686bf75ae7dd3739526d4aca1 100644
--- a/lib/utils/wrapper.py
+++ b/lib/utils/wrapper.py
@@ -181,7 +181,7 @@ def ResetTempfileModule():
   a temporary name.
 
   """
-  # pylint: disable-msg=W0212
+  # pylint: disable=W0212
   if hasattr(tempfile, "_once_lock") and hasattr(tempfile, "_name_sequence"):
     tempfile._once_lock.acquire()
     try:
diff --git a/lib/watcher/__init__.py b/lib/watcher/__init__.py
index 323283b5b23a648a9ce9db3694aafeed3f48237d..c84f3e802d3166ef1674881976d63438e4bf29f7 100644
--- a/lib/watcher/__init__.py
+++ b/lib/watcher/__init__.py
@@ -50,7 +50,7 @@ from ganeti import objects
 from ganeti import ssconf
 from ganeti import ht
 
-import ganeti.rapi.client # pylint: disable-msg=W0611
+import ganeti.rapi.client # pylint: disable=W0611
 
 from ganeti.watcher import nodemaint
 from ganeti.watcher import state
@@ -106,8 +106,8 @@ def RunWatcherHooks():
 
   try:
     results = utils.RunParts(hooks_dir)
-  except Exception: # pylint: disable-msg=W0703
-    logging.exception("RunParts %s failed: %s", hooks_dir)
+  except Exception, err: # pylint: disable=W0703
+    logging.exception("RunParts %s failed: %s", hooks_dir, err)
     return
 
   for (relname, status, runresult) in results:
@@ -193,7 +193,7 @@ def _CheckInstances(cl, notepad, instances):
         logging.info("Restarting instance '%s' (attempt #%s)",
                      inst.name, n + 1)
         inst.Restart(cl)
-      except Exception: # pylint: disable-msg=W0703
+      except Exception: # pylint: disable=W0703
         logging.exception("Error while restarting instance '%s'", inst.name)
       else:
         started.add(inst.name)
@@ -255,7 +255,7 @@ def _CheckDisks(cl, notepad, nodes, instances, started):
         try:
           logging.info("Activating disks for instance '%s'", inst.name)
           inst.ActivateDisks(cl)
-        except Exception: # pylint: disable-msg=W0703
+        except Exception: # pylint: disable=W0703
           logging.exception("Error while activating disks for instance '%s'",
                             inst.name)
 
@@ -313,7 +313,7 @@ def _VerifyDisks(cl, uuid, nodes, instances):
 
     try:
       cli.PollJob(job_id, cl=cl, feedback_fn=logging.debug)
-    except Exception: # pylint: disable-msg=W0703
+    except Exception: # pylint: disable=W0703
       logging.exception("Error while activating disks")
 
 
@@ -549,7 +549,7 @@ def _StartGroupChildren(cl, wait):
     try:
       # TODO: Should utils.StartDaemon be used instead?
       pid = os.spawnv(os.P_NOWAIT, args[0], args)
-    except Exception: # pylint: disable-msg=W0703
+    except Exception: # pylint: disable=W0703
       logging.exception("Failed to start child for group '%s' (%s)",
                         name, uuid)
     else:
@@ -596,8 +596,8 @@ def _GlobalWatcher(opts):
 
   # Run node maintenance in all cases, even if master, so that old masters can
   # be properly cleaned up
-  if nodemaint.NodeMaintenance.ShouldRun(): # pylint: disable-msg=E0602
-    nodemaint.NodeMaintenance().Exec() # pylint: disable-msg=E0602
+  if nodemaint.NodeMaintenance.ShouldRun(): # pylint: disable=E0602
+    nodemaint.NodeMaintenance().Exec() # pylint: disable=E0602
 
   try:
     client = GetLuxiClient(True)
@@ -729,11 +729,11 @@ def _GroupWatcher(opts):
   logging.debug("Using state file %s", state_path)
 
   # Global watcher
-  statefile = state.OpenStateFile(state_path) # pylint: disable-msg=E0602
+  statefile = state.OpenStateFile(state_path) # pylint: disable=E0602
   if not statefile:
     return constants.EXIT_FAILURE
 
-  notepad = state.WatcherState(statefile) # pylint: disable-msg=E0602
+  notepad = state.WatcherState(statefile) # pylint: disable=E0602
   try:
     # Connect to master daemon
     client = GetLuxiClient(False)
diff --git a/lib/watcher/nodemaint.py b/lib/watcher/nodemaint.py
index a00fc9b19fd07d457c4572604569ce06e6e2b78b..33be4f13a7dc383fd578b7fe739783b5fbd94d54 100644
--- a/lib/watcher/nodemaint.py
+++ b/lib/watcher/nodemaint.py
@@ -34,7 +34,7 @@ from ganeti import ssconf
 from ganeti import utils
 from ganeti import confd
 
-import ganeti.confd.client # pylint: disable-msg=W0611
+import ganeti.confd.client # pylint: disable=W0611
 
 
 class NodeMaintenance(object):
@@ -70,7 +70,7 @@ class NodeMaintenance(object):
         hv = hypervisor.GetHypervisor(hv_name)
         ilist = hv.ListInstances()
         results.extend([(iname, hv_name) for iname in ilist])
-      except: # pylint: disable-msg=W0702
+      except: # pylint: disable=W0702
         logging.error("Error while listing instances for hypervisor %s",
                       hv_name, exc_info=True)
     return results
@@ -121,7 +121,7 @@ class NodeMaintenance(object):
       logging.info("Following DRBD minors should not be active,"
                    " shutting them down: %s", utils.CommaJoin(drbd_running))
       for minor in drbd_running:
-        # pylint: disable-msg=W0212
+        # pylint: disable=W0212
         # using the private method as is, pending enhancements to the DRBD
         # interface
         bdev.DRBD8._ShutdownAll(minor)
diff --git a/lib/watcher/state.py b/lib/watcher/state.py
index 34630f49921f45751eed42ac00b9cc2161d63cba..be52c65b587b7c2530a8a6a026f9c5d561fc19f2 100644
--- a/lib/watcher/state.py
+++ b/lib/watcher/state.py
@@ -86,7 +86,7 @@ class WatcherState(object):
         self._data = {}
       else:
         self._data = serializer.Load(state_data)
-    except Exception, msg: # pylint: disable-msg=W0703
+    except Exception, msg: # pylint: disable=W0703
       # Ignore errors while loading the file and treat it as empty
       self._data = {}
       logging.warning(("Invalid state file. Using defaults."
diff --git a/lib/workerpool.py b/lib/workerpool.py
index d276b1837b8ca81265799ca40cbf5258f3e6bc39..4736be5e7256240d270bc8a9a37f13226719982d 100644
--- a/lib/workerpool.py
+++ b/lib/workerpool.py
@@ -59,7 +59,7 @@ class BaseWorker(threading.Thread, object):
   Users of a worker pool must override RunTask in a subclass.
 
   """
-  # pylint: disable-msg=W0212
+  # pylint: disable=W0212
   def __init__(self, pool, worker_id):
     """Constructor for BaseWorker thread.
 
@@ -169,7 +169,7 @@ class BaseWorker(threading.Thread, object):
           logging.debug("Starting task %r, priority %s", args, priority)
           assert self.getName() == self._worker_id
           try:
-            self.RunTask(*args) # pylint: disable-msg=W0142
+            self.RunTask(*args) # pylint: disable=W0142
           finally:
             self.SetTaskName(None)
           logging.debug("Done with task %r, priority %s", args, priority)
@@ -184,7 +184,7 @@ class BaseWorker(threading.Thread, object):
                         args, defer.priority)
 
           assert self._HasRunningTaskUnlocked()
-        except: # pylint: disable-msg=W0702
+        except: # pylint: disable=W0702
           logging.exception("Caught unhandled exception")
 
         assert self._HasRunningTaskUnlocked()
@@ -384,7 +384,7 @@ class WorkerPool(object):
 
     """
     for worker in self._workers + self._termworkers:
-      if worker._HasRunningTaskUnlocked(): # pylint: disable-msg=W0212
+      if worker._HasRunningTaskUnlocked(): # pylint: disable=W0212
         return True
     return False
 
diff --git a/man/ganeti-listrunner.rst b/man/ganeti-listrunner.rst
index 91a9c124a438323dd5239fbf1ca78f61a349d6e0..b6e024268d047750da45d4e7ae5be45648d5fa8e 100644
--- a/man/ganeti-listrunner.rst
+++ b/man/ganeti-listrunner.rst
@@ -72,6 +72,9 @@ The options that can be passed to the program are as follows:
 ``-A``
   Use an existing ssh-agent instead of password authentication.
 
+``--args``
+  Arguments to pass to executable (``-x``).
+
 
 EXIT STATUS
 -----------
diff --git a/man/gnt-instance.rst b/man/gnt-instance.rst
index 5d906a40399a1bec58936ba33251b34faa492bd3..9ae83c21b568c2239c5bc982cf3d8ba2668c8f63 100644
--- a/man/gnt-instance.rst
+++ b/man/gnt-instance.rst
@@ -300,6 +300,59 @@ spice\_ip\_version
     this case, if the ``spice_ip_version`` parameter is not used, the
     default IP version of the cluster will be used.
 
+spice\_password\_file
+    Valid for the KVM hypervisor.
+
+    Specifies a file containing the password that must be used when
+    connecting via the SPICE protocol. If the option is not specified,
+    passwordless connections are allowed.
+
+spice\_image\_compression
+    Valid for the KVM hypervisor.
+
+    Configures the SPICE lossless image compression. Valid values are:
+
+    - auto_glz
+    - auto_lz
+    - quic
+    - glz
+    - lz
+    - off
+
+spice\_jpeg\_wan\_compression
+    Valid for the KVM hypervisor.
+
+    Configures how SPICE should use the jpeg algorithm for lossy image
+    compression on slow links. Valid values are:
+
+    - auto
+    - never
+    - always
+
+spice\_zlib\_glz\_wan\_compression
+    Valid for the KVM hypervisor.
+
+    Configures how SPICE should use the zlib-glz algorithm for lossy image
+    compression on slow links. Valid values are:
+
+    - auto
+    - never
+    - always
+
+spice\_streaming\_video
+    Valid for the KVM hypervisor.
+
+    Configures how SPICE should detect video streams. Valid values are:
+
+    - off
+    - all
+    - filter
+
+spice\_playback\_compression
+    Valid for the KVM hypervisor.
+
+    Configures whether SPICE should compress audio streams or not.
+
 acpi
     Valid for the Xen HVM and KVM hypervisors.
 
diff --git a/qa/ganeti-qa.py b/qa/ganeti-qa.py
index 6fa83c72727bdc0da2e4c0c89f6e18c0326d1f42..fc7abbcfa9b3acc202059f2ab2bb30dd46983d55 100755
--- a/qa/ganeti-qa.py
+++ b/qa/ganeti-qa.py
@@ -23,7 +23,7 @@
 
 """
 
-# pylint: disable-msg=C0103
+# pylint: disable=C0103
 # due to invalid name
 
 import sys
@@ -46,7 +46,7 @@ from ganeti import utils
 from ganeti import rapi
 from ganeti import constants
 
-import ganeti.rapi.client # pylint: disable-msg=W0611
+import ganeti.rapi.client # pylint: disable=W0611
 
 
 def _FormatHeader(line, end=72):
@@ -54,7 +54,7 @@ def _FormatHeader(line, end=72):
 
   """
   line = "---- " + line + " "
-  line += "-" * (end-len(line))
+  line += "-" * (end - len(line))
   line = line.rstrip()
   return line
 
diff --git a/qa/qa_cluster.py b/qa/qa_cluster.py
index bfda23674505602eb7cec7da713ac736b23d9e3b..bd3d737d5d6abc2dd5edad52c227a00d480eab96 100644
--- a/qa/qa_cluster.py
+++ b/qa/qa_cluster.py
@@ -40,6 +40,7 @@ from qa_utils import AssertEqual, AssertCommand, GetCommandOutput
 #: cluster verify command
 _CLUSTER_VERIFY = ["gnt-cluster", "verify"]
 
+
 def _RemoveFileFromAllNodes(filename):
   """Removes a file from all nodes.
 
@@ -231,14 +232,14 @@ def TestClusterReservedLvs():
     (False, _CLUSTER_VERIFY),
     (False, ["gnt-cluster", "modify", "--reserved-lvs", ""]),
     (False, ["lvcreate", "-L1G", "-nqa-test", "xenvg"]),
-    (True,  _CLUSTER_VERIFY),
+    (True, _CLUSTER_VERIFY),
     (False, ["gnt-cluster", "modify", "--reserved-lvs",
              "xenvg/qa-test,.*/other-test"]),
     (False, _CLUSTER_VERIFY),
     (False, ["gnt-cluster", "modify", "--reserved-lvs", ".*/qa-.*"]),
     (False, _CLUSTER_VERIFY),
     (False, ["gnt-cluster", "modify", "--reserved-lvs", ""]),
-    (True,  _CLUSTER_VERIFY),
+    (True, _CLUSTER_VERIFY),
     (False, ["lvremove", "-f", "xenvg/qa-test"]),
     (False, _CLUSTER_VERIFY),
     ]:
@@ -251,19 +252,19 @@ def TestClusterModifyBe():
     # mem
     (False, ["gnt-cluster", "modify", "-B", "memory=256"]),
     (False, ["sh", "-c", "gnt-cluster info|grep '^ *memory: 256$'"]),
-    (True,  ["gnt-cluster", "modify", "-B", "memory=a"]),
+    (True, ["gnt-cluster", "modify", "-B", "memory=a"]),
     (False, ["gnt-cluster", "modify", "-B", "memory=128"]),
     (False, ["sh", "-c", "gnt-cluster info|grep '^ *memory: 128$'"]),
     # vcpus
     (False, ["gnt-cluster", "modify", "-B", "vcpus=4"]),
     (False, ["sh", "-c", "gnt-cluster info|grep '^ *vcpus: 4$'"]),
-    (True,  ["gnt-cluster", "modify", "-B", "vcpus=a"]),
+    (True, ["gnt-cluster", "modify", "-B", "vcpus=a"]),
     (False, ["gnt-cluster", "modify", "-B", "vcpus=1"]),
     (False, ["sh", "-c", "gnt-cluster info|grep '^ *vcpus: 1$'"]),
     # auto_balance
     (False, ["gnt-cluster", "modify", "-B", "auto_balance=False"]),
     (False, ["sh", "-c", "gnt-cluster info|grep '^ *auto_balance: False$'"]),
-    (True,  ["gnt-cluster", "modify", "-B", "auto_balance=1"]),
+    (True, ["gnt-cluster", "modify", "-B", "auto_balance=1"]),
     (False, ["gnt-cluster", "modify", "-B", "auto_balance=True"]),
     (False, ["sh", "-c", "gnt-cluster info|grep '^ *auto_balance: True$'"]),
     ]:
@@ -274,6 +275,7 @@ def TestClusterModifyBe():
   if bep:
     AssertCommand(["gnt-cluster", "modify", "-B", bep])
 
+
 def TestClusterInfo():
   """gnt-cluster info"""
   AssertCommand(["gnt-cluster", "info"])
@@ -306,7 +308,7 @@ def TestClusterRenewCrypto():
     ["--new-cluster-domain-secret", "--cluster-domain-secret=/dev/null"],
     ]
   for i in conflicting:
-    AssertCommand(cmd+i, fail=True)
+    AssertCommand(cmd + i, fail=True)
 
   # Invalid RAPI certificate
   cmd = ["gnt-cluster", "renew-crypto", "--force",
diff --git a/qa/qa_config.py b/qa/qa_config.py
index 29c6e6955729a02130f5a798d215d700511d4bb0..b4cff14f9c115bdf4d09f873525a56da1b0a29d1 100644
--- a/qa/qa_config.py
+++ b/qa/qa_config.py
@@ -39,7 +39,7 @@ def Load(path):
   """Loads the passed configuration file.
 
   """
-  global cfg # pylint: disable-msg=W0603
+  global cfg # pylint: disable=W0603
 
   cfg = serializer.LoadJson(utils.ReadFile(path))
 
diff --git a/qa/qa_env.py b/qa/qa_env.py
index e9293b0765c11cbe8aa16c27ec2e17e327436813..986128b2fe143b0d7061895d3403a62c523449cb 100644
--- a/qa/qa_env.py
+++ b/qa/qa_env.py
@@ -42,7 +42,7 @@ def TestGanetiCommands():
   """Test availibility of Ganeti commands.
 
   """
-  cmds = ( ["gnt-backup", "--version"],
+  cmds = (["gnt-backup", "--version"],
            ["gnt-cluster", "--version"],
            ["gnt-debug", "--version"],
            ["gnt-instance", "--version"],
@@ -76,7 +76,7 @@ def TestIcmpPing():
   seccmd = [pingsecondary, "-e"]
   for i in nodes:
     pricmd.append(i["primary"])
-    if i.has_key("secondary"):
+    if "secondary" in i:
       seccmd.append(i["secondary"])
 
   pristr = utils.ShellQuoteArgs(pricmd)
diff --git a/qa/qa_error.py b/qa/qa_error.py
index 131b69bc8d4e6d25f30bfc35fd7408857bfe0ebe..8b5d4b90308ffa40f53e951377a3963e51563203 100644
--- a/qa/qa_error.py
+++ b/qa/qa_error.py
@@ -23,6 +23,7 @@
 
 """
 
+
 class Error(Exception):
   """An error occurred during Q&A testing.
 
diff --git a/qa/qa_instance.py b/qa/qa_instance.py
index 4747839eb0ba9d00803496d6e49152e4d4922d0e..45ee521fd1a913acecd7f8735a51061cb54d9784 100644
--- a/qa/qa_instance.py
+++ b/qa/qa_instance.py
@@ -251,7 +251,7 @@ def TestInstanceConsole(instance):
 
 def TestReplaceDisks(instance, pnode, snode, othernode):
   """gnt-instance replace-disks"""
-  # pylint: disable-msg=W0613
+  # pylint: disable=W0613
   # due to unused pnode arg
   # FIXME: should be removed from the function completely
   def buildcmd(args):
@@ -429,7 +429,7 @@ def _TestInstanceDiskFailure(instance, node, node2, onmaster):
 
 def TestInstanceMasterDiskFailure(instance, node, node2):
   """Testing disk failure on master node."""
-  # pylint: disable-msg=W0613
+  # pylint: disable=W0613
   # due to unused args
   print qa_utils.FormatError("Disk failure on primary node cannot be"
                              " tested due to potential crashes.")
diff --git a/qa/qa_node.py b/qa/qa_node.py
index 434de3832386f58766eb8a96844a043a72d92e67..8b2d406f3ee30e6c0e6864aa51ed51be82eb5199 100644
--- a/qa/qa_node.py
+++ b/qa/qa_node.py
@@ -275,7 +275,7 @@ def TestOutOfBand():
     # Power off on master without options should fail
     AssertCommand(["gnt-node", "power", "-f", "off", master_name], fail=True)
     # With force master it should still fail
-    AssertCommand(["gnt-node", "power", "-f",  "--ignore-status", "off",
+    AssertCommand(["gnt-node", "power", "-f", "--ignore-status", "off",
                    master_name],
                   fail=True)
 
@@ -302,7 +302,7 @@ def TestOutOfBand():
     AssertCommand(["gnt-node", "health"], fail=True)
 
     # Correct Data, exit 0
-    _UpdateOobFile(data_path, serializer.DumpJson({ "powered": True }))
+    _UpdateOobFile(data_path, serializer.DumpJson({"powered": True}))
 
     AssertCommand(["gnt-node", "power", "status", node_name])
     _AssertOobCall(verify_path, "power-status %s" % full_node_name)
@@ -315,7 +315,6 @@ def TestOutOfBand():
 
     AssertCommand(["gnt-node", "health"])
 
-
     # Those commands should fail as they expect no data regardless of exit 0
     AssertCommand(["gnt-node", "power", "on", node_name], fail=True)
     _AssertOobCall(verify_path, "power-on %s" % full_node_name)
diff --git a/qa/qa_rapi.py b/qa/qa_rapi.py
index 407279efcce15321864d637e03d2c2f912d7d8ed..ece6e645277af5d0d2c2d8f272ba01d84543d1ed 100644
--- a/qa/qa_rapi.py
+++ b/qa/qa_rapi.py
@@ -35,7 +35,7 @@ from ganeti import query
 from ganeti import compat
 from ganeti import qlang
 
-import ganeti.rapi.client        # pylint: disable-msg=W0611
+import ganeti.rapi.client        # pylint: disable=W0611
 import ganeti.rapi.client_utils
 
 import qa_config
@@ -55,7 +55,7 @@ def Setup(username, password):
   """Configures the RAPI client.
 
   """
-  # pylint: disable-msg=W0603
+  # pylint: disable=W0603
   # due to global usage
   global _rapi_ca
   global _rapi_client
@@ -122,7 +122,7 @@ def Enabled():
 
 
 def _DoTests(uris):
-  # pylint: disable-msg=W0212
+  # pylint: disable=W0212
   # due to _SendRequest usage
   results = []
 
diff --git a/qa/qa_utils.py b/qa/qa_utils.py
index 441bc8768ea0e9f34ac7deac5ffb2ffb642c6ab8..671a6f21abf2ecb976a572c61a1371f590060564 100644
--- a/qa/qa_utils.py
+++ b/qa/qa_utils.py
@@ -50,7 +50,7 @@ def _SetupColours():
   """Initializes the colour constants.
 
   """
-  # pylint: disable-msg=W0603
+  # pylint: disable=W0603
   # due to global usage
   global _INFO_SEQ, _WARNING_SEQ, _ERROR_SEQ, _RESET_SEQ
 
diff --git a/test/data/proc_drbd83_sync.txt b/test/data/proc_drbd83_sync.txt
new file mode 100644
index 0000000000000000000000000000000000000000..ae94186ccab349653a265a26a79ad25693609db9
Binary files /dev/null and b/test/data/proc_drbd83_sync.txt differ
diff --git a/test/data/proc_drbd83_sync_krnl2.6.39.txt b/test/data/proc_drbd83_sync_krnl2.6.39.txt
new file mode 100644
index 0000000000000000000000000000000000000000..cf3f541d3c52a7c26bf77657133ac27d15a389fd
Binary files /dev/null and b/test/data/proc_drbd83_sync_krnl2.6.39.txt differ
diff --git a/test/ganeti.bdev_unittest.py b/test/ganeti.bdev_unittest.py
index fa1433ec169ab648a08033c2c22834a50da53c54..6f8e3d40adc1616146f36fedba55cd01daee2abc 100755
--- a/test/ganeti.bdev_unittest.py
+++ b/test/ganeti.bdev_unittest.py
@@ -166,12 +166,21 @@ class TestDRBD8Status(testutils.GanetiTestCase):
     proc_data = self._TestDataFilename("proc_drbd8.txt")
     proc80e_data = self._TestDataFilename("proc_drbd80-emptyline.txt")
     proc83_data = self._TestDataFilename("proc_drbd83.txt")
+    proc83_sync_data = self._TestDataFilename("proc_drbd83_sync.txt")
+    proc83_sync_krnl_data = \
+      self._TestDataFilename("proc_drbd83_sync_krnl2.6.39.txt")
     self.proc_data = bdev.DRBD8._GetProcData(filename=proc_data)
     self.proc80e_data = bdev.DRBD8._GetProcData(filename=proc80e_data)
     self.proc83_data = bdev.DRBD8._GetProcData(filename=proc83_data)
+    self.proc83_sync_data = bdev.DRBD8._GetProcData(filename=proc83_sync_data)
+    self.proc83_sync_krnl_data = \
+      bdev.DRBD8._GetProcData(filename=proc83_sync_krnl_data)
     self.mass_data = bdev.DRBD8._MassageProcData(self.proc_data)
     self.mass80e_data = bdev.DRBD8._MassageProcData(self.proc80e_data)
     self.mass83_data = bdev.DRBD8._MassageProcData(self.proc83_data)
+    self.mass83_sync_data = bdev.DRBD8._MassageProcData(self.proc83_sync_data)
+    self.mass83_sync_krnl_data = \
+      bdev.DRBD8._MassageProcData(self.proc83_sync_krnl_data)
 
   def testIOErrors(self):
     """Test handling of errors while reading the proc file."""
@@ -251,5 +260,15 @@ class TestDRBD8Status(testutils.GanetiTestCase):
                       stats.rrole == 'Unknown' and
                       stats.is_disk_uptodate)
 
+  def testDRBD83SyncFine(self):
+    stats = bdev.DRBD8Status(self.mass83_sync_data[3])
+    self.failUnless(stats.is_in_resync)
+    self.failUnless(stats.sync_percent is not None)
+
+  def testDRBD83SyncBroken(self):
+    stats = bdev.DRBD8Status(self.mass83_sync_krnl_data[3])
+    self.failUnless(stats.is_in_resync)
+    self.failUnless(stats.sync_percent is not None)
+
 if __name__ == '__main__':
   testutils.GanetiTestProgram()
diff --git a/test/ganeti.hypervisor.hv_kvm_unittest.py b/test/ganeti.hypervisor.hv_kvm_unittest.py
index d9e5e7377db1cb7955dedd6fcdbe729749c79bd2..14ab34546910d5a76350a4c9ca9c06aecff68d1c 100755
--- a/test/ganeti.hypervisor.hv_kvm_unittest.py
+++ b/test/ganeti.hypervisor.hv_kvm_unittest.py
@@ -21,8 +21,13 @@
 
 """Script for testing the hypervisor.hv_kvm module"""
 
+import threading
+import tempfile
 import unittest
+import socket
+import os
 
+from ganeti import serializer
 from ganeti import constants
 from ganeti import compat
 from ganeti import objects
@@ -33,6 +38,129 @@ from ganeti.hypervisor import hv_kvm
 import testutils
 
 
+class QmpStub(threading.Thread):
+  """Stub for a QMP endpoint for a KVM instance
+
+  """
+  _QMP_BANNER_DATA = {"QMP": {"version": {
+                      "package": "",
+                      "qemu": {"micro": 50, "minor": 13, "major": 0},
+                      "capabilities": [],
+                      }}}
+  _EMPTY_RESPONSE = {"return": []}
+
+  def __init__(self, socket_filename, server_responses):
+    """Creates a QMP stub
+
+    @type socket_filename: string
+    @param socket_filename: filename of the UNIX socket that will be created
+                            this class and used for the communication
+    @type server_responses: list
+    @param server_responses: list of responses that the server sends in response
+                             to whatever it receives
+    """
+    threading.Thread.__init__(self)
+    self.socket_filename = socket_filename
+    self.script = server_responses
+
+    self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+    self.socket.bind(self.socket_filename)
+    self.socket.listen(1)
+
+  def run(self):
+    # Hypothesis: the messages we receive contain only a complete QMP message
+    # encoded in JSON.
+    conn, addr = self.socket.accept()
+
+    # Send the banner as the first thing
+    conn.send(self.encode_string(self._QMP_BANNER_DATA))
+
+    # Expect qmp_capabilities and return an empty response
+    conn.recv(4096)
+    conn.send(self.encode_string(self._EMPTY_RESPONSE))
+
+    while True:
+      # We ignore the expected message, as the purpose of this object is not
+      # to verify the correctness of the communication but to act as a
+      # partner for the SUT (System Under Test, that is QmpConnection)
+      msg = conn.recv(4096)
+      if not msg:
+        break
+
+      if not self.script:
+        break
+      response = self.script.pop(0)
+      if isinstance(response, str):
+        conn.send(response)
+      elif isinstance(response, list):
+        for chunk in response:
+          conn.send(chunk)
+      else:
+        raise errors.ProgrammerError("Unknown response type for %s" % response)
+
+    conn.close()
+
+  def encode_string(self, message):
+    return (serializer.DumpJson(message, indent=False) +
+            hv_kvm.QmpConnection._MESSAGE_END_TOKEN)
+
+
+class TestQmpMessage(testutils.GanetiTestCase):
+  def testSerialization(self):
+    test_data = {"execute": "command", "arguments": ["a", "b", "c"]}
+    message = hv_kvm.QmpMessage(test_data)
+
+    for k, v in test_data.items():
+      self.failUnless(message[k] == v)
+
+    rebuilt_message = hv_kvm.QmpMessage.BuildFromJsonString(str(message))
+    self.failUnless(rebuilt_message == message)
+
+
+class TestQmp(testutils.GanetiTestCase):
+  def testQmp(self):
+    requests = [
+      {"execute": "query-kvm", "arguments": []},
+      {"execute": "eject", "arguments": {"device": "ide1-cd0"}},
+      {"execute": "query-status", "arguments": []},
+      {"execute": "query-name", "arguments": []},
+      ]
+
+    server_responses = [
+      # One message, one send()
+      '{"return": {"enabled": true, "present": true}}\r\n',
+
+      # Message sent using multiple send()
+      ['{"retur', 'n": {}}\r\n'],
+
+      # Multiple messages sent using one send()
+      '{"return": [{"name": "quit"}, {"name": "eject"}]}\r\n'
+      '{"return": {"running": true, "singlestep": false}}\r\n',
+      ]
+
+    expected_responses = [
+      {"return": {"enabled": True, "present": True}},
+      {"return": {}},
+      {"return": [{"name": "quit"}, {"name": "eject"}]},
+      {"return": {"running": True, "singlestep": False}},
+      ]
+
+    # Set up the stub
+    socket_file = tempfile.NamedTemporaryFile()
+    os.remove(socket_file.name)
+    qmp_stub = QmpStub(socket_file.name, server_responses)
+    qmp_stub.start()
+
+    # Set up the QMP connection
+    qmp_connection = hv_kvm.QmpConnection(socket_file.name)
+    qmp_connection.connect()
+
+    # Format the script
+    for request, expected_response in zip(requests, expected_responses):
+      response = qmp_connection.Execute(request)
+      self.failUnless(response == hv_kvm.QmpMessage(expected_response))
+
+
 class TestConsole(unittest.TestCase):
   def _Test(self, instance, hvparams):
     cons = hv_kvm.KVMHypervisor.GetInstanceConsole(instance, hvparams, {})
@@ -45,6 +173,7 @@ class TestConsole(unittest.TestCase):
     hvparams = {
       constants.HV_SERIAL_CONSOLE: True,
       constants.HV_VNC_BIND_ADDRESS: None,
+      constants.HV_KVM_SPICE_BIND: None,
       }
     cons = self._Test(instance, hvparams)
     self.assertEqual(cons.kind, constants.CONS_SSH)
@@ -59,6 +188,7 @@ class TestConsole(unittest.TestCase):
     hvparams = {
       constants.HV_SERIAL_CONSOLE: False,
       constants.HV_VNC_BIND_ADDRESS: "192.0.2.1",
+      constants.HV_KVM_SPICE_BIND: None,
       }
     cons = self._Test(instance, hvparams)
     self.assertEqual(cons.kind, constants.CONS_VNC)
@@ -66,6 +196,20 @@ class TestConsole(unittest.TestCase):
     self.assertEqual(cons.port, constants.VNC_BASE_PORT + 10)
     self.assertEqual(cons.display, 10)
 
+  def testSpice(self):
+    instance = objects.Instance(name="kvm.example.com",
+                                primary_node="node7235",
+                                network_port=11000)
+    hvparams = {
+      constants.HV_SERIAL_CONSOLE: False,
+      constants.HV_VNC_BIND_ADDRESS: None,
+      constants.HV_KVM_SPICE_BIND: "192.0.2.1",
+      }
+    cons = self._Test(instance, hvparams)
+    self.assertEqual(cons.kind, constants.CONS_SPICE)
+    self.assertEqual(cons.host, "192.0.2.1")
+    self.assertEqual(cons.port, 11000)
+
   def testNoConsole(self):
     instance = objects.Instance(name="kvm.example.com",
                                 primary_node="node24325",
@@ -73,6 +217,7 @@ class TestConsole(unittest.TestCase):
     hvparams = {
       constants.HV_SERIAL_CONSOLE: False,
       constants.HV_VNC_BIND_ADDRESS: None,
+      constants.HV_KVM_SPICE_BIND: None,
       }
     cons = self._Test(instance, hvparams)
     self.assertEqual(cons.kind, constants.CONS_MESSAGE)
diff --git a/test/ganeti.utils.io_unittest.py b/test/ganeti.utils.io_unittest.py
index be1b4abb65c1460f8b62332616502c1bcf167e51..8b2cc8ba80cc6d867ec4fe22a688683b729cb8ef 100755
--- a/test/ganeti.utils.io_unittest.py
+++ b/test/ganeti.utils.io_unittest.py
@@ -586,6 +586,32 @@ class TestIsNormAbsPath(unittest.TestCase):
     self._pathTestHelper("/etc/", False)
 
 
+class TestIsBelowDir(unittest.TestCase):
+  """Testing case for IsBelowDir"""
+
+  def testSamePrefix(self):
+    self.assertTrue(utils.IsBelowDir("/a/b", "/a/b/c"))
+    self.assertTrue(utils.IsBelowDir("/a/b/", "/a/b/e"))
+
+  def testSamePrefixButDifferentDir(self):
+    self.assertFalse(utils.IsBelowDir("/a/b", "/a/bc/d"))
+    self.assertFalse(utils.IsBelowDir("/a/b/", "/a/bc/e"))
+
+  def testSamePrefixButDirTraversal(self):
+    self.assertFalse(utils.IsBelowDir("/a/b", "/a/b/../c"))
+    self.assertFalse(utils.IsBelowDir("/a/b/", "/a/b/../d"))
+
+  def testSamePrefixAndTraversal(self):
+    self.assertTrue(utils.IsBelowDir("/a/b", "/a/b/c/../d"))
+    self.assertTrue(utils.IsBelowDir("/a/b", "/a/b/c/./e"))
+    self.assertTrue(utils.IsBelowDir("/a/b", "/a/b/../b/./e"))
+
+  def testBothAbsPath(self):
+    self.assertRaises(ValueError, utils.IsBelowDir, "/a/b/c", "d")
+    self.assertRaises(ValueError, utils.IsBelowDir, "a/b/c", "/d")
+    self.assertRaises(ValueError, utils.IsBelowDir, "a/b/c", "d")
+
+
 class TestPathJoin(unittest.TestCase):
   """Testing case for PathJoin"""
 
diff --git a/test/ganeti.utils.text_unittest.py b/test/ganeti.utils.text_unittest.py
index 5181db4d36aa963d1f552d59650039d186d0a696..91039e173a765a1bbb6eddc09edda95b4d651d7c 100755
--- a/test/ganeti.utils.text_unittest.py
+++ b/test/ganeti.utils.text_unittest.py
@@ -350,7 +350,7 @@ class TestUnescapeAndSplit(unittest.TestCase):
 
   def setUp(self):
     # testing more that one separator for regexp safety
-    self._seps = [",", "+", "."]
+    self._seps = [",", "+", ".", ":"]
 
   def testSimple(self):
     a = ["a", "b", "c", "d"]
@@ -375,6 +375,18 @@ class TestUnescapeAndSplit(unittest.TestCase):
       b = ["a", "b\\" + sep + "c", "d"]
       self.failUnlessEqual(utils.UnescapeAndSplit(sep.join(a), sep=sep), b)
 
+  def testEscapeAtEnd(self):
+    for sep in self._seps:
+      self.assertEqual(utils.UnescapeAndSplit("\\", sep=sep), ["\\"])
+
+      a = ["a", "b\\", "c"]
+      b = ["a", "b" + sep + "c\\"]
+      self.assertEqual(utils.UnescapeAndSplit("%s\\" % sep.join(a), sep=sep), b)
+
+      a = ["\\" + sep, "\\" + sep, "c", "d\\.moo"]
+      b = [sep, sep, "c", "d.moo\\"]
+      self.assertEqual(utils.UnescapeAndSplit("%s\\" % sep.join(a), sep=sep), b)
+
 
 class TestCommaJoin(unittest.TestCase):
   def test(self):
diff --git a/tools/burnin b/tools/burnin
index 8ab7a15f4a0a33f27c5b6e0e138c91ce0a7e4cc4..c5d1612ffa36df856f6fcc413759638626156c53 100755
--- a/tools/burnin
+++ b/tools/burnin
@@ -91,12 +91,12 @@ def Err(msg, exit_code=1):
 
 class SimpleOpener(urllib.FancyURLopener):
   """A simple url opener"""
-  # pylint: disable-msg=W0221
+  # pylint: disable=W0221
 
   def prompt_user_passwd(self, host, realm, clear_cache=0):
     """No-interaction version of prompt_user_passwd."""
     # we follow parent class' API
-    # pylint: disable-msg=W0613
+    # pylint: disable=W0613
     return None, None
 
   def http_error_default(self, url, fp, errcode, errmsg, headers):
@@ -232,7 +232,7 @@ def _DoCheckInstances(fn):
   def wrapper(self, *args, **kwargs):
     val = fn(self, *args, **kwargs)
     for instance in self.instances:
-      self._CheckInstanceAlive(instance) # pylint: disable-msg=W0212
+      self._CheckInstanceAlive(instance) # pylint: disable=W0212
     return val
 
   return wrapper
@@ -312,7 +312,7 @@ class Burner(object):
         Log("Idempotent %s succeeded after %d retries",
             msg, MAX_RETRIES - retry_count)
       return val
-    except Exception, err: # pylint: disable-msg=W0703
+    except Exception, err: # pylint: disable=W0703
       if retry_count == 0:
         Log("Non-idempotent %s failed, aborting", msg)
         raise
@@ -358,7 +358,7 @@ class Burner(object):
       cli.SetGenericOpcodeOpts(ops, self.opts)
       self.queued_ops.append((ops, name, post_process))
     else:
-      val = self.ExecOp(self.queue_retry, *ops) # pylint: disable-msg=W0142
+      val = self.ExecOp(self.queue_retry, *ops) # pylint: disable=W0142
       if post_process is not None:
         post_process()
       return val
@@ -400,10 +400,10 @@ class Burner(object):
     self.ClearFeedbackBuf()
     jex = cli.JobExecutor(cl=self.cl, feedback_fn=self.Feedback)
     for ops, name, _ in jobs:
-      jex.QueueJob(name, *ops) # pylint: disable-msg=W0142
+      jex.QueueJob(name, *ops) # pylint: disable=W0142
     try:
       results = jex.GetResults()
-    except Exception, err: # pylint: disable-msg=W0703
+    except Exception, err: # pylint: disable=W0703
       Log("Jobs failed: %s", err)
       raise BurninFailure()
 
@@ -414,7 +414,7 @@ class Burner(object):
         if post_process:
           try:
             post_process()
-          except Exception, err: # pylint: disable-msg=W0703
+          except Exception, err: # pylint: disable=W0703
             Log("Post process call for job %s failed: %s", name, err)
             fail = True
         val.append(result)
@@ -1066,7 +1066,7 @@ class Burner(object):
       if not self.opts.keep_instances:
         try:
           self.BurnRemove()
-        except Exception, err:  # pylint: disable-msg=W0703
+        except Exception, err:  # pylint: disable=W0703
           if has_err: # already detected errors, so errors in removal
                       # are quite expected
             Log("Note: error detected during instance remove: %s", err)
diff --git a/tools/cfgshell b/tools/cfgshell
index 77afab9dcdd4e2167c9c9eecf5ec63c810557856..3e79561b109d986d6204891e822fcbccb2eacc9c 100755
--- a/tools/cfgshell
+++ b/tools/cfgshell
@@ -24,7 +24,7 @@
 """
 
 # functions in this module need to have a given name structure, so:
-# pylint: disable-msg=C0103
+# pylint: disable=C0103
 
 
 import optparse
@@ -54,7 +54,7 @@ class ConfigShell(cmd.Cmd):
 
   """
   # all do_/complete_* functions follow the same API
-  # pylint: disable-msg=W0613
+  # pylint: disable=W0613
   prompt = "(/) "
 
   def __init__(self, cfg_file=None):
@@ -93,7 +93,7 @@ class ConfigShell(cmd.Cmd):
     dirs = []
     entries = []
     if isinstance(obj, objects.ConfigObject):
-      # pylint: disable-msg=W0212
+      # pylint: disable=W0212
       # yes, we're using a protected member
       for name in obj._all_slots():
         child = getattr(obj, name, None)
@@ -156,7 +156,7 @@ class ConfigShell(cmd.Cmd):
       arg = None
     try:
       self.cfg = config.ConfigWriter(cfg_file=arg, offline=True)
-      self.parents = [self.cfg._config_data] # pylint: disable-msg=W0212
+      self.parents = [self.cfg._config_data] # pylint: disable=W0212
       self.path = []
     except errors.ConfigurationError, err:
       print "Error: %s" % str(err)
@@ -288,7 +288,7 @@ class ConfigShell(cmd.Cmd):
     if self.cfg.VerifyConfig():
       print "Config data does not validate, refusing to save."
       return False
-    self.cfg._WriteConfig() # pylint: disable-msg=W0212
+    self.cfg._WriteConfig() # pylint: disable=W0212
 
   def do_rm(self, line):
     """Removes an instance or a node.
@@ -298,7 +298,7 @@ class ConfigShell(cmd.Cmd):
 
     """
     pointer = self.parents[-1]
-    data = self.cfg._config_data  # pylint: disable-msg=W0212
+    data = self.cfg._config_data  # pylint: disable=W0212
     if pointer not in (data.instances, data.nodes):
       print "Can only delete instances and nodes"
       return False
diff --git a/tools/cfgupgrade b/tools/cfgupgrade
index 7197d24840b6ed1be71718e60d4119c2de648e93..b44ea6c1fbc5845bb762faf68bf5ad03b42e095d 100755
--- a/tools/cfgupgrade
+++ b/tools/cfgupgrade
@@ -93,7 +93,7 @@ def main():
   """Main program.
 
   """
-  global options, args # pylint: disable-msg=W0603
+  global options, args # pylint: disable=W0603
 
   # Option parsing
   parser = optparse.OptionParser(usage="%prog [--debug|--verbose] [--force]")
diff --git a/tools/cfgupgrade12 b/tools/cfgupgrade12
index 7ff5daa6bbf6809f895b96aa2d1007189e3be215..83187c3d703ea32e5940a853e30ab14696fe0637 100755
--- a/tools/cfgupgrade12
+++ b/tools/cfgupgrade12
@@ -18,7 +18,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 # 02110-1301, USA.
 
-# pylint: disable-msg=C0103,E1103
+# pylint: disable=C0103,E1103
 
 # C0103: invalid name NoDefault
 # E1103: Instance of 'foor' has no 'bar' member (but some types could
@@ -281,7 +281,7 @@ def main():
   """Main program.
 
   """
-  # pylint: disable-msg=W0603
+  # pylint: disable=W0603
   global options, args
 
   program = os.path.basename(sys.argv[0])
diff --git a/tools/check-cert-expired b/tools/check-cert-expired
index f47ca984905d37766da6dee9c4dd2de39b000545..32580bd5017a822f87044dc7e931e11004aee13a 100755
--- a/tools/check-cert-expired
+++ b/tools/check-cert-expired
@@ -22,7 +22,7 @@
 
 """
 
-# pylint: disable-msg=C0103
+# pylint: disable=C0103
 # C0103: Invalid name check-cert-expired
 
 import os.path
@@ -66,7 +66,7 @@ def main():
 
   except (KeyboardInterrupt, SystemExit):
     raise
-  except Exception, err: # pylint: disable-msg=W0703
+  except Exception, err: # pylint: disable=W0703
     cli.ToStderr("Unable to check %s: %s", filename, err)
 
   sys.exit(constants.EXIT_FAILURE)
diff --git a/tools/cluster-merge b/tools/cluster-merge
index e2fc8485b302510ed094972d880e19284f623e36..40bc2596b68fbc97f1ef82c49e6aad96b17f9136 100755
--- a/tools/cluster-merge
+++ b/tools/cluster-merge
@@ -24,7 +24,7 @@ The clusters have to run the same version of Ganeti!
 
 """
 
-# pylint: disable-msg=C0103
+# pylint: disable=C0103
 # C0103: Invalid name cluster-merge
 
 import logging
@@ -374,7 +374,7 @@ class Merger(object):
       utils.WriteFile(data.config_path, data=result.stdout)
 
   # R0201: Method could be a function
-  def _KillMasterDaemon(self): # pylint: disable-msg=R0201
+  def _KillMasterDaemon(self): # pylint: disable=R0201
     """Kills the local master daemon.
 
     @raise errors.CommandError: If unable to kill
@@ -574,7 +574,7 @@ class Merger(object):
                                       other_cluster.cluster_name)
 
   # R0201: Method could be a function
-  def _GetOsHypervisor(self, cluster, os_name, hyp): # pylint: disable-msg=R0201
+  def _GetOsHypervisor(self, cluster, os_name, hyp): # pylint: disable=R0201
     if os_name in cluster.os_hvp:
       return cluster.os_hvp[os_name].get(hyp, None)
     else:
@@ -586,7 +586,7 @@ class Merger(object):
 
     ConfigWriter.AddNodeGroup takes care of making sure there are no conflicts.
     """
-    # pylint: disable-msg=R0201
+    # pylint: disable=R0201
     logging.info("Node group conflict strategy: %s", self.groups)
 
     my_grps = my_config.GetAllNodeGroupsInfo().values()
@@ -626,15 +626,15 @@ class Merger(object):
           for node_name in other_grp.members[:]:
             node = other_config.GetNodeInfo(node_name)
             # Access to a protected member of a client class
-            # pylint: disable-msg=W0212
+            # pylint: disable=W0212
             other_config._UnlockedRemoveNodeFromGroup(node)
 
             # Access to a protected member of a client class
-            # pylint: disable-msg=W0212
+            # pylint: disable=W0212
             my_grp_uuid = my_config._UnlockedLookupNodeGroup(other_grp.name)
 
             # Access to a protected member of a client class
-            # pylint: disable-msg=W0212
+            # pylint: disable=W0212
             my_config._UnlockedAddNodeToGroup(node, my_grp_uuid)
             node.group = my_grp_uuid
           # Remove from list of groups to add
@@ -645,7 +645,7 @@ class Merger(object):
       my_config.AddNodeGroup(grp, _CLUSTERMERGE_ECID)
 
   # R0201: Method could be a function
-  def _StartMasterDaemon(self, no_vote=False): # pylint: disable-msg=R0201
+  def _StartMasterDaemon(self, no_vote=False): # pylint: disable=R0201
     """Starts the local master daemon.
 
     @param no_vote: Should the masterd started without voting? default: False
@@ -683,7 +683,7 @@ class Merger(object):
                                                 result.output))
 
   # R0201: Method could be a function
-  def _StartupAllInstances(self): # pylint: disable-msg=R0201
+  def _StartupAllInstances(self): # pylint: disable=R0201
     """Starts up all instances (locally).
 
     @raise errors.CommandError: If unable to start clusters
@@ -698,7 +698,7 @@ class Merger(object):
 
   # R0201: Method could be a function
   # TODO: make this overridable, for some verify errors
-  def _VerifyCluster(self): # pylint: disable-msg=R0201
+  def _VerifyCluster(self): # pylint: disable=R0201
     """Runs gnt-cluster verify to verify the health.
 
     @raise errors.ProgrammError: If cluster fails on verification
diff --git a/tools/ganeti-listrunner b/tools/ganeti-listrunner
index 2ab63cef23637fcf4addce17f18fa19fec143780..13ab024271934431ffecc7c8c2b175d84855be36 100755
--- a/tools/ganeti-listrunner
+++ b/tools/ganeti-listrunner
@@ -49,7 +49,7 @@ Security considerations:
 
 """
 
-# pylint: disable-msg=C0103
+# pylint: disable=C0103
 # C0103: Invalid name ganeti-listrunner
 
 import errno
@@ -231,7 +231,7 @@ def SetupSshConnection(host, username, password, use_agent, logfile):
         log = logging.getLogger(transport.get_log_channel())
         log.addHandler(handler)
 
-      transport.connect(username=username, **kwargs) # pylint: disable-msg=W0142
+      transport.connect(username=username, **kwargs) # pylint: disable=W0142
       WriteLog("ssh connection established using %s" % desc, logfile)
       # strange ... when establishing the session and the immediately
       # setting up the channels for sftp & shell from that, it sometimes
@@ -267,7 +267,7 @@ def UploadFiles(connection, executable, filelist, logfile):
     sftp = paramiko.SFTPClient.from_transport(connection)
     sftp.mkdir(remote_dir, mode=0700)
     for item in filelist:
-      remote_file = "%s/%s" % (remote_dir, item.split("/").pop())
+      remote_file = "%s/%s" % (remote_dir, os.path.basename(item))
       WriteLog("uploading %s to remote %s" % (item, remote_file), logfile)
       sftp.put(item, remote_file)
       if item == executable:
@@ -287,7 +287,7 @@ def CleanupRemoteDir(connection, upload_dir, filelist, logfile):
   try:
     sftp = paramiko.SFTPClient.from_transport(connection)
     for item in filelist:
-      fullpath = "%s/%s" % (upload_dir, item.split("/").pop())
+      fullpath = "%s/%s" % (upload_dir, os.path.basename(item))
       WriteLog("removing remote %s" % fullpath, logfile)
       sftp.remove(fullpath)
     sftp.rmdir(upload_dir)
@@ -337,7 +337,7 @@ def RunRemoteCommand(connection, command, logfile):
     select.select([], [], [], .1)
 
   WriteLog("SUCCESS: command output follows", logfile)
-  for line in output.split("\n"):
+  for line in output.splitlines():
     WriteLog("output = %s" % line, logfile)
   WriteLog("command execution completed", logfile)
   session.close()
@@ -346,7 +346,7 @@ def RunRemoteCommand(connection, command, logfile):
 
 
 def HostWorker(logdir, username, password, use_agent, hostname,
-               executable, command, filelist):
+               executable, exec_args, command, filelist):
   """Per-host worker.
 
   This function does not return - it's the main code of the childs,
@@ -359,6 +359,7 @@ def HostWorker(logdir, username, password, use_agent, hostname,
   @param use_agent: whether we should instead use an agent
   @param hostname: the hostname to connect to
   @param executable: the executable to upload, if not None
+  @param exec_args: Additional arguments for executable
   @param command: the command to run
   @param filelist: auxiliary files to upload
 
@@ -375,8 +376,8 @@ def HostWorker(logdir, username, password, use_agent, hostname,
         print "  %s: uploading files" % hostname
         upload_dir = UploadFiles(connection, executable,
                                  filelist, logfile)
-        command = "cd %s && ./%s" % (upload_dir,
-                                     executable.split("/").pop())
+        command = ("cd %s && ./%s %s" %
+                   (upload_dir, os.path.basename(executable), exec_args))
       print "  %s: executing remote command" % hostname
       cmd_result = RunRemoteCommand(connection, command, logfile)
       if cmd_result is True:
@@ -412,7 +413,7 @@ def HostWorker(logdir, username, password, use_agent, hostname,
 
 
 def LaunchWorker(child_pids, logdir, username, password, use_agent, hostname,
-                 executable, command, filelist):
+                 executable, exec_args, command, filelist):
   """Launch the per-host worker.
 
   Arguments are the same as for HostWorker, except for child_pids,
@@ -426,7 +427,7 @@ def LaunchWorker(child_pids, logdir, username, password, use_agent, hostname,
     child_pids[pid] = hostname
   else:
     HostWorker(logdir, username, password, use_agent, hostname,
-               executable, command, filelist)
+               executable, exec_args, command, filelist)
 
 
 def ParseOptions():
@@ -453,7 +454,7 @@ def ParseOptions():
                     help="comma-separated list of hosts or single hostname",)
   parser.add_option("-a", dest="auxfiles", action="append", default=[],
                     help="optional auxiliary file to upload"
-                    " (can be given multiple times",
+                    " (can be given multiple times)",
                     metavar="FILE")
   parser.add_option("-c", dest="command", default=None,
                     help="shell command to run on remote host(s)")
@@ -467,6 +468,8 @@ def ParseOptions():
                     " using an agent)")
   parser.add_option("-A", dest="use_agent", default=False, action="store_true",
                     help="instead of password, use keys from an SSH agent")
+  parser.add_option("--args", dest="exec_args", default=None,
+                    help="Arguments to be passed to executable (-x)")
 
   opts, args = parser.parse_args()
 
@@ -474,6 +477,8 @@ def ParseOptions():
     parser.error("Options -x and -c conflict with each other")
   if not (opts.executable or opts.command):
     parser.error("One of -x and -c must be given")
+  if opts.command and opts.exec_args:
+    parser.error("Can't specify arguments when using custom command")
   if not opts.logdir:
     parser.error("Option -l is required")
   if opts.hostfile and opts.hostlist:
@@ -484,14 +489,15 @@ def ParseOptions():
     parser.error("This program doesn't take any arguments, passed in: %s" %
                  ", ".join(args))
 
-  return (opts.logdir, opts.executable, opts.hostfile, opts.hostlist,
+  return (opts.logdir, opts.executable, opts.exec_args,
+          opts.hostfile, opts.hostlist,
           opts.command, opts.use_agent, opts.auxfiles, opts.username,
           opts.password, opts.batch_size)
 
 
 def main():
   """main."""
-  (logdir, executable, hostfile, hostlist,
+  (logdir, executable, exec_args, hostfile, hostlist,
    command, use_agent, auxfiles, username,
    password, batch_size) = ParseOptions()
 
@@ -536,7 +542,7 @@ def main():
   child_pids = {}
   for hostname in batch:
     LaunchWorker(child_pids, logdir, username, password, use_agent, hostname,
-                 executable, command, filelist)
+                 executable, exec_args, command, filelist)
 
   while child_pids:
     pid, status = os.wait()
@@ -548,7 +554,7 @@ def main():
       failures += 1
     if hosts:
       LaunchWorker(child_pids, logdir, username, password, use_agent,
-                   hosts.pop(0), executable, command, filelist)
+                   hosts.pop(0), executable, exec_args, command, filelist)
 
   print
   print "All done, %s successful and %s failed hosts" % (successes, failures)
diff --git a/tools/lvmstrap b/tools/lvmstrap
index e24b2a6daeacc5a268ec002aa0f278115e2806a4..eb87cd2325b919d3c0c8f7a02be0048803bb53b9 100755
--- a/tools/lvmstrap
+++ b/tools/lvmstrap
@@ -170,7 +170,7 @@ def ParseOptions():
       OptionParser.parse_args
 
   """
-  global verbose_flag # pylint: disable-msg=W0603
+  global verbose_flag # pylint: disable=W0603
 
   parser = optparse.OptionParser(usage="\n%s" % USAGE,
                                  version="%%prog (ganeti) %s" %
diff --git a/tools/move-instance b/tools/move-instance
index cdf99ec971eb85ea25b2baa204b528d8028e3e06..168ad811fb7789c05cca285bbc612eb9ad38f82d 100755
--- a/tools/move-instance
+++ b/tools/move-instance
@@ -22,7 +22,7 @@
 
 """
 
-# pylint: disable-msg=C0103
+# pylint: disable=C0103
 # C0103: Invalid name move-instance
 
 import os
@@ -40,7 +40,7 @@ from ganeti import objects
 from ganeti import compat
 from ganeti import rapi
 
-import ganeti.rapi.client # pylint: disable-msg=W0611
+import ganeti.rapi.client # pylint: disable=W0611
 import ganeti.rapi.client_utils
 
 
@@ -662,7 +662,7 @@ class MoveSourceExecutor(object):
 
 
 class MoveSourceWorker(workerpool.BaseWorker):
-  def RunTask(self, rapi_factory, move): # pylint: disable-msg=W0221
+  def RunTask(self, rapi_factory, move): # pylint: disable=W0221
     """Executes an instance move.
 
     @type rapi_factory: L{RapiClientFactory}
@@ -696,7 +696,7 @@ class MoveSourceWorker(workerpool.BaseWorker):
                               (mrt.src_error_message, mrt.dest_error_message))
       else:
         move.error_message = None
-    except Exception, err: # pylint: disable-msg=W0703
+    except Exception, err: # pylint: disable=W0703
       logging.exception("Caught unhandled exception")
       move.error_message = str(err)
 
diff --git a/tools/sanitize-config b/tools/sanitize-config
index ca9c95539f3ba93b390bf04a53006cba43ac087f..f94e67084c48082b08b8dd5ba0a2ecd0092c6251 100755
--- a/tools/sanitize-config
+++ b/tools/sanitize-config
@@ -19,7 +19,7 @@
 # 02110-1301, USA.
 
 
-# pylint: disable-msg=C0103
+# pylint: disable=C0103
 
 """Tool to sanitize/randomize the configuration file.
 
@@ -79,7 +79,7 @@ def GenerateNameMap(opts, names, base):
   return name_map
 
 
-def SanitizeSecrets(opts, cfg): # pylint: disable-msg=W0613
+def SanitizeSecrets(opts, cfg): # pylint: disable=W0613
   """Cleanup configuration secrets.
 
   """
@@ -126,7 +126,7 @@ def SanitizeInstances(opts, cfg):
   RenameDictKeys(cfg["instances"], old_map, True)
 
 
-def SanitizeIps(opts, cfg): # pylint: disable-msg=W0613
+def SanitizeIps(opts, cfg): # pylint: disable=W0613
   """Sanitize the IP names.
 
   @note: we're interested in obscuring the old IPs, not in generating
@@ -161,7 +161,7 @@ def SanitizeIps(opts, cfg): # pylint: disable-msg=W0613
         nic["ip"] = _Get(nic["ip"])
 
 
-def SanitizeOsNames(opts, cfg): # pylint: disable-msg=W0613
+def SanitizeOsNames(opts, cfg): # pylint: disable=W0613
   """Sanitize the OS names.
 
   """
@@ -182,7 +182,7 @@ def SanitizeOsNames(opts, cfg): # pylint: disable-msg=W0613
     RenameDictKeys(cfg["cluster"]["os_hvp"], os_map, False)
 
 
-def SanitizeDisks(opts, cfg): # pylint: disable-msg=W0613
+def SanitizeDisks(opts, cfg): # pylint: disable=W0613
   """Cleanup disks disks.
 
   """
diff --git a/tools/setup-ssh b/tools/setup-ssh
index 112caf61bec9ee8670a3a0a6e46085160c72cf6c..8c406ce93cf7b796175e1fe76d1c8f37b3c72cd2 100755
--- a/tools/setup-ssh
+++ b/tools/setup-ssh
@@ -24,7 +24,7 @@ This is needed before we can join the node into the cluster.
 
 """
 
-# pylint: disable-msg=C0103
+# pylint: disable=C0103
 # C0103: Invalid name setup-ssh
 
 import getpass