diff --git a/Makefile.am b/Makefile.am
index 7a2d35952a3cca12c287214c7e4dc5ab05ff3527..14d2345621e088dc0ee38bac845cbc81f64a2939 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -160,8 +160,8 @@ CLEANFILES = \
 	.hpc/*.mix htools/*.tix \
 	doc/hs-lint.html
 
-# BUILT_SOURCES should only be used as a dependency on phony targets. Otherwise
-# it'll cause the target to rebuild every time.
+# BUILT_SOURCES should only be used as a dependency on phony
+# targets. Otherwise it'll cause the target to rebuild every time.
 BUILT_SOURCES = \
   $(built_base_sources) \
 	$(BUILT_PYTHON_SOURCES) \
@@ -999,13 +999,17 @@ man/footer.html: man/footer.rst
 	$(PANDOC) -f rst -t html -o $@ $<
 
 man/%.gen: man/%.rst lib/query.py lib/build/sphinx_ext.py \
-	lib/build/shell_example_lexer.py
+	lib/build/shell_example_lexer.py \
+	| $(RUN_IN_TEMPDIR) $(BUILT_PYTHON_SOURCES)
 	@echo "Checking $< for hardcoded paths..."
 	@if grep -nEf autotools/wrong-hardcoded-paths $<; then \
 	  echo "Man page $< has harcoded paths (see above)!" 1>&2 ; \
 	  exit 1; \
 	fi
-	PYTHONPATH=. $(RUN_IN_TEMPDIR) $(CURDIR)/$(DOCPP) < $< > $@
+	set -e ; \
+	trap 'echo auto-removing $@; rm $@' EXIT; \
+	PYTHONPATH=. $(RUN_IN_TEMPDIR) $(CURDIR)/$(DOCPP) < $< > $@ ;\
+	trap - EXIT
 
 man/%.7.in man/%.8.in man/%.1.in: man/%.gen man/footer.man
 	@test -n "$(PANDOC)" || \
@@ -1048,12 +1052,16 @@ vcs-version:
 	  echo "Cannot auto-generate $@ file"; exit 1; \
 	fi
 
+.PHONY: clean-vcs-version
+clean-vcs-version:
+	rm -f vcs-version
+
 .PHONY: regen-vcs-version
 regen-vcs-version:
 	set -e; \
 	cd $(srcdir); \
 	if test -d .git; then \
-	  rm -f vcs-version; \
+	  $(MAKE) clean-vcs-version; \
 	  $(MAKE) vcs-version; \
 	fi
 
@@ -1138,6 +1146,7 @@ lib/_autoconf.py: Makefile | lib/.dir
 	  echo "ENABLE_CONFD = $(ENABLE_CONFD)"; \
 	  echo "PY_CONFD = $(PY_CONFD)"; \
 	  echo "HS_CONFD = $(HS_CONFD)"; \
+	  echo "XEN_CMD = '$(XEN_CMD)'"; \
 	} > $@
 
 lib/_vcsversion.py: Makefile vcs-version | lib/.dir
@@ -1309,28 +1318,43 @@ PEP8_IGNORE = E111,E261,E501
 # For excluding pep8 expects filenames only, not whole paths
 PEP8_EXCLUDE = $(subst $(space),$(comma),$(strip $(notdir $(BUILT_PYTHON_SOURCES))))
 
+LINT_TARGETS = pylint pylint-qa
+if HAS_PEP8
+LINT_TARGETS += pep8
+endif
+if HAS_HLINT
+LINT_TARGETS += hlint
+endif
+
 .PHONY: lint
-lint: $(BUILT_SOURCES)
+lint: $(LINT_TARGETS)
+
+.PHONY: pylint
+pylint: $(BUILT_SOURCES)
 	@test -n "$(PYLINT)" || { echo 'pylint' not found during configure; exit 1; }
-	if test -z "$(PEP8)"; then \
-		echo '"pep8" not found during configure' >&2; \
-	else \
-		$(PEP8) --repeat --ignore='$(PEP8_IGNORE)' --exclude='$(PEP8_EXCLUDE)' \
-			$(pep8_python_code); \
-	fi
 	$(PYLINT) $(LINT_OPTS) $(lint_python_code)
+
+.PHONY: pylint-qa
+pylint-qa: $(BUILT_SOURCES)
+	@test -n "$(PYLINT)" || { echo 'pylint' not found during configure; exit 1; }
 	cd $(top_srcdir)/qa && \
 	  PYTHONPATH=$(abs_top_srcdir) $(PYLINT) $(LINT_OPTS) \
 	  --rcfile  ../pylintrc $(patsubst qa/%.py,%,$(qa_scripts))
 
+.PHONY: pep8
+pep8: $(BUILT_SOURCES)
+	@test -n "$(PEP8)" || { echo 'pep8' not found during configure; exit 1; }
+	$(PEP8) --ignore='$(PEP8_IGNORE)' --exclude='$(PEP8_EXCLUDE)' \
+		--repeat $(pep8_python_code)
+
 .PHONY: hlint
 hlint: $(HS_BUILT_SRCS) htools/lint-hints.hs
+	@test -n "$(HLINT)" || { echo 'hlint' not found during configure; exit 1; }
 	if tty -s; then C="-c"; else C=""; fi; \
-	hlint --report=doc/hs-lint.html --cross $$C \
+	$(HLINT) --report=doc/hs-lint.html --cross $$C \
 	  --ignore "Use first" \
 	  --ignore "Use comparing" \
 	  --ignore "Use on" \
-	  --ignore "Use Control.Exception.catch" \
 	  --ignore "Reduce duplication" \
 	  --hint htools/lint-hints \
 	  $(filter-out htools/Ganeti/THH.hs,$(HS_LIB_SRCS))
@@ -1338,8 +1362,8 @@ hlint: $(HS_BUILT_SRCS) htools/lint-hints.hs
 # a dist hook rule for updating the vcs-version file; this is
 # hardcoded due to where it needs to build the file...
 dist-hook:
-	$(MAKE) regen-vcs-version && \
-	rm -f $(top_distdir)/vcs-version && \
+	$(MAKE) regen-vcs-version
+	rm -f $(top_distdir)/vcs-version
 	cp -p $(srcdir)/vcs-version $(top_distdir)
 
 # a distcheck hook rule for catching revision control directories
@@ -1465,7 +1489,8 @@ py-coverage: $(BUILT_SOURCES) $(python_tests)
 
 .PHONY: hs-coverage
 hs-coverage: $(haskell_tests) htools/hpc-htools
-	rm -f *.tix && $(MAKE) hs-check
+	rm -f *.tix
+	$(MAKE) hs-check
 	@mkdir_p@ $(COVERAGE_HS_DIR)
 	hpc combine $(HPCEXCL) test.tix hpc-htools.tix > coverage-htools.tix
 	hpc markup --destdir=$(COVERAGE_HS_DIR) coverage-htools.tix
diff --git a/NEWS b/NEWS
index 57b070466ba93f0d45f5448996bb2a7f7cd015b1..5925f4a93404ea0541e7cdf32aaa8b84d62bb823 100644
--- a/NEWS
+++ b/NEWS
@@ -2,10 +2,10 @@ News
 ====
 
 
-Version 2.6.0 beta1
+Version 2.6.0 beta2
 -------------------
 
-*(Released Wed, 23 Mar 2011)*
+*(Released Mon, 11 Jun 2012)*
 
 New features
 ~~~~~~~~~~~~
@@ -256,6 +256,10 @@ Python 2.7 is better supported, and after Ganeti 2.6 we will investigate
 whether to still support Python 2.4 or move to Python 2.6 as minimum
 required version.
 
+Support for Fedora has been slightly improved; the provided example
+init.d script should work better on it and the INSTALL file should
+document the needed dependencies.
+
 Internal changes
 ~~~~~~~~~~~~~~~~
 
@@ -291,6 +295,30 @@ verification. This will remove some rare but hard to diagnose errors in
 import-export.
 
 
+Version 2.6.0 beta1
+-------------------
+
+*(Released Wed, 23 May 2012)*
+
+First beta release of 2.6. The following changes were done from beta1 to
+beta2:
+
+- integrated patch for distributions without ``start-stop-daemon``
+- adapted example init.d script to work on Fedora
+- fixed log handling in Haskell daemons
+- adapted checks in the watcher for pycurl linked against libnss
+- add partial support for ``xl`` instead of ``xm`` for Xen
+- fixed a type issue in cluster verification
+- fixed ssconf handling in the Haskell code (was breaking confd in IPv6
+  clusters)
+
+Plus integrated fixes from the 2.5 branch:
+
+- fixed ``kvm-ifup`` to use ``/bin/bash``
+- fixed parallel build failures
+- KVM live migration when using a custom keymap
+
+
 Version 2.5.1
 -------------
 
diff --git a/configure.ac b/configure.ac
index f93920437f1df04211ae2d59d148392a9522164a..7732351ab2b159e95f7ac4e66ca257b1f5298281 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,7 +2,7 @@
 m4_define([gnt_version_major], [2])
 m4_define([gnt_version_minor], [6])
 m4_define([gnt_version_revision], [0])
-m4_define([gnt_version_suffix], [~beta1])
+m4_define([gnt_version_suffix], [~beta2])
 m4_define([gnt_version_full],
           m4_format([%d.%d.%d%s],
                     gnt_version_major, gnt_version_minor,
@@ -98,6 +98,19 @@ AC_ARG_WITH([xen-initrd],
   [xen_initrd="/boot/initrd-3-xenU"])
 AC_SUBST(XEN_INITRD, $xen_initrd)
 
+# --with-xen-cmd=...
+AC_ARG_WITH([xen-cmd],
+  [AS_HELP_STRING([--with-xen-cmd=CMD],
+    [Sets the xen cli interface command (default is xm)]
+  )],
+  [xen_cmd="$withval"],
+  [xen_cmd="xm"])
+AC_SUBST(XEN_CMD, $xen_cmd)
+
+if ! test "$XEN_CMD" = xl -o "$XEN_CMD" = xm; then
+  AC_MSG_ERROR([Unsupported xen command specified])
+fi
+
 # --with-kvm-kernel=...
 AC_ARG_WITH([kvm-kernel],
   [AS_HELP_STRING([--with-kvm-kernel=PATH],
@@ -374,6 +387,7 @@ if test -z "$PEP8"
 then
   AC_MSG_WARN([pep8 not found, checking code will not be complete])
 fi
+AM_CONDITIONAL([HAS_PEP8], [test "$PEP8"])
 
 # Check for socat
 AC_ARG_VAR(SOCAT, [socat path])
@@ -505,6 +519,14 @@ if test "$HADDOCK" && test "$HSCOLOUR"; then
 fi
 AC_SUBST(HTOOLS_APIDOC)
 
+# Check for hlint
+HLINT=no
+AC_ARG_VAR(HLINT, [hlint path])
+AC_PATH_PROG(HLINT, [hlint], [])
+if test -z "$HLINT"; then
+  AC_MSG_WARN([hlint not found, checking code will not be possible])
+fi
+
 fi # end if enable_htools, define automake conditions
 
 if test "$HTOOLS" != "yes" && test "$HS_CONFD" = "True"; then
@@ -515,6 +537,7 @@ fi
 AM_CONDITIONAL([WANT_HTOOLS], [test x$HTOOLS = xyes])
 AM_CONDITIONAL([WANT_HTOOLSTESTS], [test "x$GHC_PKG_QUICKCHECK" != x])
 AM_CONDITIONAL([WANT_HTOOLSAPIDOC], [test x$HTOOLS_APIDOC = xyes])
+AM_CONDITIONAL([HAS_HLINT], [test "$HLINT"])
 
 # Check for fakeroot
 AC_ARG_VAR(FAKEROOT_PATH, [fakeroot path])
diff --git a/doc/design-shared-storage.rst b/doc/design-shared-storage.rst
index 9f3a0ea1c317726e64d79a7706e7e7370d5bdc17..c1754764cda81d8c8c79bc8394b65d18d42759bc 100644
--- a/doc/design-shared-storage.rst
+++ b/doc/design-shared-storage.rst
@@ -47,7 +47,7 @@ Use cases
 We consider the following use cases:
 
 - A virtualization cluster with FibreChannel shared storage, mapping at
-  leaste one LUN per instance, accessible by the whole cluster.
+  least one LUN per instance, accessible by the whole cluster.
 - A virtualization cluster with instance images stored as files on an
   NFS server.
 - A virtualization cluster storing instance images on a Ceph volume.
diff --git a/doc/hooks.rst b/doc/hooks.rst
index 50e290d64a3c912fb715ff75968452077d8f4440..ca384d685cf45837d5056a07a1912001e6ef1cf1 100644
--- a/doc/hooks.rst
+++ b/doc/hooks.rst
@@ -8,10 +8,11 @@ Documents Ganeti version 2.6
 Introduction
 ------------
 
-
-In order to allow customisation of operations, ganeti runs scripts
-under ``/etc/ganeti/hooks`` based on certain rules.
-
+In order to allow customisation of operations, Ganeti runs scripts in
+sub-directories of ``@SYSCONFDIR@/ganeti/hooks``. These sub-directories
+are named ``$hook-$phase.d``, where ``$phase`` is either ``pre`` or
+``post`` and ``$hook`` matches the directory name given for a hook (e.g.
+``cluster-verify-post.d`` or ``node-add-pre.d``).
 
 This is similar to the ``/etc/network/`` structure present in Debian
 for network interface handling.
diff --git a/htools/Ganeti/HTools/QC.hs b/htools/Ganeti/HTools/QC.hs
index 0c1ff5adbaccf4be6d56e4eea5e07685d6352511..78b88a638c2c671c8ded6f8fae48f2ada36e274c 100644
--- a/htools/Ganeti/HTools/QC.hs
+++ b/htools/Ganeti/HTools/QC.hs
@@ -41,6 +41,7 @@ module Ganeti.HTools.QC
   , testCLI
   , testJSON
   , testLUXI
+  , testSsconf
   ) where
 
 import Test.QuickCheck
@@ -58,6 +59,7 @@ import qualified Data.IntMap as IntMap
 import qualified Ganeti.OpCodes as OpCodes
 import qualified Ganeti.Jobs as Jobs
 import qualified Ganeti.Luxi as Luxi
+import qualified Ganeti.Ssconf as Ssconf
 import qualified Ganeti.HTools.CLI as CLI
 import qualified Ganeti.HTools.Cluster as Cluster
 import qualified Ganeti.HTools.Container as Container
@@ -1731,3 +1733,16 @@ prop_Luxi_CallEncoding op =
 testSuite "LUXI"
           [ 'prop_Luxi_CallEncoding
           ]
+
+-- * Ssconf tests
+
+instance Arbitrary Ssconf.SSKey where
+  arbitrary = elements [minBound..maxBound]
+
+prop_Ssconf_filename key =
+  printTestCase "Key doesn't start with correct prefix" $
+    Ssconf.sSFilePrefix `isPrefixOf` Ssconf.keyToFilename (Just "") key
+
+testSuite "Ssconf"
+  [ 'prop_Ssconf_filename
+  ]
diff --git a/htools/Ganeti/Ssconf.hs b/htools/Ganeti/Ssconf.hs
index 5910cd1a3d00c3331af70c19845e722fdcca0e29..39a3d95dfaac6badae2dfdb5148ee17bb731fc3c 100644
--- a/htools/Ganeti/Ssconf.hs
+++ b/htools/Ganeti/Ssconf.hs
@@ -30,6 +30,8 @@ module Ganeti.Ssconf
   , sSKeyToRaw
   , sSKeyFromRaw
   , getPrimaryIPFamily
+  , keyToFilename
+  , sSFilePrefix
   ) where
 
 import Ganeti.THH
@@ -51,6 +53,10 @@ import Ganeti.HTools.Utils
 maxFileSize :: Int
 maxFileSize = 131072
 
+-- | ssconf file prefix, re-exported from Constants.
+sSFilePrefix :: FilePath
+sSFilePrefix = C.ssconfFileprefix
+
 $(declareSADT "SSKey"
   [ ("SSClusterName",          'C.ssClusterName)
   , ("SSClusterTags",          'C.ssClusterTags)
@@ -80,7 +86,8 @@ $(declareSADT "SSKey"
 keyToFilename :: Maybe FilePath     -- ^ Optional config path override
               -> SSKey              -- ^ ssconf key
               -> FilePath
-keyToFilename optpath key = fromMaybe C.dataDir optpath </> sSKeyToRaw key
+keyToFilename optpath key = fromMaybe C.dataDir optpath </>
+                            sSFilePrefix ++ sSKeyToRaw key
 
 -- | Runs an IO action while transforming any error into 'Bad'
 -- values. It also accepts an optional value to use in case the error
diff --git a/htools/test.hs b/htools/test.hs
index eedefd35ba6a021be43a6bfc62b9db9608241d86..6e434274bc11ab5b9af918395897fbba307400b4 100644
--- a/htools/test.hs
+++ b/htools/test.hs
@@ -125,6 +125,7 @@ allTests =
   , (fast, testCLI)
   , (fast, testJSON)
   , (fast, testLUXI)
+  , (fast, testSsconf)
   , (slow, testCluster)
   ]
 
diff --git a/lib/cmdlib.py b/lib/cmdlib.py
index b9cd7ad3dcacb7063cc712e4e39c81f8b775d117..34902d1270e914c3c08b4d2749e8458c0092dc3c 100644
--- a/lib/cmdlib.py
+++ b/lib/cmdlib.py
@@ -3152,6 +3152,8 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
 
     for instance in self.my_inst_names:
       inst_config = self.my_inst_info[instance]
+      if inst_config.admin_state == constants.ADMINST_OFFLINE:
+        i_offline += 1
 
       for nname in inst_config.all_nodes:
         if nname not in node_image:
@@ -3291,12 +3293,6 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
         non_primary_inst = set(nimg.instances).difference(nimg.pinst)
 
         for inst in non_primary_inst:
-          # FIXME: investigate best way to handle offline insts
-          if inst.admin_state == constants.ADMINST_OFFLINE:
-            if verbose:
-              feedback_fn("* Skipping offline instance %s" % inst.name)
-            i_offline += 1
-            continue
           test = inst in self.all_inst_info
           _ErrorIf(test, constants.CV_EINSTANCEWRONGNODE, inst,
                    "instance should not run on node %s", node_i.name)
@@ -5929,7 +5925,8 @@ class LUNodeSetParams(LogicalUnit):
       if mc_remaining < mc_should:
         raise errors.OpPrereqError("Not enough master candidates, please"
                                    " pass auto promote option to allow"
-                                   " promotion", errors.ECODE_STATE)
+                                   " promotion (--auto-promote or RAPI"
+                                   " auto_promote=True)", errors.ECODE_STATE)
 
     self.old_flags = old_flags = (node.master_candidate,
                                   node.drained, node.offline)
diff --git a/lib/constants.py b/lib/constants.py
index 5cd8be503bbc95a46239d15829f52581abfbacf1..b76863e9bf4030420282adcad027c60d7dded1e8 100644
--- a/lib/constants.py
+++ b/lib/constants.py
@@ -267,7 +267,10 @@ EXPORT_CONF_FILE = "config.ini"
 XEN_BOOTLOADER = _autoconf.XEN_BOOTLOADER
 XEN_KERNEL = _autoconf.XEN_KERNEL
 XEN_INITRD = _autoconf.XEN_INITRD
-XEN_CMD = "xm"
+XEN_CMD_XM = "xm"
+XEN_CMD_XL = "xl"
+# FIXME: This will be made configurable using hvparams in Ganeti 2.7
+XEN_CMD = _autoconf.XEN_CMD
 
 KVM_PATH = _autoconf.KVM_PATH
 KVM_KERNEL = _autoconf.KVM_KERNEL
@@ -1695,6 +1698,8 @@ RSS_DESCRIPTION = {
 MAX_NICS = 8
 MAX_DISKS = 16
 
+# SSCONF file prefix
+SSCONF_FILEPREFIX = "ssconf_"
 # SSCONF keys
 SS_CLUSTER_NAME = "cluster_name"
 SS_CLUSTER_TAGS = "cluster_tags"
diff --git a/lib/hypervisor/hv_kvm.py b/lib/hypervisor/hv_kvm.py
index 8327f98be7783cf33c801787ffffb162fdbb779d..2d6e23b7bbee04d0dd47a466908a59fe027ca99a 100644
--- a/lib/hypervisor/hv_kvm.py
+++ b/lib/hypervisor/hv_kvm.py
@@ -1,7 +1,7 @@
 #
 #
 
-# Copyright (C) 2008, 2009, 2010, 2011 Google Inc.
+# Copyright (C) 2008, 2009, 2010, 2011, 2012 Google Inc.
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -420,7 +420,7 @@ class KVMHypervisor(hv_base.BaseHypervisor):
   # a separate directory, called 'chroot-quarantine'.
   _CHROOT_QUARANTINE_DIR = _ROOT_DIR + "/chroot-quarantine"
   _DIRS = [_ROOT_DIR, _PIDS_DIR, _UIDS_DIR, _CTRL_DIR, _CONF_DIR, _NICS_DIR,
-           _CHROOT_DIR, _CHROOT_QUARANTINE_DIR]
+           _CHROOT_DIR, _CHROOT_QUARANTINE_DIR, _KEYMAP_DIR]
 
   PARAMETERS = {
     constants.HV_KERNEL_PATH: hv_base.OPT_FILE_CHECK,
@@ -944,6 +944,13 @@ class KVMHypervisor(hv_base.BaseHypervisor):
   def _GenerateKVMRuntime(self, instance, block_devices, startup_paused):
     """Generate KVM information to start an instance.
 
+    @attention: this function must not have any side-effects; for
+        example, it must not write to the filesystem, or read values
+        from the current system the are expected to differ between
+        nodes, since it is only run once at instance startup;
+        actions/kvm arguments that can vary between systems should be
+        done in L{_ExecuteKVMRuntime}
+
     """
     # pylint: disable=R0914,R0915
     _, v_major, v_min, _ = self._GetKVMVersion()
@@ -1099,16 +1106,6 @@ class KVMHypervisor(hv_base.BaseHypervisor):
     elif vnc_bind_address:
       kvm_cmd.extend(["-usbdevice", constants.HT_MOUSE_TABLET])
 
-    keymap = hvp[constants.HV_KEYMAP]
-    if keymap:
-      keymap_path = self._InstanceKeymapFile(instance.name)
-      # If a keymap file is specified, KVM won't use its internal defaults. By
-      # first including the "en-us" layout, an error on loading the actual
-      # layout (e.g. because it can't be found) won't lead to a non-functional
-      # keyboard. A keyboard with incorrect keys is still better than none.
-      utils.WriteFile(keymap_path, data="include en-us\ninclude %s\n" % keymap)
-      kvm_cmd.extend(["-k", keymap_path])
-
     if vnc_bind_address:
       if netutils.IP4Address.IsValid(vnc_bind_address):
         if instance.network_port > constants.VNC_BASE_PORT:
@@ -1144,6 +1141,8 @@ class KVMHypervisor(hv_base.BaseHypervisor):
 
       kvm_cmd.extend(["-vnc", vnc_arg])
     elif spice_bind:
+      # FIXME: this is wrong here; the iface ip address differs
+      # between systems, so it should be done in _ExecuteKVMRuntime
       if netutils.IsValidInterface(spice_bind):
         # The user specified a network interface, we have to figure out the IP
         # address.
@@ -1314,7 +1313,7 @@ class KVMHypervisor(hv_base.BaseHypervisor):
       raise errors.HypervisorError("Failed to start instance %s" % name)
 
   def _ExecuteKVMRuntime(self, instance, kvm_runtime, incoming=None):
-    """Execute a KVM cmd, after completing it with some last minute data
+    """Execute a KVM cmd, after completing it with some last minute data.
 
     @type incoming: tuple of strings
     @param incoming: (target_host_ip, port)
@@ -1345,6 +1344,16 @@ class KVMHypervisor(hv_base.BaseHypervisor):
     if security_model == constants.HT_SM_USER:
       kvm_cmd.extend(["-runas", conf_hvp[constants.HV_SECURITY_DOMAIN]])
 
+    keymap = conf_hvp[constants.HV_KEYMAP]
+    if keymap:
+      keymap_path = self._InstanceKeymapFile(name)
+      # If a keymap file is specified, KVM won't use its internal defaults. By
+      # first including the "en-us" layout, an error on loading the actual
+      # layout (e.g. because it can't be found) won't lead to a non-functional
+      # keyboard. A keyboard with incorrect keys is still better than none.
+      utils.WriteFile(keymap_path, data="include en-us\ninclude %s\n" % keymap)
+      kvm_cmd.extend(["-k", keymap_path])
+
     # We have reasons to believe changing something like the nic driver/type
     # upon migration won't exactly fly with the instance kernel, so for nic
     # related parameters we'll use up_hvp
@@ -1538,7 +1547,7 @@ class KVMHypervisor(hv_base.BaseHypervisor):
     @type text: string
     @param text: output of kvm --help
     @return: (version, v_maj, v_min, v_rev)
-    @raise L{errors.HypervisorError}: when the KVM version cannot be retrieved
+    @raise errors.HypervisorError: when the KVM version cannot be retrieved
 
     """
     match = cls._VERSION_RE.search(text.splitlines()[0])
@@ -1559,7 +1568,7 @@ class KVMHypervisor(hv_base.BaseHypervisor):
     """Return the installed KVM version.
 
     @return: (version, v_maj, v_min, v_rev)
-    @raise L{errors.HypervisorError}: when the KVM version cannot be retrieved
+    @raise errors.HypervisorError: when the KVM version cannot be retrieved
 
     """
     result = utils.RunCmd([constants.KVM_PATH, "--help"])
diff --git a/lib/hypervisor/hv_xen.py b/lib/hypervisor/hv_xen.py
index 50abe1a0f41ae639916aeac0eadf064447485bac..a757617e15902ae986764a29bbf9c48cd39b8291 100644
--- a/lib/hypervisor/hv_xen.py
+++ b/lib/hypervisor/hv_xen.py
@@ -537,13 +537,19 @@ class XenHypervisor(hv_base.BaseHypervisor):
                                    " %s, cannot migrate" % (target, port))
 
     # FIXME: migrate must be upgraded for transitioning to "xl" (xen 4.1).
-    #  -l doesn't exist anymore
-    #  -p doesn't exist anymore
-    #  -C config_file must be passed
+    #        This should be reworked in Ganeti 2.7
     #  ssh must recognize the key of the target host for the migration
-    args = [constants.XEN_CMD, "migrate", "-p", "%d" % port]
-    if live:
-      args.append("-l")
+    args = [constants.XEN_CMD, "migrate"]
+    if constants.XEN_CMD == constants.XEN_CMD_XM:
+      args.extend(["-p", "%d" % port])
+      if live:
+        args.append("-l")
+    elif constants.XEN_CMD == constants.XEN_CMD_XL:
+      args.extend(["-C", self._ConfigFileName(instance.name)])
+    else:
+      raise errors.HypervisorError("Unsupported xen command: %s" %
+                                   constants.XEN_CMD)
+
     args.extend([instance.name, target])
     result = utils.RunCmd(args)
     if result.failed:
diff --git a/lib/rapi/client.py b/lib/rapi/client.py
index 15e61f4a896ae57eeb52620515187ae983d97a56..b5a4df53013912f5e5feca36fbb2920d26e2eb0c 100644
--- a/lib/rapi/client.py
+++ b/lib/rapi/client.py
@@ -256,6 +256,9 @@ def GenericCurlConfig(verbose=False, use_signal=False,
     lcsslver = sslver.lower()
     if lcsslver.startswith("openssl/"):
       pass
+    elif lcsslver.startswith("nss/"):
+      # TODO: investigate compatibility beyond a simple test
+      pass
     elif lcsslver.startswith("gnutls/"):
       if capath:
         raise Error("cURL linked against GnuTLS has no support for a"
diff --git a/lib/ssconf.py b/lib/ssconf.py
index babd148e675650a482a1b8649505b6cccf3e6873..2399d81065325a25a6524a45a082f075881f73e1 100644
--- a/lib/ssconf.py
+++ b/lib/ssconf.py
@@ -1,7 +1,7 @@
 #
 #
 
-# Copyright (C) 2006, 2007, 2008, 2010 Google Inc.
+# Copyright (C) 2006, 2007, 2008, 2010, 2011, 2012 Google Inc.
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -273,7 +273,6 @@ class SimpleStore(object):
     - keys are restricted to predefined values
 
   """
-  _SS_FILEPREFIX = "ssconf_"
   _VALID_KEYS = (
     constants.SS_CLUSTER_NAME,
     constants.SS_CLUSTER_TAGS,
@@ -314,7 +313,7 @@ class SimpleStore(object):
       raise errors.ProgrammerError("Invalid key requested from SSConf: '%s'"
                                    % str(key))
 
-    filename = self._cfg_dir + "/" + self._SS_FILEPREFIX + key
+    filename = self._cfg_dir + "/" + constants.SSCONF_FILEPREFIX + key
     return filename
 
   def _ReadFile(self, key, default=None):
diff --git a/qa/ganeti-qa.py b/qa/ganeti-qa.py
index 3440122aacd4e0e3f3bf0d66ab0ca2b1ce6b32bb..e7ad3b81f7813d2a94d35f882d3c869632b4aadf 100755
--- a/qa/ganeti-qa.py
+++ b/qa/ganeti-qa.py
@@ -464,7 +464,8 @@ def RunQa():
         for use_client in [True, False]:
           rapi_instance = RunTest(qa_rapi.TestRapiInstanceAdd, pnode,
                                   use_client)
-          RunCommonInstanceTests(rapi_instance)
+          if qa_config.TestEnabled("instance-plain-rapi-common-tests"):
+            RunCommonInstanceTests(rapi_instance)
           RunTest(qa_rapi.TestRapiInstanceRemove, rapi_instance, use_client)
           del rapi_instance
 
@@ -553,7 +554,12 @@ def main():
 
   qa_config.Load(config_file)
 
-  qa_utils.StartMultiplexer(qa_config.GetMasterNode()["primary"])
+  primary = qa_config.GetMasterNode()["primary"]
+  qa_utils.StartMultiplexer(primary)
+  print ("SSH command for primary node: %s" %
+         utils.ShellQuoteArgs(qa_utils.GetSSHCommand(primary, "")))
+  print ("SSH command for other nodes: %s" %
+         utils.ShellQuoteArgs(qa_utils.GetSSHCommand("NODE", "")))
   try:
     RunQa()
   finally:
diff --git a/qa/qa-sample.json b/qa/qa-sample.json
index d2c5f1a9a33bec100404f210b0498d88db085316..0769d0848e2a0e47470927f3f2c2db37378d9c5c 100644
--- a/qa/qa-sample.json
+++ b/qa/qa-sample.json
@@ -27,6 +27,9 @@
   "# Script to check instance status": null,
   "instance-check": null,
 
+  "# Regular expression to ignore existing tags": null,
+  "ignore-tags-re": null,
+
   "nodes": [
     {
       "# Master node": null,
@@ -42,10 +45,16 @@
 
   "instances": [
     {
-      "name": "xen-test-inst1"
+      "name": "xen-test-inst1",
+
+      "# Static MAC address": null,
+      "#nic.mac/0": "AA:00:00:11:11:11"
     },
     {
-      "name": "xen-test-inst2"
+      "name": "xen-test-inst2",
+
+      "# Static MAC address": null,
+      "#nic.mac/0": "AA:00:00:22:22:22"
     }
   ],
 
@@ -106,6 +115,7 @@
     "instance-add-plain-disk": true,
     "instance-add-drbd-disk": true,
     "instance-convert-disk": true,
+    "instance-plain-rapi-common-tests": true,
 
     "instance-export": true,
     "instance-failover": true,
diff --git a/qa/qa_config.py b/qa/qa_config.py
index 48a8a11fa4ccf6128ace5d88a5b141e28b50d501..2cf8d500a6e51c672af8949d1a321a8bf0e844b6 100644
--- a/qa/qa_config.py
+++ b/qa/qa_config.py
@@ -1,7 +1,7 @@
 #
 #
 
-# Copyright (C) 2007, 2011 Google Inc.
+# Copyright (C) 2007, 2011, 2012 Google Inc.
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -154,6 +154,13 @@ def GetInstanceCheckScript():
   return cfg.get(_INSTANCE_CHECK_KEY, None)
 
 
+def GetInstanceNicMac(inst, default=None):
+  """Returns MAC address for instance's network interface.
+
+  """
+  return inst.get("nic.mac/0", default)
+
+
 def GetMasterNode():
   return cfg["nodes"][0]
 
diff --git a/qa/qa_instance.py b/qa/qa_instance.py
index e6fd4f4cb99da85657eea538640724e22c370624..65af80e1c45eb9c6dd0ce3b46c6327914b78a594 100644
--- a/qa/qa_instance.py
+++ b/qa/qa_instance.py
@@ -42,7 +42,7 @@ def _GetDiskStatePath(disk):
   return "/sys/block/%s/device/state" % disk
 
 
-def _GetGenericAddParameters():
+def _GetGenericAddParameters(inst, force_mac=None):
   params = ["-B"]
   params.append("%s=%s,%s=%s" % (constants.BE_MINMEM,
                                  qa_config.get(constants.BE_MINMEM),
@@ -50,6 +50,15 @@ def _GetGenericAddParameters():
                                  qa_config.get(constants.BE_MAXMEM)))
   for idx, size in enumerate(qa_config.get("disk")):
     params.extend(["--disk", "%s:size=%s" % (idx, size)])
+
+  # Set static MAC address if configured
+  if force_mac:
+    nic0_mac = force_mac
+  else:
+    nic0_mac = qa_config.GetInstanceNicMac(inst)
+  if nic0_mac:
+    params.extend(["--net", "0:mac=%s" % nic0_mac])
+
   return params
 
 
@@ -60,7 +69,7 @@ def _DiskTest(node, disk_template):
             "--os-type=%s" % qa_config.get("os"),
             "--disk-template=%s" % disk_template,
             "--node=%s" % node] +
-           _GetGenericAddParameters())
+           _GetGenericAddParameters(instance))
     cmd.append(instance["name"])
 
     AssertCommand(cmd)
@@ -412,11 +421,10 @@ def TestInstanceImport(newinst, node, expnode, name):
   cmd = (["gnt-backup", "import",
           "--disk-template=plain",
           "--no-ip-check",
-          "--net", "0:mac=generate",
           "--src-node=%s" % expnode["primary"],
           "--src-dir=%s/%s" % (constants.EXPORT_DIR, name),
           "--node=%s" % node["primary"]] +
-         _GetGenericAddParameters())
+         _GetGenericAddParameters(newinst, force_mac=constants.VALUE_GENERATE))
   cmd.append(newinst["name"])
   AssertCommand(cmd)
 
diff --git a/qa/qa_rapi.py b/qa/qa_rapi.py
index e265764595c9f421f484835721c3c5a07d344b64..2a3ea1ec16aac1dc90c10044f26496b9ecd79d8b 100644
--- a/qa/qa_rapi.py
+++ b/qa/qa_rapi.py
@@ -25,6 +25,8 @@
 
 import tempfile
 import random
+import re
+import itertools
 
 from ganeti import utils
 from ganeti import constants
@@ -416,6 +418,18 @@ def TestNode(node):
     ])
 
 
+def _FilterTags(seq):
+  """Removes unwanted tags from a sequence.
+
+  """
+  ignore_re = qa_config.get("ignore-tags-re", None)
+
+  if ignore_re:
+    return itertools.ifilterfalse(re.compile(ignore_re).match, seq)
+  else:
+    return seq
+
+
 def TestTags(kind, name, tags):
   """Tests .../tags resources.
 
@@ -432,7 +446,7 @@ def TestTags(kind, name, tags):
     raise errors.ProgrammerError("Unknown tag kind")
 
   def _VerifyTags(data):
-    AssertEqual(sorted(tags), sorted(data))
+    AssertEqual(sorted(tags), sorted(_FilterTags(data)))
 
   queryargs = "&".join("tag=%s" % i for i in tags)
 
@@ -539,7 +553,11 @@ def TestRapiInstanceAdd(node, use_client):
   try:
     disk_sizes = [utils.ParseUnit(size) for size in qa_config.get("disk")]
     disks = [{"size": size} for size in disk_sizes]
-    nics = [{}]
+    nic0_mac = qa_config.GetInstanceNicMac(instance,
+                                           default=constants.VALUE_GENERATE)
+    nics = [{
+      constants.INIC_MAC: nic0_mac,
+      }]
 
     beparams = {
       constants.BE_MAXMEM: utils.ParseUnit(qa_config.get(constants.BE_MAXMEM)),
diff --git a/qa/qa_utils.py b/qa/qa_utils.py
index 7330cbc867e9266ac04b9b0f5169ea9144a58e59..a91cc94b4b366fb66456b5087272e46d6699b3ba 100644
--- a/qa/qa_utils.py
+++ b/qa/qa_utils.py
@@ -1,7 +1,7 @@
 #
 #
 
-# Copyright (C) 2007, 2011 Google Inc.
+# Copyright (C) 2007, 2011, 2012 Google Inc.
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -191,7 +191,7 @@ def AssertCommand(cmd, fail=False, node=None):
   return rcode
 
 
-def GetSSHCommand(node, cmd, strict=True, opts=None, tty=True):
+def GetSSHCommand(node, cmd, strict=True, opts=None, tty=None):
   """Builds SSH command to be executed.
 
   @type node: string
@@ -203,11 +203,14 @@ def GetSSHCommand(node, cmd, strict=True, opts=None, tty=True):
   @param strict: whether to enable strict host key checking
   @type opts: list
   @param opts: list of additional options
-  @type tty: Bool
-  @param tty: If we should use tty
+  @type tty: boolean or None
+  @param tty: if we should use tty; if None, will be auto-detected
 
   """
-  args = ["ssh", "-oEscapeChar=none", "-oBatchMode=yes", "-l", "root"]
+  args = ["ssh", "-oEscapeChar=none", "-oBatchMode=yes", "-lroot"]
+
+  if tty is None:
+    tty = sys.stdout.isatty()
 
   if tty:
     args.append("-t")
@@ -232,11 +235,15 @@ def GetSSHCommand(node, cmd, strict=True, opts=None, tty=True):
   return args
 
 
-def StartLocalCommand(cmd, **kwargs):
+def StartLocalCommand(cmd, _nolog_opts=False, **kwargs):
   """Starts a local command.
 
   """
-  print "Command: %s" % utils.ShellQuoteArgs(cmd)
+  if _nolog_opts:
+    pcmd = [i for i in cmd if not i.startswith("-")]
+  else:
+    pcmd = cmd
+  print "Command: %s" % utils.ShellQuoteArgs(pcmd)
   return subprocess.Popen(cmd, shell=False, **kwargs)
 
 
@@ -244,7 +251,8 @@ def StartSSH(node, cmd, strict=True):
   """Starts SSH.
 
   """
-  return StartLocalCommand(GetSSHCommand(node, cmd, strict=strict))
+  return StartLocalCommand(GetSSHCommand(node, cmd, strict=strict),
+                           _nolog_opts=True)
 
 
 def StartMultiplexer(node):
@@ -275,7 +283,7 @@ def CloseMultiplexers():
     utils.RemoveFile(sname)
 
 
-def GetCommandOutput(node, cmd, tty=True):
+def GetCommandOutput(node, cmd, tty=None):
   """Returns the output of a command executed on the given node.
 
   """
diff --git a/tools/lvmstrap b/tools/lvmstrap
index 6512477c9c942ae7619f4ab06579b5a072480f49..205eaeb889d0ede07b389c418df2795f9b45004c 100755
--- a/tools/lvmstrap
+++ b/tools/lvmstrap
@@ -1,7 +1,7 @@
 #!/usr/bin/python
 #
 
-# Copyright (C) 2006, 2007, 2011 Google Inc.
+# Copyright (C) 2006, 2007, 2011, 2012 Google Inc.
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -336,7 +336,7 @@ def CheckSysDev(name, devnum):
 
   @param name: the device name, e.g. 'sda'
   @param devnum: the device number, e.g. 0x803 (2051 in decimal) for sda3
-  @raises L{SysconfigError}: in case of failure of the check
+  @raises SysconfigError: in case of failure of the check
 
   """
   path = "/dev/%s" % name