diff --git a/Makefile.am b/Makefile.am
index 7a06d127fffcca9e1da5a912e0824be76f7e6d57..748704437d726ccd77b41097d081a5c63088f195 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1705,6 +1705,7 @@ HLINT_EXCLUDES = src/Ganeti/THH.hs test/hs/hpc-htools.hs
 .PHONY: hlint
 hlint: $(HS_BUILT_SRCS) src/lint-hints.hs
 	@test -n "$(HLINT)" || { echo 'hlint' not found during configure; exit 1; }
+	@rm -f doc/hs-lint.html
 	if tty -s; then C="-c"; else C=""; fi; \
 	$(HLINT) --utf8 --report=doc/hs-lint.html --cross $$C \
 	  --ignore "Use first" \
@@ -1713,6 +1714,9 @@ hlint: $(HS_BUILT_SRCS) src/lint-hints.hs
 	  --ignore "Reduce duplication" \
 	  --hint src/lint-hints \
 	  $(filter-out $(HLINT_EXCLUDES),$(HS_LIBTEST_SRCS) $(HS_PROG_SRCS))
+	@if [ ! -f doc/hs-lint.html ]; then \
+	  echo "All good" > doc/hs-lint.html; \
+	fi
 
 # a dist hook rule for updating the vcs-version file; this is
 # hardcoded due to where it needs to build the file...
diff --git a/NEWS b/NEWS
index 30b3a00ab6df3f5c78ba3fb3d39f625916e8bba7..568f37653a92530e8b9153a4d5f06860a3018736 100644
--- a/NEWS
+++ b/NEWS
@@ -5,7 +5,7 @@ News
 Version 2.7.0 beta1
 -------------------
 
-*(unreleased)*
+*(Released Wed, 6 Mar 2013)*
 
 - ``gnt-instance batch-create`` has been changed to use the bulk create
   opcode from Ganeti. This lead to incompatible changes in the format of
@@ -82,6 +82,9 @@ Version 2.7.0 beta1
 - New external storage backend, to allow managing arbitrary storage
   systems external to the cluster. See
   :manpage:`ganeti-extstorage-interface(7)`.
+- Instance renames of LVM-based instances will now update the LV tags
+  (which can be used to recover the instance-to-LV mapping in case of
+  emergencies)
 
 
 Version 2.6.2
diff --git a/README b/README
index 67d5d48f9feee1dc6f55c0447a636eaeaeebffe9..20282ee1da350a2e3e36877e2f7379b1502ea096 100644
--- a/README
+++ b/README
@@ -1,4 +1,4 @@
-Ganeti 2.6
+Ganeti 2.7
 ==========
 
 For installation instructions, read the INSTALL and the doc/install.rst
diff --git a/configure.ac b/configure.ac
index 2c93bfc6347b018e7d1adf3d1b3a2368e071a5c6..0683f5fcb8b6465e1234f628ee11bdd760434391 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,8 +1,8 @@
 # Configure script for Ganeti
 m4_define([gnt_version_major], [2])
-m4_define([gnt_version_minor], [6])
-m4_define([gnt_version_revision], [2])
-m4_define([gnt_version_suffix], [])
+m4_define([gnt_version_minor], [7])
+m4_define([gnt_version_revision], [0])
+m4_define([gnt_version_suffix], [~beta1])
 m4_define([gnt_version_full],
           m4_format([%d.%d.%d%s],
                     gnt_version_major, gnt_version_minor,
diff --git a/devel/upload b/devel/upload
index efe00217e35fee13476f82a75b708559b8b612b9..8e6cb675fb06a5b4212c26d1c23e2a9c459f383c 100755
--- a/devel/upload
+++ b/devel/upload
@@ -1,6 +1,6 @@
 #!/bin/bash
 
-# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2012 Google Inc.
+# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2012, 2013 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
@@ -25,7 +25,7 @@
 # $prefix/lib/python2.4/site-packages/ganeti and the command line utils to
 # $prefix/sbin. It needs passwordless root login to the nodes.
 
-set -e
+set -e -u
 
 usage() {
   echo "Usage: $0 [--no-restart] [--no-cron] [--no-debug] hosts..." >&2
@@ -92,6 +92,7 @@ make $make_args install DESTDIR="$TXD"
 # fixed; we can read the prefix vars/etc.
 PREFIX="$(echo @PREFIX@ | $SED)"
 SYSCONFDIR="$(echo @SYSCONFDIR@ | $SED)"
+LIBDIR="$(echo @LIBDIR@ | $SED)"
 PKGLIBDIR="$(echo @PKGLIBDIR@ | $SED)"
 
 # copy additional needed files
diff --git a/doc/design-draft.rst b/doc/design-draft.rst
index 36b052abf02688d5d739575ac05a48d8a62448fa..581a2d43bddcc83f6b0c1ae670ed4dfdc1fbe539 100644
--- a/doc/design-draft.rst
+++ b/doc/design-draft.rst
@@ -2,7 +2,7 @@
 Design document drafts
 ======================
 
-.. Last updated for Ganeti 2.6
+.. Last updated for Ganeti 2.7
 
 .. toctree::
    :maxdepth: 2
diff --git a/doc/hooks.rst b/doc/hooks.rst
index 956ad303616537b29634a25338f0b7bb0ab808cd..5ccf93575d39ea9795ebbc00775fd46c9fb9b157 100644
--- a/doc/hooks.rst
+++ b/doc/hooks.rst
@@ -1,7 +1,7 @@
 Ganeti customisation using hooks
 ================================
 
-Documents Ganeti version 2.6
+Documents Ganeti version 2.7
 
 .. contents::
 
@@ -224,7 +224,7 @@ Adds a network to the cluster.
 
 :directory: network-add
 :env. vars: NETWORK_NAME, NETWORK_SUBNET, NETWORK_GATEWAY, NETWORK_SUBNET6,
-            NETWORK_GATEWAY6, NETWORK_TYPE, NETWORK_MAC_PREFIX, NETWORK_TAGS
+            NETWORK_GATEWAY6, NETWORK_MAC_PREFIX, NETWORK_TAGS
 :pre-execution: master node
 :post-execution: master node
 
@@ -247,7 +247,7 @@ Connects a network to a nodegroup.
 :env. vars: GROUP_NAME, NETWORK_NAME,
             GROUP_NETWORK_MODE, GROUP_NETWORK_LINK,
             NETWORK_SUBNET, NETWORK_GATEWAY, NETWORK_SUBNET6,
-            NETWORK_GATEWAY6, NETWORK_TYPE, NETWORK_MAC_PREFIX, NETWORK_TAGS
+            NETWORK_GATEWAY6, NETWORK_MAC_PREFIX, NETWORK_TAGS
 :pre-execution: nodegroup nodes
 :post-execution: nodegroup nodes
 
@@ -261,7 +261,7 @@ Disconnects a network from a nodegroup.
 :env. vars: GROUP_NAME, NETWORK_NAME,
             GROUP_NETWORK_MODE, GROUP_NETWORK_LINK,
             NETWORK_SUBNET, NETWORK_GATEWAY, NETWORK_SUBNET6,
-            NETWORK_GATEWAY6, NETWORK_TYPE, NETWORK_MAC_PREFIX, NETWORK_TAGS
+            NETWORK_GATEWAY6, NETWORK_MAC_PREFIX, NETWORK_TAGS
 :pre-execution: nodegroup nodes
 :post-execution: nodegroup nodes
 
@@ -273,7 +273,7 @@ Modifies a network.
 
 :directory: network-modify
 :env. vars: NETWORK_NAME, NETWORK_SUBNET, NETWORK_GATEWAY, NETWORK_SUBNET6,
-            NETWORK_GATEWAY6, NETWORK_TYPE, NETWORK_MAC_PREFIX, NETWORK_TAGS
+            NETWORK_GATEWAY6, NETWORK_MAC_PREFIX, NETWORK_TAGS
 :pre-execution: master node
 :post-execution: master node
 
@@ -286,11 +286,19 @@ INSTANCE_NAME, INSTANCE_PRIMARY, INSTANCE_SECONDARY,
 INSTANCE_OS_TYPE, INSTANCE_DISK_TEMPLATE, INSTANCE_MEMORY,
 INSTANCE_DISK_SIZES, INSTANCE_VCPUS, INSTANCE_NIC_COUNT,
 INSTANCE_NICn_IP, INSTANCE_NICn_BRIDGE, INSTANCE_NICn_MAC,
+INSTANCE_NICn_NETWORK,
+INSTANCE_NICn_NETWORK_UUID, INSTANCE_NICn_NETWORK_SUBNET,
+INSTANCE_NICn_NETWORK_GATEWAY, INSTANCE_NICn_NETWORK_SUBNET6,
+INSTANCE_NICn_NETWORK_GATEWAY6, INSTANCE_NICn_NETWORK_MAC_PREFIX,
 INSTANCE_DISK_COUNT, INSTANCE_DISKn_SIZE, INSTANCE_DISKn_MODE.
 
 The INSTANCE_NICn_* and INSTANCE_DISKn_* variables represent the
 properties of the *n* -th NIC and disk, and are zero-indexed.
 
+The INSTANCE_NICn_NETWORK_* variables are only passed if a NIC's network
+parameter is set (that is if the NIC is associated to a network defined
+via ``gnt-network``)
+
 
 OP_INSTANCE_CREATE
 ++++++++++++++++++
@@ -383,7 +391,7 @@ and secondary before migration.
 
 :directory: instance-migrate
 :env. vars: MIGRATE_LIVE, MIGRATE_CLEANUP, OLD_PRIMARY, OLD_SECONDARY, NEW_PRIMARY, NEW_SECONDARY
-:pre-execution: master node, secondary node
+:pre-execution: master node, primary and secondary nodes
 :post-execution: master node, primary and secondary nodes
 
 
diff --git a/doc/iallocator.rst b/doc/iallocator.rst
index 633d5586da9c62edc2e2bdf763871e83365a9301..957c2bb78a429d083a1c2a18203401422da5e46b 100644
--- a/doc/iallocator.rst
+++ b/doc/iallocator.rst
@@ -1,7 +1,7 @@
 Ganeti automatic instance allocation
 ====================================
 
-Documents Ganeti version 2.6
+Documents Ganeti version 2.7
 
 .. contents::
 
diff --git a/doc/rapi.rst b/doc/rapi.rst
index f70f8ca1acb1ba4ae8ee2b15eea5da1fa0928fde..11c64c15ae087cb322587d78e08ca37a1f7f7710 100644
--- a/doc/rapi.rst
+++ b/doc/rapi.rst
@@ -698,7 +698,6 @@ Example::
         'name': 'nat',
         'network': '10.0.0.0/28',
         'network6': None,
-        'network_type': 'private',
         'reserved_count': 3,
         'tags': ['nfdhcpd'],
         …
diff --git a/doc/security.rst b/doc/security.rst
index 70d1d9556ac584c1e7fb46e49d4dd7c3b918ac98..4b4e9765d2a3a3f25d12c4a41c8a93c1d7d92683 100644
--- a/doc/security.rst
+++ b/doc/security.rst
@@ -1,7 +1,7 @@
 Security in Ganeti
 ==================
 
-Documents Ganeti version 2.6
+Documents Ganeti version 2.7
 
 Ganeti was developed to run on internal, trusted systems. As such, the
 security model is all-or-nothing.
@@ -50,10 +50,38 @@ on this node; the RPC method will run only:
   drbd devices, start/stop instances, etc;
 - run well-defined SSH commands on other nodes in the cluster
 - scripts under the ``/etc/ganeti/hooks`` directory
+- scripts under the ``/etc/ganeti/restricted-commands`` directory, if
+  this feature has been enabled at build time (see below)
 
 It is therefore important to make sure that the contents of the
-``/etc/ganeti/hooks`` directory is supervised and only trusted sources
-can populate it.
+``/etc/ganeti/hooks`` and ``/etc/ganeti/restricted-commands``
+directories are supervised and only trusted sources can populate them.
+
+Restricted commands
+~~~~~~~~~~~~~~~~~~~
+
+The restricted commands feature is new in Ganeti 2.7. It enables the
+administrator to run any commands in the
+``/etc/ganeti/restricted-commands`` directory, if the feature has been
+enabled at build time, subject to the following restrictions:
+
+- No parameters may be passed
+- No absolute or relative path may be passed, only a filename
+- The ``/etc/ganeti/restricted-commands`` directory must
+  be owned by root:root and have mode 0755 or stricter
+- Executables must be regular files or symlinks, and must be executable
+  by root:root
+
+Note that it's not possible to list the contents of the directory, and
+there is an intentional delay when trying to execute a non-existing
+command (to slow-down dictionary attacks).
+
+Since for Ganeti itself this functionality is not needed, and is only
+provided as a way to help administrate or recover nodes, it is a local
+site decision whether to enable or not the restricted commands feature.
+
+By default, this feature is disabled.
+
 
 Cluster issues
 --------------
@@ -94,6 +122,25 @@ before serving requests. This permission-based protection is documented
 and works on Linux, but is not-portable; however, Ganeti doesn't work on
 non-Linux system at the moment.
 
+Conf daemon
+-----------
+
+In Ganeti 2.7, the ``confd`` daemon (if enabled at build time), serves
+both network-originated queries (about the static configuration) and
+local (UNIX socket) queries (about the run-time configuration; answering
+these means talking to other cluster nodes, which makes use of the
+internal RPC SSL certificate). This makes it a bit more sensitive to
+bugs (a remote attacker could get direct access to the intra-cluster
+RPC), so to harden security it's recommended to:
+
+- disable confd at build time if it's not needed in your setup
+- otherwise, configure Ganeti (at build time) to use separate users, so
+  that the confd daemon doesn't also have access to the server SSL/TLS
+  certificates
+
+It is planned to split the two functionalities (local/remote querying)
+of confd into two separate daemons in a future Ganeti version.
+
 Remote API
 ----------
 
diff --git a/doc/virtual-cluster.rst b/doc/virtual-cluster.rst
index e2c0798d6a838f299bc733490221cd03cd0af2a3..54abd04dfc27af0c827a5bc5a235db6ef071dc4b 100644
--- a/doc/virtual-cluster.rst
+++ b/doc/virtual-cluster.rst
@@ -1,7 +1,7 @@
 Virtual cluster support
 =======================
 
-Documents Ganeti version 2.6
+Documents Ganeti version 2.7
 
 .. contents::
 
diff --git a/lib/backend.py b/lib/backend.py
index b6918c87fbb2e475fc272613b9af4ad592e012c7..b7dd2ab3aa383c390e9eea7f658c27bda566103c 100644
--- a/lib/backend.py
+++ b/lib/backend.py
@@ -2498,8 +2498,14 @@ def OSEnvironment(instance, inst_os, debug=0):
       result["NIC_%d_BRIDGE" % idx] = nic.nicparams[constants.NIC_LINK]
     if nic.nicparams[constants.NIC_LINK]:
       result["NIC_%d_LINK" % idx] = nic.nicparams[constants.NIC_LINK]
-    if nic.network:
-      result["NIC_%d_NETWORK" % idx] = nic.network
+    if nic.netinfo:
+      nobj = objects.Network.FromDict(nic.netinfo)
+      result.update(nobj.HooksDict("NIC_%d_" % idx))
+    elif nic.network:
+      # FIXME: broken network reference: the instance NIC specifies a network,
+      # but the relevant network entry was not in the config. This should be
+      # made impossible.
+      result["INSTANCE_NIC%d_NETWORK" % idx] = nic.network
     if constants.HV_NIC_TYPE in instance.hvparams:
       result["NIC_%d_FRONTEND_TYPE" % idx] = \
         instance.hvparams[constants.HV_NIC_TYPE]
diff --git a/lib/cli.py b/lib/cli.py
index 950578eea79e55e9f79db1d1cd14eb6f535a42e4..c82de3355660f2fafcfb6fb16db638f5672694c7 100644
--- a/lib/cli.py
+++ b/lib/cli.py
@@ -1,7 +1,7 @@
 #
 #
 
-# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 Google Inc.
+# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 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
@@ -117,7 +117,6 @@ __all__ = [
   "NET_OPT",
   "NETWORK_OPT",
   "NETWORK6_OPT",
-  "NETWORK_TYPE_OPT",
   "NEW_CLUSTER_CERT_OPT",
   "NEW_CLUSTER_DOMAIN_SECRET_OPT",
   "NEW_CONFD_HMAC_KEY_OPT",
@@ -1527,10 +1526,6 @@ REMOVE_RESERVED_IPS_OPT = cli_option("--remove-reserved-ips",
                                      help="Comma-delimited list of"
                                      " reserved IPs to remove")
 
-NETWORK_TYPE_OPT = cli_option("--network-type",
-                              action="store", default=None, dest="network_type",
-                              help="Network type: private, public, None")
-
 NETWORK6_OPT = cli_option("--network6",
                           action="store", default=None, dest="network6",
                           help="IP network in CIDR notation")
@@ -2302,8 +2297,12 @@ def FormatError(err):
   elif isinstance(err, errors.ParameterError):
     obuf.write("Failure: unknown/wrong parameter name '%s'" % msg)
   elif isinstance(err, luxi.NoMasterError):
-    obuf.write("Cannot communicate with the master daemon.\nIs it running"
-               " and listening for connections?")
+    if err.args[0] == pathutils.MASTER_SOCKET:
+      daemon = "master"
+    else:
+      daemon = "config"
+    obuf.write("Cannot communicate with the %s daemon.\nIs it running"
+               " and listening for connections?" % daemon)
   elif isinstance(err, luxi.TimeoutError):
     obuf.write("Timeout while talking to the master daemon. Jobs might have"
                " been submitted and will continue to run even if the call"
diff --git a/lib/client/gnt_network.py b/lib/client/gnt_network.py
index a51d5119868d5c433f4532c727fc8f9d309aa774..17c1bace7f63f3b430f52f61d480ebff0c898533 100644
--- a/lib/client/gnt_network.py
+++ b/lib/client/gnt_network.py
@@ -36,7 +36,7 @@ from ganeti import errors
 
 #: default list of fields for L{ListNetworks}
 _LIST_DEF_FIELDS = ["name", "network", "gateway",
-                    "network_type", "mac_prefix", "group_list", "tags"]
+                    "mac_prefix", "group_list", "tags"]
 
 
 def _HandleReservedIPs(ips):
@@ -77,7 +77,6 @@ def AddNetwork(opts, args):
                             gateway6=opts.gateway6,
                             network6=opts.network6,
                             mac_prefix=opts.mac_prefix,
-                            network_type=opts.network_type,
                             add_reserved_ips=reserved_ips,
                             conflicts_check=opts.conflicts_check,
                             tags=tags)
@@ -203,7 +202,7 @@ def ShowNetworkConfig(_, args):
   cl = GetClient()
   result = cl.QueryNetworks(fields=["name", "network", "gateway",
                                     "network6", "gateway6",
-                                    "mac_prefix", "network_type",
+                                    "mac_prefix",
                                     "free_count", "reserved_count",
                                     "map", "group_list", "inst_list",
                                     "external_reservations",
@@ -211,7 +210,7 @@ def ShowNetworkConfig(_, args):
                             names=args, use_locking=False)
 
   for (name, network, gateway, network6, gateway6,
-       mac_prefix, network_type, free_count, reserved_count,
+       mac_prefix, free_count, reserved_count,
        mapping, group_list, instances, ext_res, serial, uuid) in result:
     size = free_count + reserved_count
     ToStdout("Network name: %s", name)
@@ -222,7 +221,6 @@ def ShowNetworkConfig(_, args):
     ToStdout("  IPv6 Subnet: %s", network6)
     ToStdout("  IPv6 Gateway: %s", gateway6)
     ToStdout("  Mac Prefix: %s", mac_prefix)
-    ToStdout("  Type: %s", network_type)
     ToStdout("  Size: %d", size)
     ToStdout("  Free: %d (%.2f%%)", free_count,
              100 * float(free_count) / float(size))
@@ -278,7 +276,6 @@ def SetNetworkParams(opts, args):
     "add_reserved_ips": _HandleReservedIPs(opts.add_reserved_ips),
     "remove_reserved_ips": _HandleReservedIPs(opts.remove_reserved_ips),
     "mac_prefix": opts.mac_prefix,
-    "network_type": opts.network_type,
     "gateway6": opts.gateway6,
     "network6": opts.network6,
   }
@@ -313,7 +310,7 @@ commands = {
   "add": (
     AddNetwork, ARGS_ONE_NETWORK,
     [DRY_RUN_OPT, NETWORK_OPT, GATEWAY_OPT, ADD_RESERVED_IPS_OPT,
-     MAC_PREFIX_OPT, NETWORK_TYPE_OPT, NETWORK6_OPT, GATEWAY6_OPT,
+     MAC_PREFIX_OPT, NETWORK6_OPT, GATEWAY6_OPT,
      NOCONFLICTSCHECK_OPT, TAG_ADD_OPT, PRIORITY_OPT, SUBMIT_OPT],
     "<network_name>", "Add a new IP network to the cluster"),
   "list": (
@@ -332,7 +329,7 @@ commands = {
   "modify": (
     SetNetworkParams, ARGS_ONE_NETWORK,
     [DRY_RUN_OPT, SUBMIT_OPT, ADD_RESERVED_IPS_OPT, REMOVE_RESERVED_IPS_OPT,
-     GATEWAY_OPT, MAC_PREFIX_OPT, NETWORK_TYPE_OPT, NETWORK6_OPT, GATEWAY6_OPT,
+     GATEWAY_OPT, MAC_PREFIX_OPT, NETWORK6_OPT, GATEWAY6_OPT,
      PRIORITY_OPT],
     "<network_name>", "Alters the parameters of a network"),
   "connect": (
diff --git a/lib/client/gnt_os.py b/lib/client/gnt_os.py
index 0ffa9aca4c22c58f0ee0ae94a83f51f9e5bd0664..4bec976593d4d530b4ad55b10f56570e1b6fa962 100644
--- a/lib/client/gnt_os.py
+++ b/lib/client/gnt_os.py
@@ -1,7 +1,7 @@
 #
 #
 
-# Copyright (C) 2006, 2007, 2010 Google Inc.
+# Copyright (C) 2006, 2007, 2010, 2013 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
@@ -45,10 +45,6 @@ def ListOS(opts, args):
   op = opcodes.OpOsDiagnose(output_fields=["name", "variants"], names=[])
   result = SubmitOpCode(op, opts=opts)
 
-  if not result:
-    ToStderr("Can't get the OS list")
-    return 1
-
   if not opts.no_headers:
     headers = {"name": "Name"}
   else:
diff --git a/lib/cmdlib.py b/lib/cmdlib.py
index 6556f709372574bf5d4cd645de0b34ed3f7daec0..36376443f808a841f74c9b5735cab50f48d47885 100644
--- a/lib/cmdlib.py
+++ b/lib/cmdlib.py
@@ -1020,18 +1020,32 @@ def _CheckOutputFields(static, dynamic, selected):
                                % ",".join(delta), errors.ECODE_INVAL)
 
 
-def _CheckGlobalHvParams(params):
-  """Validates that given hypervisor params are not global ones.
+def _CheckParamsNotGlobal(params, glob_pars, kind, bad_levels, good_levels):
+  """Make sure that none of the given paramters is global.
 
-  This will ensure that instances don't get customised versions of
-  global params.
+  If a global parameter is found, an L{errors.OpPrereqError} exception is
+  raised. This is used to avoid setting global parameters for individual nodes.
+
+  @type params: dictionary
+  @param params: Parameters to check
+  @type glob_pars: dictionary
+  @param glob_pars: Forbidden parameters
+  @type kind: string
+  @param kind: Kind of parameters (e.g. "node")
+  @type bad_levels: string
+  @param bad_levels: Level(s) at which the parameters are forbidden (e.g.
+      "instance")
+  @type good_levels: strings
+  @param good_levels: Level(s) at which the parameters are allowed (e.g.
+      "cluster or group")
 
   """
-  used_globals = constants.HVC_GLOBALS.intersection(params)
+  used_globals = glob_pars.intersection(params)
   if used_globals:
-    msg = ("The following hypervisor parameters are global and cannot"
-           " be customized at instance level, please modify them at"
-           " cluster level: %s" % utils.CommaJoin(used_globals))
+    msg = ("The following %s parameters are global and cannot"
+           " be customized at %s level, please modify them at"
+           " %s level: %s" %
+           (kind, bad_levels, good_levels, utils.CommaJoin(used_globals)))
     raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
 
 
@@ -1385,7 +1399,7 @@ def _ExpandInstanceName(cfg, name):
 
 
 def _BuildNetworkHookEnv(name, subnet, gateway, network6, gateway6,
-                         network_type, mac_prefix, tags):
+                         mac_prefix, tags):
   """Builds network related env variables for hooks
 
   This builds the hook environment from individual variables.
@@ -1400,8 +1414,6 @@ def _BuildNetworkHookEnv(name, subnet, gateway, network6, gateway6,
   @param network6: the ipv6 subnet
   @type gateway6: string
   @param gateway6: the ipv6 gateway
-  @type network_type: string
-  @param network_type: the type of the network
   @type mac_prefix: string
   @param mac_prefix: the mac_prefix
   @type tags: list
@@ -1421,8 +1433,6 @@ def _BuildNetworkHookEnv(name, subnet, gateway, network6, gateway6,
     env["NETWORK_GATEWAY6"] = gateway6
   if mac_prefix:
     env["NETWORK_MAC_PREFIX"] = mac_prefix
-  if network_type:
-    env["NETWORK_TYPE"] = network_type
   if tags:
     env["NETWORK_TAGS"] = " ".join(tags)
 
@@ -1453,7 +1463,7 @@ def _BuildInstanceHookEnv(name, primary_node, secondary_nodes, os_type, status,
   @type vcpus: string
   @param vcpus: the count of VCPUs the instance has
   @type nics: list
-  @param nics: list of tuples (ip, mac, mode, link, network) representing
+  @param nics: list of tuples (ip, mac, mode, link, net, netinfo) representing
       the NICs the instance has
   @type disk_template: string
   @param disk_template: the disk template of the instance
@@ -1495,24 +1505,14 @@ def _BuildInstanceHookEnv(name, primary_node, secondary_nodes, os_type, status,
       env["INSTANCE_NIC%d_MAC" % idx] = mac
       env["INSTANCE_NIC%d_MODE" % idx] = mode
       env["INSTANCE_NIC%d_LINK" % idx] = link
-      if network:
-        env["INSTANCE_NIC%d_NETWORK" % idx] = net
-        if netinfo:
-          nobj = objects.Network.FromDict(netinfo)
-          if nobj.network:
-            env["INSTANCE_NIC%d_NETWORK_SUBNET" % idx] = nobj.network
-          if nobj.gateway:
-            env["INSTANCE_NIC%d_NETWORK_GATEWAY" % idx] = nobj.gateway
-          if nobj.network6:
-            env["INSTANCE_NIC%d_NETWORK_SUBNET6" % idx] = nobj.network6
-          if nobj.gateway6:
-            env["INSTANCE_NIC%d_NETWORK_GATEWAY6" % idx] = nobj.gateway6
-          if nobj.mac_prefix:
-            env["INSTANCE_NIC%d_NETWORK_MAC_PREFIX" % idx] = nobj.mac_prefix
-          if nobj.network_type:
-            env["INSTANCE_NIC%d_NETWORK_TYPE" % idx] = nobj.network_type
-          if nobj.tags:
-            env["INSTANCE_NIC%d_NETWORK_TAGS" % idx] = " ".join(nobj.tags)
+      if netinfo:
+        nobj = objects.Network.FromDict(netinfo)
+        env.update(nobj.HooksDict("INSTANCE_NIC%d_" % idx))
+      elif network:
+        # FIXME: broken network reference: the instance NIC specifies a
+        # network, but the relevant network entry was not in the config. This
+        # should be made impossible.
+        env["INSTANCE_NIC%d_NETWORK_NAME" % idx] = net
       if mode == constants.NIC_MODE_BRIDGED:
         env["INSTANCE_NIC%d_BRIDGE" % idx] = link
   else:
@@ -3490,22 +3490,11 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
         nimg.sbp[pnode].append(instance)
 
     es_flags = rpc.GetExclusiveStorageForNodeNames(self.cfg, self.my_node_names)
-    es_unset_nodes = []
     # The value of exclusive_storage should be the same across the group, so if
     # it's True for at least a node, we act as if it were set for all the nodes
     self._exclusive_storage = compat.any(es_flags.values())
     if self._exclusive_storage:
       node_verify_param[constants.NV_EXCLUSIVEPVS] = True
-      es_unset_nodes = [n for (n, es) in es_flags.items()
-                        if not es]
-
-    if es_unset_nodes:
-      self._Error(constants.CV_EGROUPMIXEDESFLAG, self.group_info.name,
-                  "The exclusive_storage flag should be uniform in a group,"
-                  " but these nodes have it unset: %s",
-                  utils.CommaJoin(utils.NiceSort(es_unset_nodes)))
-      self.LogWarning("Some checks required by exclusive storage will be"
-                      " performed also on nodes with the flag unset")
 
     # At this point, we have the in-memory data structures complete,
     # except for the runtime information, which we'll gather next
@@ -6135,6 +6124,8 @@ class LUNodeAdd(LogicalUnit):
 
     if self.op.ndparams:
       utils.ForceDictType(self.op.ndparams, constants.NDS_PARAMETER_TYPES)
+      _CheckParamsNotGlobal(self.op.ndparams, constants.NDC_GLOBALS, "node",
+                            "node", "cluster or group")
 
     if self.op.hv_state:
       self.new_hv_state = _MergeAndVerifyHvState(self.op.hv_state, None)
@@ -6160,9 +6151,6 @@ class LUNodeAdd(LogicalUnit):
     if vg_name is not None:
       vparams = {constants.NV_PVLIST: [vg_name]}
       excl_stor = _IsExclusiveStorageEnabledNode(cfg, self.new_node)
-      if self.op.ndparams:
-        excl_stor = self.op.ndparams.get(constants.ND_EXCLUSIVE_STORAGE,
-                                         excl_stor)
       cname = self.cfg.GetClusterName()
       result = rpcrunner.call_node_verify_light([node], vparams, cname)[node]
       (errmsgs, _) = _CheckNodePVs(result.payload, excl_stor)
@@ -6560,6 +6548,8 @@ class LUNodeSetParams(LogicalUnit):
     if self.op.ndparams:
       new_ndparams = _GetUpdatedParams(self.node.ndparams, self.op.ndparams)
       utils.ForceDictType(new_ndparams, constants.NDS_PARAMETER_TYPES)
+      _CheckParamsNotGlobal(self.op.ndparams, constants.NDC_GLOBALS, "node",
+                            "node", "cluster or group")
       self.new_ndparams = new_ndparams
 
     if self.op.hv_state:
@@ -10598,7 +10588,8 @@ class LUInstanceCreate(LogicalUnit):
     hv_type.CheckParameterSyntax(filled_hvp)
     self.hv_full = filled_hvp
     # check that we don't specify global parameters on an instance
-    _CheckGlobalHvParams(self.op.hvparams)
+    _CheckParamsNotGlobal(self.op.hvparams, constants.HVC_GLOBALS, "hypervisor",
+                          "instance", "cluster")
 
     # fill and remember the beparams dict
     self.be_full = _ComputeFullBeParams(self.op, cluster)
@@ -13296,7 +13287,8 @@ class LUInstanceSetParams(LogicalUnit):
       raise errors.OpPrereqError("No changes submitted", errors.ECODE_INVAL)
 
     if self.op.hvparams:
-      _CheckGlobalHvParams(self.op.hvparams)
+      _CheckParamsNotGlobal(self.op.hvparams, constants.HVC_GLOBALS,
+                            "hypervisor", "instance", "cluster")
 
     self.op.disks = self._UpgradeDiskNicMods(
       "disk", self.op.disks, opcodes.OpInstanceSetParams.TestDiskModifications)
@@ -13499,13 +13491,12 @@ class LUInstanceSetParams(LogicalUnit):
         elif self.op.conflicts_check:
           _CheckForConflictingIp(self, new_ip, pnode)
 
-      if old_ip:
-        if old_net:
-          try:
-            self.cfg.ReleaseIp(old_net, old_ip, self.proc.GetECId())
-          except errors.AddressPoolError:
-            logging.warning("Release IP %s not contained in network %s",
-                            old_ip, old_net)
+      if old_ip and old_net:
+        try:
+          self.cfg.ReleaseIp(old_net, old_ip, self.proc.GetECId())
+        except errors.AddressPoolError, err:
+          logging.warning("Releasing IP address '%s' from network '%s'"
+                          " failed: %s", old_ip, old_net, err)
 
     # there are no changes in (net, ip) tuple
     elif (old_net is not None and
@@ -16236,7 +16227,6 @@ class LUNetworkAdd(LogicalUnit):
       "network6": self.op.network6,
       "gateway6": self.op.gateway6,
       "mac_prefix": self.op.mac_prefix,
-      "network_type": self.op.network_type,
       "tags": self.op.tags,
       }
     return _BuildNetworkHookEnv(**args) # pylint: disable=W0142
@@ -16251,14 +16241,13 @@ class LUNetworkAdd(LogicalUnit):
                            network6=self.op.network6,
                            gateway6=self.op.gateway6,
                            mac_prefix=self.op.mac_prefix,
-                           network_type=self.op.network_type,
-                           uuid=self.network_uuid,
-                           family=constants.IP4_VERSION)
+                           uuid=self.network_uuid)
     # Initialize the associated address pool
     try:
       pool = network.AddressPool.InitializeNetwork(nobj)
-    except errors.AddressPoolError, e:
-      raise errors.OpExecError("Cannot create IP pool for this network: %s" % e)
+    except errors.AddressPoolError, err:
+      raise errors.OpExecError("Cannot create IP address pool for network"
+                               " '%s': %s" % (self.op.network_name, err))
 
     # Check if we need to reserve the nodes and the cluster master IP
     # These may not be allocated to any instances in routed mode, as
@@ -16271,25 +16260,26 @@ class LUNetworkAdd(LogicalUnit):
               pool.Reserve(ip)
               self.LogInfo("Reserved IP address of node '%s' (%s)",
                            node.name, ip)
-          except errors.AddressPoolError:
-            self.LogWarning("Cannot reserve IP address of node '%s' (%s)",
-                            node.name, ip)
+          except errors.AddressPoolError, err:
+            self.LogWarning("Cannot reserve IP address '%s' of node '%s': %s",
+                            ip, node.name, err)
 
       master_ip = self.cfg.GetClusterInfo().master_ip
       try:
         if pool.Contains(master_ip):
           pool.Reserve(master_ip)
           self.LogInfo("Reserved cluster master IP address (%s)", master_ip)
-      except errors.AddressPoolError:
-        self.LogWarning("Cannot reserve cluster master IP address (%s)",
-                        master_ip)
+      except errors.AddressPoolError, err:
+        self.LogWarning("Cannot reserve cluster master IP address (%s): %s",
+                        master_ip, err)
 
     if self.op.add_reserved_ips:
       for ip in self.op.add_reserved_ips:
         try:
           pool.Reserve(ip, external=True)
-        except errors.AddressPoolError, e:
-          raise errors.OpExecError("Cannot reserve IP %s. %s " % (ip, e))
+        except errors.AddressPoolError, err:
+          raise errors.OpExecError("Cannot reserve IP address '%s': %s" %
+                                   (ip, err))
 
     if self.op.tags:
       for tag in self.op.tags:
@@ -16386,7 +16376,6 @@ class LUNetworkSetParams(LogicalUnit):
     """
     self.network = self.cfg.GetNetwork(self.network_uuid)
     self.gateway = self.network.gateway
-    self.network_type = self.network.network_type
     self.mac_prefix = self.network.mac_prefix
     self.network6 = self.network.network6
     self.gateway6 = self.network.gateway6
@@ -16404,12 +16393,6 @@ class LUNetworkSetParams(LogicalUnit):
                                      " reserved" % self.gateway,
                                      errors.ECODE_STATE)
 
-    if self.op.network_type:
-      if self.op.network_type == constants.VALUE_NONE:
-        self.network_type = None
-      else:
-        self.network_type = self.op.network_type
-
     if self.op.mac_prefix:
       if self.op.mac_prefix == constants.VALUE_NONE:
         self.mac_prefix = None
@@ -16440,7 +16423,6 @@ class LUNetworkSetParams(LogicalUnit):
       "network6": self.network6,
       "gateway6": self.gateway6,
       "mac_prefix": self.mac_prefix,
-      "network_type": self.network_type,
       "tags": self.tags,
       }
     return _BuildNetworkHookEnv(**args) # pylint: disable=W0142
@@ -16500,9 +16482,6 @@ class LUNetworkSetParams(LogicalUnit):
     if self.op.gateway6:
       self.network.gateway6 = self.gateway6
 
-    if self.op.network_type:
-      self.network.network_type = self.network_type
-
     self.pool.Validate()
 
     self.cfg.Update(self.network, feedback_fn)
diff --git a/lib/config.py b/lib/config.py
index 55bef8d5dae0ade2e6c688f480ea347e5a83c33b..4f8c36db0d01fa76ee81ed01ebf5b46b7bd2b0cd 100644
--- a/lib/config.py
+++ b/lib/config.py
@@ -34,6 +34,7 @@ much memory.
 # pylint: disable=R0904
 # R0904: Too many public methods
 
+import copy
 import os
 import random
 import logging
@@ -741,6 +742,10 @@ class ConfigWriter:
         _helper("node %s" % node.name, "ndparams",
                 cluster.FillND(node, data.nodegroups[node.group]),
                 constants.NDS_PARAMETER_TYPES)
+      used_globals = constants.NDC_GLOBALS.intersection(node.ndparams)
+      if used_globals:
+        result.append("Node '%s' has some global parameters set: %s" %
+                      (node.name, utils.CommaJoin(used_globals)))
 
     # nodegroups checks
     nodegroups_names = set()
@@ -2029,24 +2034,22 @@ class ConfigWriter:
              (data.cluster.master_node, self._my_hostname))
       raise errors.ConfigurationError(msg)
 
-    # Upgrade configuration if needed
-    data.UpgradeConfig()
-
     self._config_data = data
     # reset the last serial as -1 so that the next write will cause
     # ssconf update
     self._last_cluster_serial = -1
 
-    # And finally run our (custom) config upgrade sequence
+    # Upgrade configuration if needed
     self._UpgradeConfig()
 
     self._cfg_id = utils.GetFileID(path=self._cfg_file)
 
   def _UpgradeConfig(self):
-    """Run upgrade steps that cannot be done purely in the objects.
+    """Run any upgrade steps.
 
-    This is because some data elements need uniqueness across the
-    whole configuration, etc.
+    This method performs both in-object upgrades and also update some data
+    elements that need uniqueness across the whole configuration or interact
+    with other objects.
 
     @warning: this function will call L{_WriteConfig()}, but also
         L{DropECReservations} so it needs to be called only from a
@@ -2055,26 +2058,31 @@ class ConfigWriter:
         created first, to avoid causing deadlock.
 
     """
-    modified = False
+    # Keep a copy of the persistent part of _config_data to check for changes
+    # Serialization doesn't guarantee order in dictionaries
+    oldconf = copy.deepcopy(self._config_data.ToDict())
+
+    # In-object upgrades
+    self._config_data.UpgradeConfig()
+
     for item in self._AllUUIDObjects():
       if item.uuid is None:
         item.uuid = self._GenerateUniqueID(_UPGRADE_CONFIG_JID)
-        modified = True
     if not self._config_data.nodegroups:
       default_nodegroup_name = constants.INITIAL_NODE_GROUP_NAME
       default_nodegroup = objects.NodeGroup(name=default_nodegroup_name,
                                             members=[])
       self._UnlockedAddNodeGroup(default_nodegroup, _UPGRADE_CONFIG_JID, True)
-      modified = True
     for node in self._config_data.nodes.values():
       if not node.group:
         node.group = self.LookupNodeGroup(None)
-        modified = True
       # This is technically *not* an upgrade, but needs to be done both when
       # nodegroups are being added, and upon normally loading the config,
       # because the members list of a node group is discarded upon
       # serializing/deserializing the object.
       self._UnlockedAddNodeToGroup(node.name, node.group)
+
+    modified = (oldconf != self._config_data.ToDict())
     if modified:
       self._WriteConfig()
       # This is ok even if it acquires the internal lock, as _UpgradeConfig is
diff --git a/lib/constants.py b/lib/constants.py
index f7bd856343b7ea92cdb3beee2a02fae25b4f5c12..add7c0c166f57f957bfffaec0b68a81359d09ad3 100644
--- a/lib/constants.py
+++ b/lib/constants.py
@@ -1219,16 +1219,6 @@ NIC_VALID_MODES = compat.UniqueFrozenset([
 RESERVE_ACTION = "reserve"
 RELEASE_ACTION = "release"
 
-# An extra description of the network.
-# Can be used by hooks/kvm-vif-bridge to apply different rules
-NETWORK_TYPE_PRIVATE = "private"
-NETWORK_TYPE_PUBLIC = "public"
-
-NETWORK_VALID_TYPES = compat.UniqueFrozenset([
-  NETWORK_TYPE_PRIVATE,
-  NETWORK_TYPE_PUBLIC,
-  ])
-
 NICS_PARAMETER_TYPES = {
   NIC_MODE: VTYPE_STRING,
   NIC_LINK: VTYPE_STRING,
@@ -1461,9 +1451,6 @@ CV_ECLUSTERDANGLINGNODES = \
 CV_ECLUSTERDANGLINGINST = \
   (CV_TNODE, "ECLUSTERDANGLINGINST",
    "Some instances have a non-existing primary node")
-CV_EGROUPMIXEDESFLAG = \
-  (CV_TGROUP, "EGROUPMIXEDESFLAG",
-   "exclusive_storage flag is not uniform within the group")
 CV_EGROUPDIFFERENTPVSIZE = \
   (CV_TGROUP, "EGROUPDIFFERENTPVSIZE", "PVs in the group have different sizes")
 CV_EINSTANCEBADNODE = \
@@ -2029,6 +2016,10 @@ NDC_DEFAULTS = {
   ND_EXCLUSIVE_STORAGE: False,
   }
 
+NDC_GLOBALS = compat.UniqueFrozenset([
+  ND_EXCLUSIVE_STORAGE,
+  ])
+
 DISK_LD_DEFAULTS = {
   LD_DRBD8: {
     LDP_RESYNC_RATE: CLASSIC_DRBD_SYNC_SPEED,
diff --git a/lib/hypervisor/hv_kvm.py b/lib/hypervisor/hv_kvm.py
index efe8ab620ac3fe794656a6ca5a41dfc45bf1d8fb..25e43b7399bd212c6bea4919ef173719656c4098 100644
--- a/lib/hypervisor/hv_kvm.py
+++ b/lib/hypervisor/hv_kvm.py
@@ -164,31 +164,6 @@ def _OpenTap(vnet_hdr=True):
   return (ifname, tapfd)
 
 
-def _BuildNetworkEnv(name, network, gateway, network6, gateway6,
-                     network_type, mac_prefix, tags, env):
-  """Build environment variables concerning a Network.
-
-  """
-  if name:
-    env["NETWORK_NAME"] = name
-  if network:
-    env["NETWORK_SUBNET"] = network
-  if gateway:
-    env["NETWORK_GATEWAY"] = gateway
-  if network6:
-    env["NETWORK_SUBNET6"] = network6
-  if gateway6:
-    env["NETWORK_GATEWAY6"] = gateway6
-  if mac_prefix:
-    env["NETWORK_MAC_PREFIX"] = mac_prefix
-  if network_type:
-    env["NETWORK_TYPE"] = network_type
-  if tags:
-    env["NETWORK_TAGS"] = " ".join(tags)
-
-  return env
-
-
 class QmpMessage:
   """QEMU Messaging Protocol (QMP) message.
 
@@ -872,9 +847,7 @@ class KVMHypervisor(hv_base.BaseHypervisor):
 
     if nic.network:
       n = objects.Network.FromDict(nic.netinfo)
-      _BuildNetworkEnv(nic.network, n.network, n.gateway,
-                       n.network6, n.gateway6, n.network_type,
-                       n.mac_prefix, n.tags, env)
+      env.update(n.HooksDict())
 
     if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
       env["BRIDGE"] = nic.nicparams[constants.NIC_LINK]
diff --git a/lib/network.py b/lib/network.py
index eaa2ddc203fd6607b28ab3f734425b2099cf3325..170a8b778935a8e3c298be971af4654047dd5de9 100644
--- a/lib/network.py
+++ b/lib/network.py
@@ -132,14 +132,12 @@ class AddressPool(object):
     return (self.reservations | self.ext_reservations)
 
   def Validate(self):
-    assert self.net.family == 4
     assert len(self.reservations) == self._GetSize()
     assert len(self.ext_reservations) == self._GetSize()
     all_res = self.reservations & self.ext_reservations
     assert not all_res.any()
 
     if self.gateway is not None:
-      assert self.net.family == self.gateway.version
       assert self.gateway in self.network
 
     if self.network6 and self.gateway6:
diff --git a/lib/objects.py b/lib/objects.py
index fc47c84cbf95705b1cf661e270580640bbc21cf3..6340d8d9559211b92f56d1f5091f826e4d8fc5d9 100644
--- a/lib/objects.py
+++ b/lib/objects.py
@@ -38,6 +38,7 @@ pass to and from external parties.
 import ConfigParser
 import re
 import copy
+import logging
 import time
 from cStringIO import StringIO
 
@@ -460,6 +461,8 @@ class ConfigData(ConfigObject):
         self.cluster.drbd_usermode_helper = constants.DEFAULT_DRBD_HELPER
     if self.networks is None:
       self.networks = {}
+    for network in self.networks.values():
+      network.UpgradeConfig()
 
 
 class NIC(ConfigObject):
@@ -1293,6 +1296,12 @@ class Node(TaggableObject):
 
     if self.ndparams is None:
       self.ndparams = {}
+    # And remove any global parameter
+    for key in constants.NDC_GLOBALS:
+      if key in self.ndparams:
+        logging.warning("Ignoring %s node parameter for node %s",
+                        key, self.name)
+        del self.ndparams[key]
 
     if self.powered is None:
       self.powered = True
@@ -1999,18 +2008,54 @@ class Network(TaggableObject):
   __slots__ = [
     "name",
     "serial_no",
-    "network_type",
     "mac_prefix",
-    "family",
     "network",
     "network6",
     "gateway",
     "gateway6",
-    "size",
     "reservations",
     "ext_reservations",
     ] + _TIMESTAMPS + _UUID
 
+  def HooksDict(self, prefix=""):
+    """Export a dictionary used by hooks with a network's information.
+
+    @type prefix: String
+    @param prefix: Prefix to prepend to the dict entries
+
+    """
+    result = {
+      "%sNETWORK_NAME" % prefix: self.name,
+      "%sNETWORK_UUID" % prefix: self.uuid,
+      "%sNETWORK_TAGS" % prefix: " ".join(self.tags),
+    }
+    if self.network:
+      result["%sNETWORK_SUBNET" % prefix] = self.network
+    if self.gateway:
+      result["%sNETWORK_GATEWAY" % prefix] = self.gateway
+    if self.network6:
+      result["%sNETWORK_SUBNET6" % prefix] = self.network6
+    if self.gateway6:
+      result["%sNETWORK_GATEWAY6" % prefix] = self.gateway6
+    if self.mac_prefix:
+      result["%sNETWORK_MAC_PREFIX" % prefix] = self.mac_prefix
+
+    return result
+
+  @classmethod
+  def FromDict(cls, val):
+    """Custom function for networks.
+
+    Remove deprecated network_type and family.
+
+    """
+    if "network_type" in val:
+      del val["network_type"]
+    if "family" in val:
+      del val["family"]
+    obj = super(Network, cls).FromDict(val)
+    return obj
+
 
 class SerializableConfigParser(ConfigParser.SafeConfigParser):
   """Simple wrapper over ConfigParse that allows serialization.
diff --git a/lib/opcodes.py b/lib/opcodes.py
index 7d67282eebd8cb64f90215277a8d7bd570065efd..2ebf29c3d9bef3e62a0a683e55023f34a9c3372c 100644
--- a/lib/opcodes.py
+++ b/lib/opcodes.py
@@ -361,8 +361,6 @@ def _CheckStorageType(storage_type):
 _PStorageType = ("storage_type", ht.NoDefault, _CheckStorageType,
                  "Storage type")
 
-_CheckNetworkType = ht.TElemOf(constants.NETWORK_VALID_TYPES)
-
 
 @ht.WithDesc("IPv4 network")
 def _CheckCIDRNetNotation(value):
@@ -2065,7 +2063,6 @@ class OpNetworkAdd(OpCode):
   OP_DSC_FIELD = "network_name"
   OP_PARAMS = [
     _PNetworkName,
-    ("network_type", None, ht.TMaybe(_CheckNetworkType), "Network type"),
     ("network", ht.NoDefault, _TIpNetwork4, "IPv4 subnet"),
     ("gateway", None, ht.TMaybe(_TIpAddress4), "IPv4 gateway"),
     ("network6", None, ht.TMaybe(_TIpNetwork6), "IPv6 subnet"),
@@ -2099,8 +2096,6 @@ class OpNetworkSetParams(OpCode):
   OP_DSC_FIELD = "network_name"
   OP_PARAMS = [
     _PNetworkName,
-    ("network_type", None, ht.TMaybeValueNone(_CheckNetworkType),
-     "Network type"),
     ("gateway", None, ht.TMaybeValueNone(_TIpAddress4), "IPv4 gateway"),
     ("network6", None, ht.TMaybeValueNone(_TIpNetwork6), "IPv6 subnet"),
     ("gateway6", None, ht.TMaybeValueNone(_TIpAddress6), "IPv6 gateway"),
diff --git a/lib/query.py b/lib/query.py
index dc07159fb150e69a16697d143f09ebc500f86b78..ef52d0899cd4a5593faa961cc177109c6f8a7b0b 100644
--- a/lib/query.py
+++ b/lib/query.py
@@ -2531,7 +2531,6 @@ _NETWORK_SIMPLE_FIELDS = {
   "network6": ("IPv6Subnet", QFT_OTHER, 0, "IPv6 subnet"),
   "gateway6": ("IPv6Gateway", QFT_OTHER, 0, "IPv6 gateway"),
   "mac_prefix": ("MacPrefix", QFT_OTHER, 0, "MAC address prefix"),
-  "network_type": ("NetworkType", QFT_OTHER, 0, "Network type"),
   "serial_no": ("SerialNo", QFT_NUMBER, 0, _SERIAL_NO_DOC % "Network"),
   "uuid": ("UUID", QFT_TEXT, 0, "Network UUID"),
   }
diff --git a/lib/rapi/baserlib.py b/lib/rapi/baserlib.py
index 15d1e3286097f320c66a42dfc3a9a2df325a46ee..ce42166ba6c7bc8af8508aad37708ff94ea4fd5d 100644
--- a/lib/rapi/baserlib.py
+++ b/lib/rapi/baserlib.py
@@ -465,13 +465,13 @@ class OpcodeResource(ResourceBase):
 
   @cvar POST_OPCODE: Set this to a class derived from L{opcodes.OpCode} to
     automatically generate a POST handler submitting the opcode
-  @cvar POST_RENAME: Set this to rename parameters in the DELETE handler (see
+  @cvar POST_RENAME: Set this to rename parameters in the POST handler (see
     L{baserlib.FillOpcode})
   @ivar GetPostOpInput: Define this to override the default method for
     getting opcode parameters (see L{baserlib.OpcodeResource._GetDefaultData})
 
   @cvar DELETE_OPCODE: Set this to a class derived from L{opcodes.OpCode} to
-    automatically generate a GET handler submitting the opcode
+    automatically generate a DELETE handler submitting the opcode
   @cvar DELETE_RENAME: Set this to rename parameters in the DELETE handler (see
     L{baserlib.FillOpcode})
   @ivar GetDeleteOpInput: Define this to override the default method for
diff --git a/lib/rapi/client.py b/lib/rapi/client.py
index 452fa95c85e8e96be2cca1ff44db1f2d445f155f..cf7971132300264afe96f5216e0e392626211580 100644
--- a/lib/rapi/client.py
+++ b/lib/rapi/client.py
@@ -1768,7 +1768,7 @@ class GanetiRapiClient(object): # pylint: disable=R0904
                              None, None)
 
   def CreateNetwork(self, network_name, network, gateway=None, network6=None,
-                    gateway6=None, mac_prefix=None, network_type=None,
+                    gateway6=None, mac_prefix=None,
                     add_reserved_ips=None, tags=None, dry_run=False):
     """Creates a new network.
 
@@ -1797,7 +1797,6 @@ class GanetiRapiClient(object): # pylint: disable=R0904
       "gateway6": gateway6,
       "network6": network6,
       "mac_prefix": mac_prefix,
-      "network_type": network_type,
       "add_reserved_ips": add_reserved_ips,
       "tags": tags,
       }
diff --git a/lib/rapi/rlib2.py b/lib/rapi/rlib2.py
index d7489139d74a86552a021df128daeba794270e10..3a28a8472d4111b4f69534337d3074c250557d9a 100644
--- a/lib/rapi/rlib2.py
+++ b/lib/rapi/rlib2.py
@@ -94,7 +94,7 @@ N_FIELDS = ["name", "offline", "master_candidate", "drained",
 
 NET_FIELDS = ["name", "network", "gateway",
               "network6", "gateway6",
-              "mac_prefix", "network_type",
+              "mac_prefix",
               "free_count", "reserved_count",
               "map", "group_list", "inst_list",
               "external_reservations", "tags",
diff --git a/man/ganeti-os-interface.rst b/man/ganeti-os-interface.rst
index 2e44021ff8289b9b56bcdc080a584217c3aa7558..ec8ec860cce8a2c19a415604cb92674fb0a639e5 100644
--- a/man/ganeti-os-interface.rst
+++ b/man/ganeti-os-interface.rst
@@ -118,6 +118,36 @@ NIC_%N_FRONTEND_TYPE
     instance, this can be one of: ``rtl8139``, ``ne2k_pci``,
     ``ne2k_isa``, ``paravirtual``.
 
+NIC_%d_NETWORK_NAME
+    (Optional) If a NIC network is specified, the network's name.
+
+NIC_%d_NETWORK_UUID
+    (Optional) If a NIC network is specified, the network's uuid.
+
+NIC_%d_NETWORK_FAMILY
+    (Optional) If a NIC network is specified, the network's family.
+
+NIC_%d_NETWORK_SUBNET
+    (Optional) If a NIC network is specified, the network's IPv4 subnet.
+
+NIC_%d_NETWORK_GATEWAY
+    (Optional) If a NIC network is specified, the network's IPv4
+    gateway.
+
+NIC_%d_NETWORK_SUBNET6
+    (Optional) If a NIC network is specified, the network's IPv6 subnet.
+
+NIC_%d_NETWORK_GATEWAY6
+    (Optional) If a NIC network is specified, the network's IPv6
+    gateway.
+
+NIC_%d_NETWORK_MAC_PREFIX
+    (Optional) If a NIC network is specified, the network's mac prefix.
+
+NIC_%d_NETWORK_TAGS
+    (Optional) If a NIC network is specified, the network's tags, space
+    separated.
+
 OSP_*name*
     Each OS parameter (see below) will be exported in its own
     variable, prefixed with ``OSP_``, and upper-cased. For example, a
diff --git a/man/ganeti.rst b/man/ganeti.rst
index 03fe65272d05e0028d02db5487e7623f100497b3..16800d128253cb924441fda24b300d23b2a5e7dd 100644
--- a/man/ganeti.rst
+++ b/man/ganeti.rst
@@ -120,7 +120,9 @@ exclusive_storage
     When this Boolean flag is enabled, physical disks on the node are
     assigned to instance disks in an exclusive manner, so as to lower I/O
     interference between instances. See the `Partitioned Ganeti
-    <design-partitioned.rst>`_ design document for more details.
+    <design-partitioned.rst>`_ design document for more details. This
+    parameter cannot be set on individual nodes, as its value must be
+    the same within each node group.
 
 
 Hypervisor State Parameters
diff --git a/man/gnt-network.rst b/man/gnt-network.rst
index 3e4a551f26fe3b37c4032be43c6d61674fd095a5..69848cde32a279e9a94b2dba154e8719f4fd203d 100644
--- a/man/gnt-network.rst
+++ b/man/gnt-network.rst
@@ -32,7 +32,6 @@ ADD
 | [--network6=*NETWORK6*]
 | [--gateway6=*GATEWAY6*]
 | [--mac-prefix=*MACPREFIX*]
-| [--network-type=*NETWORKTYPE*]
 | [--submit]
 | {*network*}
 
@@ -46,8 +45,6 @@ notation.
 The ``--gateway`` option allows you to specify the default gateway for
 this network.
 
-The ``--network-type`` can be none, private or public.
-
 IPv6 semantics can be assigned to the network via the ``--network6`` and
 ``--gateway6`` options. IP pool is meaningless for IPV6 so those two
 values can be used for EUI64 generation from a NIC's MAC address.
@@ -71,7 +68,6 @@ MODIFY
 | [--network6=*NETWORK6*]
 | [--gateway6=*GATEWAY6*]
 | [--mac-prefix=*MACPREFIX*]
-| [--network-type=*NETWORKTYPE*]
 | [--submit]
 | {*network*}
 
diff --git a/qa/ganeti-qa.py b/qa/ganeti-qa.py
index e4dc578f51873caec11f523df79b406297f7f6ad..a346eacb3e16398cdc1fbdaf195b57bc1bc27bc0 100755
--- a/qa/ganeti-qa.py
+++ b/qa/ganeti-qa.py
@@ -461,7 +461,7 @@ def RunExclusiveStorageTests():
   node = qa_config.AcquireNode()
   try:
     old_es = qa_cluster.TestSetExclStorCluster(False)
-    qa_cluster.TestExclStorSingleNode(node)
+    qa_node.TestExclStorSingleNode(node)
 
     qa_cluster.TestSetExclStorCluster(True)
     qa_cluster.TestExclStorSharedPv(node)
diff --git a/qa/qa-sample.json b/qa/qa-sample.json
index 17dc800c227007345e3b7246280bab2e5b19e2db..10b93a89f47973c8a49bdf8b23aa5872e069776c 100644
--- a/qa/qa-sample.json
+++ b/qa/qa-sample.json
@@ -24,6 +24,9 @@
   "# Cluster-level value of the exclusive-storage flag": null,
   "exclusive-storage": null,
 
+  "# Additional arguments for initializing cluster": null,
+  "cluster-init-args": [],
+
   "# Network interface for master role": null,
   "#master-netdev": "xen-br0",
 
diff --git a/qa/qa_cluster.py b/qa/qa_cluster.py
index 410196ba8d46357322d63615af43edf8ef0ab52d..60b7208164376c5659d66b222d34d110f038a348 100644
--- a/qa/qa_cluster.py
+++ b/qa/qa_cluster.py
@@ -209,7 +209,12 @@ def TestClusterInit(rapi_user, rapi_secret):
     e_s = False
   qa_config.SetExclusiveStorage(e_s)
 
+  extra_args = qa_config.get("cluster-init-args")
+  if extra_args:
+    cmd.extend(extra_args)
+
   cmd.append(qa_config.get("name"))
+
   AssertCommand(cmd)
 
   cmd = ["gnt-cluster", "modify"]
diff --git a/qa/qa_node.py b/qa/qa_node.py
index 6081a0231b56b912a51fc09a788e64b43cde8ea1..809f8795a39275554af0c0f11dc1af2af26c8409 100644
--- a/qa/qa_node.py
+++ b/qa/qa_node.py
@@ -425,3 +425,22 @@ def TestNodeListFields():
 def TestNodeListDrbd(node):
   """gnt-node list-drbd"""
   AssertCommand(["gnt-node", "list-drbd", node.primary])
+
+
+def _BuildSetESCmd(action, value, node_name):
+  cmd = ["gnt-node"]
+  if action == "add":
+    cmd.extend(["add", "--readd"])
+  else:
+    cmd.append("modify")
+  cmd.extend(["--node-parameters", "exclusive_storage=%s" % value, node_name])
+  return cmd
+
+
+def TestExclStorSingleNode(node):
+  """gnt-node add/modify cannot change the exclusive_storage flag.
+
+  """
+  for action in ["add", "modify"]:
+    for value in (True, False, "default"):
+      AssertCommand(_BuildSetESCmd(action, value, node.primary), fail=True)
diff --git a/qa/qa_utils.py b/qa/qa_utils.py
index 68656730bed21015a02ca37cd59a543e58efa6cf..9533c280b109ddb9b1a1e5c6f173a6ea217430b8 100644
--- a/qa/qa_utils.py
+++ b/qa/qa_utils.py
@@ -322,7 +322,7 @@ def GetCommandOutput(node, cmd, tty=None, fail=False):
   p = StartLocalCommand(GetSSHCommand(node, cmd, tty=tty),
                         stdout=subprocess.PIPE)
   rcode = p.wait()
-  _AssertRetCode(rcode, fail, node, cmd)
+  _AssertRetCode(rcode, fail, cmd, node)
   return p.stdout.read()
 
 
diff --git a/src/Ganeti/HTools/Backend/IAlloc.hs b/src/Ganeti/HTools/Backend/IAlloc.hs
index 6c3fdf161d5c0dbd409b314dce50cd4ceffc1589..d1d1436119b8f4c15af79908f394bd6c09731ca7 100644
--- a/src/Ganeti/HTools/Backend/IAlloc.hs
+++ b/src/Ganeti/HTools/Backend/IAlloc.hs
@@ -4,7 +4,7 @@
 
 {-
 
-Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
+Copyright (C) 2009, 2010, 2011, 2012, 2013 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
@@ -82,14 +82,14 @@ parseInstance :: NameAssoc -- ^ The node name-to-index association list
 parseInstance ktn n a = do
   base <- parseBaseInstance n a
   nodes <- fromObj a "nodes"
-  pnode <- if null nodes
-           then Bad $ "empty node list for instance " ++ n
-           else readEitherString $ head nodes
+  (pnode, snodes) <-
+    case nodes of
+      [] -> Bad $ "empty node list for instance " ++ n
+      x:xs -> readEitherString x >>= \x' -> return (x', xs)
   pidx <- lookupNode ktn n pnode
-  let snodes = tail nodes
-  sidx <- if null snodes
-            then return Node.noSecondary
-            else readEitherString (head snodes) >>= lookupNode ktn n
+  sidx <- case snodes of
+            [] -> return Node.noSecondary
+            x:_ -> readEitherString x >>= lookupNode ktn n
   return (n, Instance.setBoth (snd base) pidx sidx)
 
 -- | Parses a node as found in the cluster node list.
diff --git a/src/Ganeti/HTools/Backend/Luxi.hs b/src/Ganeti/HTools/Backend/Luxi.hs
index b3178089788a617512fd200fad78328b1a6a0ce4..febb0ab434832b4f37a00b60b532b4729383bf84 100644
--- a/src/Ganeti/HTools/Backend/Luxi.hs
+++ b/src/Ganeti/HTools/Backend/Luxi.hs
@@ -4,7 +4,7 @@
 
 {-
 
-Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
+Copyright (C) 2009, 2010, 2011, 2012, 2013 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
@@ -163,10 +163,10 @@ parseInstance ktn [ name, disk, mem, vcpus
             _ -> convert "be/memory" mem
   xvcpus <- convert "be/vcpus" vcpus
   xpnode <- convert "pnode" pnode >>= lookupNode ktn xname
-  xsnodes <- convert "snodes" snodes::Result [JSString]
-  snode <- if null xsnodes
-             then return Node.noSecondary
-             else lookupNode ktn xname (fromJSString $ head xsnodes)
+  xsnodes <- convert "snodes" snodes::Result [String]
+  snode <- case xsnodes of
+             [] -> return Node.noSecondary
+             x:_ -> lookupNode ktn xname x
   xrunning <- convert "status" status
   xtags <- convert "tags" tags
   xauto_balance <- convert "auto_balance" auto_balance
diff --git a/src/Ganeti/HTools/Backend/Rapi.hs b/src/Ganeti/HTools/Backend/Rapi.hs
index 005cfdbf17554b73774c152dd3caead885c1ae6a..eaf061c4c81d026a5900c23536ead2f455cce624 100644
--- a/src/Ganeti/HTools/Backend/Rapi.hs
+++ b/src/Ganeti/HTools/Backend/Rapi.hs
@@ -4,7 +4,7 @@
 
 {-
 
-Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
+Copyright (C) 2009, 2010, 2011, 2012, 2013 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
@@ -138,9 +138,9 @@ parseInstance ktn a = do
   vcpus <- extract "vcpus" beparams
   pnode <- extract "pnode" a >>= lookupNode ktn name
   snodes <- extract "snodes" a
-  snode <- if null snodes
-             then return Node.noSecondary
-             else readEitherString (head snodes) >>= lookupNode ktn name
+  snode <- case snodes of
+             [] -> return Node.noSecondary
+             x:_ -> readEitherString x >>= lookupNode ktn name
   running <- extract "status" a
   tags <- extract "tags" a
   auto_balance <- extract "auto_balance" beparams
diff --git a/src/Ganeti/HTools/Cluster.hs b/src/Ganeti/HTools/Cluster.hs
index 7655f366a1e0f1ba9e8938f5fb8378c22a18e60d..ddd5c17f76d83d93c70fb404fc53eba0f0d174f8 100644
--- a/src/Ganeti/HTools/Cluster.hs
+++ b/src/Ganeti/HTools/Cluster.hs
@@ -829,14 +829,13 @@ findBestAllocGroup mggl mgnl mgil allowed_gdxs inst cnt =
       all_msgs = concatMap (solutionDescription mggl) sols
       goodSols = filterMGResults mggl sols
       sortedSols = sortMGResults mggl goodSols
-  in if null sortedSols
-       then Bad $ if null groups'
-                    then "no groups for evacuation: allowed groups was" ++
-                         show allowed_gdxs ++ ", all groups: " ++
-                         show (map fst groups)
-                    else intercalate ", " all_msgs
-       else let (final_group, final_sol) = head sortedSols
-            in return (final_group, final_sol, all_msgs)
+  in case sortedSols of
+       [] -> Bad $ if null groups'
+                     then "no groups for evacuation: allowed groups was" ++
+                          show allowed_gdxs ++ ", all groups: " ++
+                          show (map fst groups)
+                     else intercalate ", " all_msgs
+       (final_group, final_sol):_ -> return (final_group, final_sol, all_msgs)
 
 -- | Try to allocate an instance on a multi-group cluster.
 tryMGAlloc :: Group.List           -- ^ The group list
diff --git a/src/Ganeti/HTools/Graph.hs b/src/Ganeti/HTools/Graph.hs
index 1fa350094b0a07dd2691bb26abb9afd1ebd8c896..3bc42d8e5ae3cfc04a2b093dbc6bffecc5cb59f1 100644
--- a/src/Ganeti/HTools/Graph.hs
+++ b/src/Ganeti/HTools/Graph.hs
@@ -28,7 +28,7 @@ University Clausthal, 1-9.
 
 {-
 
-Copyright (C) 2012, Google Inc.
+Copyright (C) 2012, 2013, 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
@@ -147,6 +147,7 @@ verticesColorSet cMap = IntSet.fromList . verticesColors cMap
 neighColors :: Graph.Graph -> VertColorMap -> Graph.Vertex -> [Color]
 neighColors g cMap v = verticesColors cMap $ neighbors g v
 
+{-# ANN colorNode "HLint: ignore Use alternative" #-}
 -- | Color one node.
 colorNode :: Graph.Graph -> VertColorMap -> Graph.Vertex -> Color
 -- use of "head" is A-ok as the source is an infinite list
diff --git a/src/Ganeti/HTools/Node.hs b/src/Ganeti/HTools/Node.hs
index eff4fb51515970301d3dfa9cce8e50d1fbb4449c..42180bf84d76c952f5cfa1158e452a3f8331230b 100644
--- a/src/Ganeti/HTools/Node.hs
+++ b/src/Ganeti/HTools/Node.hs
@@ -6,7 +6,7 @@
 
 {-
 
-Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
+Copyright (C) 2009, 2010, 2011, 2012, 2013 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
@@ -679,10 +679,13 @@ defaultFields =
   , "pfmem", "pfdsk", "rcpu"
   , "cload", "mload", "dload", "nload" ]
 
+{-# ANN computeGroups "HLint: ignore Use alternative" #-}
 -- | Split a list of nodes into a list of (node group UUID, list of
 -- associated nodes).
 computeGroups :: [Node] -> [(T.Gdx, [Node])]
 computeGroups nodes =
   let nodes' = sortBy (comparing group) nodes
       nodes'' = groupBy ((==) `on` group) nodes'
+  -- use of head here is OK, since groupBy returns non-empty lists; if
+  -- you remove groupBy, also remove use of head
   in map (\nl -> (group (head nl), nl)) nodes''
diff --git a/src/Ganeti/HTools/Program/Hail.hs b/src/Ganeti/HTools/Program/Hail.hs
index adcddf9892e2705abf104d7e933558d00c5310ab..50009a39ae46fa20c9e0637ec47c2752bd35358e 100644
--- a/src/Ganeti/HTools/Program/Hail.hs
+++ b/src/Ganeti/HTools/Program/Hail.hs
@@ -4,7 +4,7 @@
 
 {-
 
-Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
+Copyright (C) 2009, 2010, 2011, 2012, 2013 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
@@ -59,9 +59,11 @@ arguments = [ArgCompletion OptComplFile 1 (Just 1)]
 
 wrapReadRequest :: Options -> [String] -> IO Request
 wrapReadRequest opts args = do
-  when (null args) $ exitErr "This program needs an input file."
+  r1 <- case args of
+          []    -> exitErr "This program needs an input file."
+          _:_:_ -> exitErr "Only one argument is accepted (the input file)"
+          x:_   -> readRequest x
 
-  r1 <- readRequest (head args)
   if isJust (optDataFile opts) ||  (not . null . optNodeSim) opts
     then do
       cdata <- loadExternalData opts
diff --git a/src/Ganeti/HTools/Program/Hbal.hs b/src/Ganeti/HTools/Program/Hbal.hs
index 956d31827eb687c3744ef3cfb68573439418fc24..a5207fb7b60d682a272b319d4b0d0a400fc6bc24 100644
--- a/src/Ganeti/HTools/Program/Hbal.hs
+++ b/src/Ganeti/HTools/Program/Hbal.hs
@@ -4,7 +4,7 @@
 
 {-
 
-Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
+Copyright (C) 2009, 2010, 2011, 2012, 2013 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
@@ -135,8 +135,9 @@ iterateDepth printmove ini_tbl max_rounds disk_moves inst_moves nmlen imlen
        Just fin_tbl ->
          do
            let (Cluster.Table _ _ _ fin_plc) = fin_tbl
-               fin_plc_len = length fin_plc
-               cur_plc@(idx, _, _, move, _) = head fin_plc
+           cur_plc@(idx, _, _, move, _) <-
+             exitIfEmpty "Empty placement list returned for solution?!" fin_plc
+           let fin_plc_len = length fin_plc
                (sol_line, cmds) = Cluster.printSolutionLine ini_nl ini_il
                                   nmlen imlen cur_plc fin_plc_len
                afn = Cluster.involvedNodes ini_il cur_plc
@@ -261,8 +262,8 @@ selectGroup opts gl nlf ilf = do
 
   case optGroup opts of
     Nothing -> do
-      let (gidx, cdata) = head ngroups
-          grp = Container.find gidx gl
+      (gidx, cdata) <- exitIfEmpty "No groups found by splitCluster?!" ngroups
+      let grp = Container.find gidx gl
       return (Group.name grp, cdata)
     Just g -> case Container.findByName gl g of
       Nothing -> do
diff --git a/src/Ganeti/HTools/Program/Hspace.hs b/src/Ganeti/HTools/Program/Hspace.hs
index d1b62c34de3fdd64d9983980e1d9e5c25a50f5ab..02c81bf4a27637f7442226208e8491836e3056f6 100644
--- a/src/Ganeti/HTools/Program/Hspace.hs
+++ b/src/Ganeti/HTools/Program/Hspace.hs
@@ -4,7 +4,7 @@
 
 {-
 
-Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
+Copyright (C) 2009, 2010, 2011, 2012, 2013 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
@@ -105,6 +105,11 @@ specDescription :: SpecType -> String
 specDescription SpecNormal = "Standard (fixed-size)"
 specDescription SpecTiered = "Tiered (initial size)"
 
+-- | The \"name\" of a 'SpecType'.
+specName :: SpecType -> String
+specName SpecNormal = "Standard"
+specName SpecTiered = "Tiered"
+
 -- | Efficiency generic function.
 effFn :: (Cluster.CStats -> Integer)
       -> (Cluster.CStats -> Double)
@@ -191,12 +196,14 @@ printResults True _ fin_nl num_instances allocs sreason = do
                   \ != counted (%d)\n" (num_instances + allocs)
            (Cluster.csNinst fin_stats)
 
+  main_reason <- exitIfEmpty "Internal error, no failure reasons?!" sreason
+
   printKeysHTS $ printStats PFinal fin_stats
   printKeysHTS [ ("ALLOC_USAGE", printf "%.8f"
                                    ((fromIntegral num_instances::Double) /
                                    fromIntegral fin_instances))
                , ("ALLOC_INSTANCES", printf "%d" allocs)
-               , ("ALLOC_FAIL_REASON", map toUpper . show . fst $ head sreason)
+               , ("ALLOC_FAIL_REASON", map toUpper . show . fst $ main_reason)
                ]
   printKeysHTS $ map (\(x, y) -> (printf "ALLOC_%s_CNT" (show x),
                                   printf "%d" y)) sreason
@@ -210,6 +217,7 @@ printResults False ini_nl fin_nl _ allocs sreason = do
 printFinalHTS :: Bool -> IO ()
 printFinalHTS = printFinal htsPrefix
 
+{-# ANN tieredSpecMap "HLint: ignore Use alternative" #-}
 -- | Compute the tiered spec counts from a list of allocated
 -- instances.
 tieredSpecMap :: [Instance.Instance]
@@ -217,6 +225,7 @@ tieredSpecMap :: [Instance.Instance]
 tieredSpecMap trl_ixes =
   let fin_trl_ixes = reverse trl_ixes
       ix_byspec = groupBy ((==) `on` Instance.specOf) fin_trl_ixes
+      -- head is "safe" here, as groupBy returns list of non-empty lists
       spec_map = map (\ixs -> (Instance.specOf $ head ixs, length ixs))
                  ix_byspec
   in spec_map
@@ -365,7 +374,7 @@ runAllocation cdata stop_allocation actual_result spec dt mode opts = do
         Just result_noalloc -> return result_noalloc
         Nothing -> exitIfBad "failure during allocation" actual_result
 
-  let name = head . words . specDescription $ mode
+  let name = specName mode
       descr = name ++ " allocation"
       ldescr = "after " ++ map toLower descr
 
diff --git a/src/Ganeti/Objects.hs b/src/Ganeti/Objects.hs
index 388e5cfe5379089daa60599feca32a60457ed36e..6e568e63c89afd757a360b9abc2d57dc37d179a1 100644
--- a/src/Ganeti/Objects.hs
+++ b/src/Ganeti/Objects.hs
@@ -172,12 +172,8 @@ roleDescription NRMaster    = "master"
 -- haven't been exhaustively deduced from the python code yet.
 $(buildObject "Network" "network" $
   [ simpleField "name"             [t| NonEmptyString |]
-  , optionalField $
-    simpleField "network_type"     [t| NetworkType |]
   , optionalField $
     simpleField "mac_prefix"       [t| String |]
-  , optionalField $
-    simpleField "family"           [t| Int |]
   , simpleField "network"          [t| NonEmptyString |]
   , optionalField $
     simpleField "network6"         [t| String |]
@@ -185,8 +181,6 @@ $(buildObject "Network" "network" $
     simpleField "gateway"          [t| String |]
   , optionalField $
     simpleField "gateway6"         [t| String |]
-  , optionalField $
-    simpleField "size"             [t| J.JSValue |]
   , optionalField $
     simpleField "reservations"     [t| String |]
   , optionalField $
diff --git a/src/Ganeti/OpCodes.hs b/src/Ganeti/OpCodes.hs
index cd5466379c533d21a5b7d104ad09c42ac6b8b8b0..fde6b640c2628eb1e0a4a5a832e4e732c18496fb 100644
--- a/src/Ganeti/OpCodes.hs
+++ b/src/Ganeti/OpCodes.hs
@@ -501,7 +501,6 @@ $(genOpCode "OpCode"
      ])
   , ("OpNetworkAdd",
      [ pNetworkName
-     , pNetworkType
      , pNetworkAddress4
      , pNetworkGateway4
      , pNetworkAddress6
@@ -517,7 +516,6 @@ $(genOpCode "OpCode"
      ])
   , ("OpNetworkSetParams",
      [ pNetworkName
-     , pNetworkType
      , pNetworkGateway4
      , pNetworkAddress6
      , pNetworkGateway6
diff --git a/src/Ganeti/OpParams.hs b/src/Ganeti/OpParams.hs
index b247f00501f193374976a7012c64f97953c79020..8e91b8f7dae53994d091b906a6a1530922c01f65 100644
--- a/src/Ganeti/OpParams.hs
+++ b/src/Ganeti/OpParams.hs
@@ -222,7 +222,6 @@ module Ganeti.OpParams
   , pTestDummyFail
   , pTestDummySubmitJobs
   , pNetworkName
-  , pNetworkType
   , pNetworkAddress4
   , pNetworkGateway4
   , pNetworkAddress6
@@ -1358,10 +1357,6 @@ pTestDummySubmitJobs =
 pNetworkName :: Field
 pNetworkName = simpleField "network_name" [t| NonEmptyString |]
 
--- | Network type field.
-pNetworkType :: Field
-pNetworkType = optionalField $ simpleField "network_type" [t| NetworkType |]
-
 -- | Network address (IPv4 subnet). FIXME: no real type for this.
 pNetworkAddress4 :: Field
 pNetworkAddress4 =
diff --git a/src/Ganeti/Query/Network.hs b/src/Ganeti/Query/Network.hs
index e0b5a13ed298453d025300ee400b8cf5b10e3cfd..2d262bfa0a422c3f4a2783cd888c3b9091cf2ec4 100644
--- a/src/Ganeti/Query/Network.hs
+++ b/src/Ganeti/Query/Network.hs
@@ -56,8 +56,6 @@ networkFields =
      FieldSimple (rsMaybeUnavail . networkGateway6), QffNormal)
   , (FieldDefinition "mac_prefix" "MacPrefix" QFTOther "MAC address prefix",
      FieldSimple (rsMaybeUnavail . networkMacPrefix), QffNormal)
-  , (FieldDefinition "network_type" "NetworkType" QFTOther "Network type",
-     FieldSimple (rsMaybeUnavail . networkNetworkType), QffNormal)
   , (FieldDefinition "free_count" "FreeCount" QFTOther "Number of free IPs",
      FieldSimple (rsMaybeNoData . fmap getFreeCount . createAddressPool),
      QffNormal)
diff --git a/src/Ganeti/Query/Server.hs b/src/Ganeti/Query/Server.hs
index db0af61fcd05b8973add545e2c9f11218e1ead92..07fbce0fd869d96cfb492ba3f8f63c7db381a2aa 100644
--- a/src/Ganeti/Query/Server.hs
+++ b/src/Ganeti/Query/Server.hs
@@ -6,7 +6,7 @@
 
 {-
 
-Copyright (C) 2012 Google Inc.
+Copyright (C) 2012, 2013 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
@@ -87,6 +87,9 @@ handleCall :: ConfigData -> LuxiOp -> IO (ErrorResult JSValue)
 handleCall cdata QueryClusterInfo =
   let cluster = configCluster cdata
       hypervisors = clusterEnabledHypervisors cluster
+      def_hv = case hypervisors of
+                 x:_ -> showJSON x
+                 [] -> JSNull
       bits = show (bitSize (0::Int)) ++ "bits"
       arch_tuple = [bits, arch]
       obj = [ ("software_version", showJSON C.releaseVersion)
@@ -97,7 +100,7 @@ handleCall cdata QueryClusterInfo =
             , ("architecture", showJSON arch_tuple)
             , ("name", showJSON $ clusterClusterName cluster)
             , ("master", showJSON $ clusterMasterNode cluster)
-            , ("default_hypervisor", showJSON $ head hypervisors)
+            , ("default_hypervisor", def_hv)
             , ("enabled_hypervisors", showJSON hypervisors)
             , ("hvparams", showJSON $ clusterHvparams cluster)
             , ("os_hvp", showJSON $ clusterOsHvp cluster)
diff --git a/src/Ganeti/Types.hs b/src/Ganeti/Types.hs
index 2e3779a67594f6a88f36ecb529864c0e10ac87b3..c57ee2d760dfefc72c28fff6792faa16277ec872 100644
--- a/src/Ganeti/Types.hs
+++ b/src/Ganeti/Types.hs
@@ -70,8 +70,6 @@ module Ganeti.Types
   , IAllocatorTestDir(..)
   , IAllocatorMode(..)
   , iAllocatorModeToRaw
-  , NetworkType(..)
-  , networkTypeToRaw
   , NICMode(..)
   , nICModeToRaw
   , JobStatus(..)
@@ -359,13 +357,6 @@ $(THH.declareSADT "IAllocatorMode"
   ])
 $(THH.makeJSONInstance ''IAllocatorMode)
 
--- | Network type.
-$(THH.declareSADT "NetworkType"
-  [ ("PrivateNetwork", 'C.networkTypePrivate)
-  , ("PublicNetwork",  'C.networkTypePublic)
-  ])
-$(THH.makeJSONInstance ''NetworkType)
-
 -- | Network mode.
 $(THH.declareSADT "NICMode"
   [ ("NMBridged", 'C.nicModeBridged)
diff --git a/src/Ganeti/Utils.hs b/src/Ganeti/Utils.hs
index 74377aa43450091dc63bacb534021715235b28b0..0ff1984aabfd7a665ad5390a86901757f0418698 100644
--- a/src/Ganeti/Utils.hs
+++ b/src/Ganeti/Utils.hs
@@ -51,6 +51,8 @@ module Ganeti.Utils
   , chompPrefix
   , wrap
   , trim
+  , defaultHead
+  , exitIfEmpty
   ) where
 
 import Data.Char (toUpper, isAlphaNum, isDigit, isSpace)
@@ -369,3 +371,14 @@ wrap maxWidth = filter (not . null) . map trim . wrap0
 -- strings.
 trim :: String -> String
 trim = reverse . dropWhile isSpace . reverse . dropWhile isSpace
+
+-- | A safer head version, with a default value.
+defaultHead :: a -> [a] -> a
+defaultHead def []    = def
+defaultHead _   (x:_) = x
+
+-- | A 'head' version in the I/O monad, for validating parameters
+-- without which we cannot continue.
+exitIfEmpty :: String -> [a] -> IO a
+exitIfEmpty _ (x:_) = return x
+exitIfEmpty s []    = exitErr s
diff --git a/src/lint-hints.hs b/src/lint-hints.hs
index a85a477d93b67c5341b6340da1014fc962f70d43..063f0dfbaf704f3a2a95a8f1d954d5fc38c3e682 100644
--- a/src/lint-hints.hs
+++ b/src/lint-hints.hs
@@ -20,3 +20,9 @@ warn = map (\v -> (x, v)) ==> zip (repeat x)
 warn = length x > 0 ==> not (null x)
 warn = length x /= 0 ==> not (null x)
 warn = length x == 0 ==> null x
+
+-- Never use head, use 'case' which covers all possibilities
+warn = head x ==> case x of { y:_ -> y } where note = "Head is unsafe, please use case and handle the empty list as well"
+
+-- Never use tail, use 'case' which covers all possibilities
+warn = tail x ==> case x of { _:y -> y } where note = "Tail is unsafe, please use case and handle the empty list as well"
diff --git a/test/hs/Test/Ganeti/Common.hs b/test/hs/Test/Ganeti/Common.hs
index 4b163449dc14463a7fe8aaeccae235d792f2309d..b9a505f0caed88ec577f5d24f801e66338175a60 100644
--- a/test/hs/Test/Ganeti/Common.hs
+++ b/test/hs/Test/Ganeti/Common.hs
@@ -83,7 +83,10 @@ passFailOpt :: (StandardOptions b) =>
             -> c
 passFailOpt defaults failfn passfn
               (opt@(GetOpt.Option _ longs _ _, _), bad, good) =
-  let prefix = "--" ++ head longs ++ "="
+  let first_opt = case longs of
+                    [] -> error "no long options?"
+                    x:_ -> x
+      prefix = "--" ++ first_opt ++ "="
       good_cmd = prefix ++ good
       bad_cmd = prefix ++ bad in
   case (parseOptsInner defaults [bad_cmd]  "prog" [opt] [],
diff --git a/test/hs/Test/Ganeti/HTools/Container.hs b/test/hs/Test/Ganeti/HTools/Container.hs
index 5ea85f6bacc12fbbcd0f9a42f4343e54421067f5..ccd94f6a49415dd66a56dea8a655ce8b20f26e4a 100644
--- a/test/hs/Test/Ganeti/HTools/Container.hs
+++ b/test/hs/Test/Ganeti/HTools/Container.hs
@@ -7,7 +7,7 @@
 
 {-
 
-Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
+Copyright (C) 2009, 2010, 2011, 2012, 2013 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
@@ -52,8 +52,10 @@ prop_addTwo cdata i1 i2 =
 prop_nameOf :: Node.Node -> Property
 prop_nameOf node =
   let nl = makeSmallCluster node 1
-      fnode = head (Container.elems nl)
-  in Container.nameOf nl (Node.idx fnode) ==? Node.name fnode
+  in case Container.elems nl of
+       [] -> failTest "makeSmallCluster 1 returned empty cluster?"
+       _:_:_ -> failTest "makeSmallCluster 1 returned >1 node?"
+       fnode:_ -> Container.nameOf nl (Node.idx fnode) ==? Node.name fnode
 
 -- | We test that in a cluster, given a random node, we can find it by
 -- its name and alias, as long as all names and aliases are unique,
diff --git a/test/hs/Test/Ganeti/JQueue.hs b/test/hs/Test/Ganeti/JQueue.hs
index 29f525a356275e6a0fc1b9b704dddd6698556010..d2d946fec743c2c3e94bafdeb9607b95b1455ff6 100644
--- a/test/hs/Test/Ganeti/JQueue.hs
+++ b/test/hs/Test/Ganeti/JQueue.hs
@@ -6,7 +6,7 @@
 
 {-
 
-Copyright (C) 2012 Google Inc.
+Copyright (C) 2012, 2013 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
@@ -135,14 +135,12 @@ prop_JobStatus =
 case_JobStatusPri_py_equiv :: Assertion
 case_JobStatusPri_py_equiv = do
   let num_jobs = 2000::Int
-  sample_jobs <- sample' (vectorOf num_jobs $ do
-                            num_ops <- choose (1, 5)
-                            ops <- vectorOf num_ops genQueuedOpCode
-                            jid <- genJobId
-                            return $ QueuedJob jid ops justNoTs justNoTs
-                                               justNoTs)
-  let jobs = head sample_jobs
-      serialized = encode jobs
+  jobs <- genSample (vectorOf num_jobs $ do
+                       num_ops <- choose (1, 5)
+                       ops <- vectorOf num_ops genQueuedOpCode
+                       jid <- genJobId
+                       return $ QueuedJob jid ops justNoTs justNoTs justNoTs)
+  let serialized = encode jobs
   -- check for non-ASCII fields, usually due to 'arbitrary :: String'
   mapM_ (\job -> when (any (not . isAscii) (encode job)) .
                  assertFailure $ "Job has non-ASCII fields: " ++ show job
diff --git a/test/hs/Test/Ganeti/Network.hs b/test/hs/Test/Ganeti/Network.hs
index 4867830c95d90433483e542ccabcee2d08444a15..57793ca3f972435493336e3dc0fe0ce7a70662a7 100644
--- a/test/hs/Test/Ganeti/Network.hs
+++ b/test/hs/Test/Ganeti/Network.hs
@@ -4,7 +4,6 @@
 module Test.Ganeti.Network
   ( testNetwork
   , genBitStringMaxLen
-  , genNetworkType
   ) where
 
 import Test.QuickCheck
@@ -14,7 +13,6 @@ import Ganeti.Objects as Objects
 
 import Test.Ganeti.Objects
   ( genBitStringMaxLen
-  , genNetworkType
   , genValidNetwork )
 import Test.Ganeti.TestHelper
 import Test.Ganeti.TestCommon
diff --git a/test/hs/Test/Ganeti/Objects.hs b/test/hs/Test/Ganeti/Objects.hs
index 818227993157321071e5f21d8d5517b1c95b0630..dc619eccfe2e5e3ac934a88caed269db5a39369c 100644
--- a/test/hs/Test/Ganeti/Objects.hs
+++ b/test/hs/Test/Ganeti/Objects.hs
@@ -7,7 +7,7 @@
 
 {-
 
-Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
+Copyright (C) 2009, 2010, 2011, 2012, 2013 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
@@ -31,7 +31,6 @@ module Test.Ganeti.Objects
   , Node(..)
   , genEmptyCluster
   , genValidNetwork
-  , genNetworkType
   , genBitStringMaxLen
   ) where
 
@@ -45,7 +44,6 @@ import qualified Data.Map as Map
 import qualified Data.Set as Set
 import qualified Text.JSON as J
 
-import Test.Ganeti.Query.Language (genJSValue)
 import Test.Ganeti.TestHelper
 import Test.Ganeti.TestCommon
 import Test.Ganeti.Types ()
@@ -169,25 +167,18 @@ genValidNetwork = do
   -- generate netmask for the IPv4 network
   netmask <- choose (24::Int, 30)
   name <- genName >>= mkNonEmpty
-  network_type <- genMaybe genNetworkType
   mac_prefix <- genMaybe genName
-  net_family <- arbitrary
   net <- genIp4NetWithNetmask netmask
   net6 <- genMaybe genIp6Net
   gateway <- genMaybe genIp4AddrStr
   gateway6 <- genMaybe genIp6Addr
-  size <- genMaybe genJSValue
   res <- liftM Just (genBitString $ netmask2NumHosts netmask)
   ext_res <- liftM Just (genBitString $ netmask2NumHosts netmask)
   uuid <- arbitrary
-  let n = Network name network_type mac_prefix net_family net net6 gateway
-          gateway6 size res ext_res uuid 0 Set.empty
+  let n = Network name mac_prefix net net6 gateway
+          gateway6 res ext_res uuid 0 Set.empty
   return n
 
--- | Generates an arbitrary network type.
-genNetworkType :: Gen NetworkType
-genNetworkType = elements [ PrivateNetwork, PublicNetwork ]
-
 -- | Generate an arbitrary string consisting of '0' and '1' of the given length.
 genBitString :: Int -> Gen String
 genBitString len = vectorOf len (elements "01")
@@ -275,9 +266,8 @@ prop_Config_serialisation =
 case_py_compat_networks :: HUnit.Assertion
 case_py_compat_networks = do
   let num_networks = 500::Int
-  sample_networks <- sample' (vectorOf num_networks genValidNetwork)
-  let networks = head sample_networks
-      networks_with_properties = map getNetworkProperties networks
+  networks <- genSample (vectorOf num_networks genValidNetwork)
+  let networks_with_properties = map getNetworkProperties networks
       serialized = J.encode networks
   -- check for non-ASCII fields, usually due to 'arbitrary :: String'
   mapM_ (\net -> when (any (not . isAscii) (J.encode net)) .
@@ -325,9 +315,8 @@ getNetworkProperties net =
 case_py_compat_nodegroups :: HUnit.Assertion
 case_py_compat_nodegroups = do
   let num_groups = 500::Int
-  sample_groups <- sample' (vectorOf num_groups genNodeGroup)
-  let groups = head sample_groups
-      serialized = J.encode groups
+  groups <- genSample (vectorOf num_groups genNodeGroup)
+  let serialized = J.encode groups
   -- check for non-ASCII fields, usually due to 'arbitrary :: String'
   mapM_ (\group -> when (any (not . isAscii) (J.encode group)) .
                  HUnit.assertFailure $
diff --git a/test/hs/Test/Ganeti/OpCodes.hs b/test/hs/Test/Ganeti/OpCodes.hs
index cc45f663d0758b04cfcefa55b0424bdc6fd6ba2a..5015fd2e95e4c20f3b0f61e3888d66930680578e 100644
--- a/test/hs/Test/Ganeti/OpCodes.hs
+++ b/test/hs/Test/Ganeti/OpCodes.hs
@@ -7,7 +7,7 @@
 
 {-
 
-Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
+Copyright (C) 2009, 2010, 2011, 2012, 2013 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
@@ -318,14 +318,14 @@ instance Arbitrary OpCodes.OpCode where
         OpCodes.OpTestDummy <$> pure J.JSNull <*> pure J.JSNull <*>
           pure J.JSNull <*> pure J.JSNull
       "OP_NETWORK_ADD" ->
-        OpCodes.OpNetworkAdd <$> genNameNE <*> arbitrary <*> genIp4Net <*>
+        OpCodes.OpNetworkAdd <$> genNameNE <*> genIp4Net <*>
           genMaybe genIp4Addr <*> pure Nothing <*> pure Nothing <*>
           genMaybe genMacPrefix <*> genMaybe (listOf genIp4Addr) <*>
           arbitrary <*> (genTags >>= mapM mkNonEmpty)
       "OP_NETWORK_REMOVE" ->
         OpCodes.OpNetworkRemove <$> genNameNE <*> arbitrary
       "OP_NETWORK_SET_PARAMS" ->
-        OpCodes.OpNetworkSetParams <$> genNameNE <*> arbitrary <*>
+        OpCodes.OpNetworkSetParams <$> genNameNE <*>
           genMaybe genIp4Addr <*> pure Nothing <*> pure Nothing <*>
           genMaybe genMacPrefix <*> genMaybe (listOf genIp4Addr) <*>
           genMaybe (listOf genIp4Addr)
@@ -440,10 +440,9 @@ case_AllDefined = do
 case_py_compat_types :: HUnit.Assertion
 case_py_compat_types = do
   let num_opcodes = length OpCodes.allOpIDs * 100
-  sample_opcodes <- sample' (vectorOf num_opcodes
-                             (arbitrary::Gen OpCodes.MetaOpCode))
-  let opcodes = head sample_opcodes
-      with_sum = map (\o -> (OpCodes.opSummary $
+  opcodes <- genSample (vectorOf num_opcodes
+                                   (arbitrary::Gen OpCodes.MetaOpCode))
+  let with_sum = map (\o -> (OpCodes.opSummary $
                              OpCodes.metaOpCode o, o)) opcodes
       serialized = J.encode opcodes
   -- check for non-ASCII fields, usually due to 'arbitrary :: String'
diff --git a/test/hs/Test/Ganeti/TestCommon.hs b/test/hs/Test/Ganeti/TestCommon.hs
index 6dcc8ada7e857d9a11a2f149ed791872259bf780..91351b68b7396f7a56fd21b1abcbe76cf723699d 100644
--- a/test/hs/Test/Ganeti/TestCommon.hs
+++ b/test/hs/Test/Ganeti/TestCommon.hs
@@ -4,7 +4,7 @@
 
 {-
 
-Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
+Copyright (C) 2009, 2010, 2011, 2012, 2013 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
@@ -294,3 +294,12 @@ readTestData :: String -> IO String
 readTestData filename = do
     name <- testDataFilename "/test/data/" filename
     readFile name
+
+-- | Generate arbitrary values in the IO monad. This is a simple
+-- wrapper over 'sample''.
+genSample :: Gen a -> IO a
+genSample gen = do
+  values <- sample' gen
+  case values of
+    [] -> error "sample' returned an empty list of values??"
+    x:_ -> return x
diff --git a/test/hs/Test/Ganeti/Types.hs b/test/hs/Test/Ganeti/Types.hs
index 7526e01efcb9a85fcfd8c78d12da75b761a3384d..6f84782cac602a30078963032f5392db3a2ec83c 100644
--- a/test/hs/Test/Ganeti/Types.hs
+++ b/test/hs/Test/Ganeti/Types.hs
@@ -112,8 +112,6 @@ $(genArbitrary ''IAllocatorTestDir)
 
 $(genArbitrary ''IAllocatorMode)
 
-$(genArbitrary ''NetworkType)
-
 $(genArbitrary ''NICMode)
 
 $(genArbitrary ''JobStatus)
@@ -287,17 +285,6 @@ case_IAllocatorMode_pyequiv = do
       all_hs_codes = sort $ map Types.iAllocatorModeToRaw [minBound..maxBound]
   assertEqual "for IAllocatorMode equivalence" all_py_codes all_hs_codes
 
--- | Test 'NetworkType' serialisation.
-prop_NetworkType_serialisation :: NetworkType -> Property
-prop_NetworkType_serialisation = testSerialisation
-
--- | Tests equivalence with Python, based on Constants.hs code.
-case_NetworkType_pyequiv :: Assertion
-case_NetworkType_pyequiv = do
-  let all_py_codes = sort C.networkValidTypes
-      all_hs_codes = sort $ map Types.networkTypeToRaw [minBound..maxBound]
-  assertEqual "for NetworkType equivalence" all_py_codes all_hs_codes
-
 -- | Test 'NICMode' serialisation.
 prop_NICMode_serialisation :: NICMode -> Property
 prop_NICMode_serialisation = testSerialisation
@@ -417,8 +404,6 @@ testSuite "Types"
   , 'prop_IAllocatorTestDir_serialisation
   , 'prop_IAllocatorMode_serialisation
   , 'case_IAllocatorMode_pyequiv
-  , 'prop_NetworkType_serialisation
-  , 'case_NetworkType_pyequiv
   , 'prop_NICMode_serialisation
   , 'prop_OpStatus_serialization
   , 'prop_JobStatus_serialization
diff --git a/test/hs/Test/Ganeti/Utils.hs b/test/hs/Test/Ganeti/Utils.hs
index 548e4cfa318aaabc27a15e56019d593e576a5dda..28610ae2ba8d9a1b117f55d170677e7c924ff60d 100644
--- a/test/hs/Test/Ganeti/Utils.hs
+++ b/test/hs/Test/Ganeti/Utils.hs
@@ -88,24 +88,30 @@ prop_select :: Int      -- ^ Default result
             -> Gen Prop -- ^ Test result
 prop_select def lst1 lst2 =
   select def (flist ++ tlist) ==? expectedresult
-    where expectedresult = if' (null lst2) def (head lst2)
+    where expectedresult = defaultHead def lst2
           flist = zip (repeat False) lst1
           tlist = zip (repeat True)  lst2
 
+{-# ANN prop_select_undefd "HLint: ignore Use alternative" #-}
 -- | Test basic select functionality with undefined default
 prop_select_undefd :: [Int]            -- ^ List of False values
                    -> NonEmptyList Int -- ^ List of True values
                    -> Gen Prop         -- ^ Test result
 prop_select_undefd lst1 (NonEmpty lst2) =
+  -- head is fine as NonEmpty "guarantees" a non-empty list, but not
+  -- via types
   select undefined (flist ++ tlist) ==? head lst2
     where flist = zip (repeat False) lst1
           tlist = zip (repeat True)  lst2
 
+{-# ANN prop_select_undefv "HLint: ignore Use alternative" #-}
 -- | Test basic select functionality with undefined list values
 prop_select_undefv :: [Int]            -- ^ List of False values
                    -> NonEmptyList Int -- ^ List of True values
                    -> Gen Prop         -- ^ Test result
 prop_select_undefv lst1 (NonEmpty lst2) =
+  -- head is fine as NonEmpty "guarantees" a non-empty list, but not
+  -- via types
   select undefined cndlist ==? head lst2
     where flist = zip (repeat False) lst1
           tlist = zip (repeat True)  lst2
diff --git a/test/py/ganeti.config_unittest.py b/test/py/ganeti.config_unittest.py
index b06f774c404ba76a15d9e3eda554317542c3eba4..da82fc0bd93ab5db5e792751ebd4a27a9f5645a0 100755
--- a/test/py/ganeti.config_unittest.py
+++ b/test/py/ganeti.config_unittest.py
@@ -74,6 +74,8 @@ class TestConfigRunner(unittest.TestCase):
     """Initializes the cfg object"""
     me = netutils.Hostname()
     ip = constants.IP4_ADDRESS_LOCALHOST
+    # master_ip must not conflict with the node ip address
+    master_ip = "127.0.0.2"
 
     cluster_config = objects.Cluster(
       serial_no=1,
@@ -87,7 +89,7 @@ class TestConfigRunner(unittest.TestCase):
       tcpudp_port_pool=set(),
       enabled_hypervisors=[constants.HT_FAKE],
       master_node=me.name,
-      master_ip="127.0.0.1",
+      master_ip=master_ip,
       master_netdev=constants.DEFAULT_BRIDGE,
       cluster_name="cluster.local",
       file_storage_dir="/tmp",
@@ -174,6 +176,44 @@ class TestConfigRunner(unittest.TestCase):
     self.failUnlessRaises(errors.ConfigurationError, cfg.Update, fake_instance,
                           None)
 
+  def testUpgradeSave(self):
+    """Test that any modification done during upgrading is saved back"""
+    cfg = self._get_object()
+
+    # Remove an element, run upgrade, and check if the element is
+    # back and the file upgraded
+    node = cfg.GetNodeInfo(cfg.GetNodeList()[0])
+    # For a ConfigObject, None is the same as a missing field
+    node.ndparams = None
+    oldsaved = utils.ReadFile(self.cfg_file)
+    cfg._UpgradeConfig()
+    self.assertTrue(node.ndparams is not None)
+    newsaved = utils.ReadFile(self.cfg_file)
+    # We rely on the fact that at least the serial number changes
+    self.assertNotEqual(oldsaved, newsaved)
+
+    # Add something that should not be there this time
+    key = list(constants.NDC_GLOBALS)[0]
+    node.ndparams[key] = constants.NDC_DEFAULTS[key]
+    cfg._WriteConfig(None)
+    oldsaved = utils.ReadFile(self.cfg_file)
+    cfg._UpgradeConfig()
+    self.assertTrue(node.ndparams.get(key) is None)
+    newsaved = utils.ReadFile(self.cfg_file)
+    self.assertNotEqual(oldsaved, newsaved)
+
+    # Do the upgrade again, this time there should be no update
+    oldsaved = newsaved
+    cfg._UpgradeConfig()
+    newsaved = utils.ReadFile(self.cfg_file)
+    self.assertEqual(oldsaved, newsaved)
+
+    # Reload the configuration again: it shouldn't change the file
+    oldsaved = newsaved
+    self._get_object()
+    newsaved = utils.ReadFile(self.cfg_file)
+    self.assertEqual(oldsaved, newsaved)
+
   def testNICParameterSyntaxCheck(self):
     """Test the NIC's CheckParameterSyntax function"""
     mode = constants.NIC_MODE
@@ -388,6 +428,27 @@ class TestConfigRunner(unittest.TestCase):
     finally:
       node2.group = orig_group
 
+  def testVerifyConfig(self):
+    cfg = self._get_object()
+
+    errs = cfg.VerifyConfig()
+    self.assertFalse(errs)
+
+    node = cfg.GetNodeInfo(cfg.GetNodeList()[0])
+    key = list(constants.NDC_GLOBALS)[0]
+    node.ndparams[key] = constants.NDC_DEFAULTS[key]
+    errs = cfg.VerifyConfig()
+    self.assertTrue(len(errs) >= 1)
+    self.assertTrue(_IsErrorInList("has some global parameters set", errs))
+
+    del node.ndparams[key]
+    errs = cfg.VerifyConfig()
+    self.assertFalse(errs)
+
+
+def _IsErrorInList(err_str, err_list):
+  return any(map(lambda e: err_str in e, err_list))
+
 
 class TestTRM(unittest.TestCase):
   EC_ID = 1
diff --git a/test/py/ganeti.objects_unittest.py b/test/py/ganeti.objects_unittest.py
index 400c32b2fd9260ac779d9ceceaa8f1a5a5e00300..fc9a6027cc694bcd4e4b4d5a7e3ab7ed2647268e 100755
--- a/test/py/ganeti.objects_unittest.py
+++ b/test/py/ganeti.objects_unittest.py
@@ -382,6 +382,23 @@ class TestNode(unittest.TestCase):
     self.assertEqual(node2.disk_state[constants.LD_LV]["lv2082"].total, 512)
     self.assertEqual(node2.disk_state[constants.LD_LV]["lv32352"].total, 128)
 
+  def testFilterEsNdp(self):
+    node1 = objects.Node(name="node11673.example.com", ndparams={
+      constants.ND_EXCLUSIVE_STORAGE: True,
+      })
+    node2 = objects.Node(name="node11674.example.com", ndparams={
+      constants.ND_SPINDLE_COUNT: 3,
+      constants.ND_EXCLUSIVE_STORAGE: False,
+      })
+    self.assertTrue(constants.ND_EXCLUSIVE_STORAGE in node1.ndparams)
+    node1.UpgradeConfig()
+    self.assertFalse(constants.ND_EXCLUSIVE_STORAGE in node1.ndparams)
+    self.assertTrue(constants.ND_EXCLUSIVE_STORAGE in node2.ndparams)
+    self.assertTrue(constants.ND_SPINDLE_COUNT in node2.ndparams)
+    node2.UpgradeConfig()
+    self.assertFalse(constants.ND_EXCLUSIVE_STORAGE in node2.ndparams)
+    self.assertTrue(constants.ND_SPINDLE_COUNT in node2.ndparams)
+
 
 if __name__ == "__main__":
   testutils.GanetiTestProgram()
diff --git a/tools/cfgupgrade b/tools/cfgupgrade
index 8d97f5c436aca5bafc5cbc7278d5af6a0988b5a1..bccb34e4a80fefdab809f44a9cd174fd149413f2 100755
--- a/tools/cfgupgrade
+++ b/tools/cfgupgrade
@@ -52,7 +52,7 @@ args = None
 #: Target major version we will upgrade to
 TARGET_MAJOR = 2
 #: Target minor version we will upgrade to
-TARGET_MINOR = 6
+TARGET_MINOR = 7
 
 
 class Error(Exception):
@@ -204,8 +204,8 @@ def main():
     raise Error("Inconsistent configuration: found config_version in"
                 " configuration file")
 
-  # Upgrade from 2.0/2.1/2.2/2.3 to 2.4
-  if config_major == 2 and config_minor in (0, 1, 2, 3, 4, 5):
+  # Upgrade from 2.{0..6} to 2.7
+  if config_major == 2 and config_minor in (0, 1, 2, 3, 4, 5, 6):
     if config_revision != 0:
       logging.warning("Config revision is %s, not 0", config_revision)